一呼百應, "one call, a hundred responses"
Loading...
Searching...
No Matches
profiling.hpp
Go to the documentation of this file.
1#pragma once
2
11#include <unistd.h>
12#include <sys/types.h>
13#include <spdlog/spdlog.h>
14#include <nlohmann/json.hpp>
15#include "attributes/common.hpp"
16#include "static_string.hpp"
17
18// tools for producing chrome:://tracing compatible trace files
19// see https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.nb4ul0l9tsgk
20
23namespace ein::profiling {
24
25enum class scope : char {
26 global = 'g',
27 process = 'p',
28 thread = 't',
29 none = '\0'
30};
31
33NLOHMANN_JSON_SERIALIZE_ENUM( scope, {
34 { scope::global, "g" },
35 { scope::process, "p" },
36 { scope::thread, "t" }
37 // none is not a scope
38})
40
41enum event_type : char {
42 duration_begin = 'B', duration_end = 'E', complete = 'X', instant = 'i',
43 counter = 'C', async_start = 'b', async_instant = 'n', async_end = 'e',
44 flow_start = 's', flow_step = 't', flow_end = 'f', sample = 'P',
47 mark = 'R', clock_sync = 'c', context_enter = '(', context_leave = ')'
48};
49
51NLOHMANN_JSON_SERIALIZE_ENUM( event_type, {
63})
64namespace detail {
65 template <typename T> struct duration_suffix_impl;
66 template <> struct duration_suffix_impl<std::chrono::milliseconds> {
67 static constexpr char const * value = "ms";
68 };
69 template <> struct duration_suffix_impl<std::chrono::nanoseconds> {
70 static constexpr char const * value = "ns";
71 };
72}
75template <typename T>
76static constexpr char const * duration_suffix = detail::duration_suffix_impl<T>::value;
77
78// can adjust granularity as required
79template <typename Duration = std::chrono::nanoseconds, typename Clock = std::chrono::high_resolution_clock>
81 using duration = Duration;
82 using clock = Clock;
86 size_t ts = std::chrono::duration_cast<duration>(clock::now().time_since_epoch()).count();
87 pid_t pid = getpid();
88 size_t tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
90 std::optional<string> id{}; // null // an actual real string
92 nlohmann::json args{}; // empty object
93};
94
95template <typename Duration, typename Clock>
96void to_json(nlohmann::json& j, const profile_event<Duration, Clock> & t) {
97 if (not t.name) j["name"] = t.name;
98 if (not t.cat) j["cat"] = t.cat;
99 j["ph"] = t.ph;
100 j["ts"] = t.ts;
101 j["pid"] = t.pid;
102 j["tid"] = t.tid;
103 if (t.s != scope::none) j["s"] = t.s;
104 if (t.id.has_value()) j["id"] = *t.id;
105 if (t.bp) j["bp"] = t.bp;
106 if (not t.args.is_null()) j["args"] = t.args;
107}
108
109template <
110 typename Mutex = std::mutex, // for globally accessible profilers
111 typename Duration = std::chrono::nanoseconds,
112 typename Clock = std::chrono::high_resolution_clock
113>
114struct profile {
115 using mutex = Mutex;
116 using duration = Duration;
117 using clock = Clock;
119
121 std::vector<event> events;
122 nlohmann::json metadata;
123 bool saved;
124
125 profile(profile const &) = delete;
126 profile(profile &&) = delete;
127
128 std::filesystem::path filename;
129 profile() noexcept
130 : events_mutex()
131 , events()
132 , metadata(nlohmann::json{})
133 , saved(true) {
134 }
135
136 ~profile() noexcept {
137 // TODO: complain if not saved
138 if (!saved)
139 spdlog::warn("Warning: unsaved profile, discarding {} event(s)", events.size());
140 }
141
142 profile & operator = (profile const &) = delete;
144
146 void log(event const & e) noexcept {
147 std::lock_guard<Mutex> lock(events_mutex);
148 events.emplace_back(e);
149 saved = false;
150 }
151
153 void set_process_name(std::string name = "") noexcept {
154#if defined(__APPLE__)
155 if (name.empty())
156 name = getprogname();
157#elif defined(__linux__)
158 if (name.empty())
159 name = program_invocation_short_name;
160#endif
161 log({.name = "process_name"_ss, .ph = event_type::metadata, .args = { {"name",name}}});
162 }
163
165 void set_thread_name(std::string const & name) noexcept {
166 log({.name = "thread_name"_ss, .ph = event_type::metadata, .args = { {"name",name}}});
167 }
168
170 void instant(static_string name, scope s) noexcept {
171 log({.name = name, .ph = event_type::instant, .s = s});
172 }
173
175 void counter(static_string name, nlohmann::json args) noexcept {
176 log({.name = name, .ph = event_type::counter, .args = std::move(args) });
177 }
178
180 void flow_start(static_string name, static_string category, static_string id) noexcept {
181 log({.name = name, .id = id, .ph = event_type::flow_start, .cat = category});
182 }
183
185 void flow_end(std::string name, static_string cat, static_string id) noexcept {
186 log({.name = std::move(name), .cat = std::move(cat), .ph = event_type::flow_end, .id = std::move(id), .bp = "e"_ss});
187 }
188
190 void save(std::filesystem::path filename) noexcept {
191 spdlog::info("Saving profile: {}",filename.c_str());
192 std::ofstream o(filename);
193 o << *this;
194 }
195
196 template <typename OStream>
198 friend
199 OStream & operator << (OStream & o, profile & p) {
200 o << std::setw(2) << nlohmann::json(p) << std::endl;
201 p.saved = true;
202 return o;
203 }
204};
205
206// for tdnr
207template <typename Mutex, typename Duration, typename Clock>
209void to_json(nlohmann::json & j, profile<Mutex,Duration,Clock> const & p) noexcept {
210 j = {
211 {"traceEvents", p.events},
212 {"displayTimeUnit", static_cast<const char *>(duration_suffix<Duration>) }
213 };
214 if (p.metadata.is_object())
215 for (const auto &e : p.metadata.template get<nlohmann::json::object_t>())
216 j[e.first] = e.second;
217};
218
219template <typename Profile>
221 using duration = typename Profile::duration;
222 using clock = typename Profile::clock;
223 using mutex = typename Profile::mutex;
225 Profile & profile;
226
228 scope_event(Profile & profile, static_string name) noexcept
229 : profile(profile) {
230 profile.log({
231 .name = name,
233 });
234 }
235
237 ~scope_event() noexcept {
238 profile.log({
240 });
241 }
242};
243
244// for manual toggling
245template <typename Profile>
247 using event = typename Profile::event;
248 Profile & profile;
251
254 : profile(profile), started(false), name(name) {}
255
257 void start() noexcept {
258 assert(not started);
259 profile.log({
260 .name = name,
262 });
263 started = true;
264 }
265
267 void stop() noexcept {
268 assert(started);
269 profile.log({
270 .name = name,
272 });;
273 started = false;
274 }
275};
276
277// creates a profile that automatically saves to disk when it goes out of scope
278
279template <
280 typename Mutex = std::mutex, // for globally accessible profilers
281 typename Duration = std::chrono::nanoseconds,
282 typename Clock = std::chrono::high_resolution_clock
284 using duration = Duration;
285 using clock = Clock;
288
289 std::filesystem::path filename;
291
292 profile_scope() = delete;
293 profile_scope(profile_scope const &) = delete;
295
297 profile_scope(std::filesystem::path filename) noexcept
298 : filename(std::move(filename)) {
299 }
300
301 ~profile_scope() noexcept {
302 p.save(filename);
303 }
304
307
309 operator profile const & () const noexcept {
310 return p;
311 }
312
314 operator profile & () noexcept {
315 return p;
316 }
317};
318
319inline void make_args(nlohmann::json &) noexcept {}
320
321template <typename K, typename V, typename ... Args>
322inline void make_args(nlohmann::json & j, K && k, V && v, Args && ... args) {
323 j[std::forward(k)] = std::forward(v);
324 make_args(j,std::forward<Args>(args)...);
325}
326
327// every thread would get one of these generated automatically
328// extern thread_local profile_scope<no_mutex> current_thread_profile;
329// extern profiler<std::mutex> process_profile;
330
331// better still:
332
333// inside forward() method, we can use
334// static auto p = profile_scope("forward.json");
335
336// auto fwd = scope_event("forward"); // will place each call as a top level entry in it.
337
338// if we want to reduce locking overhead, you can use something like
339// thread_local auto driver_profile = profile_scope<no_mutex>(fmt::format("driver-{}.json", std::hash<std::thread::id>(std::this_thread::get_id())));
340
341// auto fwd = scope_event("forward"); // will place each call as a top level entry in it.
342// ...
343// and it'll automatically give you one global profiler that saves on (graceful) program exit
344// and traces all invocations together
345
346// with these we should be able to assemble something like:
347// #define EIN_PROFILE_FN ein_maybe_unused auto tron_profile_fn = tron::profiling::scope_event (current_thread_profile,__PRETTY_FUNCTION__)
348
349// and in another view, task by task, we should be able to assemble a view using duration_events letting us see the time skew.
350
351} // ein;
352
353#if defined(EIN_TESTING) || defined(EIN_TESTING_PROFILING)
354TEST_CASE("profiling","[profiling]") {
355 using namespace nlohmann;
356 using namespace ein;
357
358 using namespace ein::profiling;
359 using std::filesystem::path;
360 using namespace std::chrono;
361
362 SECTION("scope enum serialization") {
363 json j = scope::global;
364 CHECK(j == "g");
365
366 j = scope::process;
367 CHECK(j == "p");
368
369 j = scope::thread;
370 CHECK(j == "t");
371 }
372
373 SECTION("event_type enum serialization") {
374 json j = event_type::duration_begin;
375 CHECK(j == "B");
376
377 j = event_type::duration_end;
378 CHECK(j == "E");
379
380 j = event_type::complete;
381 CHECK(j == "X");
382 }
383}
384
385#if 0
386TEST_CASE("profiling logging (broken)","[.profiling]") {
387 using namespace nlohmann;
388 using namespace ein;
389 using namespace ein::profiling;
390 using std::filesystem::path;
391 using namespace std::chrono;
392 SECTION("profile_event JSON serialization") {
394 .name = "test_event"_ss,
395 .cat = "test_category"_ss,
396 .ph = event_type::instant,
397 .s = scope::thread
398 };
399
400 json j = event;
401 CHECK(j["name"] == "test_event");
402 CHECK(j["cat"] == "test_category");
403 CHECK(j["ph"] == "i");
404 CHECK(j["s"] == "t");
405 }
406
407 SECTION("profile logging") {
408 profile<> profiler;
410 .name = "log_event"_ss,
411 .ph = event_type::instant,
412 .s = scope::thread
413 };
414
415 profiler.log(event);
416
417 CHECK(profiler.events.size() == 1);
418 CHECK(profiler.events[0].name == "log_event"_scs);
419 CHECK(profiler.events[0].ph == event_type::instant);
420 CHECK(profiler.events[0].s == scope::thread);
421 }
422
423 SECTION("profile_scope automatic saving") {
424 path test_path = "test_profile.json";
425 {
426 profile_scope<> profiler(test_path);
428 .name = "scope_event"_ss,
429 .ph = event_type::instant,
430 .s = scope::thread
431 };
432
433 profiler.p.log(event);
434 } // `profile_scope` destructor should save the profile here
435
436 CHECK(exists(test_path));
437
438 // Clean up
439 remove(test_path);
440 }
441}
442#endif
443#endif
#define ein_inline
inline [[always_inline]]
Definition common.hpp:188
#define ein_nodiscard
C++17 [[nodiscard]].
Definition common.hpp:165
void make_args(nlohmann::json &) noexcept
void to_json(nlohmann::json &j, const profile_event< Duration, Clock > &t)
Definition profiling.hpp:96
static constexpr char const * duration_suffix
Definition profiling.hpp:76
std::optional< string > id
Definition profiling.hpp:90
Definition cpuid.cpp:16
Definition bf16.cpp:11
duration_event(Profile &profile, static_string name) noexcept
typename Profile::event event
profile_scope(profile_scope const &)=delete
profile_scope(std::filesystem::path filename) noexcept
std::filesystem::path filename
profile_scope & operator=(profile_scope &&)=delete
profile_scope(profile_scope &&)=delete
profile(profile const &)=delete
friend OStream & operator<<(OStream &o, profile &p)
void set_process_name(std::string name="") noexcept
nlohmann::json metadata
void flow_start(static_string name, static_string category, static_string id) noexcept
void flow_end(std::string name, static_string cat, static_string id) noexcept
std::filesystem::path filename
void set_thread_name(std::string const &name) noexcept
profile & operator=(profile const &)=delete
void log(event const &e) noexcept
profile(profile &&)=delete
std::vector< event > events
void counter(static_string name, nlohmann::json args) noexcept
void save(std::filesystem::path filename) noexcept
void instant(static_string name, scope s) noexcept
typename Profile::duration duration
typename Profile::clock clock
scope_event(Profile &profile, static_string name) noexcept
typename Profile::mutex mutex