Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
trace.hpp
Go to the documentation of this file.
1// trace.hpp — Pulp tracing macros (Perfetto-backed, DEV ONLY, OFF by default).
2//
3// This is the ONLY header instrumentation code includes for tracing. With
4// PULP_TRACING=OFF — the default and only shippable configuration — every macro
5// compiles to nothing and NO Perfetto header is pulled in: zero cost, zero
6// symbols (see tools/cmake/PulpTracing.cmake and the guards it names).
7//
8// RT-safety: D1 (planning/2026-07-08-perfetto-tracing-plan.md §0c) proved
9// Perfetto's TRACE_EVENT is NOT real-time-safe — it locks a mutex on chunk
10// rollover. These macros must NEVER be placed on the audio thread's live
11// process() path. They are for UI / render / process-level and OFFLINE-audio
12// spans only; live DSP observability uses the fixed-slot per-node telemetry
13// fallback instead.
14#pragma once
15
16namespace pulp::runtime {
17
19#if defined(PULP_TRACING_ENABLED) && PULP_TRACING_ENABLED
20inline constexpr bool kTracingEnabled = true;
21#else
22inline constexpr bool kTracingEnabled = false;
23#endif
24
25} // namespace pulp::runtime
26
27#if defined(PULP_TRACING_ENABLED) && PULP_TRACING_ENABLED
28
29#include <array>
30#include <string_view>
31
32#include "perfetto.h"
33
34// Category taxonomy — the fixed query vocabulary. Declared here (storage in
35// trace.cpp via PERFETTO_TRACK_EVENT_STATIC_STORAGE). `dsp`/`dsp.node` are used
36// only from OFFLINE renders, never live process() (D1).
37PERFETTO_DEFINE_CATEGORIES(
38 perfetto::Category("dsp").SetDescription("Offline DSP block timing"),
39 perfetto::Category("dsp.node").SetDescription("Offline per-node DSP timing"),
40 perfetto::Category("render").SetDescription("Frame render pipeline"),
41 perfetto::Category("layout").SetDescription("Yoga layout passes"),
42 perfetto::Category("canvas").SetDescription("Canvas 2D drawing"),
43 perfetto::Category("text").SetDescription("Text shaping (prepare vs layout)"),
44 perfetto::Category("js").SetDescription("QuickJS bridge dispatch"),
45 perfetto::Category("gpu").SetDescription("Dawn/Graphite GPU passes"),
46 perfetto::Category("state").SetDescription("Parameter/state changes"),
47 perfetto::Category("io").SetDescription("File / network I/O"),
48 perfetto::Category("lifecycle")
49 .SetDescription("Plug-in instantiation, activation and editor open (host threads only)"));
50
51namespace pulp::runtime::trace_detail {
52
53// Compile-time PRETTY_FUNCTION → "Class::method" trimmer.
54//
55// Adapted from sudara/melatonin_perfetto (MIT). Its clever compile-time approach
56// keeps the trimmed name a compile-time value, so the span name stays a
57// perfetto::StaticString with zero runtime formatting on the hot path. Behavior
58// preserved. See NOTICE.md (melatonin_perfetto).
59//
60// The lambda-wrapped-string trick (accu.org/journals/overload/30/172/wu) lets a
61// string be a template parameter so trimming happens at compile time.
62#define PULP_TRACE_WRAP_CT_STRING(x) [] { return (x); }
63#define PULP_TRACE_UNWRAP_CT_STRING(x) (x)()
64
65template <typename WrappedString>
66constexpr auto prettify_function(WrappedString wrapped) {
67 constexpr auto src = PULP_TRACE_UNWRAP_CT_STRING(wrapped);
68 constexpr auto size = std::string_view(src).size();
69 std::array<char, size> result{};
70
71 for (size_t i = 0; i < size; ++i) {
72 // Skip the return type (everything up to the first space).
73 if (src[i] == ' ') {
74 ++i;
75 // MSVC emits an extra "__cdecl " after the return type.
76 if (src[i + 1] == '_') i += 8;
77
78 size_t j = 0;
79 // Copy the qualified name; stop at the argument list. clang/gcc use
80 // '(', MSVC uses '<' (the lambda identifier).
81 while ((src[i] != '(' && src[i] != '<') && i < size && j < size) {
82 result[j] = src[i];
83 ++i;
84 ++j;
85 }
86 // MSVC clean-up: strip the trailing "::" before "<lambda_1>".
87 if (src[i] == '<') result[j - 2] = '\0';
88 return result;
89 }
90 }
91 return result;
92}
93
94// Compile-time self-tests for the trimmer (mirrors the melatonin battery). If
95// any fires, the trimmer regressed on this compiler's PRETTY_FUNCTION shape.
96namespace prettify_test {
97template <size_t N, size_t M>
98constexpr bool eq(const std::array<char, N>& r, const char (&t)[M]) {
99 static_assert(M > 1);
100 static_assert(N + 1 >= M);
101 return std::string_view(t, M) == std::string_view(r.data(), M);
102}
103static_assert(eq(prettify_function(PULP_TRACE_WRAP_CT_STRING("int main")), "main"));
104static_assert(eq(prettify_function(PULP_TRACE_WRAP_CT_STRING(
105 "void Foo::bar(int, float)::(anonymous class)::operator()()")),
106 "Foo::bar"));
107} // namespace prettify_test
108
109} // namespace pulp::runtime::trace_detail
110
111// Public macro surface. `category` is one of the taxonomy string literals.
112// Auto-named spans use the enclosing function's prettified name. The span name
113// is always a perfetto::StaticString, so a dynamic std::string does NOT compile
114// — the compile-time ban on RT-unsafe dynamic names (plan §0b).
115#define PULP_TRACE_SCOPE(category) \
116 static constexpr auto _pulp_tr_pf = \
117 ::pulp::runtime::trace_detail::prettify_function( \
118 PULP_TRACE_WRAP_CT_STRING(PERFETTO_DEBUG_FUNCTION_IDENTIFIER())); \
119 TRACE_EVENT(category, ::perfetto::StaticString(_pulp_tr_pf.data()))
120
121#define PULP_TRACE_SCOPE_NAMED(category, name) \
122 TRACE_EVENT(category, ::perfetto::StaticString(name))
123
124// A named scope span whose name is computed at runtime (a parameter name, a
125// node id, a file basename). `name_expr` is any std::string / const char*
126// expression; it is wrapped in perfetto::DynamicString so Perfetto copies the
127// bytes per event instead of treating them as an interned StaticString.
128//
129// This is the ONLY sanctioned dynamic-name entry point, and it is a deliberate
130// per-call-site opt-in: a static name is the default because a dynamic name
131// allocates/copies on every event and multiplies `slice.name` cardinality, so
132// the `GROUP BY name` presets degrade. Reach for it only when the runtime value
133// is the thing you need to query on.
134#define PULP_TRACE_SCOPE_DYNAMIC(category, name_expr) \
135 TRACE_EVENT(category, ::perfetto::DynamicString{name_expr})
136
137// A named scope span carrying typed debug-annotation args as trailing
138// "key", value pairs — e.g.
139// PULP_TRACE_SCOPE_NAMED_ARGS("dsp", "offline_block",
140// "block_index", idx, "position_samples", pos);
141// Each key must be a string literal; Perfetto stores it as arg
142// `debug.<key>`, so SQL reads it via EXTRACT_ARG(arg_set_id, 'debug.<key>').
143#define PULP_TRACE_SCOPE_NAMED_ARGS(category, name, ...) \
144 TRACE_EVENT(category, ::perfetto::StaticString(name), __VA_ARGS__)
145
146#define PULP_TRACE_BEGIN(category, name) \
147 TRACE_EVENT_BEGIN(category, ::perfetto::StaticString(name))
148
149// Begin a cross-call named span with the same typed annotation contract as
150// PULP_TRACE_SCOPE_NAMED_ARGS. The matching PULP_TRACE_END must run on the
151// same non-audio thread.
152#define PULP_TRACE_BEGIN_ARGS(category, name, ...) \
153 TRACE_EVENT_BEGIN(category, ::perfetto::StaticString(name), __VA_ARGS__)
154
155// Begin a cross-call named span whose name is computed at runtime. Same
156// dynamic-name caveats as PULP_TRACE_SCOPE_DYNAMIC: the bytes are copied per
157// event and the name multiplies `slice.name` cardinality, so reach for it only
158// when the runtime value is the thing you need to query on. The matching
159// PULP_TRACE_END must run on the same non-audio thread.
160#define PULP_TRACE_BEGIN_DYNAMIC(category, name_expr) \
161 TRACE_EVENT_BEGIN(category, ::perfetto::DynamicString{name_expr})
162
163#define PULP_TRACE_END(category) TRACE_EVENT_END(category)
164
165// A zero-duration instant event: a thing HAPPENED at a point in time, with no
166// span to close. Use it for one-shot facts a duration would misrepresent — a
167// device error, a lost device, a dropped frame. The name is a
168// perfetto::StaticString, so a dynamic std::string does NOT compile.
169#define PULP_TRACE_INSTANT(category, name) \
170 TRACE_EVENT_INSTANT(category, ::perfetto::StaticString(name))
171
172// An instant event carrying typed debug-annotation args as trailing
173// "key", value pairs, matching PULP_TRACE_SCOPE_NAMED_ARGS. Prefer this over
174// PULP_TRACE_INSTANT_DYNAMIC when the runtime text is a payload to read rather
175// than a key to group by: the name stays interned and the variable part rides
176// along as arg `debug.<key>`, readable via
177// EXTRACT_ARG(arg_set_id, 'debug.<key>').
178#define PULP_TRACE_INSTANT_ARGS(category, name, ...) \
179 TRACE_EVENT_INSTANT(category, ::perfetto::StaticString(name), __VA_ARGS__)
180
181// An instant event whose name is computed at runtime. Same dynamic-name caveats
182// as PULP_TRACE_SCOPE_DYNAMIC — the bytes are copied per event and the name
183// multiplies `slice.name` cardinality — so reach for it only when the runtime
184// value is the thing you need to group or filter on.
185#define PULP_TRACE_INSTANT_DYNAMIC(category, name_expr) \
186 TRACE_EVENT_INSTANT(category, ::perfetto::DynamicString{name_expr})
187
188#define PULP_TRACE_COUNTER(category, name, value) \
189 TRACE_COUNTER(category, ::perfetto::StaticString(name), (value))
190
191#else // PULP_TRACING_ENABLED
192
193// OFF: names exist as no-ops in every configuration so instrumentation sites
194// compile and cost nothing. No Perfetto header is pulled in.
195#define PULP_TRACE_SCOPE(category) ((void)0)
196#define PULP_TRACE_SCOPE_NAMED(category, name) ((void)0)
197#define PULP_TRACE_SCOPE_DYNAMIC(category, name_expr) ((void)0)
198#define PULP_TRACE_SCOPE_NAMED_ARGS(category, name, ...) ((void)0)
199#define PULP_TRACE_BEGIN(category, name) ((void)0)
200#define PULP_TRACE_BEGIN_ARGS(category, name, ...) ((void)0)
201#define PULP_TRACE_BEGIN_DYNAMIC(category, name_expr) ((void)0)
202#define PULP_TRACE_END(category) ((void)0)
203#define PULP_TRACE_INSTANT(category, name) ((void)0)
204#define PULP_TRACE_INSTANT_ARGS(category, name, ...) ((void)0)
205#define PULP_TRACE_INSTANT_DYNAMIC(category, name_expr) ((void)0)
206#define PULP_TRACE_COUNTER(category, name, value) ((void)0)
207
208#endif // PULP_TRACING_ENABLED
Definition generated_event_source.hpp:33
constexpr bool kTracingEnabled
True only in a PULP_TRACING=ON build. Every shipping/default build is false.
Definition trace.hpp:22
@ j
Definition input_events.hpp:40
@ r
Definition input_events.hpp:41