GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
3 / 3
100.0%
Branches:
1 / 2
50.0%
Functions:
-
-%

util/profiler.h
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file profiler.h
3 /// @brief Ernie's in-process CPU and GPU zone profiler, its zone macros, and averaged reports.
4 ///
5 /// Records `ERNIE_ZONE` frame by frame for the Profiler panel, and with -DERNIE_TRACY=ON for
6 /// Tracy too; `ERNIE_GPU_ZONE` times commands with timestamp queries the device context reads
7 /// back into `AddGpuZone`, and reaches Tracy too.
8 /// Off until `SetEnabled`; a zone then costs one atomic load. CPU zones belong to the frame they
9 /// end in; GPU zones to the frame that recorded their commands. Reports average frames into zone
10 /// trees, modelled on Omniverse Kit's profiler window, and export text, JSON and Chrome traces. GPU
11 /// zones are in device/profile.h.
12 ///
13 /// // The rest of the scope, one per scope; a string literal.
14 /// ERNIE_ZONE("Sync");
15 /// // Text attached to the scope's zone (Tracy only).
16 /// ERNIE_ZONE_TEXT(path.String());
17 /// // A value plotted over time (Tracy only).
18 /// ERNIE_PLOT("views", 256);
19 /// // The end of a frame of a named frame set (Tracy only).
20 /// ERNIE_FRAME_MARK("Step");
21 ///
22 /// References:
23 /// - B. Taudul, "Tracy Profiler". https://github.com/wolfpld/tracy
24 /// - NVIDIA, "Profiling", Omniverse Kit SDK manual (the Profiler window: GPU and CPU zone trees,
25 /// depths, averaging, exports).
26 /// https://docs.omniverse.nvidia.com/kit/docs/kit-manual/latest/guide/profiling.html
27 /// - Google, "Trace Event Format" (complete events, "ph": "X", and thread-name metadata), read by
28 /// Perfetto and chrome://tracing.
29 /// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU
30 #pragma once
31
32 #include "ernie/export.h"
33
34 #include <cstdint>
35 #include <span>
36 #include <string>
37 #include <string_view>
38 #include <vector>
39
40 namespace ernie::profiler {
41
42 /// @brief A timed span on a CPU thread or the GPU.
43 struct Zone {
44 /// A string literal; zones keep the pointer.
45 const char* name = "";
46 /// Start, in nanoseconds.
47 std::uint64_t start = 0;
48 /// End, in nanoseconds.
49 std::uint64_t end = 0;
50 /// The CPU thread's index; unused for GPU zones.
51 std::uint32_t thread = 0;
52 /// How many zones of the same thread or recording enclose it.
53 std::uint32_t depth = 0;
54 };
55
56 /// @brief One frame's zones.
57 /// CPU times use the steady clock (`Now`). GPU times are converted to it: calibrated where the
58 /// device reads its clock with the host's, else aligned to when the first GPU zone was recorded.
59 41 struct Frame {
60 /// The frame's index.
61 std::uint64_t index = 0;
62 /// Start on the steady clock, in nanoseconds.
63 std::uint64_t start = 0;
64 /// End on the steady clock, in nanoseconds.
65 std::uint64_t end = 0;
66 /// CPU zones.
67 std::vector<Zone> cpu;
68 /// GPU zones.
69 std::vector<Zone> gpu;
70 };
71
72 /// Newest frames whose GPU zones may still be arriving.
73 inline constexpr std::size_t kFramesInFlight = 2;
74
75 /// @brief Starts or stops recording both CPU and GPU zones.
76 /// Stopping keeps the frames recorded so far.
77 /// @param enabled Whether to record.
78 ERNIE_API void SetEnabled(bool enabled);
79 /// @return Whether CPU or GPU zones are recording.
80 ERNIE_API bool Enabled();
81
82 /// @brief Starts or stops recording CPU zones (`ERNIE_ZONE`).
83 /// @param enabled Whether to record.
84 ERNIE_API void SetCpuEnabled(bool enabled);
85 /// @return Whether CPU zones are recording.
86 ERNIE_API bool CpuEnabled();
87 /// @brief Starts or stops recording GPU zones (`ERNIE_GPU_ZONE`).
88 /// @param enabled Whether to record.
89 ERNIE_API void SetGpuEnabled(bool enabled);
90 /// @return Whether GPU zones are recording.
91 ERNIE_API bool GpuEnabled();
92
93 /// @return Nanoseconds on the steady clock.
94 ERNIE_API std::uint64_t Now();
95
96 /// @brief Ends the current frame and starts the next.
97 /// The ended frame is kept even when not recording, so frame times
98 /// show before any zones.
99 ERNIE_API void BeginFrame();
100
101 /// @return The index of the frame being recorded.
102 ERNIE_API std::uint64_t CurrentFrame();
103
104 /// @return The kept finished frames, oldest first, at most
105 /// `History`.
106 ERNIE_API std::vector<Frame> Frames();
107 /// @brief Sets how many finished frames are kept.
108 /// @param frames The frame count.
109 ERNIE_API void SetHistory(std::size_t frames);
110 /// @return How many finished frames are kept.
111 ERNIE_API std::size_t History();
112 /// @brief Drops all kept frames.
113 ERNIE_API void Clear();
114
115 /// @return The calling thread's index, as in `Zone::thread`.
116 ERNIE_API std::uint32_t ThreadIndex();
117 /// @brief Names the calling thread.
118 /// @param name The name; unnamed threads are "Thread <index>".
119 ERNIE_API void SetThreadName(std::string name);
120 /// @return Thread names by index.
121 ERNIE_API std::vector<std::string> ThreadNames();
122
123 /// @brief Copies a run-time name to process-lifetime storage, since
124 /// zones keep their name's pointer.
125 /// @param name The name.
126 /// @return The copy; the same pointer for the same name.
127 ERNIE_API const char* Intern(std::string_view name);
128
129 /// @brief Adds a GPU zone to the frame that recorded it.
130 /// @param frame The frame index; dropped if no longer kept.
131 /// @param zone The zone.
132 ERNIE_API void AddGpuZone(std::uint64_t frame, const Zone& zone);
133
134 /// @brief Times the rest of the enclosing scope as a CPU zone.
135 class ERNIE_API ScopedZone {
136 public:
137 /// @brief Starts the zone if recording.
138 /// @param name A string literal naming the zone.
139 explicit ScopedZone(const char* name);
140 ~ScopedZone();
141 ScopedZone(const ScopedZone&) = delete;
142 ScopedZone& operator=(const ScopedZone&) = delete;
143
144 private:
145 const char* name_;
146 std::uint64_t start_ = 0;
147 bool active_ = false;
148 };
149
150 // ----------------------------------------------------------------------------------------------
151 // Reports
152
153 /// @brief A zone averaged over frames.
154 /// Zones of the same name under the same parent merge.
155
1/2
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
56 struct ReportNode {
156 /// The zone's name.
157 std::string name;
158 /// Mean time per frame, in milliseconds.
159 double ms = 0.0;
160 /// Mean runs per frame.
161 double calls = 0.0;
162 /// Share of its section's total, in percent.
163 double percent = 0.0;
164 /// Nested zones, in the order they first ran.
165 std::vector<ReportNode> children;
166 };
167
168 /// @brief GPU and CPU zone trees averaged over frames.
169 12 struct Report {
170 /// The GPU's zones.
171 std::vector<ReportNode> gpu;
172 /// The CPU's zones, under a node per thread.
173 std::vector<ReportNode> cpu;
174 /// Mean GPU time of the outermost zones, in milliseconds.
175 double gpuMs = 0.0;
176 /// Mean host frame time, in milliseconds.
177 double cpuMs = 0.0;
178 /// Frames whose GPU zones were averaged.
179 std::size_t gpuFrames = 0;
180 /// Frames averaged.
181 std::size_t cpuFrames = 0;
182 };
183
184 /// @brief Averages frames' zones into a report.
185 /// @param frames The frames; GPU zones count only once arrived.
186 /// @param threadNames CPU thread names by index (`ThreadNames`).
187 /// @return The report.
188 ERNIE_API Report Summarize(std::span<const Frame> frames,
189 const std::vector<std::string>& threadNames);
190
191 /// @brief Which zones of a report to list.
192 struct ReportFilter {
193 /// Deepest nesting shown; 0 lists the outermost alone.
194 std::uint32_t depth = 8;
195 /// Shortest zone shown, in milliseconds.
196 double minMs = 0.0;
197 /// Sort siblings longest first instead of by run order.
198 bool byTime = false;
199 };
200
201 /// @brief A listed zone and how deep it is shown.
202 struct ReportRow {
203 /// The zone.
204 const ReportNode* node = nullptr;
205 /// Its nesting depth.
206 std::uint32_t depth = 0;
207 /// Whether it has listed children.
208 bool parent = false;
209 };
210
211 /// @brief Flattens a zone tree into listed rows.
212 /// @param nodes The tree.
213 /// @param filter Which zones to list.
214 /// @return The rows, parents before their children.
215 ERNIE_API std::vector<ReportRow> Rows(const std::vector<ReportNode>& nodes,
216 const ReportFilter& filter);
217
218 /// @brief Formats a report as indented names, times and shares.
219 /// @param report The report.
220 /// @param filter Which zones to list.
221 /// @return The text.
222 ERNIE_API std::string ReportText(const Report& report, const ReportFilter& filter);
223
224 /// @brief Formats a report as JSON.
225 /// The shape is {"gpu": {"ms", "zones": [...]}, "cpu": ...}, each
226 /// zone {"name", "ms", "calls", "percent", "children"}.
227 /// @param report The report.
228 /// @return The JSON text.
229 ERNIE_API std::string ReportJson(const Report& report);
230
231 /// @brief Exports frames in the Trace Event Format.
232 /// CPU threads are threads of process 1, GPU zones process 2; times
233 /// are microseconds from the first frame. GPU zones are placed from
234 /// their frame's start since the GPU clock differs from the host's.
235 /// @param frames The frames.
236 /// @param threadNames CPU thread names by index.
237 /// @return The trace JSON.
238 ERNIE_API std::string TraceJson(std::span<const Frame> frames,
239 const std::vector<std::string>& threadNames);
240
241 } // namespace ernie::profiler
242
243 #define ERNIE_PROFILE_CONCAT_(a, b) a##b
244 #define ERNIE_PROFILE_CONCAT(a, b) ERNIE_PROFILE_CONCAT_(a, b)
245 #define ERNIE_OWN_ZONE(name) \
246 const ::ernie::profiler::ScopedZone ERNIE_PROFILE_CONCAT(ernieZone, __LINE__)(name)
247
248 #if ERNIE_TRACY
249 #include <tracy/Tracy.hpp>
250
251 #define ERNIE_ZONE(name) \
252 ZoneScopedN(name); \
253 ERNIE_OWN_ZONE(name)
254 #define ERNIE_ZONE_TEXT(text) \
255 do { \
256 const auto& ernieZoneText = (text); \
257 ZoneText(ernieZoneText.data(), ernieZoneText.size()); \
258 } while (false)
259 #define ERNIE_PLOT(name, value) TracyPlot(name, static_cast<double>(value))
260 #define ERNIE_FRAME_MARK(name) FrameMarkNamed(name)
261 #else
262 #define ERNIE_ZONE(name) ERNIE_OWN_ZONE(name)
263 #define ERNIE_ZONE_TEXT(text)
264 #define ERNIE_PLOT(name, value)
265 #define ERNIE_FRAME_MARK(name)
266 #endif
267