7 #include <source_location>
8 #ifdef LIBCORO_ENABLE_TRACE
18 unsigned long __stdcall GetCurrentThreadId();
19 unsigned long __stdcall GetModuleFileNameA(
void *,
char *,
unsigned long);
27 enum class record_type: char {
45 inline constexpr
char separator =
'\t';
52 static std::string get_file_name() {
54 char szFileName[1024];
55 GetModuleFileNameA(NULL, szFileName,
sizeof(szFileName));
56 std::filesystem::path exe_path = szFileName;
59 std::filesystem::path exe_path = std::filesystem::read_symlink(
"/proc/self/exe");
61 std::string exe_name = exe_path.stem().string();
62 std::string trace_filename = exe_name +
".corotrace";
63 return trace_filename;
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);
79 static std::atomic<unsigned int> counter;
82 thread_state():id(++counter) {}
85 std::ostream &header(record_type rt) {
89 auto tid = GetCurrentThreadId();
93 f << _state.id << separator << static_cast<char>(record_type::thread) << separator
95 _state.is_new =
false;
97 f << _state.id << separator << static_cast<char>(rt) << separator;
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);
107 for (
unsigned int i = 0; i <
sizeof(_txt);++i) {
108 _txt[
sizeof(_txt)-i-1] = letters[val & 0xF];
113 char _txt[
sizeof(std::uintptr_t)*2];
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;
123 std::lock_guard _(_mx);
124 header(record_type::destroy) << pointer(ptr) << std::endl;
126 void on_resume_enter(
const void *ptr) {
127 std::lock_guard _(_mx);
128 header(record_type::resume_enter) << pointer(ptr) << std::endl;
130 void on_resume_exit() {
131 std::lock_guard _(_mx);
132 header(record_type::resume_exit) << std::endl;
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();
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;
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;
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;
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;
162 void hline(std::string_view text) {
163 std::lock_guard _(_mx);
164 header(record_type::hr) << text << std::endl;
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;
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;
178 template<
typename ... Args>
179 void user_report(Args && ... args) {
180 std::lock_guard _(_mx);
181 auto &f = header(record_type::user_report);
187 static impl _instance;
188 static thread_local thread_state _state;
196 std::ofstream _foutput;
201 concept await_suspend_with_location = requires(T awt, std::coroutine_handle<> h, std::source_location loc) {
202 {awt.await_suspend(h, loc)};
206 struct ident_awt : std::suspend_always{
207 static constexpr std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {
212 inline constexpr ident_awt ident = {};
215 class base_promise_type {
220 class trace_awaiter {
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());
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);
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);
240 impl::_instance.on_switch(h.address(),
nullptr, &loc);
241 return proxy_await_suspend(_awt,h,loc);
244 decltype(
auto) await_resume() {
245 return _awt.await_resume();
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);
257 return awt.await_suspend(h);
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();});
268 return trace_awaiter<X &>([&]()->X &{
return awt;});
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;
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());
284 impl::_instance.on_resume_exit();
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);
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);
295 inline void on_suspend(std::coroutine_handle<> from,
const std::source_location *loc =
nullptr) {
296 impl::_instance.on_switch(from.address(),
nullptr, loc);
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));
309 template<
typename Arg>
310 inline void on_yield(std::coroutine_handle<> h,
const Arg &arg) {
311 impl::_instance.on_yield(h.address(), arg);
314 inline void set_class(std::coroutine_handle<> h,
const char *class_name) {
315 impl::_instance.set_coroutine_type(h.address(), class_name);
318 template<
typename ... Args>
319 inline void log(
const Args & ... args) {impl::_instance.user_report(args...);}
322 inline void awaiting_ref(ident_t source, std::coroutine_handle<> awaiting) {
323 impl::_instance.on_link(source.address(), awaiting.address(), 0);
325 inline void awaiting_ref(ident_t source,
const void *awaiting_obj) {
326 impl::_instance.on_link(source.address(), awaiting_obj, 0);
329 inline void awaiting_ref(
const T &source, std::coroutine_handle<> awaiting) {
330 impl::_instance.on_link(&source, awaiting.address(),
sizeof(source));
333 inline void awaiting_ref(
const T &source,
const void *awaiting_obj) {
334 impl::_instance.on_link(&source, awaiting_obj,
sizeof(source));
338 inline void section(std::string_view text) {::coro::trace::impl::_instance.hline(text);}
340 struct suspend_always :
public std::suspend_always{
341 static void await_suspend(std::coroutine_handle<> h) noexcept {
382 inline void resume(std::coroutine_handle<> h) noexcept {h.resume();}
393 inline std::coroutine_handle<>
on_switch(std::coroutine_handle<> , std::coroutine_handle<> to,
const void *) {
return to;}
406 inline void on_suspend(std::coroutine_handle<> ,
const void *) {}
409 template<
typename Arg>
410 inline void on_yield(std::coroutine_handle<> ,
const Arg &) {}
412 inline void set_class(std::coroutine_handle<>, std::string_view ) {}
414 template<
typename ... Args>
415 inline void log(
const Args & ... ) {}
417 inline void awaiting_ref(ident_t , std::coroutine_handle<> ) {}
418 inline void awaiting_ref(ident_t ,
const void *) {}
420 inline void awaiting_ref(
const T &, std::coroutine_handle<> ) {}
422 inline void awaiting_ref(
const T &,
const void *) {}
424 inline void section(std::string_view) {}
430 using suspend_always = std::suspend_always;
432 class base_promise_type {};
434 struct ident_awt : std::suspend_always{
435 static constexpr std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) noexcept {
440 inline constexpr ident_awt ident = {};
442 template<std::invocable<> Fn,
typename Ref>
443 inline void on_block(Fn &&fn, Ref &) {
444 std::forward<Fn>(fn)();
Suspend current coroutine and switch to another coroutine ready to run.
void on_create(const void *, std::size_t)
Record creation of an coroutine.
void resume(std::coroutine_handle<> h) noexcept
Record resumption of an coroutine.
void on_destroy(const void *, std::size_t)
Record destruction of an coroutine.
bool on_switch(std::coroutine_handle<>, bool suspend, const void *)
Record switch (symmetric transfer) when boolean is returned from await_suspend.