├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── etw-metadata.h ├── etw-provider.h ├── example-etw-provider.cc ├── example-etw-provider.h ├── main.cc └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .vscode/ 3 | out/ 4 | build/ 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | # Comment out the below 3 set statements to use MSVC on Windows 4 | # Put clang-cl on the path before running 5 | set(CMAKE_CXX_COMPILER clang-cl.exe) 6 | set(CMAKE_C_COMPILER clang-cl.exe) 7 | set(CMAKE_LINKER lld-link.exe) 8 | 9 | set(CMAKE_CXX_FLAGS "/std:c++14") 10 | 11 | project(cpp-etw) 12 | 13 | add_executable (cpp-etw "main.cc" "example-etw-provider.cc" "example-etw-provider.h" "etw-provider.h" "etw-metadata.h") 14 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "-v", 12 | "ctestCommandArgs": "", 13 | "variables": [] 14 | }, 15 | { 16 | "name": "x64-Release", 17 | "generator": "Ninja", 18 | "configurationType": "RelWithDebInfo", 19 | "buildRoot": "${projectDir}\\out\\build\\${name}", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "-v", 23 | "ctestCommandArgs": "", 24 | "inheritEnvironments": [ "msvc_x64_x64" ], 25 | "variables": [] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Bill Ticehurst. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of the copyright holder nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /etw-metadata.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Bill Ticehurst. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | // Provides templates and functions to enable the `constexpr` declaration 6 | // of metadata for ETW events. 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | namespace etw { 14 | 15 | // Structure to treat a string literal, or char[], as a constexpr byte sequence 16 | template 17 | struct str_bytes { 18 | template 19 | constexpr str_bytes(char const (&str)[count], std::index_sequence) 20 | : bytes{str[idx]...}, size(count) {} 21 | 22 | // Concatenate two str_bytes 23 | template 25 | constexpr str_bytes(const str_bytes& s1, std::index_sequence, 26 | const str_bytes& s2, std::index_sequence) 27 | : bytes{s1.bytes[idx1]..., s2.bytes[idx2]...}, size(count) {} 28 | 29 | char bytes[count]; // NOLINT 30 | size_t size; 31 | }; 32 | 33 | // Specialization for 0 (base case when joining fields) 34 | template <> 35 | struct str_bytes<0> { 36 | constexpr str_bytes() : bytes{}, size(0) {} 37 | char bytes[1]; // MSVC doesn't like an array of 0 bytes 38 | size_t size; 39 | }; 40 | 41 | // Factory function to simplify creating a str_bytes from a string literal 42 | template > 43 | constexpr auto MakeStrBytes(char const (&s)[count]) { 44 | return str_bytes{s, idx{}}; 45 | } 46 | 47 | // Concatenates two str_bytes into one 48 | template 49 | constexpr auto JoinBytes(const str_bytes& str1, 50 | const str_bytes& str2) { 51 | auto idx1 = std::make_index_sequence(); 52 | auto idx2 = std::make_index_sequence(); 53 | return str_bytes{str1, idx1, str2, idx2}; 54 | } 55 | 56 | // Creates an str_bytes which is the field name suffixed with the field type 57 | template 58 | constexpr auto Field(char const (&s)[count], uint8_t type) { 59 | auto field_name = MakeStrBytes(s); 60 | const char type_arr[1] = {static_cast(type)}; 61 | return JoinBytes(field_name, MakeStrBytes(type_arr)); 62 | } 63 | 64 | // Creates the ETW event metadata header, which consists of a uint16_t 65 | // representing the total size, and a tag byte (always 0x00 currently). 66 | constexpr auto Header(size_t size) { 67 | const char header_bytes[3] = {static_cast(size & 0xFF), 68 | static_cast(size >> 8 & 0xFF), 69 | '\0'}; 70 | return MakeStrBytes(header_bytes); 71 | } 72 | 73 | // The JoinFields implementations below are a set of overloads for constructing 74 | // a str_bytes representing the concatenated fields from a parameter pack. 75 | 76 | // Empty case needed for events with no fields. 77 | constexpr auto JoinFields() { return str_bytes<0>{}; } 78 | 79 | // Only one field, or base case when multiple fields. 80 | template 81 | constexpr auto JoinFields(T1 field) { 82 | return field; 83 | } 84 | 85 | // Join two or more fields together. 86 | template 87 | constexpr auto JoinFields(T1 field1, T2 field2, Ts... args) { 88 | auto bytes = JoinBytes(field1, field2); 89 | return JoinFields(bytes, args...); 90 | } 91 | 92 | // Creates a constexpr char[] representing the metadata for an ETW event. 93 | // Declare the variable as `constexpr static auto` and provide the event name, 94 | // followed by a series of `Field` invocations for each field. 95 | // 96 | // Example: 97 | // constexpr static auto event_meta = EventMetadata("my1stEvent", 98 | // Field("MyIntVal", kTypeInt32), 99 | // Field("MyMsg", kTypeAnsiStr), 100 | // Field("Address", kTypePointer)); 101 | template 102 | constexpr auto EventMetadata(char const (&name)[count], Ts... field_args) { 103 | auto name_bytes = MakeStrBytes(name); 104 | auto fields = JoinFields(field_args...); 105 | auto data = JoinBytes(name_bytes, fields); 106 | 107 | auto header = Header(data.size + 3); // Size includes the 2 byte size + tag 108 | return JoinBytes(header, data); 109 | } 110 | 111 | } // namespace etw 112 | -------------------------------------------------------------------------------- /etw-provider.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Bill Ticehurst. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | // Provides constants and a base class to use in the implementation of an ETW 6 | // provider. To use, derive a class from EtwProvider which constructs the base 7 | // class with the GUID and name for the provider, e.g. 8 | // 9 | // ExampleEtwProvider::ExampleEtwProvider() 10 | // : EtwProvider(example_provider_guid, example_provider_name) {} 11 | // 12 | // To log events, implement member functions that define the necessary event 13 | // metadata, and then call the LogEventData member function with the metadata 14 | // and field values, e.g. 15 | // 16 | // void ExampleEtwProvider::Log3Fields(int val, const std::string& msg, void* addr) 17 | // { 18 | // constexpr static auto event_desc = EventDescriptor(100); 19 | // constexpr static auto event_meta = EventMetadata("my1stEvent", 20 | // Field("MyIntVal", etw::kTypeInt32), 21 | // Field("MyMsg", etw::kTypeAnsiStr), 22 | // Field("Address", etw::kTypePointer)); 23 | // 24 | // LogEventData(&event_desc, &event_meta, val, msg, addr); 25 | // } 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #include "./etw-metadata.h" 36 | 37 | namespace etw { 38 | 39 | // Taken from the TRACE_LEVEL_* macros in 40 | const UCHAR kLevelNone = 0; 41 | const UCHAR kLevelFatal = 1; 42 | const UCHAR kLevelError = 2; 43 | const UCHAR kLevelWarning = 3; 44 | const UCHAR kLevelInfo = 4; 45 | const UCHAR kLevelVerbose = 5; 46 | 47 | // Taken from the EVENT_TRACE_TYPE_* macros in 48 | const UCHAR kOpCodeInfo = 0; 49 | const UCHAR kOpCodeStart = 1; 50 | const UCHAR kOpCodeStop = 2; 51 | 52 | // See "enum TlgIn_t" in 53 | const UCHAR kTypeAnsiStr = 2; 54 | const UCHAR kTypeInt8 = 3; 55 | const UCHAR kTypeInt32 = 7; 56 | const UCHAR kTypeDouble = 12; 57 | const UCHAR kTypePointer = 21; 58 | 59 | // All "manifest-free" events should go to channel 11 by default 60 | const UCHAR kManifestFreeChannel = 11; 61 | 62 | // Creates a constexpr EVENT_DESCRIPTOR structure for use with ETW calls 63 | constexpr auto EventDescriptor(USHORT id, UCHAR level = 0, 64 | ULONGLONG keyword = 0, UCHAR opcode = 0, 65 | USHORT task = 0) { 66 | return EVENT_DESCRIPTOR{id, 67 | 0, // Version 68 | kManifestFreeChannel, 69 | level, 70 | opcode, 71 | task, 72 | keyword}; 73 | } 74 | 75 | class EtwProvider { 76 | public: 77 | // An event provider should be a singleton in a process. Disable copy/move. 78 | EtwProvider(const EtwProvider&) = delete; 79 | EtwProvider& operator=(const EtwProvider&) = delete; 80 | 81 | // GCC/Clang supported builtin for branch hints 82 | #if defined(__GNUC__) 83 | #define LIKELY(condition) (__builtin_expect(!!(condition), 1)) 84 | #else 85 | #define LIKELY(condition) (condition) 86 | #endif 87 | 88 | // For use by this class before calling EventWrite 89 | bool IsEventEnabled(const EVENT_DESCRIPTOR* event_desc) { 90 | if (LIKELY(this->enabled_ == false)) return false; 91 | return (event_desc->Level <= this->level_) && 92 | (event_desc->Keyword == 0 || 93 | ((event_desc->Keyword & this->keywords_) != 0)); 94 | } 95 | 96 | // For use by user-code before constructing event data 97 | bool IsEventEnabled(UCHAR level, ULONGLONG keywords = 0) { 98 | if (LIKELY(this->enabled_ == false)) return false; 99 | return (level <= this->level_) && 100 | (keywords == 0 || ((keywords & this->keywords_) != 0)); 101 | } 102 | 103 | #undef LIKELY 104 | 105 | void SetMetaDescriptors(EVENT_DATA_DESCRIPTOR* data_descriptor, 106 | const void* metadata, 107 | size_t size) { 108 | // Note: May be able to just set the name on the provider if only Win10 or 109 | // later can be supported. See the docs for EventSetInformation and 110 | // https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits 111 | EventDataDescCreate(data_descriptor, traits_.data(), traits_.size()); 112 | data_descriptor->Type = EVENT_DATA_DESCRIPTOR_TYPE_PROVIDER_METADATA; 113 | ++data_descriptor; 114 | EventDataDescCreate(data_descriptor, metadata, static_cast(size)); 115 | data_descriptor->Type = EVENT_DATA_DESCRIPTOR_TYPE_EVENT_METADATA; 116 | } 117 | 118 | ULONG LogEvent(const EVENT_DESCRIPTOR* event_descriptor, 119 | EVENT_DATA_DESCRIPTOR* data_descriptor, ULONG desc_count) { 120 | if (reg_handle_ == 0) return ERROR_SUCCESS; 121 | return EventWriteTransfer(reg_handle_, event_descriptor, 122 | NULL /* ActivityId */, 123 | NULL /* RelatedActivityId */, 124 | desc_count, 125 | data_descriptor); 126 | } 127 | 128 | // One or more fields to set 129 | template 130 | void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR *data_descriptors, 131 | const T& value, const Ts&... rest) { 132 | EventDataDescCreate(data_descriptors, &value, sizeof(value)); 133 | SetFieldDescriptors(++data_descriptors, rest...); 134 | } 135 | 136 | // Specialize for strings 137 | template 138 | void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR *data_descriptors, 139 | const std::string& value, const Ts&... rest) { 140 | EventDataDescCreate(data_descriptors, value.data(), 141 | static_cast(value.size() + 1)); 142 | SetFieldDescriptors(++data_descriptors, rest...); 143 | } 144 | 145 | // Base case, no fields left to set 146 | void SetFieldDescriptors(EVENT_DATA_DESCRIPTOR *data_descriptors) {} 147 | 148 | // Template LogEvent used to simplify call 149 | template 150 | void LogEventData(const EVENT_DESCRIPTOR* event_descriptor, T* meta, 151 | const Fs&... fields) { 152 | if (!IsEventEnabled(event_descriptor)) return; 153 | 154 | const size_t descriptor_count = sizeof...(fields) + 2; 155 | EVENT_DATA_DESCRIPTOR descriptors[sizeof...(fields) + 2]; 156 | 157 | SetMetaDescriptors(descriptors, meta->bytes, meta->size); 158 | 159 | EVENT_DATA_DESCRIPTOR *data_descriptors = descriptors + 2; 160 | SetFieldDescriptors(data_descriptors, fields...); 161 | 162 | LogEvent(event_descriptor, descriptors, descriptor_count); 163 | } 164 | 165 | // Called whenever the the state of providers listening changes. 166 | // Also called immediately on registering if there is already a listener. 167 | static void NTAPI EnableCallback( 168 | const GUID *source_id, 169 | ULONG is_enabled, 170 | UCHAR level, // Is 0xFF if not specified by the session 171 | ULONGLONG match_any_keyword, // 0xFF...FF if not specified by the session 172 | ULONGLONG match_all_keyword, 173 | EVENT_FILTER_DESCRIPTOR *filter_data, 174 | VOID *callback_context) { 175 | if (callback_context == nullptr) return; 176 | EtwProvider* the_provider = static_cast(callback_context); 177 | switch (is_enabled) { 178 | case 0: // EVENT_CONTROL_CODE_DISABLE_PROVIDER 179 | the_provider->enabled_ = false; 180 | break; 181 | case 1: // EVENT_CONTROL_CODE_ENABLE_PROVIDER 182 | the_provider->enabled_ = true; 183 | the_provider->level_ = level; 184 | the_provider->keywords_ = match_any_keyword; 185 | break; 186 | } 187 | } 188 | 189 | bool enabled() { return enabled_; } 190 | void set_enabled(bool value) { enabled_ = value; } // For testing only 191 | 192 | protected: 193 | // All creation/deletion should be via derived classes, hence protected. 194 | EtwProvider(const GUID& provider_guid, const std::string& provider_name) 195 | : enabled_(false), 196 | provider_(provider_guid), 197 | name_(provider_name), 198 | reg_handle_(0), 199 | level_(0), 200 | keywords_(0) { 201 | ULONG result = 202 | EventRegister(&provider_, 203 | EtwProvider::EnableCallback, this, ®_handle_); 204 | if (result != ERROR_SUCCESS) { 205 | // Note: Fail silenty here, rather than throw. Tracing is typically not 206 | // critical, and this means no exception support is needed. 207 | reg_handle_ = 0; 208 | return; 209 | } 210 | 211 | // Copy the provider name, prefixed by a UINT16 length, to a buffer. 212 | // The string in the buffer should be null terminated. 213 | // See https://docs.microsoft.com/en-us/windows/win32/etw/provider-traits 214 | size_t traits_bytes = sizeof(UINT16) + name_.size() + 1; 215 | traits_.resize(traits_bytes, '\0'); // Trailing byte will already be null 216 | *reinterpret_cast(traits_.data()) = traits_bytes; 217 | name_.copy(traits_.data() + sizeof(UINT16), name_.size(), 0); 218 | } 219 | 220 | ~EtwProvider() { 221 | if (reg_handle_ != 0) EventUnregister(reg_handle_); 222 | } 223 | 224 | private: 225 | bool enabled_; 226 | const GUID provider_; 227 | const std::string name_; 228 | REGHANDLE reg_handle_; 229 | std::vector traits_; 230 | UCHAR level_; 231 | ULONGLONG keywords_; 232 | }; 233 | 234 | } // namespace etw 235 | -------------------------------------------------------------------------------- /example-etw-provider.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Bill Ticehurst. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include "./example-etw-provider.h" 6 | 7 | #include 8 | 9 | namespace example { 10 | 11 | ExampleEtwProvider::ExampleEtwProvider() 12 | : EtwProvider(example_provider_guid, example_provider_name) {} 13 | 14 | ExampleEtwProvider& ExampleEtwProvider::GetProvider() { 15 | // The below pattern means the destructor will not run at process exit. (Which 16 | // is unnecessary anyway as the provider will unregister at process exit). 17 | // See "Static and Global Variables" in https://google.github.io/styleguide/cppguide.html 18 | static ExampleEtwProvider &the_provider = *(new ExampleEtwProvider()); 19 | return the_provider; 20 | } 21 | 22 | // Any non-trivial logging should be a separate function call, not inlined 23 | void ExampleEtwProvider::Log3Fields(int val, 24 | const std::string& msg, void* addr) { 25 | constexpr static auto event_desc = EventDescriptor(100); 26 | constexpr static auto event_meta = EventMetadata("my1stEvent", 27 | Field("MyIntVal", etw::kTypeInt32), 28 | Field("MyMsg", etw::kTypeAnsiStr), 29 | Field("Address", etw::kTypePointer)); 30 | 31 | LogEventData(&event_desc, &event_meta, val, msg, addr); 32 | } 33 | 34 | } // namespace example 35 | -------------------------------------------------------------------------------- /example-etw-provider.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Bill Ticehurst. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "./etw-provider.h" 10 | 11 | namespace example { 12 | 13 | using etw::EtwProvider; 14 | using etw::EventDescriptor; 15 | using etw::EventMetadata; 16 | using etw::Field; 17 | 18 | /* 19 | Note: Below should be run from an admin prompt. 20 | 21 | For simple testing, use "logman" to create a trace for this provider via: 22 | 23 | logman create trace -n example -o example.etl -p {f0c59bc0-7da6-58c1-b1b0-e97dd10ac324} 24 | 25 | After the provider GUID, you can optionally specificy keywords and level, e.g. 26 | 27 | -p {f0c59bc0-7da6-58c1-b1b0-e97dd10ac324} 0xBEEF 0x05 28 | 29 | To capture events, start/stop the trace via: 30 | 31 | logman start example 32 | logman stop example 33 | 34 | When finished recording, remove the configured trace via: 35 | 36 | logman delete example 37 | 38 | Alternatively, use a tool such as PerfView or WPR to configure and record 39 | traces. 40 | */ 41 | 42 | // {f0c59bc0-7da6-58c1-b1b0-e97dd10ac324} 43 | constexpr GUID example_provider_guid = { 44 | 0xf0c59bc0, 45 | 0x7da6, 46 | 0x58c1, 47 | {0xb1, 0xb0, 0xe9, 0x7d, 0xd1, 0x0a, 0xc3, 0x24}}; 48 | constexpr char example_provider_name[] = "example"; 49 | 50 | class ExampleEtwProvider : public EtwProvider { 51 | public: 52 | static ExampleEtwProvider& GetProvider(); 53 | void Initialized(); 54 | void StartSort(int element_count); 55 | void StopSort(); 56 | void Finished(int total_elements); 57 | void Log3Fields(int val, const std::string& msg, void* addr); 58 | 59 | private: 60 | ExampleEtwProvider(); 61 | }; 62 | 63 | // For minimal overhead in instrumented code, make the functions inline to avoid 64 | // a call. 65 | inline void ExampleEtwProvider::Initialized() { 66 | constexpr static auto event_desc = EventDescriptor(101, etw::kLevelInfo); 67 | constexpr static auto event_meta = EventMetadata("Initialized"); 68 | 69 | LogEventData(&event_desc, &event_meta); 70 | } 71 | 72 | inline void ExampleEtwProvider::StartSort(int element_count) { 73 | constexpr static auto event_desc = 74 | EventDescriptor(102, etw::kLevelInfo, 0 /*keyword*/, etw::kOpCodeStart); 75 | constexpr static auto event_meta = 76 | EventMetadata("StartSort", Field("element_count", etw::kTypeInt32)); 77 | 78 | LogEventData(&event_desc, &event_meta, element_count); 79 | } 80 | 81 | inline void ExampleEtwProvider::StopSort() { 82 | constexpr static auto event_desc = 83 | EventDescriptor(103, etw::kLevelInfo, 0, etw::kOpCodeStop); 84 | constexpr static auto event_meta = EventMetadata("StopSort"); 85 | 86 | LogEventData(&event_desc, &event_meta); 87 | } 88 | 89 | inline void ExampleEtwProvider::Finished(int total_elements) { 90 | constexpr static auto event_desc = EventDescriptor(104, etw::kLevelInfo); 91 | constexpr static auto event_meta = 92 | EventMetadata("Finished", Field("element_count", etw::kTypeInt32)); 93 | 94 | LogEventData(&event_desc, &event_meta, total_elements); 95 | } 96 | 97 | } // namespace example 98 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Bill Ticehurst. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #include 6 | #include 7 | 8 | #include "./example-etw-provider.h" 9 | 10 | namespace { 11 | 12 | example::ExampleEtwProvider* example_etw_provider = nullptr; 13 | int total_elements = 0; 14 | 15 | void PrintArray(int* p_start, int count) { 16 | for (int i = 0; i < count; ++i) { 17 | printf("%d ", *(p_start + i)); 18 | } 19 | printf("\n"); 20 | } 21 | 22 | int64_t SortArray() { 23 | LARGE_INTEGER starting_time, ending_time, elapsed_microseconds; 24 | LARGE_INTEGER frequency; 25 | QueryPerformanceFrequency(&frequency); 26 | QueryPerformanceCounter(&starting_time); 27 | 28 | example_etw_provider->Initialized(); 29 | 30 | for (int i = 0; i < 10000; ++i) { 31 | // Returns a value between 0 and RAND_MAX (0x7fff i.e. 32767) 32 | // The below gets a random number between 1000 & 2000 33 | int r = (rand() * 1000 / 32767) + 1000; // NOLINT 34 | total_elements += r; 35 | 36 | // Allocate an array of ints and fill it with random numbers 37 | int* p_elems = new int[r]; 38 | for (int j = 0; j < r; ++j) { 39 | *(p_elems + j) = rand(); // NOLINT 40 | } 41 | 42 | example_etw_provider->StartSort(r); 43 | 44 | qsort(p_elems, r, sizeof(int), [](void const* a, void const* b) -> int { 45 | int _a = *(static_cast(a)); 46 | int _b = *(static_cast(b)); 47 | if (_a == _b) return 0; 48 | return _a > _b ? 1 : -1; 49 | }); 50 | 51 | example_etw_provider->StopSort(); 52 | 53 | delete[] p_elems; 54 | } 55 | 56 | example_etw_provider->Finished(total_elements); 57 | 58 | QueryPerformanceCounter(&ending_time); 59 | elapsed_microseconds.QuadPart = ending_time.QuadPart - starting_time.QuadPart; 60 | // We now have the elapsed number of ticks, along with the 61 | // number of ticks-per-second. Convert to the number of elapsed microseconds. 62 | elapsed_microseconds.QuadPart *= 1000000; 63 | elapsed_microseconds.QuadPart /= frequency.QuadPart; 64 | return elapsed_microseconds.QuadPart; 65 | } 66 | 67 | } // namespace 68 | 69 | int main() { 70 | example_etw_provider = &example::ExampleEtwProvider::GetProvider(); 71 | if (example_etw_provider->enabled() == false) { 72 | printf("Enable the provider before running the tests"); 73 | return -1; 74 | } 75 | 76 | printf("enabled disabled\n"); 77 | for (int i = 0; i < 20; ++i) { 78 | int64_t duration; 79 | 80 | // Constant seed to ensure the same work on each run 81 | srand(51); 82 | total_elements = 0; 83 | example_etw_provider->set_enabled(true); 84 | duration = SortArray(); 85 | printf("%8d ", static_cast(duration)); 86 | 87 | srand(51); 88 | total_elements = 0; 89 | example_etw_provider->set_enabled(false); 90 | duration = SortArray(); 91 | printf("%8d\n", static_cast(duration)); 92 | } 93 | return 0; 94 | } 95 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # cpp-etw 2 | 3 | This repo demonstrates how to log manifest-free ETW events using only the Win32 4 | APIs and C++, and without the `TraceLoggingProvider.h` header file, which uses 5 | a large number of macros, and various pragmas, declspecs, etc. which may not 6 | work correctly on different compilers. 7 | 8 | The `manifest-free` ETW mechanism consists of using ETW channel 11 to log the 9 | events, and providing a couple of additional data descriptors to describe the 10 | event metadata. (One for the provider info, and one for the event layout). 11 | 12 | This metadata must be tightly packed in a specific format, which is 13 | the purpose of the `constexpr` templates in `etw-metadata.h`. This allows for 14 | the declaring of the event metadata (the event name, followed by the field 15 | names and types), using a `constexpr` such as the below: 16 | 17 | ```cpp 18 | constexpr static auto event_meta = EventMetadata("myEventName", 19 | Field("MyIntVal", kTypeInt32), 20 | Field("MyMsg", kTypeAnsiStr), 21 | Field("Address", kTypePointer)); 22 | ``` 23 | 24 | The event descriptor, to describe the event id, severity level, flags, etc. 25 | is declared similarly: 26 | 27 | ```cpp 28 | constexpr static auto event_desc = 29 | EventDescriptor(102, kLevelInfo, 0 /*keyword*/, kOpCodeStart); 30 | ``` 31 | 32 | The logging of the event uses a variadic template function, with the field 33 | values simply provided in order after the metadata. Thus an entire logging 34 | function in a provider class would appear something like the below: 35 | 36 | ```cpp 37 | void MyProvider::Log3Fields(INT32 val, const std::string& msg, void* addr) { 38 | constexpr static auto event_desc = EventDescriptor(100, kLevelVerbose); 39 | constexpr static auto event_meta = EventMetadata("myEventName", 40 | Field("MyIntVal", kTypeInt32), 41 | Field("MyMsg", kTypeAnsiStr), 42 | Field("Address", kTypePointer)); 43 | 44 | LogEventData(&event_desc, &event_meta, val, msg, addr); 45 | } 46 | ``` 47 | 48 | As the first 2 lines are `constexpr` variables, these result in no runtime code 49 | and are evaluated entirely at compile time. 50 | 51 | The code in `etw-provider.h` declares the `EtwProvider` class, which your provider 52 | should derive from. See the example implementation in the `example-etw-provider.*` files. The 53 | `example-etw-provider.cc` file also shows code to ensure only a single instance of the ETW 54 | provider is instantiated. (It does not demonstrate unregistering currently). 55 | 56 | The `main.cc` file shows running a simple experiment for measuring perf impact. 57 | 58 | Comments near the top of `example-etw-provider.h` indicate how to start/stop a simple trace 59 | from the command line. 60 | 61 | ## Building 62 | 63 | This project builds using CMake and the Windows SDK. By default the build uses 64 | Clang (see `CMakeLists.txt` in the root). 65 | 66 | To build to the `./build` directory run the following (from the project root): 67 | 68 | ```cmd 69 | cmake -G Ninja -B ./build -D CMAKE_BUILD_TYPE=RelWithDebInfo 70 | cmake --build ./build 71 | ``` 72 | 73 | ## Implementation notes 74 | 75 | - Using only `constexpr auto...` and not `static constexpr auto...` inside 76 | functions results in a large amount of assembly in the function to push 77 | a representation of the constant data onto the stack. Using `static` results 78 | in this data being in the read-only segment, with the function simply 79 | referencing it - which is much cheaper. (This is true of MSVC at least). 80 | - Enabling tracing of the provider on my Surface Book 2 laptop with a CPU and 81 | memory bound process, logging around 10,000 ETW events/sec, resulted in 82 | about a 3 percent increase in execution time. (Impact was not observable if 83 | there was no session recording the events). 84 | - Even logging 3 fields plus metadata (as per the `Log3Fields` function shown 85 | above), which also checks if the provider is enabled first, only added 44 86 | assembly instructions inline into the instrumented function, none of which 87 | were loops or calls. Most of this was to prepare the data descriptors for 88 | the event instance specific data for `EventWrite`, which seems unavoidable. 89 | If the provider is not enabled (likely most of the time), only 2 to 4 90 | assembly instructions are executed before jumping over the rest of the 91 | event logging code. 92 | - By implementing the bulk of the code in header files, it will generally be 93 | implemented inline in the function where the logging calls are made. By using 94 | the [`__builtin_expect`](https://stackoverflow.com/questions/7346929/what-is-the-advantage-of-gccs-builtin-expect-in-if-else-statements) 95 | GCC/Clang built-in, where the check is made if the provider is enabled, the 96 | bulk of the tracing code is placed at the end of the function, and in the usual 97 | case of the provider not being enabled, no branch is taken, and the tracing 98 | instructions may not even be fetched into the instruction cache to execute, 99 | resulting in even lower overhead. 100 | 101 | ## Misc 102 | 103 | - ETW overview: 104 | 105 | 106 | ## TODO 107 | 108 | - Not all field data types have been implemented yet. 109 | - Add support for activities. 110 | - Add support for unregistering the provider. 111 | --------------------------------------------------------------------------------