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