libcoro  1.0
Coroutine support library for C++20
trace.h
1 #pragma once
2 
3 
4 #include "common.h"
5 #include <mutex>
6 #include <string_view>
7 #include <source_location>
8 #ifdef LIBCORO_ENABLE_TRACE
9 #include <fstream>
10 #include <filesystem>
11 #include <atomic>
12 #include <typeinfo>
13 #include <thread>
14 #include <bit>
15 
16 #ifdef _WIN32
17 extern "C" {
18 unsigned long __stdcall GetCurrentThreadId();
19 unsigned long __stdcall GetModuleFileNameA(void *, char *, unsigned long);
20 }
21 #endif
22 
23 namespace coro {
24 
25 namespace trace {
26 
27 enum class record_type: char {
28  create = 'c',
29  destroy = 'x',
30  resume_enter = 'e',
31  resume_exit = 'r',
32  sym_switch = 's',
33  awaits_on = 'a',
34  yield = 'y',
35  user_report = 'U',
36  thread = 'T',
37  hr = 'H',
38  coroutine_type = 't',
39  link = 'l',
40  proxy = 'p',
41  block = 'b',
42  unblock = 'u'
43 };
44 
45 inline constexpr char separator = '\t';
46 
47 
48 class impl {
49 public:
50 
51 
52  static std::string get_file_name() {
53  #ifdef _WIN32
54  char szFileName[1024];
55  GetModuleFileNameA(NULL, szFileName, sizeof(szFileName));
56  std::filesystem::path exe_path = szFileName;
57 
58  #else
59  std::filesystem::path exe_path = std::filesystem::read_symlink("/proc/self/exe");
60  #endif
61  std::string exe_name = exe_path.stem().string();
62  std::string trace_filename = exe_name + ".corotrace";
63  return trace_filename;
64  }
65 
66 
67 
68 
69  std::ostream &stream() {
70  if (_foutput.is_open()) return _foutput;
71  std::string trace_file = get_file_name();
72  _foutput.open(trace_file, std::ios::trunc);
73  if (!_foutput) throw std::runtime_error("Failed to create trace file:" + trace_file);
74  return _foutput;
75  }
76 
77 
78  struct thread_state {
79  static std::atomic<unsigned int> counter;
80  unsigned int id = 0;
81  bool is_new = true;
82  thread_state():id(++counter) {}
83  };
84 
85  std::ostream &header(record_type rt) {
86  auto &f = stream();
87  if (_state.is_new) {
88 #ifdef _WIN32
89  auto tid = GetCurrentThreadId();
90 #else
91  auto tid = gettid();
92 #endif
93  f << _state.id << separator << static_cast<char>(record_type::thread) << separator
94  << tid << std::endl;
95  _state.is_new = false;
96  }
97  f << _state.id << separator << static_cast<char>(rt) << separator;
98  return f;
99 
100  }
101 
102  struct pointer: std::string_view {
103  static constexpr std::string_view letters = "0123456789ABCDEF";
104  pointer(const void *p): std::string_view(_txt, sizeof(_txt)) {
105  std::uintptr_t val = std::bit_cast<std::uintptr_t>(p);
106 
107  for (unsigned int i = 0; i < sizeof(_txt);++i) {
108  _txt[sizeof(_txt)-i-1] = letters[val & 0xF];
109  val >>= 4;
110  }
111  }
112 
113  char _txt[sizeof(std::uintptr_t)*2];
114 
115  };
116 
117  void on_create(const void *ptr, std::size_t size) {
118  std::lock_guard _(_mx);
119  header(record_type::create) << pointer(ptr) << separator << size << std::endl;
120  _foutput.flush();
121  }
122  void on_destroy(const void *ptr) {
123  std::lock_guard _(_mx);
124  header(record_type::destroy) << pointer(ptr) << std::endl;
125  }
126  void on_resume_enter(const void *ptr) {
127  std::lock_guard _(_mx);
128  header(record_type::resume_enter) << pointer(ptr) << std::endl;
129  }
130  void on_resume_exit() {
131  std::lock_guard _(_mx);
132  header(record_type::resume_exit) << std::endl;
133  }
134  void on_switch(const void *from, const void *to, const std::source_location *loc) {
135  std::lock_guard _(_mx);
136  if (to == std::noop_coroutine().address()) to = nullptr;
137  auto &f = header(record_type::sym_switch);
138  f << pointer(from) << separator << pointer(to);
139  if (loc) f << separator << loc->file_name() << separator << loc->line() << separator << loc->function_name();
140  f << std::endl;
141  }
142  void on_await_on(const void *coro, const void *on, const char *awt_name) {
143  std::lock_guard _(_mx);
144  header(record_type::awaits_on) << pointer(coro) << separator << awt_name << separator << pointer(on) << std::endl;
145  }
146  template<typename Arg>
147  void on_yield(const void *coro, Arg &) {
148  std::lock_guard _(_mx);
149  header(record_type::yield) << pointer(coro) << separator << typeid(Arg).name() << std::endl;
150  }
151 
152  void set_coroutine_type(const void *ptr, const char *type) {
153  std::lock_guard _(_mx);
154  header(record_type::coroutine_type) << pointer(ptr) << separator << type << std::endl;
155  }
156 
157  void on_link(const void *from, const void *to, std::size_t object_size) {
158  std::lock_guard _(_mx);
159  header(record_type::link) << pointer(from) << separator << pointer(to) << separator << object_size << std::endl;
160  }
161 
162  void hline(std::string_view text) {
163  std::lock_guard _(_mx);
164  header(record_type::hr) << text << std::endl;
165  }
166 
167  void on_block(void *ptr, std::size_t sz) {
168  std::lock_guard _(_mx);
169  header(record_type::block) << pointer(ptr) << separator << sz << std::endl;
170  }
171 
172  void on_unblock(void *ptr, std::size_t sz) {
173  std::lock_guard _(_mx);
174  header(record_type::unblock) << pointer(ptr) << separator << sz << std::endl;
175  }
176 
177 
178  template<typename ... Args>
179  void user_report(Args && ... args) {
180  std::lock_guard _(_mx);
181  auto &f = header(record_type::user_report);
182  ((f << args), ...);
183  f << std::endl;
184  }
185 
186 
187  static impl _instance;
188  static thread_local thread_state _state;
189 
190  ~impl() {
191  _mx.lock();
192  _mx.unlock(); //Windows complains when race condition
193  }
194 
195 protected:
196  std::ofstream _foutput;
197  std::mutex _mx;
198 };
199 
200 template<typename T>
201 concept await_suspend_with_location = requires(T awt, std::coroutine_handle<> h, std::source_location loc) {
202  {awt.await_suspend(h, loc)};
203 };
204 
205 
206 struct ident_awt : std::suspend_always{
207  static constexpr std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {
208  return h;
209  }
210 };
211 
212 inline constexpr ident_awt ident = {};
213 
214 
215 class base_promise_type {
216 public:
217 
218 
219  template<typename X>
220  class trace_awaiter {
221  public:
222 
223  template<std::invocable<> Fn>
224  trace_awaiter(Fn &&fn):_awt(fn()) {}
225  bool await_ready() {return _awt.await_ready();}
226  auto await_suspend(std::coroutine_handle<> h, std::source_location loc = std::source_location::current()) {
227  using RetVal = decltype(_awt.await_suspend(h));
228  if constexpr(!std::is_same_v<std::decay_t<X>, ident_awt>) {
229  impl::_instance.on_await_on(h.address(), &_awt, typeid(X).name());
230  }
231  if constexpr(std::is_convertible_v<RetVal, std::coroutine_handle<> >) {
232  std::coroutine_handle<> r = proxy_await_suspend(_awt,h, loc);
233  impl::_instance.on_switch(h.address(), r.address(), &loc);
234  return r;
235  } else if constexpr(std::is_convertible_v<RetVal, bool>) {
236  bool b = proxy_await_suspend(_awt,h,loc);
237  if (b) impl::_instance.on_switch(h.address(), nullptr, &loc);
238  return b;
239  } else {
240  impl::_instance.on_switch(h.address(), nullptr, &loc);
241  return proxy_await_suspend(_awt,h,loc);
242  }
243  }
244  decltype(auto) await_resume() {
245  return _awt.await_resume();
246  }
247 
248 
249 
250  protected:
251  X _awt;
252 
253  static auto proxy_await_suspend(X &awt, std::coroutine_handle<> h, std::source_location loc) {
254  if constexpr(await_suspend_with_location<X>) {
255  return awt.await_suspend(h, loc);
256  } else {
257  return awt.await_suspend(h);
258  }
259  }
260  };
261 
262  template<typename X>
263  inline auto await_transform(X &&awt) {
264  if constexpr(indirectly_awaitable<X>) {
265  using T = decltype(awt.operator co_await());
266  return trace_awaiter<T>([&]{return awt.operator co_await();});
267  } else {
268  return trace_awaiter<X &>([&]()->X &{return awt;});
269  }
270  }
271 
272 };
273 
274 
275 inline impl impl::_instance;
276 inline std::atomic<unsigned int> impl::thread_state::counter ={0};
277 inline thread_local impl::thread_state impl::_state;
278 
279 inline void on_create(const void *ptr, std::size_t size) {impl::_instance.on_create(ptr, size);}
280 inline void on_destroy(const void *ptr, std::size_t ) {impl::_instance.on_destroy(ptr);}
281 inline void resume(std::coroutine_handle<> h) noexcept {
282  impl::_instance.on_resume_enter(h.address());
283  h.resume();
284  impl::_instance.on_resume_exit();
285 }
286 inline std::coroutine_handle<> on_switch(std::coroutine_handle<> from, std::coroutine_handle<> to, const std::source_location *loc = nullptr) {
287  impl::_instance.on_switch(from.address(), to.address(), loc);
288  return to;
289 }
290 
291 inline bool on_switch(std::coroutine_handle<> from, bool suspend, const std::source_location *loc = nullptr) {
292  if (suspend) impl::_instance.on_switch(from.address(), nullptr, loc);
293  return suspend;
294 }
295 inline void on_suspend(std::coroutine_handle<> from, const std::source_location *loc = nullptr) {
296  impl::_instance.on_switch(from.address(), nullptr, loc);
297 }
298 
299 template<std::invocable<> Fn, typename Ref>
300 inline void on_block(Fn &&fn, Ref &r) {
301  impl::_instance.on_block(&r, sizeof(r));
302  std::forward<Fn>(fn)();
303  impl::_instance.on_unblock(&r, sizeof(r));
304 }
305 
306 
307 
308 
309 template<typename Arg>
310 inline void on_yield(std::coroutine_handle<> h, const Arg &arg) {
311  impl::_instance.on_yield(h.address(), arg);
312 }
313 
314 inline void set_class(std::coroutine_handle<> h, const char *class_name) {
315  impl::_instance.set_coroutine_type(h.address(), class_name);
316 }
317 
318 template<typename ... Args>
319 inline void log(const Args & ... args) {impl::_instance.user_report(args...);}
320 
321 
322 inline void awaiting_ref(ident_t source, std::coroutine_handle<> awaiting) {
323  impl::_instance.on_link(source.address(), awaiting.address(), 0);
324 }
325 inline void awaiting_ref(ident_t source, const void *awaiting_obj) {
326  impl::_instance.on_link(source.address(), awaiting_obj, 0);
327 }
328 template<typename T>
329 inline void awaiting_ref(const T &source, std::coroutine_handle<> awaiting) {
330  impl::_instance.on_link(&source, awaiting.address(), sizeof(source));
331 }
332 template<typename T>
333 inline void awaiting_ref(const T &source, const void *awaiting_obj) {
334  impl::_instance.on_link(&source, awaiting_obj, sizeof(source));
335 }
336 
337 
338 inline void section(std::string_view text) {::coro::trace::impl::_instance.hline(text);}
339 
340 struct suspend_always : public std::suspend_always{
341  static void await_suspend(std::coroutine_handle<> h) noexcept {
342  on_suspend(h, {});
343  }
344 };
345 
346 }
347 }
348 #else
349 
350 namespace coro {
351 
352  namespace trace {
354 
362  inline void on_create(const void *, std::size_t ) {}
364 
371  inline void on_destroy(const void *, std::size_t ) {}
372 
374 
382  inline void resume(std::coroutine_handle<> h) noexcept {h.resume();}
383 
385 
393  inline std::coroutine_handle<> on_switch(std::coroutine_handle<> , std::coroutine_handle<> to, const void *) {return to;}
395 
403  inline bool on_switch(std::coroutine_handle<> , bool suspend, const void *) {return suspend;}
404 
406  inline void on_suspend(std::coroutine_handle<> , const void *) {}
407 
408 
409  template<typename Arg>
410  inline void on_yield(std::coroutine_handle<> , const Arg &) {}
411 
412  inline void set_class(std::coroutine_handle<>, std::string_view ) {}
413 
414  template<typename ... Args>
415  inline void log(const Args & ... ) {}
416 
417  inline void awaiting_ref(ident_t , std::coroutine_handle<> ) {}
418  inline void awaiting_ref(ident_t , const void *) {}
419  template<typename T>
420  inline void awaiting_ref(const T &, std::coroutine_handle<> ) {}
421  template<typename T>
422  inline void awaiting_ref(const T &, const void *) {}
423 
424  inline void section(std::string_view) {}
425 
427 
430  using suspend_always = std::suspend_always;
431 
432  class base_promise_type {};
433 
434  struct ident_awt : std::suspend_always{
435  static constexpr std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {
436  return h;
437  }
438  };
439 
440  inline constexpr ident_awt ident = {};
441 
442  template<std::invocable<> Fn, typename Ref>
443  inline void on_block(Fn &&fn, Ref &) {
444  std::forward<Fn>(fn)();
445  }
446 
447 
448 }
449 
450 
451 
452 }
453 
454 
455 #endif
456 
Suspend current coroutine and switch to another coroutine ready to run.
Definition: cooperative.h:44
void on_create(const void *, std::size_t)
Record creation of an coroutine.
Definition: trace.h:362
void resume(std::coroutine_handle<> h) noexcept
Record resumption of an coroutine.
Definition: trace.h:382
void on_destroy(const void *, std::size_t)
Record destruction of an coroutine.
Definition: trace.h:371
bool on_switch(std::coroutine_handle<>, bool suspend, const void *)
Record switch (symmetric transfer) when boolean is returned from await_suspend.
Definition: trace.h:403
main namespace
Definition: aggregator.h:8