From 9ddb991b948ca85a2f036a384d0efe1976b3edc3 Mon Sep 17 00:00:00 2001
From: Abdelrahman <said.abdelrahman89@gmail.com>
Date: Sat, 9 Sep 2023 21:25:32 +0100
Subject: [PATCH] Basic repetition testing implementation

---
 .../include/repetition_testing/reptester.h    |  44 ++++++
 haversine_02/src/repetition_testing/main.cpp  | 134 ++++++++++++++++++
 .../src/repetition_testing/reptester.cpp      |  62 ++++++++
 3 files changed, 240 insertions(+)
 create mode 100644 haversine_02/include/repetition_testing/reptester.h
 create mode 100644 haversine_02/src/repetition_testing/main.cpp
 create mode 100644 haversine_02/src/repetition_testing/reptester.cpp

diff --git a/haversine_02/include/repetition_testing/reptester.h b/haversine_02/include/repetition_testing/reptester.h
new file mode 100644
index 0000000..796f869
--- /dev/null
+++ b/haversine_02/include/repetition_testing/reptester.h
@@ -0,0 +1,44 @@
+#ifndef REPTESTER_H
+#define REPTESTER_H
+
+#include "aliases.h"
+
+struct reptest_params {
+  const char *filename;
+  char *buffer;
+  u64 read_size;
+  u64 read_count;
+};
+
+struct reptest_results {
+  u64 bytes_read;
+  u64 read_time;
+};
+
+struct reptester {
+  reptest_params params;
+
+  const u64 cpu_freq;
+
+  f64 wait_time_secs;
+  f64 test_time_secs;
+  u64 test_start_time;
+
+  u64 current_run;
+  u64 min;
+  u64 max;
+  u64 avg;
+  u64 total;
+
+  reptest_results results;
+};
+
+struct reptest_func {
+  const char *name;
+  void (*func)(reptester *tester);
+};
+
+void run_func_test(reptester *tester, reptest_func func_obj);
+void print_results(reptester *tester, const char *name);
+
+#endif // !REPTESTER_H
diff --git a/haversine_02/src/repetition_testing/main.cpp b/haversine_02/src/repetition_testing/main.cpp
new file mode 100644
index 0000000..f11e9d2
--- /dev/null
+++ b/haversine_02/src/repetition_testing/main.cpp
@@ -0,0 +1,134 @@
+#include "aliases.h"
+#include "profiler/timer.h"
+#include "repetition_testing/reptester.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ARR_LEN(ARR) sizeof(ARR) / sizeof(*ARR)
+
+void test_fread(reptester *tester);
+void test_read(reptester *tester);
+u64 get_file_length(FILE *fp);
+
+int main(int argc, char *argv[]) {
+  const char *filename = NULL;
+  u64 waves = 1;
+
+  switch (argc) {
+  case 3:
+    waves = atol(argv[2]);
+  // break left intentionally
+  case 2:
+    filename = argv[1];
+    break;
+  default:
+    printf("Usage: reptest FILENAME [WAVE_COUNT]\n");
+    return -1;
+    break;
+  }
+
+  // clang-format off
+  reptester tester = {
+      {filename, NULL, 0, 0}, // params
+      get_cpu_freq(500),          // cpu_freq
+
+      10.0,                       // wait_time_secs
+      0.0,                        // test_time_secs
+      read_cpu_timer(),           // test_start_time
+
+      1,                          // current_run
+      UINT64_MAX,                 // min
+      0,                          // max
+		  0,                          // avg
+      0,                          // total
+		  {},                         // results
+  };
+  // clang-format on
+
+  FILE *fp = fopen(tester.params.filename, "rb");
+  if (!fp) {
+    return -1;
+  }
+
+  reptest_func funcs[] = {{"FREAD", test_fread}, {"READ", test_read}};
+
+  tester.params.read_size = get_file_length(fp);
+  tester.params.read_count = 1;
+  tester.params.buffer = (char *)malloc(tester.params.read_size + 1);
+  memset(tester.params.buffer, 0, tester.params.read_size + 1);
+
+  for (u64 i = 0; i < waves; ++i) {
+    for (u64 j = 0; j < ARR_LEN(funcs); ++j) {
+      run_func_test(&tester, funcs[j]);
+    }
+  }
+
+  fclose(fp);
+
+  free(tester.params.buffer);
+
+  return 0;
+}
+
+void test_fread(reptester *tester) {
+  FILE *fp = fopen(tester->params.filename, "rb");
+  if (!fp) {
+    return;
+  }
+
+  u64 start = read_cpu_timer();
+  u64 obj_count = fread(tester->params.buffer, tester->params.read_size,
+                        tester->params.read_count, fp);
+  u64 end = read_cpu_timer();
+
+  u64 bytes_read = obj_count * tester->params.read_size;
+
+  u64 read_time = end - start;
+
+  tester->results = {
+      bytes_read,
+      read_time,
+  };
+
+  fclose(fp);
+}
+
+void test_read(reptester *tester) {
+  FILE *fp = fopen(tester->params.filename, "rb");
+  if (!fp) {
+    return;
+  }
+
+  i32 fd = fileno(fp);
+
+  u64 start = read_cpu_timer();
+  u64 bytes_read = read(fd, tester->params.buffer,
+                        tester->params.read_size * tester->params.read_count);
+  u64 end = read_cpu_timer();
+
+  u64 read_time = end - start;
+
+  tester->results = {
+      bytes_read,
+      read_time,
+  };
+
+  fclose(fp);
+}
+
+u64 get_file_length(FILE *fp) {
+  if (!fp) {
+    return 0;
+  }
+
+  fseek(fp, 0, SEEK_END);
+
+  u64 length = ftell(fp);
+
+  fseek(fp, 0, SEEK_SET);
+
+  return length;
+}
diff --git a/haversine_02/src/repetition_testing/reptester.cpp b/haversine_02/src/repetition_testing/reptester.cpp
new file mode 100644
index 0000000..9ccc6c4
--- /dev/null
+++ b/haversine_02/src/repetition_testing/reptester.cpp
@@ -0,0 +1,62 @@
+#include "repetition_testing/reptester.h"
+#include "profiler/timer.h"
+#include <stdio.h>
+
+void run_func_test(reptester *tester, reptest_func func_obj) {
+  tester->test_start_time = read_cpu_timer();
+  tester->test_time_secs = 0.0;
+  tester->current_run = 1;
+  tester->min = UINT64_MAX;
+  tester->max = 0;
+  tester->avg = 0;
+  tester->total = 0;
+  tester->results = {};
+
+  while (tester->test_time_secs <= tester->wait_time_secs) {
+    func_obj.func(tester);
+
+    if (tester->results.bytes_read <
+        tester->params.read_size * tester->params.read_count) {
+      printf("Failed to read the entire file (Total size: %lu, Bytes read: "
+             "%lu)\n",
+             tester->params.read_size, tester->results.bytes_read);
+
+      return;
+    }
+
+    tester->total += tester->results.read_time;
+
+    if (tester->results.read_time > tester->max) {
+      tester->max = tester->results.read_time;
+    } else if (tester->results.read_time < tester->min) {
+      tester->test_start_time = read_cpu_timer();
+      tester->min = tester->results.read_time;
+    }
+
+    tester->test_time_secs = time_in_seconds(
+        read_cpu_timer() - tester->test_start_time, tester->cpu_freq);
+
+    ++(tester->current_run);
+  }
+
+  print_results(tester, func_obj.name);
+}
+
+void print_results(reptester *tester, const char *name) {
+  f64 gb = 1024.0 * 1024.0 * 1024.0;
+
+  f64 size_in_gb =
+      (f64)(tester->params.read_size * tester->params.read_count) / gb;
+
+  u64 run_count = tester->current_run - 1;
+
+  tester->avg = tester->total / run_count;
+
+  printf("\n%s: %lu runs\n", name, run_count);
+  printf("MIN: %lu (%fGB/s)\n", tester->min,
+         size_in_gb / time_in_seconds(tester->min, tester->cpu_freq));
+  printf("MAX: %lu (%fGB/s)\n", tester->max,
+         size_in_gb / time_in_seconds(tester->max, tester->cpu_freq));
+  printf("AVG: %lu (%fGB/s)\n", tester->avg,
+         size_in_gb / time_in_seconds(tester->avg, tester->cpu_freq));
+}