Add FS timing tests

This commit is contained in:
Léo Lam
2018-03-28 20:23:29 +02:00
parent c6cd6c1ebe
commit a7ee9ceb5d
4 changed files with 324 additions and 0 deletions

View File

@@ -1 +1,2 @@
add_hwtest(MODULE iostest TEST ipc_timing FILES ipc_timing.cpp ipc.cpp)
add_hwtest(MODULE iostest TEST fs_timing FILES fs_timing.cpp ipc.cpp fs.cpp)

53
iostest/fs.cpp Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2018 leoetlino <leo@leolam.fr>
// Licensed under GPLv2
#include <array>
#include <cstring>
#include "common/CommonTypes.h"
#include "iostest/fs.h"
#include "iostest/ipc.h"
namespace fs {
enum
{
ISFS_IOCTL_FORMAT = 1,
ISFS_IOCTL_GETSTATS = 2,
ISFS_IOCTL_CREATEDIR = 3,
ISFS_IOCTLV_READDIR = 4,
ISFS_IOCTL_SETATTR = 5,
ISFS_IOCTL_GETATTR = 6,
ISFS_IOCTL_DELETE = 7,
ISFS_IOCTL_RENAME = 8,
ISFS_IOCTL_CREATEFILE = 9,
ISFS_IOCTL_SETFILEVERCTRL = 10,
ISFS_IOCTL_GETFILESTATS = 11,
ISFS_IOCTLV_GETUSAGE = 12,
ISFS_IOCTL_SHUTDOWN = 13,
};
ipc::Result CreateFile(int fs_fd, const char* path, u8 owner, u8 group, u8 other, u8 attribute) {
ISFSParams params{};
std::strncpy(params.path.data(), path, params.path.size());
params.owner_mode = owner;
params.group_mode = group;
params.other_mode = other;
params.attribute = attribute;
return ipc::Ioctl(fs_fd, ISFS_IOCTL_CREATEFILE, &params, sizeof(params), nullptr, 0);
}
ipc::Result DeleteFile(int fs_fd, const char* path) {
std::array<char, 64> in_buffer;
std::strncpy(in_buffer.data(), path, in_buffer.size());
return ipc::Ioctl(fs_fd, ISFS_IOCTL_DELETE, in_buffer.data(), in_buffer.size(), nullptr, 0);
}
ipc::Result GetMetadata(int fs_fd, const char* path, ISFSParams* metadata) {
std::array<char, 64> in_buffer;
std::strncpy(in_buffer.data(), path, in_buffer.size());
std::strncpy(metadata->path.data(), path, in_buffer.size());
return ipc::Ioctl(fs_fd, ISFS_IOCTL_GETATTR, in_buffer.data(), in_buffer.size(), metadata,
sizeof(ISFSParams));
}
} // namespace fs

28
iostest/fs.h Normal file
View File

@@ -0,0 +1,28 @@
// Copyright 2018 leoetlino <leo@leolam.fr>
// Licensed under GPLv2
#pragma once
#include "common/CommonTypes.h"
#include "iostest/ipc.h"
namespace fs {
#pragma pack(push, 1)
struct ISFSParams
{
u32 uid;
u16 gid;
std::array<char, 64> path;
u8 owner_mode;
u8 group_mode;
u8 other_mode;
u8 attribute;
};
#pragma pack(pop)
ipc::Result CreateFile(int fs_fd, const char* path, u8 owner, u8 group, u8 other, u8 attribute);
ipc::Result DeleteFile(int fs_fd, const char* path);
ipc::Result GetMetadata(int fs_fd, const char* path, ISFSParams* metadata);
} // namespace fs

242
iostest/fs_timing.cpp Normal file
View File

@@ -0,0 +1,242 @@
// Copyright 2018 leoetlino <leo@leolam.fr>
// Licensed under GPLv2
// IOS FS timing test.
#include <cinttypes>
#include <cstring>
#include <numeric>
#include <string>
#include <vector>
#include <ogc/ios.h>
#include <unistd.h>
#include "common/CommonTypes.h"
#include "common/hwtests.h"
#include "iostest/fs.h"
#include "iostest/ipc.h"
#include "iostest/result_printer.h"
namespace {
enum class Expect {
Success,
Failure,
};
std::vector<u64> TestOpens(const char* path, Expect expect) {
std::vector<u64> ticks;
for (size_t i = 0; i < 100; ++i) {
const ipc::Result open = ipc::Open(path, ipc::OpenMode::Read);
if (open.result < 0 && expect == Expect::Success)
return {};
if (open.result >= 0 && ipc::Close(open.result).result < 0)
return {};
ticks.emplace_back(open.reply_ticks);
}
return ticks;
}
std::vector<u64> TestCloses() {
std::vector<u64> ticks;
for (size_t i = 0; i < 100; ++i) {
const int fd = ipc::Open("/shared2/sys/SYSCONF", ipc::OpenMode::Read).result;
if (fd < 0)
return {};
const ipc::Result close = ipc::Close(fd);
if (close.result < 0)
return {};
ticks.emplace_back(close.reply_ticks);
}
return ticks;
}
alignas(64) static u8 buffer[0x8000];
enum class ReadMode
{
Cached,
Uncached,
};
// size must be <= 0x8000
std::vector<u64> TestReads(const char* path, size_t size, ReadMode mode) {
std::vector<u64> ticks;
for (size_t i = 0; i < 100; ++i) {
const int fd = ipc::Open(path, ipc::OpenMode::Read).result;
if (fd < 0)
return {};
if (mode == ReadMode::Cached) {
// Bring the file into the cache
if (ipc::Read(fd, buffer, 0x1).result < 0 || ipc::Seek(fd, 0, ipc::SeekMode::Set).result < 0)
return {};
}
const ipc::Result read = ipc::Read(fd, buffer, std::min(size, sizeof(buffer)));
if (read.result < 0)
return {};
ticks.emplace_back(read.reply_ticks);
if (ipc::Close(fd).result < 0)
return {};
}
return ticks;
}
std::pair<std::vector<u64>, std::vector<u64>> TestFileCreationAndDeletion() {
const int fd = ipc::Open("/dev/fs", ipc::OpenMode::None).result;
if (fd < 0)
return {};
std::vector<u64> create_ticks, delete_ticks;
[&] {
for (size_t i = 0; i < 20; ++i) {
const ipc::Result create_res = fs::CreateFile(fd, "/tmp/test", 3, 0, 0, 0);
if (create_res.result < 0)
return;
create_ticks.emplace_back(create_res.reply_ticks);
const ipc::Result delete_res = fs::DeleteFile(fd, "/tmp/test");
if (delete_res.result < 0)
return;
delete_ticks.emplace_back(delete_res.reply_ticks);
}
}();
ipc::Close(fd);
return std::make_pair(std::move(create_ticks), std::move(delete_ticks));
}
enum class WriteFileMode {
BlankFile,
NonEmptyFile,
};
std::pair<std::vector<u64>, std::vector<u64>> TestWrites(size_t size, WriteFileMode mode) {
size = std::min(size, sizeof(buffer));
const int fd = ipc::Open("/dev/fs", ipc::OpenMode::None).result;
if (fd < 0)
return {};
std::vector<u64> write_ticks, close_ticks;
int file_fd = -1;
[&] {
for (size_t i = 0; i < 20; ++i) {
constexpr const char* path = "/tmp/writetest";
if (fs::CreateFile(fd, path, 3, 0, 0, 0).result < 0)
return;
file_fd = ipc::Open(path, ipc::OpenMode::ReadWrite).result;
if (file_fd < 0)
return;
if (mode == WriteFileMode::NonEmptyFile) {
if (ipc::Write(file_fd, buffer, sizeof(buffer)).result < 0)
return;
if (ipc::Close(file_fd).result < 0)
return;
file_fd = ipc::Open(path, ipc::OpenMode::ReadWrite).result;
if (file_fd < 0)
return;
}
const ipc::Result write = ipc::Write(file_fd, buffer, size);
if (write.result != size)
return;
const ipc::Result close = ipc::Close(file_fd);
if (close.result < 0)
return;
write_ticks.emplace_back(write.reply_ticks);
close_ticks.emplace_back(close.reply_ticks);
fs::DeleteFile(fd, path);
}
}();
ipc::Close(fd);
ipc::Close(file_fd);
return std::make_pair(std::move(write_ticks), std::move(close_ticks));
}
std::vector<u64> TestGetMetadata(const char* path) {
const int fd = ipc::Open("/dev/fs", ipc::OpenMode::None).result;
if (fd < 0)
return {};
std::vector<u64> ticks;
fs::ISFSParams metadata;
for (size_t i = 0; i < 100; ++i) {
const ipc::Result result = fs::GetMetadata(fd, path, &metadata);
if (result.result < 0)
{
ipc::Close(fd);
return {};
}
ticks.emplace_back(result.reply_ticks);
}
ipc::Close(fd);
return ticks;
}
} // end of anonymous namespace
int main() {
ResultPrinter<std::vector<u64>> results;
ipc::Init();
results.Add("Open /invalid/path/", TestOpens("/invalid/path/", Expect::Failure));
results.Add("Open /title", TestOpens("/title", Expect::Failure));
results.Add("Open /title/00000001", TestOpens("/title/00000001", Expect::Failure));
results.Add("Open /sys/cert.sys", TestOpens("/sys/cert.sys", Expect::Success));
results.Add("Open /shared2/sys/SYSCONF", TestOpens("/shared2/sys/SYSCONF", Expect::Success));
results.Add("Close (no cache flush)", TestCloses());
auto test_reads = [&](const char* path, ReadMode mode) {
for (const size_t size : {0x0, 0x4, 0x100, 0x200, 0x300, 0x800, 0x1000, 0x2000, 0x4000}) {
const std::string mode_string = mode == ReadMode::Cached ? "cached" : "uncached";
results.Add("Read " + std::to_string(size) + " bytes (" + mode_string + ")",
TestReads(path, size, mode));
}
};
// These are expected to all yield pretty much the same result, because IOS has
// to read a full cluster (0x4000) from the NAND.
test_reads("/shared2/sys/SYSCONF", ReadMode::Uncached);
// These are expected to take linear time because FS has already read the cluster
// and all it needs to do is memcpy it to PPC memory.
test_reads("/shared2/sys/SYSCONF", ReadMode::Cached);
auto create_and_delete_results = TestFileCreationAndDeletion();
results.Add("CreateFile", std::move(create_and_delete_results.first));
results.Add("DeleteFileOrDirectory (file)", std::move(create_and_delete_results.second));
results.Add("GetMetadata /title", TestGetMetadata("/title"));
results.Add("GetMetadata /title/00000001", TestGetMetadata("/title/00000001"));
results.Add("GetMetadata /sys/cert.sys", TestGetMetadata("/sys/cert.sys"));
results.Add("GetMetadata /shared2/sys/SYSCONF", TestGetMetadata("/shared2/sys/SYSCONF"));
auto test_writes = [&](WriteFileMode mode) {
for (const size_t size : {0x0, 0x4, 0x100, 0x1000, 0x2000, 0x4000, 0x4001, 0x8000}) {
const std::string mode_string = mode == WriteFileMode::BlankFile ? "blank" : "non-empty";
auto write_and_close_results = TestWrites(size, mode);
results.Add("Write " + std::to_string(size) + " bytes (" + mode_string + ")",
std::move(write_and_close_results.first));
results.Add(" -- Flush", std::move(write_and_close_results.second));
}
};
test_writes(WriteFileMode::BlankFile);
test_writes(WriteFileMode::NonEmptyFile);
ipc::Shutdown();
network_init();
network_printf("IOS version %u\n\n", IOS_GetVersion());
results.Print([](const std::string& description, const std::vector<u64>& ticks) {
if (ticks.empty())
{
network_printf("\e[0;34m%s\e[0;0m: error\n", description.c_str());
return;
}
network_printf("\e[0;34m%s\e[0;0m: reply_ticks=%" PRIu64" (%u tests)\n",
description.c_str(),
std::accumulate(ticks.begin(), ticks.end(), u64(0)) / ticks.size(),
static_cast<u32>(ticks.size()));
});
network_shutdown();
return 0;
}