benchmark_runner.h 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12

#ifndef BENCHMARK_RUNNER_H
#define BENCHMARK_RUNNER_H

#include <string>
#include <cstdlib>
#include <vector>
#include <chrono>
#include <numeric>
#include <iostream>
#include <fstream>
#include <bits/stdc++.h>
FritzFlorian committed
13 14
#include <thread>
#include <map>
15
#include <time.h>
FritzFlorian committed
16 17 18 19 20 21

#include <tuple>
#include <unistd.h>
#include <string>
#include <fstream>
#include <sstream>
22 23 24 25 26 27 28 29 30 31 32

using namespace std;

class benchmark_runner {
 private:
  string csv_path_;
  string csv_name_;

  chrono::steady_clock::time_point last_start_time_;
  vector<long> times_;

33 34 35 36 37 38
  bool memory_stats_enabled_{false};
  const string MEMORY_PRE_RUN = "memory_pages_pre_run";
  const string MEMORY_POST_RUN = "memory_pages_post_run";
  unsigned long memory_pre_run_;
  unsigned long memory_post_run_;

39 40 41 42 43 44
  bool wall_time_enabled_{false};
  const string WALL_TIME_PRE_RUN = "wall_time_pre_run_us";
  const string WALL_TIME_POST_RUN = "wall_time_post_run_us";
  unsigned long wall_time_pre_run_;
  unsigned long wall_time_post_run_;

45 46 47 48
  const string WALL_TIME_ITERATION_START = "wall_time_iteration_start_us";
  const string WALL_TIME_ITERATION_END = "wall_time_iteration_end_us";

  map<string, vector<unsigned long>> custom_stats_;
FritzFlorian committed
49

50 51 52
  void print_statistics() {
    long time_sum = std::accumulate(times_.begin(), times_.end(), 0l);
    cout << "Average Runtime (us): " << (time_sum / times_.size()) << endl;
FritzFlorian committed
53 54 55 56 57

    for (auto &iter : custom_stats_) {
      long custom_stat_sum = std::accumulate(iter.second.begin(), iter.second.end(), 0l);
      cout << "Average " << iter.first << ": " << (custom_stat_sum / times_.size()) << endl;
    }
58 59 60 61 62 63 64 65
  }

  inline bool file_exists(const std::string &name) {
    ifstream f(name);
    return f.good();
  }

 public:
FritzFlorian committed
66 67 68
  benchmark_runner(string csv_path, string csv_name) : csv_path_{std::move(csv_path)},
                                                       csv_name_{std::move(csv_name)},
                                                       times_{} {
69 70 71 72 73 74
    string command = "mkdir -p " + csv_path_;
    int res = system(command.c_str());
    if (res) {
      cout << "Error while creating directory!" << endl;
      exit(1);
    }
FritzFlorian committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
  }

  /**
   * Queries the pages used by the current process.
   * Splits between private and shared pages.
   *
   * @return Tuple(private_pages, shared_pages) used by the process.
   */
  static std::pair<unsigned long, unsigned long> query_process_memory_pages() {
    pid_t my_pid = getpid();
    std::ostringstream proc_stats_path_builder;
    proc_stats_path_builder << "/proc/" << my_pid << "/smaps_rollup";
    std::string proc_stats_path = proc_stats_path_builder.str();

    std::ifstream proc_stats_file{proc_stats_path};

    std::string line;
    unsigned long total_shared = 0;
    unsigned long total_private = 0;
    while (std::getline(proc_stats_file, line)) {
      std::stringstream line_input(line);
      std::string type;
      unsigned long size;
      line_input >> type >> size;

      if (type.find("Shared") != std::string::npos) {
        total_shared += size;
      }
      if (type.find("Private") != std::string::npos) {
        total_private += size;
      }
    }

    return std::make_pair(total_private, total_shared);
  }
110

FritzFlorian committed
111 112
  void add_custom_stats_field(const string name) {
    custom_stats_.insert({name, {}});
113 114
  }

115 116 117 118 119 120
  void enable_memory_stats() {
    memory_stats_enabled_ = true;
    add_custom_stats_field(MEMORY_PRE_RUN);
    add_custom_stats_field(MEMORY_POST_RUN);
  }

121 122 123 124
  void enable_wall_time_stats() {
    wall_time_enabled_ = true;
    add_custom_stats_field(WALL_TIME_PRE_RUN);
    add_custom_stats_field(WALL_TIME_POST_RUN);
125 126
    add_custom_stats_field(WALL_TIME_ITERATION_START);
    add_custom_stats_field(WALL_TIME_ITERATION_END);
127 128
  }

129
  void pre_allocate_stats(size_t num = 500000) {
130 131 132 133 134 135 136 137
    times_.reserve(num);
    memset(times_.data(), 'a', num * sizeof(long));
    for (auto &iter : custom_stats_) {
      iter.second.reserve(num);
      memset(iter.second.data(), 'a', num * sizeof(long));
    }
  }

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
  struct benchmark_settings {
    enum TYPE { ISOLATED, PERIODIC };

    string output_directory_;
    size_t size_;
    unsigned num_threads_;

    TYPE type_;

    size_t iterations_;

    unsigned long interval_period_;
    unsigned long interval_deadline_;
  };

  static benchmark_settings parse_parameters(int argc, char **argv) {
    benchmark_settings result;
    string tmp;

    if (argc != 5 && argc != 7) {
      printf("usage 1: `benchmark <output_directory> <size> <num_threads> <iterations>`\n");
      printf("usage 2: `benchmark <output_directory> <size> <num_threads> <iterations> <period> <deadline>`\n");
160 161 162
      exit(1);
    }

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    result.output_directory_ = argv[1];
    tmp = argv[2];
    result.size_ = std::stoi(tmp);
    tmp = argv[3];
    result.num_threads_ = std::stoi(tmp);

    if (argc == 5) {
      result.type_ = benchmark_settings::ISOLATED;

      tmp = argv[4];
      result.iterations_ = std::stoi(tmp);
    } else {
      result.type_ = benchmark_settings::PERIODIC;

      tmp = argv[4];
      result.iterations_ = std::stoi(tmp);
      tmp = argv[5];
      result.interval_period_ = std::stoi(tmp);
      tmp = argv[6];
      result.interval_deadline_ = std::stoi(tmp);
    }

    return result;
186 187 188
  }

  void start_iteration() {
189 190 191 192
    if (memory_stats_enabled_) {
      auto memory_stats = query_process_memory_pages();
      memory_pre_run_ = memory_stats.first;
    }
193 194
    if (wall_time_enabled_) {
      wall_time_pre_run_ =
195
          chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count();
196
    }
197

198 199 200 201
    last_start_time_ = chrono::steady_clock::now();
  }

  void end_iteration() {
202 203
    size_t iteration_index = times_.size();

204 205
    auto end_time = chrono::steady_clock::now();
    long time = chrono::duration_cast<chrono::microseconds>(end_time - last_start_time_).count();
FritzFlorian committed
206

207
    times_.emplace_back(time);
FritzFlorian committed
208 209 210
    for (auto &iter : custom_stats_) {
      iter.second.emplace_back(0);
    }
211 212 213 214 215 216 217
    if (memory_stats_enabled_) {
      auto memory_stats = query_process_memory_pages();
      memory_post_run_ = memory_stats.first;

      custom_stats_[MEMORY_PRE_RUN][iteration_index] = memory_pre_run_;
      custom_stats_[MEMORY_POST_RUN][iteration_index] = memory_post_run_;
    }
218 219
    if (wall_time_enabled_) {
      wall_time_post_run_ =
220 221
          chrono::duration_cast<chrono::microseconds>(chrono::system_clock::now().time_since_epoch()).count();

222 223 224
      custom_stats_[WALL_TIME_PRE_RUN][iteration_index] = wall_time_pre_run_;
      custom_stats_[WALL_TIME_POST_RUN][iteration_index] = wall_time_post_run_;
    }
FritzFlorian committed
225 226 227 228 229
  }

  void store_custom_stat(const string &name, long value) {
    auto &stat_vector = custom_stats_[name];
    stat_vector[stat_vector.size() - 1] = value;
230 231
  }

232 233
  void run_iterations(int count,
                      const function<void(void)> measure,
FritzFlorian committed
234 235
                      const function<void(void)> prepare = []() {},
                      const function<void(void)> finish = []() {}) {
236
    for (int i = 0; i < count; i++) {
FritzFlorian committed
237 238
      using namespace std::literals;
      this_thread::sleep_for(100us);
239
      prepare();
240
      start_iteration();
241
      measure();
242
      end_iteration();
FritzFlorian committed
243
      finish();
244 245 246
    }
  }

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
  void add_to_timespec(timespec &timespec, size_t seconds, size_t nanoseconds) {
    timespec.tv_sec += seconds;
    timespec.tv_nsec += nanoseconds;

    while (timespec.tv_nsec > 1000l * 1000l * 1000l) {
      timespec.tv_nsec -= 1000l * 1000l * 1000l;
      timespec.tv_sec++;
    }
  }

  void run_periodic(size_t count,
                    size_t period_us,
                    size_t deadline_us,
                    const function<void(void)> measure) {
    size_t period_nanoseconds = 1000lu * period_us;
    size_t period_seconds = period_nanoseconds / (1000lu * 1000lu * 1000lu);
    period_nanoseconds = period_nanoseconds - period_seconds * 1000lu * 1000lu * 1000lu;

    size_t deadline_nanoseconds = 1000lu * deadline_us;
    size_t deadline_seconds = deadline_nanoseconds / (1000lu * 1000lu * 1000lu);
    deadline_nanoseconds = deadline_nanoseconds - deadline_seconds * 1000lu * 1000lu * 1000lu;

    // Prepare basic time spec for first iteration
    timespec iteration_start, iteration_end, deadline_end, finish_time;
    if (clock_gettime(CLOCK_MONOTONIC, &iteration_start) == -1) {
      perror("clock_gettime");
      exit(1);
    }
    add_to_timespec(iteration_start, period_seconds, period_nanoseconds);

    size_t current_iteration = 0;
278
    size_t deadline_misses = 0;
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
    while (current_iteration < count) {
      // Sleep until the next iteration
      long sleep_error = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &iteration_start, nullptr);
      if (sleep_error) {
        printf("Sleep Error %ld\n", sleep_error);
      }

      // Invoke iteration
      start_iteration();
      measure();
      end_iteration();

      // Calculate all relevant time points for this iteration
      if (clock_gettime(CLOCK_MONOTONIC, &finish_time) == -1) {
        perror("clock_gettime");
        exit(1);
      }

      iteration_end = iteration_start;
      add_to_timespec(iteration_end, period_seconds, period_nanoseconds);

      deadline_end = iteration_start;
      add_to_timespec(deadline_end, deadline_seconds, deadline_nanoseconds);

303 304 305 306 307 308 309 310
      // Keep stats...
      if (wall_time_enabled_) {
        custom_stats_[WALL_TIME_ITERATION_START][current_iteration] =
            iteration_start.tv_sec * 1000 * 1000 + iteration_start.tv_nsec / 1000;
        custom_stats_[WALL_TIME_ITERATION_END][current_iteration] =
            iteration_end.tv_sec * 1000 * 1000 + iteration_end.tv_nsec / 1000;
      }

311
      // Store 'actual' wall time instead of iteration time (we want to include sleeping here!)
312
      unsigned long wall_time_us = 0;
313
      wall_time_us += (finish_time.tv_sec - iteration_start.tv_sec) * 1000l * 1000l;
314 315
      long nano_second_difference = ((long) finish_time.tv_nsec - (long) iteration_start.tv_nsec) / 1000l;
      wall_time_us += nano_second_difference;
316 317 318
      times_[current_iteration] = wall_time_us;

      if (finish_time.tv_sec >= deadline_end.tv_sec && finish_time.tv_nsec > deadline_end.tv_nsec) {
319
        deadline_misses++;
320 321 322
      }

      // Skip iterations if their start time is later than the current time (skipping)
323 324
      while (finish_time.tv_sec > iteration_end.tv_sec
          || (finish_time.tv_sec == iteration_end.tv_sec && finish_time.tv_nsec > iteration_end.tv_nsec)) {
325 326 327 328 329 330 331 332 333 334 335 336 337 338
        iteration_start = iteration_end;

        iteration_end = iteration_start;
        add_to_timespec(iteration_end, period_seconds, period_nanoseconds);

        current_iteration++;
        start_iteration();
        end_iteration();
        times_[current_iteration] = 0;
      }
      // Progress to next iteration (normally)
      current_iteration++;
      iteration_start = iteration_end;
    }
339 340

    printf("%ld deadline misses!\n", deadline_misses);
341 342
  }

343 344 345 346 347 348 349 350 351 352
  void commit_results(bool print_stats) {
    if (print_stats) {
      print_statistics();
    }

    string full_filename = csv_path_ + csv_name_;
    bool write_header = !file_exists(full_filename);

    { // Scope for output file
      ofstream o(full_filename, std::fstream::out | std::fstream::app);
FritzFlorian committed
353

354
      if (write_header) {
FritzFlorian committed
355 356 357 358 359
        o << "runtime_us";
        for (auto &iter : custom_stats_) {
          o << ";" << iter.first;
        }
        o << endl;
360
      }
FritzFlorian committed
361 362 363 364 365 366 367 368

      // TODO: make this more efficient
      for (size_t i = 0; i < times_.size(); i++) {
        o << times_[i];
        for (auto &iter : custom_stats_) {
          o << ";" << iter.second[i];
        }
        o << endl;
369 370 371 372
      }
    } // End Scope for output file

    times_.clear();
FritzFlorian committed
373 374 375
    for (auto &iter : custom_stats_) {
      iter.second.clear();
    }
376 377 378 379
  }
};

#endif //BENCHMARK_RUNNER_H