cyqlone develop
Fast, parallel and vectorized solver for linear systems with optimal control structure.
Loading...
Searching...
No Matches
tracing.cpp
Go to the documentation of this file.
1#include <cyqlone/tracing.hpp>
2
3#if GUANAQO_WITH_TRACING
4
5#include <batmat/assume.hpp>
6#include <chrono>
7#include <cstring>
8#include <filesystem>
9#include <format>
10#include <fstream>
11#include <functional>
12#include <map>
13#include <memory>
14#include <ratio>
15#include <span>
16#include <string>
17#include <string_view>
18
19#if CYQLONE_WITH_ZLIB
20#include <zlib.h>
21#endif
22
23namespace cyqlone {
24
25namespace fs = std::filesystem;
26
27inline auto ns_to_us(std::chrono::nanoseconds ns) {
28 return std::chrono::duration<double, std::micro>(ns).count();
29}
30
31inline auto gflops(const guanaqo::TraceLogger::Log &log) {
32 bool valid = log.duration.count() > 0 && log.flop_count >= 0;
33 auto duration = std::chrono::duration<double, std::nano>(log.duration).count();
34 return valid ? static_cast<double>(log.flop_count) / duration : 0.0;
35}
36
37inline bool is_barrier_event(const guanaqo::TraceLogger::Log &log) {
38 static constexpr std::string_view prefix = "barrier-";
39 return std::strncmp(log.name, prefix.data(), prefix.size()) == 0;
40}
41
42inline bool is_read_event(const guanaqo::TraceLogger::Log &log) {
43 static constexpr std::string_view prefix = "r:";
44 return std::strncmp(log.name, prefix.data(), prefix.size()) == 0;
45}
46
47inline bool is_write_event(const guanaqo::TraceLogger::Log &log) {
48 static constexpr std::string_view prefix = "w:";
49 return std::strncmp(log.name, prefix.data(), prefix.size()) == 0;
50}
51
52#if CYQLONE_WITH_ZLIB
53struct gzfile_deleter {
54 void operator()(gzFile f) const noexcept { gzclose(f); }
55};
56using gzfile_ptr = std::unique_ptr<std::remove_pointer_t<gzFile>, gzfile_deleter>;
57
58inline void gzwrite(gzFile f, std::string_view sv) {
59 gzwrite(f, sv.data(), static_cast<unsigned>(sv.size()));
60}
61#endif
62
63inline std::function<void(std::string_view)> get_writer(const fs::path &path) {
64 if (path.filename().string().ends_with(".json.gz")) {
65#if CYQLONE_WITH_ZLIB
66 gzfile_ptr file{gzopen(path.string().c_str(), "wb")};
67 if (!file)
68 throw std::runtime_error("Failed to open file " + path.string() + " for writing");
69 return [file = std::shared_ptr{std::move(file)}](std::string_view sv) {
70 gzwrite(file.get(), sv); // TODO: check return value
71 };
72#else
73 throw std::runtime_error("Cannot write .gz file: zlib support not enabled");
74#endif
75 } else if (path.filename().string().ends_with(".json")) {
76 auto file = std::make_shared<std::ofstream>(path);
77 if (!file || !file->is_open())
78 throw std::runtime_error("Failed to open file " + path.string() + " for writing");
79 return [file = std::move(file)](std::string_view sv) {
80 file->write(sv.data(), static_cast<ptrdiff_t>(sv.size())); // TODO: check bad bit
81 };
82 } else {
83 throw std::runtime_error("Unsupported file extension: should be .json or .json.gz");
84 }
85}
86
87void write_chrome_trace(const fs::path &path, std::span<const guanaqo::TraceLogger::Log> logs,
88 const TracingOptions &opts) {
89 using namespace std::string_view_literals;
90 auto writer = get_writer(path);
91 std::string buf(4095, '\0');
92 bool first = true;
93 writer("{\"traceEvents\":["sv);
94 std::map<std::size_t, std::size_t> thread_ids;
95 std::map<std::size_t, uint64_t> thread_flops;
96 std::size_t num_threads = 0;
97 for (const auto &log : logs)
98 if (log.name == "thread_id"sv && thread_ids.try_emplace(log.thread_id, log.instance).second)
99 num_threads = std::max(num_threads, static_cast<std::size_t>(log.instance + 1));
100 for (const auto &log : logs)
101 if (thread_ids.try_emplace(log.thread_id, num_threads).second)
102 ++num_threads;
103 auto get_thread_id = [&](std::size_t orig_id) {
104 auto it = thread_ids.find(orig_id);
105 return it != thread_ids.end() ? it->second : orig_id;
106 };
107 for (const auto &log : logs) {
108 if (log.name == "thread_id"sv)
109 continue;
110 if (opts.no_barrier && is_barrier_event(log))
111 continue;
112 BATMAT_ASSERT(!std::string_view{log.name}.contains('\"')); // TODO: proper escaping
113 const auto ts_us = ns_to_us(log.start_time);
114 const auto dur_us = ns_to_us(log.duration);
115 auto tid = get_thread_id(log.thread_id);
116 auto entry = [&] {
117 if (is_read_event(log)) {
118 return std::format_to_n( //
119 buf.data(), static_cast<ptrdiff_t>(buf.size()),
120 "{}{{\"cat\":\"{}\",\"id\":{},\"ph\":\"f\",\"ts\":{:.3f},"
121 "\"pid\":0,\"tid\":{}}}",
122 first ? "\n" : ",\n", std::string_view{log.name}.substr(2), log.instance, ts_us,
123 tid);
124 } else if (is_write_event(log)) {
125 return std::format_to_n( //
126 buf.data(), static_cast<ptrdiff_t>(buf.size()),
127 "{}{{\"cat\":\"{}\",\"id\":{},\"ph\":\"s\",\"ts\":{:.3f},"
128 "\"pid\":0,\"tid\":{}}}",
129 first ? "\n" : ",\n", std::string_view{log.name}.substr(2), log.instance, ts_us,
130 tid);
131 } else if (log.flop_count == -1) {
132 std::string_view cat = is_barrier_event(log) ? "barrier" : "trace";
133 if (log.duration.count() > 0)
134 return std::format_to_n( //
135 buf.data(), static_cast<ptrdiff_t>(buf.size()),
136 "{}{{\"name\":\"{}\",\"cat\":\"{}\",\"ph\":\"X\",\"ts\":{:.3f},"
137 "\"pid\":0,\"tid\":{},\"dur\":{:.3f},"
138 "\"args\":{{\"instance\":{}}}}}",
139 first ? "\n" : ",\n", log.name, cat, ts_us, tid, dur_us, log.instance);
140 else
141 return std::format_to_n( //
142 buf.data(), static_cast<ptrdiff_t>(buf.size()),
143 "{}{{\"name\":\"{}\",\"cat\":\"{}\",\"ph\":\"i\",\"ts\":{:.3f},"
144 "\"pid\":0,\"tid\":{},"
145 "\"args\":{{\"instance\":{}}}}}",
146 first ? "\n" : ",\n", log.name, cat, ts_us, tid, log.instance);
147 } else {
148 uint64_t &total_flops = thread_flops[tid];
149 auto old_flops = 1e-9 * static_cast<double>(total_flops);
150 auto new_flops = 1e-9 * static_cast<double>(total_flops += log.flop_count);
151 return std::format_to_n( //
152 buf.data(), static_cast<ptrdiff_t>(buf.size()),
153 "{0}{{\"name\":\"{1}\",\"cat\":\"gflops\",\"ph\":\"X\",\"ts\":{2:.3f},"
154 "\"pid\":0,\"tid\":{3},\"dur\":{4:.3f},\"args\":{{\"instance\":{5},"
155 "\"flop_count\":{6},\"gflops\":{7:.6f}}}}},\n"
156 " {{\"name\":\"gflops_{3}\",\"cat\":\"{1}\",\"ph\":\"C\",\"ts\":{2:.3f},"
157 "\"pid\":0,\"tid\":{3},\"args\":{{\"gflop_count\":{8:.9f}}}}},\n"
158 " {{\"name\":\"gflops_{3}\",\"cat\":\"{1}\",\"ph\":\"C\",\"ts\":{9:.3f},"
159 "\"pid\":0,\"tid\":{3},\"args\":{{\"gflop_count\":{10:.9f}}}}}",
160 first ? "\n" : ",\n", // 0
161 log.name, // 1
162 ts_us, // 2
163 tid, // 3
164 dur_us, // 4
165 log.instance, // 5
166 log.flop_count, // 6
167 gflops(log), // 7
168 old_flops, // 8
169 ts_us + dur_us, // 9
170 new_flops // 10
171 );
172 }
173 }();
174 writer(std::string_view{buf.data(), static_cast<size_t>(entry.size)});
175 first = false;
176 }
177 writer("\n]}"sv);
178}
179
180} // namespace cyqlone
181
182#endif
#define BATMAT_ASSERT(x)
std::chrono::nanoseconds start_time
std::chrono::nanoseconds duration