Add mode to ssd_profiler to simulate logger

When --rate_limit is specified, write every 100ms like the logger does.
--write_bandwidth can be used to specify the effective write rate in
MB/s.

Change-Id: Ib6a0d4fec249ad1d559caf80a0f352565c8e3832
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/y2023/ssd_profiler.cc b/y2023/ssd_profiler.cc
index 008ed5e..e7493ff 100644
--- a/y2023/ssd_profiler.cc
+++ b/y2023/ssd_profiler.cc
@@ -32,6 +32,12 @@
 DEFINE_uint64(overall_size, 0,
               "If nonzero, write this many bytes and then stop.  Must be a "
               "multiple of --write_size");
+DEFINE_bool(rate_limit, false,
+            "If true, kick off writes every 100ms to mimic logger write "
+            "patterns more correctly.");
+DEFINE_double(write_bandwidth, 120.0,
+              "Write speed in MB/s to simulate. This is only used when "
+              "--rate_limit is specified.");
 
 // Stolen from aos/events/logging/DummyEncoder
 class AlignedReallocator {
@@ -116,9 +122,19 @@
   PCHECK(fd != -1);
 
   start_time = aos::monotonic_clock::now();
-  aos::monotonic_clock::time_point last_time = start_time;
+  aos::monotonic_clock::time_point last_print_time = start_time;
+  aos::monotonic_clock::time_point cycle_start_time = start_time;
   size_t last_written_data = 0;
   written_data = 0;
+  // Track how much data we write per cycle. When --rate_limit is specified,
+  // --write_bandwidth is the amount of data we want to write per second, and we
+  // want to write it in cycles of 100ms to simulate the logger.
+  size_t cycle_written_data = 0;
+  size_t data_per_cycle = std::numeric_limits<size_t>::max();
+  if (FLAGS_rate_limit) {
+    data_per_cycle =
+        static_cast<size_t>((FLAGS_write_bandwidth * 1024 * 1024) / 10);
+  }
 
   if (FLAGS_nice != 0) {
     PCHECK(-1 != setpriority(PRIO_PROCESS, 0, FLAGS_nice))
@@ -167,14 +183,52 @@
     }
 
     written_data += data.size();
+    cycle_written_data += data.size();
+
+    // Simulate the logger by writing the specified amount of data in periods of
+    // 100ms.
+    bool reset_cycle = false;
+    if (cycle_written_data > data_per_cycle && FLAGS_rate_limit) {
+      // Check how much data we should have already written based on
+      // --write_bandwidth.
+      const size_t current_target =
+          FLAGS_write_bandwidth * 1024 * 1024 *
+          chrono::duration<double>(aos::monotonic_clock::now() - start_time)
+              .count();
+      const bool caught_up = written_data > current_target;
+      if (caught_up) {
+        // If we're on track, sleep for the rest of this cycle, as long as we
+        // didn't use up all the cycle time writing.
+        const aos::monotonic_clock::time_point monotonic_now =
+            aos::monotonic_clock::now();
+        const auto sleep_duration =
+            (cycle_start_time + chrono::milliseconds(100)) - monotonic_now;
+        if (sleep_duration.count() > 0) {
+          VLOG(2) << "Sleeping for " << sleep_duration.count();
+          std::this_thread::sleep_for(sleep_duration);
+        } else {
+          LOG(WARNING) << "It took longer than 100ms to write "
+                       << data_per_cycle << " bytes.";
+        }
+        reset_cycle = true;
+      } else {
+        // If we aren't on track, don't sleep.
+        LOG(WARNING) << "Still catching up to target write rate.";
+      }
+      // Either way, reset the data we're counting for this "cycle". If we're
+      // still behind, let's check again after writing another data_per_cycle
+      // bytes.
+      cycle_written_data = 0;
+    }
 
     const aos::monotonic_clock::time_point monotonic_now =
         aos::monotonic_clock::now();
     // Print out MB/s once it has been at least 1 second since last time.
-    if (monotonic_now > last_time + chrono::seconds(1)) {
+    if (monotonic_now > last_print_time + chrono::seconds(1)) {
       LOG(INFO)
           << ((written_data - last_written_data) /
-              chrono::duration<double>(monotonic_now - last_time).count() /
+              chrono::duration<double>(monotonic_now - last_print_time)
+                  .count() /
               1024. / 1024.)
           << " MB/s, average of "
           << (written_data /
@@ -182,9 +236,16 @@
               1024. / 1024.)
           << " MB/s for " << static_cast<double>(written_data) / 1024. / 1024.
           << "MB";
-      last_time = monotonic_now;
+      last_print_time = monotonic_now;
       last_written_data = written_data;
     }
+
+    // Do this at the end so that we're setting the next cycle start time as
+    // accurately as possible.
+    if (reset_cycle) {
+      cycle_start_time = monotonic_now;
+      VLOG(1) << cycle_start_time;
+    }
   }
 
   return 0;