1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <fcntl.h>
6 #include <stdint.h>
7 #include <stdlib.h>
8 #include <sys/stat.h>
9 
10 #include <fbl/function.h>
11 #include <fbl/string.h>
12 #include <fbl/string_buffer.h>
13 #include <fbl/string_printf.h>
14 #include <fbl/unique_fd.h>
15 #include <fs-management/mount.h>
16 #include <fs-test-utils/fixture.h>
17 #include <fs-test-utils/perftest.h>
18 #include <perftest/perftest.h>
19 #include <unittest/unittest.h>
20 
21 #include <utility>
22 
23 namespace fs_bench {
24 namespace {
25 
26 using fs_test_utils::Fixture;
27 using fs_test_utils::FixtureOptions;
28 using fs_test_utils::PerformanceTestOptions;
29 using fs_test_utils::TestCaseInfo;
30 using fs_test_utils::TestInfo;
31 
32 constexpr int kWriteReadCycles = 3;
33 
GetBigFilePath(const Fixture & fixture)34 fbl::String GetBigFilePath(const Fixture& fixture) {
35     fbl::String path = fbl::StringPrintf("%s/bigfile.txt", fixture.fs_path().c_str());
36     return path;
37 }
38 
WriteBigFile(ssize_t data_size,perftest::RepeatState * state,Fixture * fixture)39 bool WriteBigFile(ssize_t data_size, perftest::RepeatState* state, Fixture* fixture) {
40     BEGIN_HELPER;
41 
42     fbl::unique_fd fd(open(GetBigFilePath(*fixture).c_str(), O_CREAT | O_WRONLY));
43     ASSERT_TRUE(fd);
44     state->DeclareStep("write");
45     uint8_t data[data_size];
46     uint8_t pattern = static_cast<uint8_t>(rand_r(fixture->mutable_seed()) % (1 << 8));
47     memset(data, pattern, data_size);
48 
49     while (state->KeepRunning()) {
50         ASSERT_EQ(write(fd.get(), data, data_size), data_size);
51     }
52 
53     END_HELPER;
54 }
55 
ReadBigFile(ssize_t data_size,perftest::RepeatState * state,Fixture * fixture)56 bool ReadBigFile(ssize_t data_size, perftest::RepeatState* state, Fixture* fixture) {
57     BEGIN_HELPER;
58     fbl::unique_fd fd(open(GetBigFilePath(*fixture).c_str(), O_RDONLY));
59 
60     uint8_t pattern = static_cast<uint8_t>(rand_r(fixture->mutable_seed()) % (1 << 8));
61     ASSERT_TRUE(fd);
62     state->DeclareStep("read");
63     uint8_t data[data_size];
64 
65     while (state->KeepRunning()) {
66         ASSERT_EQ(read(fd.get(), data, data_size), data_size);
67         ASSERT_EQ(data[0], pattern);
68     }
69 
70     END_HELPER;
71 }
72 
73 constexpr char kBaseComponent[] = "/aaa";
74 
75 constexpr size_t kComponentLength = fbl::constexpr_strlen(kBaseComponent);
76 
77 struct PathComponentGen {
PathComponentGenfs_bench::__anon95977d2f0111::PathComponentGen78     PathComponentGen() { memcpy(current, kBaseComponent, kComponentLength + 1); }
79 
80     // Advances current to the next component, following alphabetical order.
81     // E.g: /aaa -> /aab ..../aaz -> /aba
Nextfs_bench::__anon95977d2f0111::PathComponentGen82     void Next() {
83         for (int i = 3; i > 0; --i) {
84             char next = static_cast<char>(static_cast<uint8_t>(current[i]) + 1);
85             if (next > 'z') {
86                 current[i] = 'a';
87             } else {
88                 current[i] = next;
89                 break;
90             }
91         }
92     }
93     // Add extra byte for null termination.
94     char current[kComponentLength + 1];
95 };
96 
PathWalkDown(const fbl::String & op_name,const fbl::Function<int (const char *)> & op,perftest::RepeatState * state,Fixture * fixture,fbl::StringBuffer<fs_test_utils::kPathSize> * path)97 bool PathWalkDown(const fbl::String& op_name, const fbl::Function<int(const char*)>& op,
98                   perftest::RepeatState* state, Fixture* fixture,
99                   fbl::StringBuffer<fs_test_utils::kPathSize>* path) {
100     BEGIN_HELPER;
101     PathComponentGen component;
102     path->Append(fixture->fs_path());
103 
104     while (state->KeepRunning()) {
105         path->Append(component.current);
106         ASSERT_EQ(op(path->c_str()), 0);
107         component.Next();
108     }
109     END_HELPER;
110 }
111 
PathWalkUp(const fbl::String & op_name,const fbl::Function<int (const char *)> & op,perftest::RepeatState * state,Fixture * fixture,fbl::StringBuffer<fs_test_utils::kPathSize> * path)112 bool PathWalkUp(const fbl::String& op_name, const fbl::Function<int(const char*)>& op,
113                 perftest::RepeatState* state, Fixture* fixture,
114                 fbl::StringBuffer<fs_test_utils::kPathSize>* path) {
115     BEGIN_HELPER;
116     while (state->KeepRunning() && *path != fixture->fs_path()) {
117         ASSERT_EQ(op(path->c_str()), 0, path->c_str());
118         uint32_t new_size = static_cast<uint32_t>(path->length() - kComponentLength);
119         path->Resize(new_size);
120     }
121     END_HELPER;
122 }
123 
124 // Wrapper so state can be shared across calls.
125 class PathWalkOp {
126 public:
127     PathWalkOp() = default;
128     PathWalkOp(const PathWalkOp&) = delete;
129     PathWalkOp(PathWalkOp&&) = delete;
130     PathWalkOp& operator=(const PathWalkOp&) = delete;
131     PathWalkOp& operator=(PathWalkOp&&) = delete;
132     ~PathWalkOp() = default;
133 
134     // Will add components until |state::KeepGoing| returns false.
Mkdir(perftest::RepeatState * state,Fixture * fixture)135     bool Mkdir(perftest::RepeatState* state, Fixture* fixture) {
136         path_.Clear();
137         return PathWalkDown("mkdir", [](const char* path) { return mkdir(path, 0666); }, state,
138                             fixture, &path_);
139     }
140 
141     // Will stat components until |state::KeepGoing| returns false.
Stat(perftest::RepeatState * state,Fixture * fixture)142     bool Stat(perftest::RepeatState* state, Fixture* fixture) {
143         path_.Clear();
144         return PathWalkDown("stat",
145                             [](const char* path) {
146                                 struct stat buff;
147                                 return stat(path, &buff);
148                             },
149                             state, fixture, &path_);
150     }
151 
152     // Will unlink components until |state::KeepGoing| returns false.
Unlink(perftest::RepeatState * state,Fixture * fixture)153     bool Unlink(perftest::RepeatState* state, Fixture* fixture) {
154         return PathWalkUp("unlink", unlink, state, fixture, &path_);
155     }
156 
157 private:
158     fbl::StringBuffer<fs_test_utils::kPathSize> path_;
159 };
160 
161 } // namespace
162 
RunBenchmark(int argc,char ** argv)163 bool RunBenchmark(int argc, char** argv) {
164     FixtureOptions f_opts = FixtureOptions::Default(DISK_FORMAT_MINFS);
165     PerformanceTestOptions p_opts;
166     const int rw_test_sample_counts[] = {
167         1024,
168         2048,
169         4096,
170         8192,
171         16384,
172     };
173 
174     if (!fs_test_utils::ParseCommandLineArgs(argc, argv, &f_opts, &p_opts)) {
175         return false;
176     }
177 
178     fbl::Vector<TestCaseInfo> testcases;
179     // Just do a single cycle for unittest mode.
180     int cycles = (p_opts.is_unittest) ? 1 : kWriteReadCycles;
181     // Read Write tests.
182     for (int test_sample_count : rw_test_sample_counts) {
183         TestCaseInfo testcase;
184         testcase.sample_count = test_sample_count;
185         testcase.name = fbl::StringPrintf("%s/Bigfile/16Kbytes/%d-Ops",
186                                           disk_format_string_[f_opts.fs_type], test_sample_count);
187         testcase.teardown = false;
188         for (int cycle = 0; cycle < cycles; ++cycle) {
189             TestInfo write_test, read_test;
190             write_test.name =
191                 fbl::StringPrintf("%s/%d-Cycle/Write", testcase.name.c_str(), cycle + 1);
192             write_test.test_fn = [](perftest::RepeatState* state, Fixture* fixture) {
193                 return WriteBigFile(16 * (1 << 10), state, fixture);
194             };
195             write_test.required_disk_space = test_sample_count * 16 * 1024 * (cycle + 1);
196             testcase.tests.push_back(std::move(write_test));
197 
198             read_test.name =
199                 fbl::StringPrintf("%s/%d-Cycle/Read", testcase.name.c_str(), cycle + 1);
200             read_test.test_fn = [](perftest::RepeatState* state, Fixture* fixture) {
201                 return ReadBigFile(16 * (1 << 10), state, fixture);
202             };
203             read_test.required_disk_space = test_sample_count * 16 * 1024 * (cycle + 1);
204             testcase.tests.push_back(std::move(read_test));
205         }
206         testcases.push_back(std::move(testcase));
207     }
208 
209     // Path walk tests.
210     const int path_walk_sample_counts[] = {
211         125,
212         250,
213         500,
214         1000,
215     };
216 
217     PathWalkOp pw_op;
218     for (int test_sample_count : path_walk_sample_counts) {
219         TestCaseInfo testcase;
220         testcase.name = fbl::StringPrintf("%s/PathWalk/%d-Components",
221                                           disk_format_string_[f_opts.fs_type], test_sample_count);
222         testcase.sample_count = test_sample_count;
223         testcase.teardown = false;
224 
225         TestInfo mkdir_test;
226         mkdir_test.name = fbl::StringPrintf("%s/Mkdir", testcase.name.c_str());
227         mkdir_test.test_fn = fbl::BindMember(&pw_op, &PathWalkOp::Mkdir);
228         testcase.tests.push_back(std::move(mkdir_test));
229 
230         TestInfo stat_test;
231         stat_test.name = fbl::StringPrintf("%s/Stat", testcase.name.c_str());
232         stat_test.test_fn = fbl::BindMember(&pw_op, &PathWalkOp::Stat);
233         testcase.tests.push_back(std::move(stat_test));
234 
235         TestInfo unlink_test;
236         unlink_test.name = fbl::StringPrintf("%s/Unlink", testcase.name.c_str());
237         unlink_test.test_fn = fbl::BindMember(&pw_op, &PathWalkOp::Unlink);
238 
239         testcase.tests.push_back(std::move(unlink_test));
240         testcases.push_back(std::move(testcase));
241     }
242 
243     return fs_test_utils::RunTestCases(f_opts, p_opts, testcases);
244 }
245 } // namespace fs_bench
246 
main(int argc,char ** argv)247 int main(int argc, char** argv) {
248     return fs_test_utils::RunWithMemFs(
249         [argc, argv]() { return fs_bench::RunBenchmark(argc, argv) ? 0 : -1; });
250 }
251