#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#ifndef BASE_TRACE_EVENT_TRACE_LOGGING_MINIMAL_WIN_H_
#define BASE_TRACE_EVENT_TRACE_LOGGING_MINIMAL_WIN_H_
* TraceLogging minimal dynamic provider
*
* TlmProvider is a simple class that implements an Event Tracing for Windows
* (ETW) provider that generates TraceLogging events with string fields. Unlike
* the Windows SDK's TraceLoggingProvider.h, this provider class supports
* runtime-variable settings for event name, level, keyword, and field name.
*
* Note that this approach is not recommended for general use. Support for
* runtime-variable settings is not normally needed, and it requires extra
* buffering as compared to the approach used by TraceLoggingProvider.h. It is
* needed in this case because we're trying to feed data from the existing call
* sites (which use a runtime-variable function-call syntax) into ETW. If this
* were new code, it would be better to update each call site to use a syntax
* compatible with compile-time event settings compatible with structured
* logging like TraceLoggingProvider.h.
*/
#include <windows.h>
#include <evntprov.h>
#include <stdint.h>
#include <concepts>
#include <cstdint>
#include "base/functional/callback.h"
* An instance of TlmProvider represents a logger through which data can be
* sent to Event Tracing for Windows (ETW). This logger generates
* TraceLogging-encoded events (compatible with the events generated by the
* Windows SDK's TraceLoggingProvider.h header). In most cases, a developer
* would prefer using TraceLoggingProvider.h over TlmProvider
* (TraceLoggingProvider.h is more efficient and more full-featured), but
* TlmProvider allows for configuring the event parameters (event name,
* level, keyword, field names) at runtime (TraceLoggingProvider.h requires
* these to be set at compile time).
*
* Note that the Register/Unregister operations are relatively expensive, so
* the TlmProvider instance should be a long-lived variable (i.e. global
* variable, static variable, or field of a long-lived object), not a local
* variable andnot a field of a short-lived object.
*
* Note that provider name and provider GUID are a tightly-bound pair, i.e.
* they should each uniquely map to each other. Once a provider name and
* provider GUID have been used together, no other GUID should be used with
* that name and no other name should be used with that GUID. Normally this
* goal is achieved by using a hashing algorithm to generate the GUID from
* a hash of the name.
*
* Note that each event should use a non-zero level and a non-zero keyword.
* Predefined level constants are defined in <evntrace.h>: 0=Always,
* 1=Critical, 2=Error, 3=Warning, 4=Info, 5=Verbose (other level values can
* be used but are not well-defined and are not generally useful). A keyword
* is a bitmask of "category" bits, where each bit indicates whether or not
* the event belongs in a particular category of event. The low 48 bits are
* user-defined and the upper 16 bits are Microsoft-defined (in <winmeta.h>).
*
* General usage:
*
* // During component initialization (main or DllMain), call Register().
* // Note that there is an overload of the TlmProvider constructor that
* // calls Register(), but it's often convenient to do this manually
* // (i.e. to control the timing of the call to Register).
* my_provider.Register(
* "MyCompany.MyComponentName",
* MyComponentGuid);
*
* // To log an event with minimal code:
* my_provider.WriteEvent("MyEventName",
* TlmEventDescriptor(
* TRACE_LEVEL_VERBOSE, // Level defined in <evntrace.h>
* 0x20), // Keyword bits are user-defined.
* // Value must not be null for the string fields.
* TlmUtf8StringField("MyUtf8Field", GetValue1()),
* TlmMbcsStringField("MyAsciiField", GetValue2()));
*
* // Note that the minimal-code example has a bit of overhead, as it
* // will make the calls to GetValue1(), GetValue2(), and WriteEvent()
* // even if nobody is listening to the event. WriteEvent() will return
// immediately if nobody is listening, but there is still some
* // overhead. To minimize the overhead when nobody is listening,
* // add an extra IF condition:
* static const auto MyEventDescriptor = TlmEventDescriptor(
* TRACE_LEVEL_VERBOSE, // Level defined in <evntrace.h>
* 0x20); // Keyword bits are user-defined.
* if (my_provider.IsEnabled(MyEventDescriptor))
* {
* // The IF condition is primarily to prevent unnecessary
* // calls to GetValue1() and GetValue2().
* my_provider.WriteEvent("MyEventName",
* MyEventDescriptor,
* // Value must not be null for the string fields.
* TlmUtf8StringField("MyUtf8Field", GetValue1()),
* TlmMbcsStringField("MyAsciiField", GetValue2()));
* }
*
* // During component shutdown (main or DllMain), call Unregister().
* // Note that the TlmProvider destructor will also call
* // Unregister(), butit's often convenient to do this manually
* // (i.e. to control the timingof the call to Unregister).
* my_provider.Unregister();
*/
#include <variant>
#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
namespace base::trace_event {
class MultiEtwPayloadHandler;
template <typename T>
concept EtwFieldBaseType = requires(T t) {
{ t.Name() } -> std::same_as<std::string_view>;
{
t.FillEventDescriptor(std::declval<EVENT_DATA_DESCRIPTOR*>())
} -> std::same_as<void>;
{ t.GetInType() } -> std::same_as<uint8_t>;
{ t.GetOutType() } -> std::same_as<uint8_t>;
};
}
class BASE_EXPORT TlmProvider {
public:
enum class EventControlCode {
kDisableProvider = 0,
kEnableProvider = 1,
kCaptureState = 2,
kHighest = kCaptureState
};
TlmProvider() noexcept;
TlmProvider(
const char* provider_name,
const GUID& provider_guid,
base::RepeatingCallback<void(EventControlCode)> on_updated) noexcept;
~TlmProvider();
TlmProvider(const TlmProvider&) = delete;
TlmProvider& operator=(const TlmProvider&) = delete;
void Unregister() noexcept;
ULONG Register(
const char* provider_name,
const GUID& provider_guid,
base::RepeatingCallback<void(EventControlCode)> on_updated) noexcept;
bool IsEnabled() const noexcept;
bool IsEnabled(uint8_t level) const noexcept;
bool IsEnabled(uint8_t level, uint64_t keyword) const noexcept;
bool IsEnabled(const EVENT_DESCRIPTOR& event_descriptor) const noexcept;
uint64_t keyword_any() const { return keyword_any_; }
template <base::trace_event::EtwFieldBaseType... FieldTys>
ULONG WriteEvent(std::string_view event_name,
const EVENT_DESCRIPTOR& event_descriptor,
const FieldTys&... event_fields) const noexcept {
if (!IsEnabled(event_descriptor)) {
return 0;
}
char metadata[kMaxEventMetadataSize];
uint16_t metadata_index;
metadata_index = EventBegin(metadata, event_name);
{
char dummy[sizeof...(FieldTys) == 0 ? 1 : sizeof...(FieldTys)] = {
EventAddField(metadata, &metadata_index, event_fields.GetInType(),
event_fields.GetOutType(), event_fields.Name())...};
DCHECK(dummy);
}
constexpr uint8_t kDescriptorsCount =
2 + DataDescCountSum<FieldTys...>::value;
EVENT_DATA_DESCRIPTOR descriptors[kDescriptorsCount];
uint8_t descriptors_index = 2;
{
char dummy[sizeof...(FieldTys) == 0 ? 1 : sizeof...(FieldTys)] = {
EventDescriptorFill(descriptors, &descriptors_index,
event_fields)...};
DCHECK(dummy);
}
return EventEnd(metadata, metadata_index, descriptors, descriptors_index,
event_descriptor);
}
private:
friend class base::trace_event::MultiEtwPayloadHandler;
friend class TlmProviderTest;
static constexpr uint16_t kMaxProviderMetadataSize = 128;
static constexpr uint16_t kMaxEventMetadataSize = 256;
template <class... FieldTys>
struct DataDescCountSum;
template <>
struct DataDescCountSum<> {
static constexpr uint8_t value = 0;
};
template <class FieldTy1, class... FieldTyRest>
struct DataDescCountSum<FieldTy1, FieldTyRest...> {
static constexpr uint8_t value =
FieldTy1::data_desc_count_ + DataDescCountSum<FieldTyRest...>::value;
};
template <class FieldTy>
static char EventDescriptorFill(EVENT_DATA_DESCRIPTOR* descriptors,
uint8_t* pdescriptors_index,
const FieldTy& event_field) noexcept {
event_field.FillEventDescriptor(&descriptors[*pdescriptors_index]);
*pdescriptors_index += FieldTy::data_desc_count_;
return 0;
}
static void __stdcall StaticEnableCallback(
const GUID* source_id,
ULONG is_enabled,
UCHAR level,
ULONGLONG match_any_keyword,
ULONGLONG match_all_keyword,
PEVENT_FILTER_DESCRIPTOR FilterData,
PVOID callback_context);
uint16_t EventBegin(char* metadata,
std::string_view event_name) const noexcept;
char EventAddField(char* metadata,
uint16_t* metadata_index,
uint8_t in_type,
uint8_t out_type,
std::string_view field_name) const noexcept;
ULONG EventEnd(char* metadata,
uint16_t metadata_index,
EVENT_DATA_DESCRIPTOR* descriptors,
uint32_t descriptors_index,
const EVENT_DESCRIPTOR& event_descriptor) const noexcept;
bool KeywordEnabled(uint64_t keyword) const noexcept;
uint16_t AppendNameToMetadata(char* metadata,
uint16_t metadata_size,
uint16_t metadata_index,
std::string_view name) const noexcept;
uint32_t level_plus1_ = 0;
uint16_t provider_metadata_size_ = 0;
uint64_t keyword_any_ = 0;
uint64_t keyword_all_ = 0;
uint64_t reg_handle_ = 0;
base::RepeatingCallback<void(EventControlCode)> on_updated_callback_;
char provider_metadata_[kMaxProviderMetadataSize] = {};
};
class BASE_EXPORT TlmFieldBase {
public:
constexpr std::string_view Name() const noexcept { return name_; }
protected:
explicit TlmFieldBase(const char* name) noexcept;
explicit TlmFieldBase(std::string_view name) noexcept;
TlmFieldBase(TlmFieldBase&&) noexcept;
TlmFieldBase& operator=(TlmFieldBase&&) noexcept;
~TlmFieldBase();
private:
std::string_view name_;
};
template <uint8_t data_desc_count,
uint8_t in_type,
uint8_t out_type = 0>
class TlmFieldWithConstants : public TlmFieldBase {
public:
uint8_t GetDataDescCount() const noexcept { return data_desc_count_; }
uint8_t GetInType() const noexcept { return in_type_; }
uint8_t GetOutType() const noexcept { return out_type_; }
protected:
explicit constexpr TlmFieldWithConstants(const char* name) noexcept
: TlmFieldBase(name) {}
private:
friend class TlmProvider;
static constexpr uint8_t data_desc_count_ = data_desc_count;
static constexpr uint8_t in_type_ = in_type;
static constexpr uint8_t out_type_ = out_type;
};
class BASE_EXPORT TlmMbcsStringField
: public TlmFieldWithConstants<1, 2>
{
public:
TlmMbcsStringField(const char* name, const char* value) noexcept;
const char* Value() const noexcept;
void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
private:
const char* value_;
};
class BASE_EXPORT TlmUtf8StringField
: public TlmFieldWithConstants<1, 2, 35>
{
public:
TlmUtf8StringField(const char* name, const char* value) noexcept;
const char* Value() const noexcept;
void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
private:
const char* value_;
};
class BASE_EXPORT TlmInt64Field
: public TlmFieldWithConstants<1,
9>
{
public:
TlmInt64Field(const char* name, const int64_t value) noexcept;
int64_t Value() const noexcept;
void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
private:
const int64_t value_;
};
class BASE_EXPORT TlmUInt64Field
: public TlmFieldWithConstants<1, 10>
{
public:
TlmUInt64Field(const char* name, const uint64_t value) noexcept;
uint64_t Value() const noexcept;
void FillEventDescriptor(EVENT_DATA_DESCRIPTOR* descriptors) const noexcept;
private:
const uint64_t value_;
};
constexpr EVENT_DESCRIPTOR TlmEventDescriptor(uint8_t level,
uint64_t keyword) noexcept {
return {
0,
0,
11,
level,
0,
0,
keyword};
}
#endif