1 | // Copyright 2019 The Marl Authors. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | // The Trace API produces a trace event file that can be consumed with Chrome's |
16 | // chrome://tracing viewer. |
17 | // Documentation can be found at: |
18 | // https://www.chromium.org/developers/how-tos/trace-event-profiling-tool |
19 | // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit |
20 | |
21 | #ifndef marl_trace_h |
22 | #define marl_trace_h |
23 | |
24 | #define MARL_TRACE_ENABLED 0 |
25 | |
26 | #if MARL_TRACE_ENABLED |
27 | |
28 | #include <array> |
29 | #include <atomic> |
30 | #include <chrono> |
31 | #include <condition_variable> |
32 | #include <cstdarg> |
33 | #include <cstring> |
34 | #include <mutex> |
35 | #include <ostream> |
36 | #include <queue> |
37 | #include <thread> |
38 | |
39 | namespace marl { |
40 | |
41 | // Trace writes a trace event file into the current working directory that can |
42 | // be consumed with Chrome's chrome://tracing viewer. |
43 | // Use the MARL_* macros below instead of using this class directly. |
44 | class Trace { |
45 | public: |
46 | static constexpr size_t MaxEventNameLength = 64; |
47 | |
48 | static Trace* get(); |
49 | |
50 | void nameThread(const char* fmt, ...); |
51 | void beginEvent(const char* fmt, ...); |
52 | void endEvent(); |
53 | void beginAsyncEvent(uint32_t id, const char* fmt, ...); |
54 | void endAsyncEvent(uint32_t id, const char* fmt, ...); |
55 | |
56 | class ScopedEvent { |
57 | public: |
58 | inline ScopedEvent(const char* fmt, ...); |
59 | inline ~ScopedEvent(); |
60 | |
61 | private: |
62 | Trace* const trace; |
63 | }; |
64 | |
65 | class ScopedAsyncEvent { |
66 | public: |
67 | inline ScopedAsyncEvent(uint32_t id, const char* fmt, ...); |
68 | inline ~ScopedAsyncEvent(); |
69 | |
70 | private: |
71 | Trace* const trace; |
72 | const uint32_t id; |
73 | std::string name; |
74 | }; |
75 | |
76 | private: |
77 | Trace(); |
78 | ~Trace(); |
79 | Trace(const Trace&) = delete; |
80 | Trace& operator=(const Trace&) = delete; |
81 | |
82 | struct Event { |
83 | enum class Type : uint8_t { |
84 | Begin = 'B', |
85 | End = 'E', |
86 | Complete = 'X', |
87 | Instant = 'i', |
88 | Counter = 'C', |
89 | AsyncStart = 'b', |
90 | AsyncInstant = 'n', |
91 | AsyncEnd = 'e', |
92 | FlowStart = 's', |
93 | FlowStep = 't', |
94 | FlowEnd = 'f', |
95 | Sample = 'P', |
96 | ObjectCreated = 'N', |
97 | ObjectSnapshot = 'O', |
98 | ObjectDestroyed = 'D', |
99 | Metadata = 'M', |
100 | GlobalMemoryDump = 'V', |
101 | ProcessMemoryDump = 'v', |
102 | Mark = 'R', |
103 | ClockSync = 'c', |
104 | ContextEnter = '(', |
105 | ContextLeave = ')', |
106 | |
107 | // Internal types |
108 | Shutdown = 'S', |
109 | }; |
110 | |
111 | Event(); |
112 | virtual ~Event() = default; |
113 | virtual Type type() const = 0; |
114 | virtual void write(std::ostream& out) const; |
115 | |
116 | char name[MaxEventNameLength] = {}; |
117 | const char** categories = nullptr; |
118 | uint64_t timestamp = 0; // in microseconds |
119 | uint32_t processID = 0; |
120 | uint32_t threadID; |
121 | uint32_t fiberID; |
122 | }; |
123 | |
124 | struct BeginEvent : public Event { |
125 | Type type() const override { return Type::Begin; } |
126 | }; |
127 | struct EndEvent : public Event { |
128 | Type type() const override { return Type::End; } |
129 | }; |
130 | struct MetadataEvent : public Event { |
131 | Type type() const override { return Type::Metadata; } |
132 | }; |
133 | struct Shutdown : public Event { |
134 | Type type() const override { return Type::Shutdown; } |
135 | }; |
136 | |
137 | struct AsyncEvent : public Event { |
138 | void write(std::ostream& out) const override; |
139 | uint32_t id; |
140 | }; |
141 | |
142 | struct AsyncStartEvent : public AsyncEvent { |
143 | Type type() const override { return Type::AsyncStart; } |
144 | }; |
145 | struct AsyncEndEvent : public AsyncEvent { |
146 | Type type() const override { return Type::AsyncEnd; } |
147 | }; |
148 | |
149 | struct NameThreadEvent : public MetadataEvent { |
150 | void write(std::ostream& out) const override; |
151 | }; |
152 | |
153 | uint64_t timestamp(); // in microseconds |
154 | |
155 | void put(Event*); |
156 | std::unique_ptr<Event> take(); |
157 | |
158 | struct EventQueue { |
159 | std::queue<std::unique_ptr<Event> > data; // guarded by mutes |
160 | std::condition_variable condition; |
161 | std::mutex mutex; |
162 | }; |
163 | // TODO: Increasing this from 1 can cause events to go out of order. |
164 | // Investigate, fix. |
165 | std::array<EventQueue, 1> eventQueues; |
166 | std::atomic<unsigned int> eventQueueWriteIdx = {0}; |
167 | unsigned int eventQueueReadIdx = 0; |
168 | std::chrono::time_point<std::chrono::high_resolution_clock> createdAt = |
169 | std::chrono::high_resolution_clock::now(); |
170 | std::thread thread; |
171 | std::atomic<bool> stopped = {false}; |
172 | }; |
173 | |
174 | Trace::ScopedEvent::ScopedEvent(const char* fmt, ...) : trace(Trace::get()) { |
175 | if (trace != nullptr) { |
176 | char name[Trace::MaxEventNameLength]; |
177 | va_list vararg; |
178 | va_start(vararg, fmt); |
179 | vsnprintf(name, Trace::MaxEventNameLength, fmt, vararg); |
180 | va_end(vararg); |
181 | |
182 | trace->beginEvent(name); |
183 | } |
184 | } |
185 | |
186 | Trace::ScopedEvent::~ScopedEvent() { |
187 | if (trace != nullptr) { |
188 | trace->endEvent(); |
189 | } |
190 | } |
191 | |
192 | Trace::ScopedAsyncEvent::ScopedAsyncEvent(uint32_t id, const char* fmt, ...) |
193 | : trace(Trace::get()), id(id) { |
194 | if (trace != nullptr) { |
195 | char buf[Trace::MaxEventNameLength]; |
196 | va_list vararg; |
197 | va_start(vararg, fmt); |
198 | vsnprintf(buf, Trace::MaxEventNameLength, fmt, vararg); |
199 | va_end(vararg); |
200 | name = buf; |
201 | |
202 | trace->beginAsyncEvent(id, "%s" , buf); |
203 | } |
204 | } |
205 | |
206 | Trace::ScopedAsyncEvent::~ScopedAsyncEvent() { |
207 | if (trace != nullptr) { |
208 | trace->endAsyncEvent(id, "%s" , name.c_str()); |
209 | } |
210 | } |
211 | |
212 | } // namespace marl |
213 | |
214 | #define MARL_CONCAT_(a, b) a##b |
215 | #define MARL_CONCAT(a, b) MARL_CONCAT_(a, b) |
216 | #define MARL_SCOPED_EVENT(...) \ |
217 | marl::Trace::ScopedEvent MARL_CONCAT(scoped_event, __LINE__)(__VA_ARGS__); |
218 | #define MARL_BEGIN_ASYNC_EVENT(id, ...) \ |
219 | do { \ |
220 | if (auto t = marl::Trace::get()) { \ |
221 | t->beginAsyncEvent(id, __VA_ARGS__); \ |
222 | } \ |
223 | } while (false); |
224 | #define MARL_END_ASYNC_EVENT(id, ...) \ |
225 | do { \ |
226 | if (auto t = marl::Trace::get()) { \ |
227 | t->endAsyncEvent(id, __VA_ARGS__); \ |
228 | } \ |
229 | } while (false); |
230 | #define MARL_SCOPED_ASYNC_EVENT(id, ...) \ |
231 | marl::Trace::ScopedAsyncEvent MARL_CONCAT(defer_, __LINE__)(id, __VA_ARGS__); |
232 | #define MARL_NAME_THREAD(...) \ |
233 | do { \ |
234 | if (auto t = marl::Trace::get()) { \ |
235 | t->nameThread(__VA_ARGS__); \ |
236 | } \ |
237 | } while (false); |
238 | |
239 | #else // MARL_TRACE_ENABLED |
240 | |
241 | #define MARL_SCOPED_EVENT(...) |
242 | #define MARL_BEGIN_ASYNC_EVENT(id, ...) |
243 | #define MARL_END_ASYNC_EVENT(id, ...) |
244 | #define MARL_SCOPED_ASYNC_EVENT(id, ...) |
245 | #define MARL_NAME_THREAD(...) |
246 | |
247 | #endif // MARL_TRACE_ENABLED |
248 | |
249 | #endif // marl_trace_h |
250 | |