morph
0.1.0
Asynchronous bridge between GUI thread and business models
Toggle main menu visibility
Loading...
Searching...
No Matches
profiler.hpp
Go to the documentation of this file.
1
// SPDX-License-Identifier: Apache-2.0
2
3
#pragma once
4
5
/// @file
6
/// @brief Compile-time profiler instrumentation: Tracy zones, plots, messages
7
/// and thread names, behind morph's own macro names.
8
///
9
/// Configure with `-DMORPH_ENABLE_TRACY=ON` and the macros below forward to
10
/// Tracy (`MORPH_TRACY_ENABLED` is then defined on every target that links
11
/// `morph::morph`). Otherwise each macro is a stub that names its arguments
12
/// only inside `decltype`, an unevaluated operand: a disabled build evaluates
13
/// nothing, warns about no unused argument, and needs no Tracy headers.
14
///
15
/// @par Why morph's names and not Tracy's
16
/// Lightweight defines stubs under Tracy's own names (`ZoneScoped`,
17
/// `ZoneScopedN`, `TracyPlot`, ...) when its own Tracy option is off, and an
18
/// application may include Lightweight and morph in one translation unit. Were
19
/// morph to do the same, a build with one library's Tracy on and the other's
20
/// off would have one header's stubs redefine the other's real macros. Every
21
/// macro here is spelled `MORPH_*` and none of Tracy's names is defined.
22
///
23
/// @par One zone, one phase, one thread
24
/// A Tracy zone must begin and end on the same thread. A dispatch crosses
25
/// threads -- the caller, the model's strand, the callback executor -- so each
26
/// phase is its own zone on the thread that runs it, and the phases of one
27
/// call are linked by writing `session::Context::requestId` as zone text
28
/// (`MORPH_ZONE_TEXT`). No zone spans a hand-off, and none stays open across a
29
/// `co_await`: a suspended coroutine's thread goes on to run other work, whose
30
/// zones would then close out of order with the open one.
31
///
32
/// @par Every translation unit must agree
33
/// morph is header-only, so these macros change the bodies of inline
34
/// functions. A program in which one translation unit sees
35
/// `MORPH_TRACY_ENABLED` and another does not holds two different definitions
36
/// of the same inline function, which is an ODR violation: the linker keeps
37
/// one of them, silently. CMake consumers get agreement by construction,
38
/// because the definition is an `INTERFACE` property of `morph::morph`. A
39
/// build that does not use morph's CMake package must define
40
/// `MORPH_TRACY_ENABLED` for all of its translation units or for none, and
41
/// link exactly one `TracyClient`. MSVC and clang-cl turn a mismatch into a
42
/// link error (`detect_mismatch` below); other toolchains do not detect it.
43
44
// NOLINTBEGIN(cppcoreguidelines-macro-usage) — the macros are the API: a zone
45
// is a scoped local the call site's scope has to own, and a disabled argument
46
// must not be evaluated, neither of which a function can do.
47
48
#ifdef MORPH_TRACY_ENABLED
49
50
#include <string_view>
51
#include <tracy/Tracy.hpp>
52
53
namespace
morph::profiler::detail {
54
55
/// Attaches @p text to @p zone, skipping an empty one so a zone without a
56
/// request id carries no blank annotation.
57
inline
void
zoneText(::tracy::ScopedZone& zone, std::string_view text) {
58
if
(!
text
.empty()) {
59
zone.Text(
text
.data(),
text
.size());
60
}
61
}
62
63
/// Sends @p text to the timeline as a message.
64
inline
void
message(std::string_view text) { TracyMessage(
text
.data(),
text
.size()); }
65
66
}
// namespace morph::profiler::detail
67
68
// Every zone is a local named `morphProfilerZone`, so MORPH_ZONE_TEXT finds
69
// the innermost one; a nested zone therefore shadows the outer one on
70
// purpose, and the warning is silenced around the declaration. Not Tracy's
71
// own `___tracy_scoped_zone`: spelled in this header, a name with a double
72
// underscore is a reserved identifier at every call site.
73
// Not Tracy's ZoneScopedN: whether that expansion ends in a ';' depends on
74
// the compiler, and the one the call site writes would then be an empty
75
// statement. The trailing static_assert takes the call site's ';' instead.
76
#if defined(__clang__)
77
#define MORPH_ZONE(name) \
78
_Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wshadow\"") \
79
ZoneNamedN(morphProfilerZone, name, true); \
80
_Pragma("clang diagnostic pop") static_assert(true)
81
#elif defined(__GNUC__)
82
#define MORPH_ZONE(name) \
83
_Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wshadow\"") \
84
ZoneNamedN(morphProfilerZone, name, true); \
85
_Pragma("GCC diagnostic pop") static_assert(true)
86
#elif defined(_MSC_VER)
87
#define MORPH_ZONE(name) \
88
_Pragma("warning(push)") _Pragma("warning(disable : 4456)") ZoneNamedN(morphProfilerZone, name, true); \
89
_Pragma("warning(pop)") static_assert(true)
90
#else
91
#define MORPH_ZONE(name) ZoneNamedN(morphProfilerZone, name, true)
92
#endif
93
#define MORPH_ZONE_TEXT(text) ::morph::profiler::detail::zoneText(morphProfilerZone, (text))
94
#define MORPH_PLOT(name, value) TracyPlot(name, static_cast<double>(value))
95
#define MORPH_THREAD_NAME(name) ::tracy::SetThreadName(name)
96
#define MORPH_MESSAGE(text) ::morph::profiler::detail::message((text))
97
98
#ifdef _MSC_VER
99
#pragma detect_mismatch("morph_tracy_enabled", "1")
100
#endif
101
102
#else
103
104
namespace
morph::profiler::detail {
105
106
/// What a disabled macro builds from its arguments' types: naming an argument
107
/// in `decltype` uses it without evaluating it, and an empty object costs
108
/// nothing to discard.
109
template
<
typename
... Args>
110
struct
Unevaluated {};
111
112
}
// namespace morph::profiler::detail
113
114
/// @brief Opens a profiler zone named @p name that ends with the enclosing
115
/// scope. At most one per scope.
116
/// @param name A string literal: Tracy keeps a pointer to it for the life of
117
/// the process.
118
#define MORPH_ZONE(name) static_cast<void>(::morph::profiler::detail::Unevaluated<decltype(name)>{})
119
120
/// @brief Attaches @p text to the enclosing scope's `MORPH_ZONE`; an empty
121
/// text attaches nothing.
122
///
123
/// Only valid after a `MORPH_ZONE` in the same or an enclosing scope.
124
/// @param text Anything convertible to `std::string_view`; Tracy copies it.
125
/// Not evaluated in a build without Tracy.
126
#define MORPH_ZONE_TEXT(text) static_cast<void>(::morph::profiler::detail::Unevaluated<decltype(text)>{})
127
128
/// @brief Records @p value on the plot named @p name.
129
/// @param name A string literal, the plot's identity in the capture.
130
/// @param value An arithmetic value, recorded as a `double`. Not evaluated in
131
/// a build without Tracy.
132
#define MORPH_PLOT(name, value) \
133
static_cast<void>(::morph::profiler::detail::Unevaluated<decltype(name), decltype(value)>{})
134
135
/// @brief Names the calling thread in the capture.
136
/// @param name A NUL-terminated string; Tracy copies it.
137
#define MORPH_THREAD_NAME(name) static_cast<void>(::morph::profiler::detail::Unevaluated<decltype(name)>{})
138
139
/// @brief Sends @p text to the capture's timeline as a message.
140
/// @param text Anything convertible to `std::string_view`; Tracy copies it.
141
/// Not evaluated in a build without Tracy.
142
#define MORPH_MESSAGE(text) static_cast<void>(::morph::profiler::detail::Unevaluated<decltype(text)>{})
143
144
#ifdef _MSC_VER
145
#pragma detect_mismatch("morph_tracy_enabled", "0")
146
#endif
147
148
#endif
149
150
// NOLINTEND(cppcoreguidelines-macro-usage)
morph::ui::author::text
Node text(TextOptions const &options)
A text node.
Definition
author.hpp:3126
morph
core
profiler.hpp
Generated by
1.17.0