benchmark_runner.h 10.9 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_;

FritzFlorian committed
45 46
  map<string, vector<long>> custom_stats_;

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

    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;
    }
55 56 57 58 59 60 61 62
  }

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

 public:
FritzFlorian committed
63 64 65
  benchmark_runner(string csv_path, string csv_name) : csv_path_{std::move(csv_path)},
                                                       csv_name_{std::move(csv_name)},
                                                       times_{} {
66 67 68 69 70 71
    string command = "mkdir -p " + csv_path_;
    int res = system(command.c_str());
    if (res) {
      cout << "Error while creating directory!" << endl;
      exit(1);
    }
FritzFlorian committed
72 73 74 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
  }

  /**
   * 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);
  }
107

FritzFlorian committed
108 109
  void add_custom_stats_field(const string name) {
    custom_stats_.insert({name, {}});
110 111
  }

112 113 114 115 116 117
  void enable_memory_stats() {
    memory_stats_enabled_ = true;
    add_custom_stats_field(MEMORY_PRE_RUN);
    add_custom_stats_field(MEMORY_POST_RUN);
  }

118 119 120 121 122 123
  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);
  }

124 125 126 127 128 129 130 131 132
  void pre_allocate_stats(size_t num = 100000) {
    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));
    }
  }

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
  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");
155 156 157
      exit(1);
    }

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    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;
181 182 183
  }

  void start_iteration() {
184 185 186 187
    if (memory_stats_enabled_) {
      auto memory_stats = query_process_memory_pages();
      memory_pre_run_ = memory_stats.first;
    }
188 189 190 191 192
    if (wall_time_enabled_) {
      wall_time_pre_run_ =
          std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::time_point_cast<std::chrono::microseconds>(
              std::chrono::system_clock::now()).time_since_epoch()).count();
    }
193

194 195 196 197
    last_start_time_ = chrono::steady_clock::now();
  }

  void end_iteration() {
198 199
    size_t iteration_index = times_.size();

200 201
    auto end_time = chrono::steady_clock::now();
    long time = chrono::duration_cast<chrono::microseconds>(end_time - last_start_time_).count();
FritzFlorian committed
202

203
    times_.emplace_back(time);
FritzFlorian committed
204 205 206
    for (auto &iter : custom_stats_) {
      iter.second.emplace_back(0);
    }
207 208 209 210 211 212 213
    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_;
    }
214 215 216 217 218 219 220
    if (wall_time_enabled_) {
      wall_time_post_run_ =
          std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::time_point_cast<std::chrono::microseconds>(
              std::chrono::system_clock::now()).time_since_epoch()).count();
      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
221 222 223 224 225
  }

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

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

243 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 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
  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;
    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);

      // Store 'actual' wall time instead of iteration time (we want to include sleeping here!)
      long wall_time_us = 0;
      wall_time_us += (finish_time.tv_sec - iteration_start.tv_sec) * 1000l * 1000l;
      wall_time_us += ((long) finish_time.tv_nsec - (long) iteration_start.tv_nsec) / 1000l;
302
      printf("Difference: %ld\n", wall_time_us - times_[current_iteration]);
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
      times_[current_iteration] = wall_time_us;

      if (finish_time.tv_sec >= deadline_end.tv_sec && finish_time.tv_nsec > deadline_end.tv_nsec) {
        printf("Deadline Miss!\n"); // TODO: Remove
      }

      // Skip iterations if their start time is later than the current time (skipping)
      while (finish_time.tv_sec >= iteration_end.tv_sec && finish_time.tv_nsec > iteration_end.tv_nsec) {
        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;
    }
  }

327 328 329 330 331 332 333 334 335 336
  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
337

338
      if (write_header) {
FritzFlorian committed
339 340 341 342 343
        o << "runtime_us";
        for (auto &iter : custom_stats_) {
          o << ";" << iter.first;
        }
        o << endl;
344
      }
FritzFlorian committed
345 346 347 348 349 350 351 352

      // 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;
353 354 355 356
      }
    } // End Scope for output file

    times_.clear();
FritzFlorian committed
357 358 359
    for (auto &iter : custom_stats_) {
      iter.second.clear();
    }
360 361 362 363
  }
};

#endif //BENCHMARK_RUNNER_H