1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #ifndef BASE_TRACE_EVENT_TRACE_LOGGING_MINIMAL_WIN_H_
11 #define BASE_TRACE_EVENT_TRACE_LOGGING_MINIMAL_WIN_H_
12
13 /*
14 * TraceLogging minimal dynamic provider
15 *
16 * TlmProvider is a simple class that implements an Event Tracing for Windows
17 * (ETW) provider that generates TraceLogging events with string fields. Unlike
18 * the Windows SDK's TraceLoggingProvider.h, this provider class supports
19 * runtime-variable settings for event name, level, keyword, and field name.
20 *
21 * Note that this approach is not recommended for general use. Support for
22 * runtime-variable settings is not normally needed, and it requires extra
23 * buffering as compared to the approach used by TraceLoggingProvider.h. It is
24 * needed in this case because we're trying to feed data from the existing call
25 * sites (which use a runtime-variable function-call syntax) into ETW. If this
26 * were new code, it would be better to update each call site to use a syntax
27 * compatible with compile-time event settings compatible with structured
28 * logging like TraceLoggingProvider.h.
29 */
30
31 #include <windows.h>
32
33 #include <evntprov.h>
34 #include <stdint.h>
35
36 #include <concepts>
37 #include <cstdint>
38 // TODO(joel@microsoft.com) Update headers and use defined constants instead
39 // of magic numbers after crbug.com/1089996 is resolved.
40
41 #include "base/functional/callback.h"
42
43 /*
44 * An instance of TlmProvider represents a logger through which data can be
45 * sent to Event Tracing for Windows (ETW). This logger generates
46 * TraceLogging-encoded events (compatible with the events generated by the
47 * Windows SDK's TraceLoggingProvider.h header). In most cases, a developer
48 * would prefer using TraceLoggingProvider.h over TlmProvider
49 * (TraceLoggingProvider.h is more efficient and more full-featured), but
50 * TlmProvider allows for configuring the event parameters (event name,
51 * level, keyword, field names) at runtime (TraceLoggingProvider.h requires
52 * these to be set at compile time).
53 *
54 * Note that the Register/Unregister operations are relatively expensive, so
55 * the TlmProvider instance should be a long-lived variable (i.e. global
56 * variable, static variable, or field of a long-lived object), not a local
57 * variable andnot a field of a short-lived object.
58 *
59 * Note that provider name and provider GUID are a tightly-bound pair, i.e.
60 * they should each uniquely map to each other. Once a provider name and
61 * provider GUID have been used together, no other GUID should be used with
62 * that name and no other name should be used with that GUID. Normally this
63 * goal is achieved by using a hashing algorithm to generate the GUID from
64 * a hash of the name.
65 *
66 * Note that each event should use a non-zero level and a non-zero keyword.
67 * Predefined level constants are defined in <evntrace.h>: 0=Always,
68 * 1=Critical, 2=Error, 3=Warning, 4=Info, 5=Verbose (other level values can
69 * be used but are not well-defined and are not generally useful). A keyword
70 * is a bitmask of "category" bits, where each bit indicates whether or not
71 * the event belongs in a particular category of event. The low 48 bits are
72 * user-defined and the upper 16 bits are Microsoft-defined (in <winmeta.h>).
73 *
74 * General usage:
75 *
76 * // During component initialization (main or DllMain), call Register().
77 * // Note that there is an overload of the TlmProvider constructor that
78 * // calls Register(), but it's often convenient to do this manually
79 * // (i.e. to control the timing of the call to Register).
80 * my_provider.Register(
81 * "MyCompany.MyComponentName",
82 * MyComponentGuid);
83 *
84 * // To log an event with minimal code:
85 * my_provider.WriteEvent("MyEventName",
86 * TlmEventDescriptor(
87 * TRACE_LEVEL_VERBOSE, // Level defined in <evntrace.h>
88 * 0x20), // Keyword bits are user-defined.
89 * // Value must not be null for the string fields.
90 * TlmUtf8StringField("MyUtf8Field", GetValue1()),
91 * TlmMbcsStringField("MyAsciiField", GetValue2()));
92 *
93 * // Note that the minimal-code example has a bit of overhead, as it
94 * // will make the calls to GetValue1(), GetValue2(), and WriteEvent()
95 * // even if nobody is listening to the event. WriteEvent() will return
96 // immediately if nobody is listening, but there is still some
97 * // overhead. To minimize the overhead when nobody is listening,
98 * // add an extra IF condition:
99 * static const auto MyEventDescriptor = TlmEventDescriptor(
100 * TRACE_LEVEL_VERBOSE, // Level defined in <evntrace.h>
101 * 0x20); // Keyword bits are user-defined.
102 * if (my_provider.IsEnabled(MyEventDescriptor))
103 * {
104 * // The IF condition is primarily to prevent unnecessary
105 * // calls to GetValue1() and GetValue2().
106 * my_provider.WriteEvent("MyEventName",
107 * MyEventDescriptor,
108 * // Value must not be null for the string fields.
109 * TlmUtf8StringField("MyUtf8Field", GetValue1()),
110 * TlmMbcsStringField("MyAsciiField", GetValue2()));
111 * }
112 *
113 * // During component shutdown (main or DllMain), call Unregister().
114 * // Note that the TlmProvider destructor will also call
115 * // Unregister(), butit's often convenient to do this manually
116 * // (i.e. to control the timingof the call to Unregister).
117 * my_provider.Unregister();
118 */
119
120 #include "base/base_export.h"
121 #include "base/functional/callback.h"
122 #include "base/memory/raw_ptr.h"
123 #include "third_party/abseil-cpp/absl/types/variant.h"
124
125 namespace base::trace_event {
126 class MultiEtwPayloadHandler;
127
128 template <typename T>
requires(T t)129 concept EtwFieldBaseType = requires(T t) {
130 { t.Name() } -> std::same_as<std::string_view>;
131 {
132 t.FillEventDescriptor(std::declval<EVENT_DATA_DESCRIPTOR*>())
133 } -> std::same_as<void>;
134 { t.GetInType() } -> std::same_as<uint8_t>;
135 { t.GetOutType() } -> std::same_as<uint8_t>;
136 };
137 } // namespace base::trace_event
138
139 class BASE_EXPORT TlmProvider {
140 public:
141 enum class EventControlCode {
142 kDisableProvider = 0,
143 kEnableProvider = 1,
144 kCaptureState = 2,
145 kHighest = kCaptureState
146 };
147
148 // Initialize a provider in the unregistered state.
149 // Note that WriteEvent and Unregister operations on an unregistered
150 // provider are safe no-ops.
151 TlmProvider() noexcept;
152
153 // Initializes a provider and attempts to register it.
154 // If there is an error, provider will be left unregistered.
155 // Note that WriteEvent and Unregister operations on an unregistered
156 // provider are safe no-ops.
157 TlmProvider(
158 const char* provider_name,
159 const GUID& provider_guid,
160 base::RepeatingCallback<void(EventControlCode)> on_updated) noexcept;
161
162 // If provider is registered, unregisters provider.
163 ~TlmProvider();
164
165 // Disable copy operations.
166 TlmProvider(const TlmProvider&) = delete;
167 TlmProvider& operator=(const TlmProvider&) = delete;
168
169 // Unregisters this provider.
170 // Calling Unregister on an unregistered provider is a safe no-op.
171 // Not thread safe - caller must ensure serialization between calls to
172 // Register() and calls to Unregister().
173 void Unregister() noexcept;
174
175 // Registers this provider. Returns Win32 error code or 0 for success.
176 // Error code is primarily for debugging and should generally be ignored
177 // in production (failure to register means Unregister and WriteEvent are
178 // safe no-ops.)
179 // Calling Register on an already-registered provider is a fatal error.
180 // Not thread safe - caller must ensure serialization between calls to
181 // Register() and calls to Unregister().
182 ULONG Register(
183 const char* provider_name,
184 const GUID& provider_guid,
185 base::RepeatingCallback<void(EventControlCode)> on_updated) noexcept;
186
187 // Returns true if any active trace listeners are interested in any events
188 // from this provider.
189 // Equivalent to IsEnabled(0, 0).
190 bool IsEnabled() const noexcept;
191
192 // Returns true if any active trace listeners are interested in events
193 // from this provider with the specified level.
194 // Equivalent to IsEnabled(level, 0).
195 bool IsEnabled(uint8_t level) const noexcept;
196
197 // Returns true if any active trace listeners are interested in events
198 // from this provider with the specified level and keyword.
199 bool IsEnabled(uint8_t level, uint64_t keyword) const noexcept;
200
201 // Returns true if any active trace listeners are interested in events
202 // from this provider with the specified level and keyword.
203 // Equivalent to IsEnabled(event_descriptor.level, event_descriptor.keyword).
204 bool IsEnabled(const EVENT_DESCRIPTOR& event_descriptor) const noexcept;
205
keyword_any()206 uint64_t keyword_any() const { return keyword_any_; }
207
208 // If any active trace listeners are interested in events from this provider
209 // with the specified level and keyword, packs the data into an event and
210 // sends it to ETW. Returns Win32 error code or 0 for success.
211 template <base::trace_event::EtwFieldBaseType... FieldTys>
WriteEvent(std::string_view event_name,const EVENT_DESCRIPTOR & event_descriptor,const FieldTys &...event_fields)212 ULONG WriteEvent(std::string_view event_name,
213 const EVENT_DESCRIPTOR& event_descriptor,
214 const FieldTys&... event_fields) const noexcept {
215 if (!IsEnabled(event_descriptor)) {
216 // If nobody is listening, report success.
217 return 0;
218 }
219 // Pack the event metadata.
220 char metadata[kMaxEventMetadataSize];
221 uint16_t metadata_index;
222 metadata_index = EventBegin(metadata, event_name);
223 { // scope for dummy array (simulates a C++17 comma-fold expression)
224 char dummy[sizeof...(FieldTys) == 0 ? 1 : sizeof...(FieldTys)] = {
225 EventAddField(metadata, &metadata_index, event_fields.GetInType(),
226 event_fields.GetOutType(), event_fields.Name())...};
227 DCHECK(dummy);
228 }
229
230 // Pack the event data.
231 constexpr uint8_t kDescriptorsCount =
232 2 + DataDescCountSum<FieldTys...>::value;
233 EVENT_DATA_DESCRIPTOR descriptors[kDescriptorsCount];
234 uint8_t descriptors_index = 2;
235 { // scope for dummy array (simulates a C++17 comma-fold expression)
236 char dummy[sizeof...(FieldTys) == 0 ? 1 : sizeof...(FieldTys)] = {
237 EventDescriptorFill(descriptors, &descriptors_index,
238 event_fields)...};
239 DCHECK(dummy);
240 }
241
242 // Finalize event and call EventWrite.
243 return EventEnd(metadata, metadata_index, descriptors, descriptors_index,
244 event_descriptor);
245 }
246
247 private:
248 friend class base::trace_event::MultiEtwPayloadHandler;
249 friend class TlmProviderTest;
250
251 // Size of the buffer used for provider metadata (field within the
252 // TlmProvider object). Provider metadata consists of the nul-terminated
253 // provider name plus a few sizes and flags, so this buffer needs to be
254 // just a few bytes larger than the largest expected provider name.
255 static constexpr uint16_t kMaxProviderMetadataSize = 128;
256
257 // Size of the buffer used for event metadata (stack-allocated in the
258 // WriteEvent method). Event metadata consists of nul-terminated event
259 // name, nul-terminated field names, field types (1 or 2 bytes per field),
260 // and a few bytes for sizes and flags.
261 static constexpr uint16_t kMaxEventMetadataSize = 256;
262
263 template <class... FieldTys>
264 struct DataDescCountSum; // undefined
265
266 template <>
267 struct DataDescCountSum<> {
268 static constexpr uint8_t value = 0;
269 };
270
271 template <class FieldTy1, class... FieldTyRest>
272 struct DataDescCountSum<FieldTy1, FieldTyRest...> {
273 static constexpr uint8_t value =
274 FieldTy1::data_desc_count_ + DataDescCountSum<FieldTyRest...>::value;
275 };
276
277 template <class FieldTy>
278 static char EventDescriptorFill(EVENT_DATA_DESCRIPTOR* descriptors,
279 uint8_t* pdescriptors_index,
280 const FieldTy& event_field) noexcept {
281 event_field.FillEventDescriptor(&descriptors[*pdescriptors_index]);
282 *pdescriptors_index += FieldTy::data_desc_count_;
283 return 0;
284 }
285
286 // This is called from the OS, so use the required call type.
287 static void __stdcall StaticEnableCallback(
288 const GUID* source_id,
289 ULONG is_enabled,
290 UCHAR level,
291 ULONGLONG match_any_keyword,
292 ULONGLONG match_all_keyword,
293 PEVENT_FILTER_DESCRIPTOR FilterData,
294 PVOID callback_context);
295
296 // Returns initial value of metadata_index.
297 uint16_t EventBegin(char* metadata,
298 std::string_view event_name) const noexcept;
299
300 char EventAddField(char* metadata,
301 uint16_t* metadata_index,
302 uint8_t in_type,
303 uint8_t out_type,
304 std::string_view field_name) const noexcept;
305
306 // Returns Win32 error code, or 0 for success.
307 ULONG EventEnd(char* metadata,
308 uint16_t metadata_index,
309 EVENT_DATA_DESCRIPTOR* descriptors,
310 uint32_t descriptors_index,
311 const EVENT_DESCRIPTOR& event_descriptor) const noexcept;
312
313 bool KeywordEnabled(uint64_t keyword) const noexcept;
314
315 uint16_t AppendNameToMetadata(char* metadata,
316 uint16_t metadata_size,
317 uint16_t metadata_index,
318 std::string_view name) const noexcept;
319
320 uint32_t level_plus1_ = 0;
321 uint16_t provider_metadata_size_ = 0;
322 uint64_t keyword_any_ = 0;
323 uint64_t keyword_all_ = 0;
324 uint64_t reg_handle_ = 0;
325 base::RepeatingCallback<void(EventControlCode)> on_updated_callback_;
326 char provider_metadata_[kMaxProviderMetadataSize] = {};
327 };
328
329 // Base class for field types.
330 // It's expected that data (name, value) will outlive the TlmFieldBase object.
331 class BASE_EXPORT TlmFieldBase {
332 public:
333 constexpr std::string_view Name() const noexcept { return name_; }
334
335 protected:
336 explicit TlmFieldBase(const char* name) noexcept;
337 explicit TlmFieldBase(std::string_view name) noexcept;
338
339 // Copy operations are suppressed. Only declare move operations.
340 TlmFieldBase(TlmFieldBase&&) noexcept;
341 TlmFieldBase& operator=(TlmFieldBase&&) noexcept;
342 ~TlmFieldBase();
343
344 private:
345 std::string_view name_;
346 };
347
348 template <uint8_t data_desc_count,
349 uint8_t in_type,
350 uint8_t out_type = 0> // Default out_type is TlgOutNULL
351 class TlmFieldWithConstants : public TlmFieldBase {
352 public:
353 uint8_t GetDataDescCount() const noexcept { return data_desc_count_; }
354 uint8_t GetInType() const noexcept { return in_type_; }
355 uint8_t GetOutType() const noexcept { return out_type_; }
356
357 protected:
358 explicit constexpr TlmFieldWithConstants(const char* name) noexcept
359 : TlmFieldBase(name) {}
360
361 private:
362 friend class TlmProvider;
363
364 static constexpr uint8_t data_desc_count_ = data_desc_count;
365 static constexpr uint8_t in_type_ = in_type;
366 static constexpr uint8_t out_type_ = out_type;
367 };
368
369 // Class that represents an event field containing nul-terminated MBCS data
370 class BASE_EXPORT TlmMbcsStringField
371 : public TlmFieldWithConstants<1, 2> // 1 data descriptor, Type =
372 // TlgInANSISTRING
373 {
374 public:
375 // name is a utf-8 nul-terminated string.
376 // value is MBCS nul-terminated string (assumed to be in system's default code
377 // page).
378 TlmMbcsStringField(const char* name, const char* value) noexcept;
379
380 const char* Value() const noexcept;
381
382 void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
383
384 private:
385 const char* value_;
386 };
387
388 // Class that represents an event field containing nul-terminated UTF-8 data.
389 class BASE_EXPORT TlmUtf8StringField
390 : public TlmFieldWithConstants<1, 2, 35> // 1 data descriptor, Type =
391 // TlgInANSISTRING + TlgOutUTF8
392 {
393 public:
394 // name and value are utf-8 nul-terminated strings.
395 TlmUtf8StringField(const char* name, const char* value) noexcept;
396
397 const char* Value() const noexcept;
398
399 void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
400
401 private:
402 const char* value_;
403 };
404
405 // Class that represents an event field containing a 64 bit signed integer.
406 class BASE_EXPORT TlmInt64Field
407 : public TlmFieldWithConstants<1,
408 9> // 1 data descriptor, Type = _TlgInINT64
409 {
410 public:
411 // name is a utf-8 nul-terminated string.
412 // value is 64 bit signed integer
413 TlmInt64Field(const char* name, const int64_t value) noexcept;
414 int64_t Value() const noexcept;
415 void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
416
417 private:
418 const int64_t value_;
419 };
420
421 class BASE_EXPORT TlmUInt64Field
422 : public TlmFieldWithConstants<1, 10> // 1 data descriptor, Type =
423 // _TlgInUINT64
424 {
425 public:
426 // name is a utf-8 nul-terminated string.
427 // value is 64 bit signed integer
428 TlmUInt64Field(const char* name, const uint64_t value) noexcept;
429 uint64_t Value() const noexcept;
430 void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
431
432 private:
433 const uint64_t value_;
434 };
435
436 // Helper for creating event descriptors for use with WriteEvent.
437 constexpr EVENT_DESCRIPTOR TlmEventDescriptor(uint8_t level,
438 uint64_t keyword) noexcept {
439 return {// Id
440 // TraceLogging generally uses the event's Name instead of Id+Version,
441 // so Id is normally set to 0 for TraceLogging events.
442 0,
443
444 // Version
445 // TraceLogging generally uses the event's Name instead of Id+Version,
446 // so Version is normally set to 0 for TraceLogging events.
447 0,
448
449 // Channel (WINEVENT_CHANNEL_*)
450 // TraceLogging-based events normally use channel 11.
451 11, // = WINEVENT_CHANNEL_TRACELOGGING
452
453 // Level (WINEVENT_LEVEL_*)
454 // 0=always, 1=fatal, 2=error, 3=warning, 4=info, 5=verbose.
455 // Levels higher than 5 are for user-defined debug levels.
456 level,
457
458 // Opcode (WINEVENT_OPCODE_*)
459 // Set Opcode for special semantics such as starting/ending an
460 // activity.
461 0, // = WINEVENT_OPCODE_INFO
462
463 // Task
464 // Set Task for user-defined semantics.
465 0, // = WINEVENT_TASK_NONE
466
467 // Keyword
468 // A keyword is a 64-bit value used for filtering events. Each bit of
469 // the keyword indicates whether the event belongs to a particular
470 // category of events. The top 16 bits of keyword have
471 // Microsoft-defined semantics and should be set to 0. The low 48 bits
472 // of keyword have user-defined semantics. All events should use a
473 // nonzero keyword to support effective event filtering (events with
474 // keyword set to 0 always pass keyword filtering).
475 keyword};
476 }
477
478 #endif // BASE_TRACE_EVENT_TRACE_LOGGING_MINIMAL_WIN_H_
479