blob: 7b34c89405ee0acb8ffb26e2ced9de723879415c [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <zircon/device/vfs.h>
#include <zircon/syscalls.h>
#include <fbl/alloc_checker.h>
#include <fbl/string_piece.h>
#include <fbl/unique_ptr.h>
#include <unittest/unittest.h>
#define MOUNT_POINT "/benchmark"
constexpr size_t KB = (1 << 10);
constexpr size_t MB = (1 << 20);
constexpr uint8_t kMagicByte = 0xee;
// Return "true" if the fs matches the 'banned' criteria.
template <size_t len>
bool benchmark_banned(int fd, const char (&banned_fs)[len]) {
char buf[sizeof(vfs_query_info_t) + MAX_FS_NAME_LEN + 1];
vfs_query_info_t* info = reinterpret_cast<vfs_query_info_t*>(buf);
ssize_t r = ioctl_vfs_query_fs(fd, info, sizeof(buf) - 1);
if (r != static_cast<ssize_t>(sizeof(vfs_query_info_t) + len)) {
return false;
}
buf[r] = '\0';
const char* name = reinterpret_cast<const char*>(buf + sizeof(vfs_query_info_t));
return strncmp(banned_fs, name, len - 1) == 0;
}
inline void time_end(const char *str, uint64_t start) {
uint64_t end = zx_ticks_get();
uint64_t ticks_per_msec = zx_ticks_per_second() / 1000;
printf("Benchmark %s: [%10lu] msec\n", str, (end - start) / ticks_per_msec);
}
constexpr int kWriteReadCycles = 3;
// The goal of this benchmark is to get a basic idea of some large read / write
// times for a file.
//
// Caching will no doubt play a part with this benchmark, but it's simple,
// and should give us a rough rule-of-thumb regarding how we're doing.
template <size_t DataSize, size_t NumOps>
bool benchmark_write_read(void) {
BEGIN_TEST;
int fd = open(MOUNT_POINT "/bigfile", O_CREAT | O_RDWR, 0644);
ASSERT_GT(fd, 0, "Cannot create file (FS benchmarks assume mounted FS exists at '/benchmark')");
const size_t size_mb = (DataSize * NumOps) / MB;
if (size_mb > 64 && benchmark_banned(fd, "memfs")) {
return true;
}
printf("\nBenchmarking Write + Read (%lu MB)\n", size_mb);
fbl::AllocChecker ac;
fbl::unique_ptr<uint8_t[]> data(new (&ac) uint8_t[DataSize]);
ASSERT_EQ(ac.check(), true);
memset(data.get(), kMagicByte, DataSize);
uint64_t start;
size_t count;
for (int i = 0; i < kWriteReadCycles; i++) {
char str[100];
snprintf(str, sizeof(str), "write %d", i);
start = zx_ticks_get();
count = NumOps;
while (count--) {
ASSERT_EQ(write(fd, data.get(), DataSize), DataSize);
}
time_end(str, start);
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
snprintf(str, sizeof(str), "read %d", i);
start = zx_ticks_get();
count = NumOps;
while (count--) {
ASSERT_EQ(read(fd, data.get(), DataSize), DataSize);
ASSERT_EQ(data[0], kMagicByte);
}
time_end(str, start);
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
}
ASSERT_EQ(close(fd), 0);
ASSERT_EQ(unlink(MOUNT_POINT "/bigfile"), 0);
END_TEST;
}
#define START_STRING "/aaa"
size_t constexpr kComponentLength = fbl::constexpr_strlen(START_STRING);
template <size_t len>
void increment_str(char* str) {
// "Increment" the string alphabetically.
// '/aaa' --> '/aab, '/aaz' --> '/aba', etc
for (size_t j = len - 1; j > 0; j--) {
str[j] = static_cast<char>(str[j] + 1);
if (str[j] > 'z') {
str[j] = 'a';
} else {
return;
}
}
}
template <size_t MaxComponents>
bool walk_down_path_components(char* path, bool (*cb)(const char* path)) {
static_assert(MaxComponents * kComponentLength + fbl::constexpr_strlen(MOUNT_POINT) < PATH_MAX,
"Path depth is too long");
size_t path_len = strlen(path);
char path_component[kComponentLength + 1];
strcpy(path_component, START_STRING);
for (size_t i = 0; i < MaxComponents; i++) {
strcpy(path + path_len, path_component);
path_len += kComponentLength;
ASSERT_TRUE(cb(path), "Callback failure");
increment_str<kComponentLength>(path_component);
}
return true;
}
bool walk_up_path_components(char* path, bool (*cb)(const char* path)) {
size_t path_len = strlen(path);
while (path_len != fbl::constexpr_strlen(MOUNT_POINT)) {
ASSERT_TRUE(cb(path), "Callback failure");
path[path_len - kComponentLength] = 0;
path_len -= kComponentLength;
}
return true;
}
bool mkdir_callback(const char* path) {
ASSERT_EQ(mkdir(path, 0666), 0, "Could not make directory");
return true;
}
bool stat_callback(const char* path) {
struct stat buf;
ASSERT_EQ(stat(path, &buf), 0, "Could not stat directory");
return true;
}
bool unlink_callback(const char* path) {
ASSERT_EQ(unlink(path), 0, "Could not unlink directory");
return true;
}
template <size_t MaxComponents>
bool benchmark_path_walk(void) {
BEGIN_TEST;
printf("\nBenchmarking Long path walk (%lu components)\n", MaxComponents);
char path[PATH_MAX];
strcpy(path, MOUNT_POINT);
uint64_t start;
start = zx_ticks_get();
ASSERT_TRUE(walk_down_path_components<MaxComponents>(path, mkdir_callback));
time_end("mkdir", start);
strcpy(path, MOUNT_POINT);
start = zx_ticks_get();
ASSERT_TRUE(walk_down_path_components<MaxComponents>(path, stat_callback));
time_end("stat", start);
start = zx_ticks_get();
ASSERT_TRUE(walk_up_path_components(path, unlink_callback));
time_end("unlink", start);
END_TEST;
}
BEGIN_TEST_CASE(basic_benchmarks)
RUN_TEST_PERFORMANCE((benchmark_write_read<16 * KB, 1024>))
RUN_TEST_PERFORMANCE((benchmark_write_read<16 * KB, 2048>))
RUN_TEST_PERFORMANCE((benchmark_write_read<16 * KB, 4096>))
RUN_TEST_PERFORMANCE((benchmark_write_read<16 * KB, 8192>))
RUN_TEST_PERFORMANCE((benchmark_write_read<16 * KB, 16384>))
RUN_TEST_PERFORMANCE((benchmark_path_walk<125>))
RUN_TEST_PERFORMANCE((benchmark_path_walk<250>))
RUN_TEST_PERFORMANCE((benchmark_path_walk<500>))
RUN_TEST_PERFORMANCE((benchmark_path_walk<1000>))
END_TEST_CASE(basic_benchmarks)