blob: e7493ffc0088eb0141c826c89cf520bf6f2195a9 [file] [log] [blame]
Austin Schuhe6b2b882023-02-04 11:42:40 -08001#include <fcntl.h>
James Kuszmaul4b208072023-05-03 09:39:32 -05002#include <sys/resource.h>
Austin Schuhe6b2b882023-02-04 11:42:40 -08003#include <sys/stat.h>
4#include <sys/types.h>
Austin Schuhf97b4632023-02-11 16:52:58 -08005#include <sys/uio.h>
Austin Schuhe6b2b882023-02-04 11:42:40 -08006
7#include <chrono>
Brennan Coslett7a8533f2023-04-13 13:08:17 -05008#include <csignal>
9#include <filesystem>
Austin Schuhe6b2b882023-02-04 11:42:40 -080010
Philipp Schrader790cb542023-07-05 21:06:52 -070011#include "gflags/gflags.h"
12#include "glog/logging.h"
13
Brennan Coslett7a8533f2023-04-13 13:08:17 -050014#include "aos/containers/resizeable_buffer.h"
Austin Schuhe6b2b882023-02-04 11:42:40 -080015#include "aos/init.h"
16#include "aos/realtime.h"
17#include "aos/time/time.h"
Austin Schuhe6b2b882023-02-04 11:42:40 -080018
19namespace chrono = std::chrono;
20
21DEFINE_string(file, "/media/sda1/foo", "File to write to.");
22
23DEFINE_uint32(write_size, 4096, "Size of hunk to write");
Brennan Coslett7a8533f2023-04-13 13:08:17 -050024DEFINE_bool(cleanup, true, "If true, delete the created file");
James Kuszmaul4b208072023-05-03 09:39:32 -050025DEFINE_int32(nice, -20,
26 "Priority to nice to. Set to 0 to not change the priority.");
Austin Schuhe6b2b882023-02-04 11:42:40 -080027DEFINE_bool(sync, false, "If true, sync the file after each written block.");
Austin Schuhf97b4632023-02-11 16:52:58 -080028DEFINE_bool(writev, false, "If true, use writev.");
Austin Schuhe6b2b882023-02-04 11:42:40 -080029DEFINE_bool(direct, false, "If true, O_DIRECT.");
Austin Schuhf97b4632023-02-11 16:52:58 -080030DEFINE_uint32(chunks, 1, "Chunks to write using writev.");
31DEFINE_uint32(chunk_size, 512, "Chunk size to write using writev.");
Austin Schuh6049d342023-06-01 12:20:51 -070032DEFINE_uint64(overall_size, 0,
33 "If nonzero, write this many bytes and then stop. Must be a "
34 "multiple of --write_size");
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -070035DEFINE_bool(rate_limit, false,
36 "If true, kick off writes every 100ms to mimic logger write "
37 "patterns more correctly.");
38DEFINE_double(write_bandwidth, 120.0,
39 "Write speed in MB/s to simulate. This is only used when "
40 "--rate_limit is specified.");
Austin Schuhe6b2b882023-02-04 11:42:40 -080041
Brennan Coslett7a8533f2023-04-13 13:08:17 -050042// Stolen from aos/events/logging/DummyEncoder
43class AlignedReallocator {
44 public:
45 static void *Realloc(void *old, size_t old_size, size_t new_capacity) {
46 void *new_memory = std::aligned_alloc(512, new_capacity);
47 if (old) {
48 memcpy(new_memory, old, old_size);
49 free(old);
50 }
51 return new_memory;
52 }
53};
54
55void trap_sig(int signum) { exit(signum); }
56
Austin Schuh6049d342023-06-01 12:20:51 -070057aos::monotonic_clock::time_point start_time = aos::monotonic_clock::min_time;
58std::atomic<size_t> written_data = 0;
59
Brennan Coslett7a8533f2023-04-13 13:08:17 -050060void cleanup() {
Austin Schuh6049d342023-06-01 12:20:51 -070061 LOG(INFO) << "Overall average write speed: "
62 << ((written_data) /
63 chrono::duration<double>(aos::monotonic_clock::now() -
64 start_time)
65 .count() /
66 1024. / 1024.)
67 << " MB/s for " << static_cast<double>(written_data) / 1024. / 1024.
68 << "MB";
69
Brennan Coslett7a8533f2023-04-13 13:08:17 -050070 // Delete FLAGS_file at shutdown
71 PCHECK(std::filesystem::remove(FLAGS_file) != 0) << "Failed to cleanup file";
72}
73
Philipp Schrader790cb542023-07-05 21:06:52 -070074int main(int argc, char **argv) {
Austin Schuhe6b2b882023-02-04 11:42:40 -080075 aos::InitGoogle(&argc, &argv);
Brennan Coslett7a8533f2023-04-13 13:08:17 -050076 // c++ needs bash's trap <fcn> EXIT
77 // instead we get this mess :(
78 if (FLAGS_cleanup) {
79 std::signal(SIGINT, trap_sig);
80 std::signal(SIGTERM, trap_sig);
81 std::signal(SIGKILL, trap_sig);
82 std::signal(SIGHUP, trap_sig);
83 std::atexit(cleanup);
84 }
85 aos::AllocatorResizeableBuffer<AlignedReallocator> data;
Austin Schuhe6b2b882023-02-04 11:42:40 -080086
Austin Schuhe6b2b882023-02-04 11:42:40 -080087 {
Brennan Coslett7a8533f2023-04-13 13:08:17 -050088 // We want uncompressible data. The easiest way to do this is to grab a
89 // good sized block from /dev/random, and then reuse it.
Austin Schuhe6b2b882023-02-04 11:42:40 -080090 int random_fd = open("/dev/random", O_RDONLY | O_CLOEXEC);
91 PCHECK(random_fd != -1) << ": Failed to open /dev/random";
92 data.resize(FLAGS_write_size);
93 size_t written = 0;
94 while (written < data.size()) {
95 const size_t result =
96 read(random_fd, data.data() + written, data.size() - written);
97 PCHECK(result > 0);
98 written += result;
99 }
100
101 PCHECK(close(random_fd) == 0);
102 }
103
Austin Schuhf97b4632023-02-11 16:52:58 -0800104 std::vector<struct iovec> iovec;
Brennan Coslett7a8533f2023-04-13 13:08:17 -0500105 if (FLAGS_writev) {
106 iovec.resize(FLAGS_chunks);
107 CHECK_LE(FLAGS_chunks * FLAGS_chunk_size, FLAGS_write_size);
Austin Schuhf97b4632023-02-11 16:52:58 -0800108
Brennan Coslett7a8533f2023-04-13 13:08:17 -0500109 for (size_t i = 0; i < FLAGS_chunks; ++i) {
110 iovec[i].iov_base = &data.at(i * FLAGS_chunk_size);
111 iovec[i].iov_len = FLAGS_chunk_size;
112 }
113 iovec[FLAGS_chunks - 1].iov_base =
114 &data.at((FLAGS_chunks - 1) * FLAGS_chunk_size);
115 iovec[FLAGS_chunks - 1].iov_len =
116 data.size() - (FLAGS_chunks - 1) * FLAGS_chunk_size;
Austin Schuhf97b4632023-02-11 16:52:58 -0800117 }
Austin Schuhf97b4632023-02-11 16:52:58 -0800118
Philipp Schrader790cb542023-07-05 21:06:52 -0700119 int fd =
120 open(FLAGS_file.c_str(),
121 O_RDWR | O_CLOEXEC | O_CREAT | (FLAGS_direct ? O_DIRECT : 0), 0774);
Austin Schuhe6b2b882023-02-04 11:42:40 -0800122 PCHECK(fd != -1);
123
Austin Schuh6049d342023-06-01 12:20:51 -0700124 start_time = aos::monotonic_clock::now();
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700125 aos::monotonic_clock::time_point last_print_time = start_time;
126 aos::monotonic_clock::time_point cycle_start_time = start_time;
Austin Schuhe6b2b882023-02-04 11:42:40 -0800127 size_t last_written_data = 0;
Austin Schuh6049d342023-06-01 12:20:51 -0700128 written_data = 0;
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700129 // Track how much data we write per cycle. When --rate_limit is specified,
130 // --write_bandwidth is the amount of data we want to write per second, and we
131 // want to write it in cycles of 100ms to simulate the logger.
132 size_t cycle_written_data = 0;
133 size_t data_per_cycle = std::numeric_limits<size_t>::max();
134 if (FLAGS_rate_limit) {
135 data_per_cycle =
136 static_cast<size_t>((FLAGS_write_bandwidth * 1024 * 1024) / 10);
137 }
Austin Schuhe6b2b882023-02-04 11:42:40 -0800138
James Kuszmaul4b208072023-05-03 09:39:32 -0500139 if (FLAGS_nice != 0) {
140 PCHECK(-1 != setpriority(PRIO_PROCESS, 0, FLAGS_nice))
141 << ": Renicing to " << FLAGS_nice << " failed";
142 }
143
Austin Schuhe6b2b882023-02-04 11:42:40 -0800144 while (true) {
Austin Schuh6049d342023-06-01 12:20:51 -0700145 // Bail if we have written our limit.
146 if (written_data >= FLAGS_overall_size && FLAGS_overall_size != 0) {
147 break;
148 }
149
Austin Schuhf97b4632023-02-11 16:52:58 -0800150 if (FLAGS_writev) {
151 PCHECK(writev(fd, iovec.data(), iovec.size()) ==
152 static_cast<ssize_t>(data.size()))
153 << ": Failed after "
154 << chrono::duration<double>(aos::monotonic_clock::now() - start_time)
155 .count();
156 } else {
157 PCHECK(write(fd, data.data(), data.size()) ==
158 static_cast<ssize_t>(data.size()))
159 << ": Failed after "
160 << chrono::duration<double>(aos::monotonic_clock::now() - start_time)
161 .count();
162 }
Austin Schuhe6b2b882023-02-04 11:42:40 -0800163
164 // Trigger a flush if asked.
165 if (FLAGS_sync) {
166 const aos::monotonic_clock::time_point monotonic_now =
167 aos::monotonic_clock::now();
168 sync_file_range(fd, written_data, data.size(), SYNC_FILE_RANGE_WRITE);
169
170 // Now, blocking flush the previous page so we don't get too far ahead.
171 // This is Linus' recommendation.
172 if (written_data > 0) {
173 sync_file_range(fd, written_data - data.size(), data.size(),
174 SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE |
175 SYNC_FILE_RANGE_WAIT_AFTER);
176 posix_fadvise(fd, written_data - data.size(), data.size(),
177 POSIX_FADV_DONTNEED);
178 }
179 VLOG(1) << "Took "
180 << chrono::duration<double>(aos::monotonic_clock::now() -
181 monotonic_now)
182 .count();
183 }
184
185 written_data += data.size();
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700186 cycle_written_data += data.size();
187
188 // Simulate the logger by writing the specified amount of data in periods of
189 // 100ms.
190 bool reset_cycle = false;
191 if (cycle_written_data > data_per_cycle && FLAGS_rate_limit) {
192 // Check how much data we should have already written based on
193 // --write_bandwidth.
194 const size_t current_target =
195 FLAGS_write_bandwidth * 1024 * 1024 *
196 chrono::duration<double>(aos::monotonic_clock::now() - start_time)
197 .count();
198 const bool caught_up = written_data > current_target;
199 if (caught_up) {
200 // If we're on track, sleep for the rest of this cycle, as long as we
201 // didn't use up all the cycle time writing.
202 const aos::monotonic_clock::time_point monotonic_now =
203 aos::monotonic_clock::now();
204 const auto sleep_duration =
205 (cycle_start_time + chrono::milliseconds(100)) - monotonic_now;
206 if (sleep_duration.count() > 0) {
207 VLOG(2) << "Sleeping for " << sleep_duration.count();
208 std::this_thread::sleep_for(sleep_duration);
209 } else {
210 LOG(WARNING) << "It took longer than 100ms to write "
211 << data_per_cycle << " bytes.";
212 }
213 reset_cycle = true;
214 } else {
215 // If we aren't on track, don't sleep.
216 LOG(WARNING) << "Still catching up to target write rate.";
217 }
218 // Either way, reset the data we're counting for this "cycle". If we're
219 // still behind, let's check again after writing another data_per_cycle
220 // bytes.
221 cycle_written_data = 0;
222 }
Austin Schuhe6b2b882023-02-04 11:42:40 -0800223
224 const aos::monotonic_clock::time_point monotonic_now =
225 aos::monotonic_clock::now();
226 // Print out MB/s once it has been at least 1 second since last time.
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700227 if (monotonic_now > last_print_time + chrono::seconds(1)) {
Austin Schuhe6b2b882023-02-04 11:42:40 -0800228 LOG(INFO)
229 << ((written_data - last_written_data) /
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700230 chrono::duration<double>(monotonic_now - last_print_time)
231 .count() /
Austin Schuhe6b2b882023-02-04 11:42:40 -0800232 1024. / 1024.)
Austin Schuh6049d342023-06-01 12:20:51 -0700233 << " MB/s, average of "
234 << (written_data /
235 chrono::duration<double>(monotonic_now - start_time).count() /
236 1024. / 1024.)
237 << " MB/s for " << static_cast<double>(written_data) / 1024. / 1024.
238 << "MB";
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700239 last_print_time = monotonic_now;
Austin Schuhe6b2b882023-02-04 11:42:40 -0800240 last_written_data = written_data;
241 }
Sanjay Narayanan2cefd5c2023-06-01 16:52:03 -0700242
243 // Do this at the end so that we're setting the next cycle start time as
244 // accurately as possible.
245 if (reset_cycle) {
246 cycle_start_time = monotonic_now;
247 VLOG(1) << cycle_start_time;
248 }
Austin Schuhe6b2b882023-02-04 11:42:40 -0800249 }
250
251 return 0;
252}