aegir
Phlex-based simulation framework for the SHiP experiment.
Loading...
Searching...
No Matches
chrome_trace.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 CERN for the benefit of the SHiP Collaboration
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5// chrome_trace.hpp — minimal Chrome Trace Event emitter.
6//
7// Output is the JSON Array Format documented at
8// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU,
9// which both chrome://tracing and ui.perfetto.dev load directly. A small
10// RAII helper writes one "complete" event (ph="X") per scope. Emission is
11// gated at compile time by AEGIR_ENABLE_TRACE; with the flag off, every
12// macro expands to a no-op and the binary has zero tracing overhead.
13//
14// Destination: the path in $AEGIR_TRACE_FILE. If unset, tracing is silently
15// disabled even when AEGIR_ENABLE_TRACE is on.
16
17#pragma once
18
19#ifdef AEGIR_ENABLE_TRACE
20
21#include <sys/syscall.h>
22#include <unistd.h>
23
24#include <atomic>
25#include <chrono>
26#include <cstdio>
27#include <cstdlib>
28#include <mutex>
29#include <string>
30
31namespace aegir::trace {
32
33inline std::FILE* file_handle() {
34 static std::FILE* f = []() -> std::FILE* {
35 char const* path = std::getenv("AEGIR_TRACE_FILE");
36 if (!path || !*path) return nullptr;
37 std::FILE* fp = std::fopen(path, "w");
38 if (!fp) return nullptr;
39 std::fprintf(fp, "[\n");
40 std::atexit([] {
41 // Re-fetch the cached handle (atexit runs after main).
42 static std::FILE* cached = file_handle();
43 if (cached) {
44 std::fprintf(cached, "\n]\n");
45 std::fflush(cached);
46 std::fclose(cached);
47 }
48 });
49 return fp;
50 }();
51 return f;
52}
53
54inline std::mutex& write_mutex() {
55 static std::mutex m;
56 return m;
57}
58
59inline std::atomic<bool>& first_event() {
60 static std::atomic<bool> v{true};
61 return v;
62}
63
64inline long long now_us() {
65 using namespace std::chrono;
66 return duration_cast<microseconds>(steady_clock::now().time_since_epoch())
67 .count();
68}
69
70// Kernel thread ID (matches what perf/ps/top show) fits in 32 bits, so the
71// JSON parser in ui.perfetto.dev sees distinct values —
72// std::hash<std::thread::id> produces ~10^18 numbers that JavaScript's float64
73// collapses into one track.
74inline long long thread_id() {
75 static thread_local long long id =
76 static_cast<long long>(syscall(SYS_gettid));
77 return id;
78}
79
80inline void write_complete(char const* cat, char const* name, long long ts,
81 long long dur) {
82 auto* f = file_handle();
83 if (!f) return;
84 std::lock_guard lock(write_mutex());
85 std::fprintf(f,
86 "%s{\"name\":\"%s\",\"cat\":\"%s\",\"ph\":\"X\","
87 "\"ts\":%lld,\"dur\":%lld,\"pid\":1,\"tid\":%lld}",
88 first_event().exchange(false) ? "" : ",\n", name, cat, ts, dur,
89 thread_id());
90}
91
92inline void write_counter(char const* cat, char const* name, long long value) {
93 auto* f = file_handle();
94 if (!f) return;
95 std::lock_guard lock(write_mutex());
96 std::fprintf(f,
97 "%s{\"name\":\"%s\",\"cat\":\"%s\",\"ph\":\"C\","
98 "\"ts\":%lld,\"pid\":1,\"tid\":%lld,\"args\":{\"value\":%lld}}",
99 first_event().exchange(false) ? "" : ",\n", name, cat, now_us(),
100 thread_id(), value);
101}
102
103inline void set_thread_name(std::string const& name) {
104 auto* f = file_handle();
105 if (!f) return;
106 std::lock_guard lock(write_mutex());
107 std::fprintf(f,
108 "%s{\"name\":\"thread_name\",\"ph\":\"M\","
109 "\"pid\":1,\"tid\":%lld,\"args\":{\"name\":\"%s\"}}",
110 first_event().exchange(false) ? "" : ",\n", thread_id(),
111 name.c_str());
112}
113
114class ScopedEvent {
115 public:
116 ScopedEvent(char const* cat, char const* name)
117 : cat_{cat}, name_{name}, start_{now_us()} {}
118 ~ScopedEvent() {
119 auto end = now_us();
120 write_complete(cat_, name_, start_, end - start_);
121 }
122
123 private:
124 char const* cat_;
125 char const* name_;
126 long long start_;
127};
128
129} // namespace aegir::trace
130
131#define AEGIR_TRACE_CONCAT_(a, b) a##b
132#define AEGIR_TRACE_CONCAT(a, b) AEGIR_TRACE_CONCAT_(a, b)
133#define AEGIR_TRACE_EVENT(cat, name) \
134 ::aegir::trace::ScopedEvent AEGIR_TRACE_CONCAT(_aegir_trace_, __LINE__) { \
135 cat, name \
136 }
137#define AEGIR_TRACE_COUNTER(cat, name, value) \
138 ::aegir::trace::write_counter(cat, name, static_cast<long long>(value))
139#define AEGIR_TRACE_THREAD_NAME(name) ::aegir::trace::set_thread_name(name)
140
141#else // !AEGIR_ENABLE_TRACE
142
143#define AEGIR_TRACE_EVENT(cat, name) ((void)0)
144#define AEGIR_TRACE_COUNTER(cat, name, value) ((void)0)
145#define AEGIR_TRACE_THREAD_NAME(name) ((void)0)
146
147#endif