ILLIXR: Illinois Extended Reality Testbed
cpu_timer.hpp
1 #pragma once
2 
3 //
4 
5 #include "error_util.hpp"
6 
7 #include <chrono>
8 #include <cstring>
9 #include <functional>
10 #include <iostream>
11 #include <spdlog/spdlog.h>
12 #include <string>
13 #include <thread>
14 #include <utility>
15 
22 static inline std::chrono::nanoseconds cpp_clock_gettime(clockid_t clock_id) {
23  /* This ensures the compiler won't reorder this function call; Pretend like it has memory side-effects. */
24  asm volatile(""
25  : /* OutputOperands */
26  : /* InputOperands */
27  : "memory" /* Clobbers */);
28 
29  struct timespec ts { };
30 
31  RAC_ERRNO_MSG("cpu_timer before clock_gettime");
32 
33  if (clock_gettime(clock_id, &ts)) {
34  throw std::runtime_error{std::string{"clock_gettime returned "} + strerror(errno)};
35  }
36  RAC_ERRNO_MSG("cpu_timer after clock_gettime");
37 
38  asm volatile(""
39  : /* OutputOperands */
40  : /* InputOperands */
41  : "memory" /* Clobbers */);
42  return std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec};
43 }
44 
48 static inline std::chrono::nanoseconds thread_cpu_time() {
49  RAC_ERRNO_MSG("cpu_timer before cpp_clock_gettime");
50  return cpp_clock_gettime(CLOCK_THREAD_CPUTIME_ID);
51 }
52 
78 template<typename now_fn, typename time_point = decltype(std::declval<now_fn>()()),
79  typename durationt = decltype(std::declval<time_point>() - std::declval<time_point>())>
80 class timer {
81 public:
82  timer(const now_fn& now, durationt& _duration)
83  : _p_now{now}
84  , _p_duration{_duration} {
85  _p_start = _p_now();
86  }
87 
88  ~timer() {
89  _p_duration = _p_now() - _p_start;
90  }
91 
92 private:
93  const now_fn& _p_now;
94  durationt& _p_duration;
95  time_point _p_start;
96 };
97 
98 template<typename Duration, typename Out = decltype(std::declval<Duration>().count())>
99 typename std::enable_if<std::is_integral<Out>::value, Out>::type count_duration(Duration t) {
100  return std::chrono::duration_cast<std::chrono::nanoseconds, typename Duration::rep, typename Duration::period>(t).count();
101 }
102 
103 template<typename Duration>
104 typename std::enable_if<std::is_integral<Duration>::value, Duration>::type count_duration(Duration t) {
105  return t;
106 }
107 
114 template<typename now_fn, typename time_point = decltype(std::declval<now_fn>()()),
115  typename duration = decltype(std::declval<time_point>() - std::declval<time_point>())>
116 class print_timer {
117 private:
118  class print_in_destructor {
119  public:
120  print_in_destructor(std::string account_name, const duration& _duration)
121  : _p_account_name{std::move(account_name)}
122  , _p_duration{_duration} { }
123 
124  ~print_in_destructor() {
125  // std::ostringstream os;
126  // os << "cpu_timer," << _p_account_name << "," << count_duration<duration>(_p_duration) << "\n";
127  if (rand() % 100 == 0) {
128 #ifndef NDEBUG
129  spdlog::get("illixr")->info("cpu_timer.hpp is DEPRECATED. See logging.hpp.");
130 #endif
131  }
132  }
133 
134  private:
135  const std::string _p_account_name;
136  const duration& _p_duration;
137  };
138 
139  // NOTE that the destructors get called in reverse order!
140  // This is important, because _p_timer's destructor records the timing information
141  // Then, _p_print_in_destructor prints it
142  // Then, we can destroy _p_duration.
143  duration _p_duration;
144  const print_in_destructor _p_print_in_destructor;
146 
147 public:
148  print_timer(const std::string& name, const now_fn& now)
149  : _p_print_in_destructor{name, _p_duration}
150  , _p_timer{now, _p_duration} { }
151 };
152 
153 static std::size_t gen_serial_no() {
154  return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch())
155  .count();
156 }
157 
158 class should_profile_class {
159 public:
160  should_profile_class() {
161  const char* ILLIXR_STDOUT_METRICS = getenv("ILLIXR_STDOUT_METRICS");
162  actually_should_profile = ILLIXR_STDOUT_METRICS && (strcmp(ILLIXR_STDOUT_METRICS, "y") == 0);
163  }
164 
165  bool operator()() const {
166  return actually_should_profile;
167  }
168 
169 private:
170  bool actually_should_profile;
171 };
172 
173 static should_profile_class should_profile;
174 
175 class print_timer2 {
176 private:
177  const std::string name;
178  const std::size_t serial_no;
179  std::size_t wall_time_start;
180  std::size_t cpu_time_start;
181 
182 public:
183  explicit print_timer2(std::string name_)
184  : name{std::move(name_)}
185  , serial_no{should_profile() ? gen_serial_no() : std::size_t{0}}
186  , wall_time_start{should_profile() ? std::chrono::duration_cast<std::chrono::nanoseconds>(
187  std::chrono::high_resolution_clock::now().time_since_epoch())
188  .count()
189  : std::size_t{0}}
190  , cpu_time_start{should_profile() ? thread_cpu_time().count() : std::size_t{0}} { }
191 
192  ~print_timer2() {
193  if (should_profile()) {
194  auto cpu_time_stop = thread_cpu_time().count();
195  auto wall_time_stop = std::chrono::duration_cast<std::chrono::nanoseconds>(
196  std::chrono::high_resolution_clock::now().time_since_epoch())
197  .count();
198 
199  spdlog::get("illixr")->info("[cpu_timer] cpu_timer,{},{},{},{},{},{}", name, serial_no, wall_time_start,
200  wall_time_stop, cpu_time_start, cpu_time_stop);
201  }
202  }
203 };
204 
205 #define PRINT_CPU_TIME_FOR_THIS_BLOCK(name) \
206  print_timer<decltype((thread_cpu_time))> PRINT_CPU_TIME_FOR_THIS_BLOCK{name, thread_cpu_time};
207 
208 #define PRINT_WALL_TIME_FOR_THIS_BLOCK(name) \
209  print_timer<decltype((std::chrono::high_resolution_clock::now))> PRINT_WALL_TIME_FOR_THIS_BLOCK{ \
210  name, std::chrono::high_resolution_clock::now};
211 
212 #define PRINT_RECORD_FOR_THIS_BLOCK(name) print_timer2 PRINT_RECORD_FOR_THIS_BLOCK_timer{name};
213 
217 template<class Function, class... Args>
218 std::thread timed_thread(const std::string& account_name, Function&& f, Args&&... args) {
219  // Unfortunately we make copies of f and args.
220  // According to StackOverflow, this is unavoidable.
221  // See Sam Varshavchik's comment on https://stackoverflow.com/a/62380971/1078199
222  return std::thread([=] {
223  {
224  PRINT_RECORD_FOR_THIS_BLOCK(account_name)
225  std::invoke(f, args...);
226  }
227  });
228 }
Like timer, but prints the output.
Definition: cpu_timer.hpp:116
a timer that times until the end of the code block ([RAII]).
Definition: cpu_timer.hpp:80