diff --git a/.gitignore b/.gitignore index 46dc357..294960a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +release/ *.o tmp/ *.log diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index acaa545..6b212a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,4 +8,18 @@ elseif(UNIX) # Linux, BSD etc target_link_libraries(fuse-cpp-ramfs fuse) endif() target_link_libraries(fuse-cpp-ramfs pthread) + +add_executable(copy-bench copy_bench.cpp directory.cpp inode.cpp symlink.cpp file.cpp special_inode.cpp fuse_cpp_ramfs.cpp) +set_property(TARGET copy-bench PROPERTY CXX_STANDARD 17) +target_compile_definitions(copy-bench PRIVATE FUSE_USE_VERSION=30 _FILE_OFFSET_BITS=64) +if(APPLE) + target_link_libraries(copy-bench osxfuse) +elseif(UNIX) # Linux, BSD etc + target_link_libraries(copy-bench fuse) +endif() +find_package(gflags REQUIRED) +find_package(benchmark REQUIRED) +target_link_libraries(copy-bench gflags benchmark profiler) + install(TARGETS fuse-cpp-ramfs DESTINATION bin) +install(TARGETS copy-bench DESTINATION bin) diff --git a/src/copy_bench.cpp b/src/copy_bench.cpp new file mode 100644 index 0000000..1e960d8 --- /dev/null +++ b/src/copy_bench.cpp @@ -0,0 +1,326 @@ +#define FILE_OFFSET_BITS 64 +#include "common.h" + +#define VERIFS2_COPY_BENCH +#include "inode.hpp" +#include "file.hpp" +#include "directory.hpp" +#include "symlink.hpp" +#include "special_inode.hpp" + +#include +#include +#include +#include + +DEFINE_int32(inodes, 100000, "Number of inodes to generate"); +DEFINE_int32(fsize_min, 0, "Minimum size of regular file (in bytes)"); +DEFINE_int32(fsize_max, 65536, "Maximum size of regular file (in bytes)"); +DEFINE_int32(dirent_min, 2, "Minimum number of children in each directory"); +DEFINE_int32(dirent_max, 10000, "Max number of children in each directory"); +DEFINE_double(file_ratio, 0.75, "Ratio of regular files"); +DEFINE_double(dir_ratio, 0.15, "Ratio of directories"); +DEFINE_double(symlink_ratio, 0.09, "Ratio of symlinks"); +DEFINE_double(special_ratio, 0.01, "Ratio of special files"); + +enum benchmark::TimeUnit display_time_unit; +std::string& toLower(std::string &str) { + const int kDiff = 'a' - 'A'; + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] >= 'A' && str[i] <= 'Z') + str[i] -= kDiff; + } + return str; +} +bool validate_unit_option(const char *flagname, const std::string &value) { + std::string val2 = value; + toLower(val2); + if (val2 == "ms" || val2 == "millisecond" || val2 == "milliseconds") + display_time_unit = benchmark::kMillisecond; + else if (val2 == "us" || val2 == "microsecond" || val2 == "microseconds") + display_time_unit = benchmark::kMicrosecond; + else if (val2 == "ns" || val2 == "nanosecond" || val2 == "nanoseconds") + display_time_unit = benchmark::kNanosecond; + else if (val2 == "s" || val2 == "second" || val2 == "seconds") + display_time_unit = benchmark::kSecond; + else + return false; + return true; +} +DEFINE_string(time_unit, "ms", "Unit of time"); +DEFINE_validator(time_unit, &validate_unit_option); + +int rand_range(int lower, int upper) { + long randval = rand(); + return (randval % (upper - lower + 1)) + lower; +} + +template +T rand_select(std::vector &choices) { + int idx = rand_range(0, choices.size() - 1); + return choices[idx]; +} + +std::string gen_rand_string(int minlen, int maxlen) { + static std::string alphabet("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); + int len = rand_range(minlen, maxlen); + std::string result; + for (int i = 0; i < len; ++i) { + result += alphabet[rand_range(0, alphabet.size() - 1)]; + } + return result; +} + +void fill_buf_with_rand(void *buf, size_t size) { + int *ptr = (int *)buf; + for (size_t i = 0; i < size; i += sizeof(int), ++ptr) { + int r = rand(); + if (size - i >= sizeof(int)) + *ptr = r; + else + memcpy(ptr, &r, size - i); + } +} + +size_t fill_file(File *f) { + int datasize = rand_range(FLAGS_fsize_min, FLAGS_fsize_max); + char *data = new char[datasize]; + fill_buf_with_rand(data, datasize); + f->_set_buffer(data); + return datasize; +} + +size_t fill_directory(Directory *d) { + int nchildren = rand_range(FLAGS_dirent_min, FLAGS_dirent_max); + size_t total_size = 0;; + for (int i = 0; i < nchildren; ++i) { + std::string name = gen_rand_string(1, NAME_MAX); + auto it = d->_children().find(name); + if (it != d->_children().end()) { + i--; + continue; + } + d->_children().insert({name, 0}); + total_size += name.capacity() + sizeof(std::string) + sizeof(fuse_ino_t) + + sizeof(std::_Rb_tree_node_base); + } + return total_size; +} + +Inode *create_inode() { + double rn = (double)rand() / RAND_MAX; + Inode *res; + size_t total_size = 0; + + // double file_threshold = 0; + double dir_threshold = FLAGS_file_ratio; + double symlink_threshold = FLAGS_file_ratio + FLAGS_dir_ratio; + double special_threshold = FLAGS_file_ratio + FLAGS_dir_ratio + + FLAGS_symlink_ratio; + + if (rn < dir_threshold) { + File *file = new File(); + total_size += fill_file(file) + sizeof(File); + res = dynamic_cast(file); + res->_attrs().st_mode = S_IFREG; + } else if (rn >= dir_threshold && rn < symlink_threshold) { + Directory *dir = new Directory(); + total_size += fill_directory(dir) + sizeof(Directory); + res = dynamic_cast(dir); + res->_attrs().st_mode = S_IFDIR; + } else if (rn >= symlink_threshold && rn < special_threshold) { + std::string linkpath = gen_rand_string(1, PATH_MAX); + SymLink *sym = new SymLink(linkpath); + total_size += sizeof(SymLink) + linkpath.capacity(); + res = dynamic_cast(sym); + res->_attrs().st_mode = S_IFLNK; + } else if (rn >= special_threshold) { + std::vector choices({ + SPECIAL_INODE_TYPE_NO_BLOCK, SPECIAL_INODE_CHAR_DEV, + SPECIAL_INODE_BLOCK_DEV, SPECIAL_INODE_FIFO, SPECIAL_INODE_SOCK}); + enum SpecialInodeTypes itype = rand_select(choices); + SpecialInode *sp = new SpecialInode(itype); + total_size += sizeof(SpecialInode); + res = dynamic_cast(sp); + mode_t mode; + switch(itype) { + case SPECIAL_INODE_CHAR_DEV: + mode = S_IFCHR; + break; + + case SPECIAL_INODE_BLOCK_DEV: + mode = S_IFBLK; + break; + + case SPECIAL_INODE_FIFO: + mode = S_IFIFO; + break; + + case SPECIAL_INODE_SOCK: + mode = S_IFSOCK; + break; + + default: + mode = 0; + break; + } + res->_attrs().st_mode = mode; + res->_attrs().st_dev = 0; + } + res->_attrs().st_size = total_size; + + return res; +} + +size_t generate_inodes(std::vector &table) { + size_t total_size = 0; + for (int i = 0; i < FLAGS_inodes; ++i) { + Inode *inode = create_inode(); + table.push_back(inode); + total_size += sizeof(Inode *) + inode->Size(); + } + return total_size; +} + +void destroy_inodes(std::vector &table) { + for (Inode *inode : table) { + delete inode; + } + table.clear(); +} + +void generate_files(std::vector &files, std::vector &ref) { + for (Inode *iptr : ref) { + /* Determine the total size of this inode */ + size_t thissize = sizeof(Inode *) + iptr->Size(); + if (S_ISREG(iptr->_attrs().st_mode)) { + thissize += sizeof(File); + } else if (S_ISDIR(iptr->_attrs().st_mode)) { + thissize += sizeof(Directory); + } else if (S_ISLNK(iptr->_attrs().st_mode)) { + thissize += sizeof(SymLink); + } else { + thissize += sizeof(SpecialInode); + } + /* The size of data buffer */ + size_t bufsize = (thissize > sizeof(File)) ? thissize - sizeof(File) : 0; + void *data = malloc(bufsize); + assert(data != nullptr); + fill_buf_with_rand(data, bufsize); + files.emplace_back(data, bufsize); + } +} + +void copy_files(std::vector &dest, const std::vector &src) { + dest = src; +} + +void copy_inodes(std::vector &dest, const std::vector &src) { + for (size_t i = 0; i < src.size(); ++i) { + Inode *src_i = src[i]; + if (S_ISREG(src_i->_attrs().st_mode)) { + File *src_f = dynamic_cast(src_i); + File *dest_f = new File(*src_f); + dest.push_back(dynamic_cast(dest_f)); + } else if (S_ISDIR(src_i->_attrs().st_mode)) { + Directory *src_d = dynamic_cast(src_i); + Directory *dst_d = new Directory(*src_d); + dest.push_back(dynamic_cast(dst_d)); + } else if (S_ISLNK(src_i->_attrs().st_mode)) { + SymLink *src_l = dynamic_cast(src_i); + SymLink *dst_l = new SymLink(*src_l); + dest.push_back(dynamic_cast(dst_l)); + } else { + SpecialInode *src_sp = dynamic_cast(src_i); + SpecialInode *dst_sp = new SpecialInode(*src_sp); + dest.push_back(dynamic_cast(dst_sp)); + } + } +} + +static std::vector source; +static std::vector files; +static size_t total_size; +static void *data; + +class CopyBenchTool { +public: + static constexpr double eps = 1e-6; + void SetUp() { + double total_ratio = FLAGS_file_ratio + FLAGS_dir_ratio + + FLAGS_symlink_ratio + FLAGS_special_ratio; + if (std::abs(1.0 - total_ratio) > eps) { + printf("file_ratio + dir_ratio + symlink_ratio + special_ratio " + "must be equal to 1.0.\n"); + exit(1); + } + + setvbuf(stdout, NULL, _IONBF, 0); + printf("Generating inode table..."); + total_size = generate_inodes(source); + printf("%zu bytes (%.2f MB)\n", total_size, 1.0 * total_size / 1024 / 1024); + printf("Generating a list of files that have the same amount of data..."); + generate_files(files, source); + printf("Done.\n"); + printf("Generating the same amount of contiguous data..."); + data = malloc(total_size); + int *ptr = (int *)data; + for (size_t i = 0; i < total_size; i += sizeof(int), ptr++) { + *ptr = rand(); + } + printf("Done.\n"); + } + + void TearDown() { + destroy_inodes(source); + free(data); + } +}; + +static void BM_CopyInodeTable(benchmark::State &state) { + for (auto _ : state) { + std::vector dest; + copy_inodes(dest, source); + + state.PauseTiming(); + destroy_inodes(dest); + state.ResumeTiming(); + } +} + +static void BM_CopyFiles(benchmark::State &state) { + for (auto _ : state) { + std::vector dest; + copy_files(dest, files); + + state.PauseTiming(); + dest.clear(); + state.ResumeTiming(); + } +} + +static void BM_CopyData(benchmark::State &state) { + for (auto _ : state) { + void *copy_data = malloc(total_size); + memcpy(copy_data, data, total_size); + + state.PauseTiming(); + free(copy_data); + state.ResumeTiming(); + } +} + +int main(int argc, char **argv) { + ::benchmark::Initialize(&argc, argv); + gflags::ParseCommandLineFlags(&argc, &argv, false); + // The purpose of this is to "use" a function in libprofilee so that the + // compiler will always link the library. + ProfilerEnable(); + CopyBenchTool copybench; + copybench.SetUp(); + BENCHMARK(BM_CopyInodeTable)->Unit(display_time_unit); + BENCHMARK(BM_CopyFiles)->Unit(display_time_unit); + BENCHMARK(BM_CopyData)->Unit(display_time_unit); + ::benchmark::RunSpecifiedBenchmarks(); + copybench.TearDown(); +} diff --git a/src/directory.hpp b/src/directory.hpp index dd6f5ec..e8fc884 100644 --- a/src/directory.hpp +++ b/src/directory.hpp @@ -31,6 +31,11 @@ class Directory : public Inode { public: ~Directory() {} + Directory() {} + Directory(const Directory &d) : Inode(d) { + m_children = d.m_children; + } + void Initialize(fuse_ino_t ino, mode_t mode, nlink_t nlink, gid_t gid, uid_t uid); fuse_ino_t _ChildInodeNumberWithName(const std::string &name); fuse_ino_t ChildInodeNumberWithName(const std::string &name); @@ -51,6 +56,13 @@ class Directory : public Inode { const std::map &Children() { return m_children; } std::shared_mutex& DirLock() { return childrenRwSem; } + +#ifdef VERIFS2_COPY_BENCH + /* Only for benchmarking purpose! */ + std::map& _children() { + return m_children; + } +#endif }; #endif /* directory_hpp */ diff --git a/src/file.hpp b/src/file.hpp index cf94f2d..d23e44a 100644 --- a/src/file.hpp +++ b/src/file.hpp @@ -8,16 +8,51 @@ class File : public Inode { private: void *m_buf; + + void copy_from_other(const File &f) { + size_t bufsize = f.m_fuseEntryParam.attr.st_size; + m_buf = malloc(bufsize); + memcpy(m_buf, f.m_buf, bufsize); + } public: File() : m_buf(NULL) {} + +#ifdef VERIFS2_COPY_BENCH + File(void *buf, size_t bufsize) : m_buf(buf) { + m_fuseEntryParam.attr.st_size = bufsize; + } +#endif + + File(const File &f) : Inode(f) { + copy_from_other(f); + } + + File& operator=(const File &f) { + if (&f != this) { + Inode::operator=(f); + copy_from_other(f); + } + return *this; + } ~File(); int WriteAndReply(fuse_req_t req, const char *buf, size_t size, off_t off); int ReadAndReply(fuse_req_t req, size_t size, off_t off); int FileTruncate(size_t newSize); + +#ifdef VERIFS2_COPY_BENCH + /* Only for use for benchmarking purpose! */ + void _set_buffer(void *buf) { + m_buf = buf; + } + + void *_get_buffer(void) { + return m_buf; + } +#endif // size_t Size(); }; diff --git a/src/inode.hpp b/src/inode.hpp index 0539e33..7bd3af9 100644 --- a/src/inode.hpp +++ b/src/inode.hpp @@ -17,6 +17,13 @@ class Inode { std::shared_mutex entryRwSem; std::map > m_xattr; std::shared_mutex xattrRwSem; + + void copy_from_others(const Inode &src) { + m_markedForDeletion = src.m_markedForDeletion; + m_nlookup.store(src.m_nlookup.load()); + m_fuseEntryParam = src.m_fuseEntryParam; + m_xattr = src.m_xattr; + } public: static const size_t BufBlockSize = 512; @@ -26,6 +33,16 @@ class Inode { m_markedForDeletion(false), m_nlookup(0) {} + + Inode(const Inode &src) { + copy_from_others(src); + } + + Inode& operator=(const Inode &src) { + if (&src != this) + copy_from_others(src); + return *this; + } virtual ~Inode() = 0; @@ -78,6 +95,13 @@ class Inode { fuse_ino_t GetIno() { return m_fuseEntryParam.attr.st_ino; } bool Forgotten() { return m_nlookup == 0; } + +#ifdef VERIFS2_COPY_BENCH + /* Only for benchmarking purposes */ + struct stat& _attrs() { + return m_fuseEntryParam.attr; + } +#endif }; #endif /* inode_hpp */ diff --git a/src/special_inode.hpp b/src/special_inode.hpp index b540ab7..b3e318f 100644 --- a/src/special_inode.hpp +++ b/src/special_inode.hpp @@ -19,6 +19,10 @@ class SpecialInode : public Inode { public: SpecialInode(enum SpecialInodeTypes type, dev_t dev = 0); ~SpecialInode() {}; + + SpecialInode(const SpecialInode &sp) : Inode(sp) { + m_type = sp.m_type; + } int WriteAndReply(fuse_req_t req, const char *buf, size_t size, off_t off); diff --git a/src/symlink.hpp b/src/symlink.hpp index 84f179e..550eb7c 100644 --- a/src/symlink.hpp +++ b/src/symlink.hpp @@ -12,6 +12,10 @@ class SymLink : public Inode { public: SymLink(const std::string &link) : m_link(link) {} + + SymLink(const SymLink &sym) : Inode(sym) { + m_link = sym.m_link; + } ~SymLink() {};