Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/ByteBuffer.h>
9#include <AK/DeprecatedString.h>
10#include <AK/ScopeGuard.h>
11#include <AK/Types.h>
12#include <AK/Vector.h>
13#include <LibCore/ArgsParser.h>
14#include <LibCore/ElapsedTimer.h>
15#include <LibCore/System.h>
16#include <LibMain/Main.h>
17#include <fcntl.h>
18#include <stdio.h>
19#include <sys/stat.h>
20#include <unistd.h>
21
22struct Result {
23 u64 write_bps {};
24 u64 read_bps {};
25};
26
27static Result average_result(Vector<Result> const& results)
28{
29 Result average;
30
31 for (auto& res : results) {
32 average.write_bps += res.write_bps;
33 average.read_bps += res.read_bps;
34 }
35
36 average.write_bps /= results.size();
37 average.read_bps /= results.size();
38
39 return average;
40}
41
42static ErrorOr<Result> benchmark(DeprecatedString const& filename, int file_size, ByteBuffer& buffer, bool allow_cache);
43
44ErrorOr<int> serenity_main(Main::Arguments arguments)
45{
46 using namespace AK::TimeLiterals;
47
48 DeprecatedString directory = ".";
49 i64 time_per_benchmark_sec = 10;
50 Vector<size_t> file_sizes;
51 Vector<size_t> block_sizes;
52 bool allow_cache = false;
53
54 Core::ArgsParser args_parser;
55 args_parser.add_option(allow_cache, "Allow using disk cache", "cache", 'c');
56 args_parser.add_option(directory, "Path to a directory where we can store the disk benchmark temp file", "directory", 'd', "directory");
57 args_parser.add_option(time_per_benchmark_sec, "Time elapsed per benchmark (seconds)", "time-per-benchmark", 't', "time-per-benchmark");
58 args_parser.add_option(file_sizes, "A comma-separated list of file sizes", "file-size", 'f', "file-size");
59 args_parser.add_option(block_sizes, "A comma-separated list of block sizes", "block-size", 'b', "block-size");
60 args_parser.parse(arguments);
61
62 Time const time_per_benchmark = Time::from_seconds(time_per_benchmark_sec);
63
64 if (file_sizes.size() == 0) {
65 file_sizes = { 131072, 262144, 524288, 1048576, 5242880 };
66 }
67 if (block_sizes.size() == 0) {
68 block_sizes = { 8192, 32768, 65536 };
69 }
70
71 umask(0644);
72
73 auto filename = DeprecatedString::formatted("{}/disk_benchmark.tmp", directory);
74
75 for (auto file_size : file_sizes) {
76 for (auto block_size : block_sizes) {
77 if (block_size > file_size)
78 continue;
79
80 auto buffer_result = ByteBuffer::create_uninitialized(block_size);
81 if (buffer_result.is_error()) {
82 warnln("Not enough memory to allocate space for block size = {}", block_size);
83 continue;
84 }
85 Vector<Result> results;
86
87 outln("Running: file_size={} block_size={}", file_size, block_size);
88 auto timer = Core::ElapsedTimer::start_new();
89 while (timer.elapsed_time() < time_per_benchmark) {
90 out(".");
91 fflush(stdout);
92 auto result = TRY(benchmark(filename, file_size, buffer_result.value(), allow_cache));
93 results.append(result);
94 usleep(100);
95 }
96 auto average = average_result(results);
97 outln("Finished: runs={} time={}ms write_bps={} read_bps={}", results.size(), timer.elapsed(), average.write_bps, average.read_bps);
98
99 sleep(1);
100 }
101 }
102
103 return 0;
104}
105
106ErrorOr<Result> benchmark(DeprecatedString const& filename, int file_size, ByteBuffer& buffer, bool allow_cache)
107{
108 int flags = O_CREAT | O_TRUNC | O_RDWR;
109 if (!allow_cache)
110 flags |= O_DIRECT;
111
112 int fd = TRY(Core::System::open(filename, flags, 0644));
113
114 auto fd_cleanup = ScopeGuard([fd, filename] {
115 auto void_or_error = Core::System::close(fd);
116 if (void_or_error.is_error())
117 warnln("{}", void_or_error.release_error());
118
119 void_or_error = Core::System::unlink(filename);
120 if (void_or_error.is_error())
121 warnln("{}", void_or_error.release_error());
122 });
123
124 Result result;
125
126 auto timer = Core::ElapsedTimer::start_new();
127
128 ssize_t total_written = 0;
129 while (total_written < file_size) {
130 auto nwritten = TRY(Core::System::write(fd, buffer));
131 total_written += nwritten;
132 }
133
134 result.write_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000;
135
136 TRY(Core::System::lseek(fd, 0, SEEK_SET));
137
138 timer.start();
139 ssize_t total_read = 0;
140 while (total_read < file_size) {
141 auto nread = TRY(Core::System::read(fd, buffer));
142 total_read += nread;
143 }
144
145 result.read_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000;
146 return result;
147}