/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ETWTools_h #define ETWTools_h #include "mozilla/BaseProfilerMarkers.h" #include "mozilla/TimeStamp.h" #include "nsString.h" #if defined(XP_WIN) && !defined(RUST_BINDGEN) && !defined(__MINGW32__) # include "mozilla/ProfilerState.h" # include # include # include namespace ETW { extern std::atomic gETWCollectionMask; // Forward-declare the g_hMyComponentProvider variable that you will use for // tracing in this component TRACELOGGING_DECLARE_PROVIDER(kFirefoxTraceLoggingProvider); void Init(); void Shutdown(); // This describes the base fields for all markers (information extracted from // MarkerOptions. struct BaseMarkerDescription { using MS = mozilla::MarkerSchema; static constexpr MS::PayloadField PayloadFields[] = { {"StartTime", MS::InputType::TimeStamp, "Start Time"}, {"EndTime", MS::InputType::TimeStamp, "End Time"}, {"InnerWindowId", MS::InputType::Uint64, "Inner Window ID"}, {"CategoryPair", MS::InputType::Uint32, "Category Pair"}}; }; // This is the MarkerType object for markers with no statically declared type, // their name is written dynamically. struct SimpleMarkerType { using MS = mozilla::MarkerSchema; static constexpr const char* Name = "SimpleMarker"; static constexpr MS::PayloadField PayloadFields[] = { {"MarkerName", MS::InputType::CString, "Simple Marker Name"}}; }; // This gets the space required in the Tlg static struct to pack the fields. template constexpr std::size_t GetPackingSpace() { size_t length = 0; for (size_t i = 0; i < std::size(T::PayloadFields); i++) { length += std::string_view{T::PayloadFields[i].Key}.size() + 1; length += sizeof(uint8_t); } return length; } // Convert our InputType to Tlgs input type. constexpr uint8_t GetTlgInputType(mozilla::MarkerSchema::InputType aInput) { using InputType = mozilla::MarkerSchema::InputType; switch (aInput) { case InputType::Boolean: return TlgInUINT8; case InputType::Uint32: return TlgInUINT32; case InputType::Uint64: case InputType::TimeStamp: case InputType::TimeDuration: return TlgInUINT64; case InputType::CString: return TlgInANSISTRING; case InputType::String: return TlgInUNICODESTRING; default: return 0; } } // This class represents the format ETW TraceLogging uses to describe its // metadata. Since we have an abstraction layer we need to statically // declare this ourselves and we cannot rely on TraceLogging's official // macros. This does mean if TraceLogging ships big changes (on an SDK update) // we may need to adapt. __pragma(pack(push, 1)) _tlgEvtTagDecl(0); template struct StaticMetaData { _tlgEventMetadata_t metaData; _tlgEvtTagType _tlgEvtTag; char name[std::string_view{T::Name}.size() + 1]; char fieldStorage[GetPackingSpace() + GetPackingSpace()]; // constexpr constructor constexpr StaticMetaData() : metaData{_TlgBlobEvent4, 11, // WINEVENT_CHANNEL_TRACELOGGING, 5, // Verbose 0, 0, sizeof(StaticMetaData) - _tlg_EVENT_METADATA_PREAMBLE - 1}, _tlgEvtTag(_tlgEvtTagInit) { for (uint32_t i = 0; i < std::string_view{T::Name}.size() + 1; i++) { name[i] = T::Name[i]; } size_t pos = 0; for (uint32_t i = 0; i < std::size(BaseMarkerDescription::PayloadFields); i++) { for (size_t c = 0; c < std::string_view{BaseMarkerDescription::PayloadFields[i].Key} .size() + 1; c++) { fieldStorage[pos++] = BaseMarkerDescription::PayloadFields[i].Key[c]; } fieldStorage[pos++] = GetTlgInputType(BaseMarkerDescription::PayloadFields[i].InputTy); } for (uint32_t i = 0; i < std::size(T::PayloadFields); i++) { for (size_t c = 0; c < std::string_view{T::PayloadFields[i].Key}.size() + 1; c++) { fieldStorage[pos++] = T::PayloadFields[i].Key[c]; } fieldStorage[pos++] = GetTlgInputType(T::PayloadFields[i].InputTy); } } }; __pragma(pack(pop)); // This defines the amount of storage available on the stack to store POD // values. const size_t kStackStorage = 512; struct PayloadBuffer { EVENT_DATA_DESCRIPTOR* mDescriptors = nullptr; size_t mOffset = 0; std::array mStorage; }; // This processes POD objects and stores them in a temporary buffer. // Theoretically we could probably avoid these assignments when passed a POD // variable we know is going to be alive but that would require some more // template magic. template static void CreateDataDescForPayload(PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const T& aPayload) { static_assert(std::is_pod::value, "Writing a non-POD payload requires template specialization."); // Ensure we never overflow our stack buffer. MOZ_RELEASE_ASSERT((aBuffer.mOffset + sizeof(T)) < kStackStorage); T* storedValue = reinterpret_cast(aBuffer.mStorage.data() + aBuffer.mOffset); *storedValue = aPayload; aBuffer.mOffset += sizeof(T); EventDataDescCreate(&aDescriptor, storedValue, sizeof(T)); } template <> inline void CreateDataDescForPayload( PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const mozilla::ProfilerString8View& aPayload) { EventDataDescCreate(&aDescriptor, aPayload.StringView().data(), aPayload.StringView().size() + 1); } template <> inline void CreateDataDescForPayload( PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const mozilla::TimeStamp& aPayload) { if (aPayload.RawQueryPerformanceCounterValue().isNothing()) { // This should never happen? EventDataDescCreate(&aDescriptor, nullptr, 0); return; } CreateDataDescForPayload(aBuffer, aDescriptor, aPayload.RawQueryPerformanceCounterValue().value()); } template <> inline void CreateDataDescForPayload( PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const mozilla::TimeDuration& aPayload) { CreateDataDescForPayload(aBuffer, aDescriptor, aPayload.ToMilliseconds()); } // For reasons that are beyond me if this isn't marked inline it generates an // unused function warning despite being a template specialization. template inline void CreateDataDescForPayload(PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const nsTString& aPayload) { EventDataDescCreate(&aDescriptor, aPayload.BeginReading(), (aPayload.Length() + 1) * sizeof(T)); } template inline void CreateDataDescForPayload(PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const nsTSubstring& aPayload) { EventDataDescCreate(&aDescriptor, aPayload.BeginReading(), (aPayload.Length() + 1) * sizeof(T)); } // Template specialization that writes out empty data descriptors for an empty // Maybe template void CreateDataDescForPayload(PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const mozilla::Maybe& aPayload) { if (aPayload.isNothing()) { EventDataDescCreate(&aDescriptor, nullptr, 0); } else { CreateDataDescForPayload(aBuffer, aDescriptor, *aPayload); } } template struct MarkerSupportsETW : std::false_type {}; template struct MarkerSupportsETW> : std::true_type {}; template struct MarkerHasTranslator : std::false_type {}; template struct MarkerHasTranslator< T, std::void_t> : std::true_type {}; struct BaseEventStorage { uint64_t mStartTime; uint64_t mEndTime; uint64_t mWindowID; uint32_t mCategoryPair; }; static inline void StoreBaseEventDataDesc( BaseEventStorage& aStorage, EVENT_DATA_DESCRIPTOR* aDescriptors, const mozilla::MarkerCategory& aCategory, const mozilla::MarkerOptions& aOptions) { if (!aOptions.IsTimingUnspecified()) { aStorage.mStartTime = aOptions.Timing().StartTime().RawQueryPerformanceCounterValue().value(); aStorage.mEndTime = aOptions.Timing().EndTime().RawQueryPerformanceCounterValue().value(); } if (!aOptions.InnerWindowId().IsUnspecified()) { aStorage.mWindowID = aOptions.InnerWindowId().Id(); } aStorage.mCategoryPair = uint32_t(aCategory.CategoryPair()); EventDataDescCreate(&aDescriptors[2], &aStorage.mStartTime, sizeof(uint64_t)); EventDataDescCreate(&aDescriptors[3], &aStorage.mEndTime, sizeof(uint64_t)); EventDataDescCreate(&aDescriptors[4], &aStorage.mWindowID, sizeof(uint64_t)); EventDataDescCreate(&aDescriptors[5], &aStorage.mCategoryPair, sizeof(uint32_t)); } // This is used for markers with no explicit type or markers which have not // been converted to the updated schema yet. static inline void EmitETWMarker(const mozilla::ProfilerString8View& aName, const mozilla::MarkerCategory& aCategory, const mozilla::MarkerOptions& aOptions = {}) { if (!(gETWCollectionMask & uint64_t(mozilla::MarkerSchema::ETWMarkerGroup::Generic))) { return; } static const __declspec(allocate(_tlgSegMetadataEvents)) __declspec( align(1)) constexpr StaticMetaData staticData; std::array descriptors = {}; // This is storage space allocated on the stack for POD values. BaseEventStorage dataStorage = {}; StoreBaseEventDataDesc(dataStorage, descriptors.data(), aCategory, std::move(aOptions)); EventDataDescCreate(&descriptors[6], aName.StringView().data(), aName.StringView().size() + 1); _tlgWriteTransfer(kFirefoxTraceLoggingProvider, &staticData.metaData.Channel, NULL, NULL, descriptors.size(), descriptors.data()); } template static inline void EmitETWMarker(const mozilla::ProfilerString8View& aName, const mozilla::MarkerCategory& aCategory, const mozilla::MarkerOptions& aOptions, MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) { // If our MarkerType has not been made to support ETW, emit only the base // event. Avoid attempting to compile the rest of the function. if constexpr (!MarkerSupportsETW::value) { return EmitETWMarker(aName, aCategory, aOptions); } else { if (!(gETWCollectionMask & uint64_t(MarkerType::Group))) { return; } static const __declspec(allocate(_tlgSegMetadataEvents)) __declspec( align(1)) constexpr StaticMetaData staticData; // Allocate the exact amount of descriptors required by this event. std::array descriptors = {}; // Memory allocated on the stack for storing intermediate values. BaseEventStorage dataStorage = {}; PayloadBuffer buffer; StoreBaseEventDataDesc(dataStorage, descriptors.data(), aCategory, aOptions); if constexpr (MarkerHasTranslator::value) { // When this function is implemented the arguments are passed back to the // MarkerType object which is expected to call OutputMarkerSchema with // the correct argument format. buffer.mDescriptors = descriptors.data() + 2 + std::size(BaseMarkerDescription::PayloadFields); MarkerType::TranslateMarkerInputToSchema(&buffer, aPayloadArguments...); } else { const size_t argCount = sizeof...(PayloadArguments); static_assert( argCount == std::size(MarkerType::PayloadFields), "Number and type of fields must be equal to number and type of " "payload arguments. If this is not the case a " "TranslateMarkerInputToSchema function must be defined."); size_t i = 2 + std::size(BaseMarkerDescription::PayloadFields); (CreateDataDescForPayload(buffer, descriptors[i++], aPayloadArguments), ...); } _tlgWriteTransfer(kFirefoxTraceLoggingProvider, &staticData.metaData.Channel, NULL, NULL, descriptors.size(), descriptors.data()); } } // This function allows markers to specify a translator function for when // their arguments to profiler_add_marker do not exactly match the schema or // when they need to make other adjustments to the data. template void OutputMarkerSchema(void* aContext, MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) { const size_t argCount = sizeof...(PayloadArguments); static_assert(argCount == std::size(MarkerType::PayloadFields), "Number and type of fields must be equal to number and type of " "payload arguments."); PayloadBuffer* buffer = static_cast(aContext); size_t i = 0; (CreateDataDescForPayload(*buffer, buffer->mDescriptors[i++], aPayloadArguments), ...); } } // namespace ETW #else namespace ETW { static inline void Init() {} static inline void Shutdown() {} static inline void EmitETWMarker(const mozilla::ProfilerString8View& aName, const mozilla::MarkerCategory& aCategory, const mozilla::MarkerOptions& aOptions = {}) {} template static inline void EmitETWMarker(const mozilla::ProfilerString8View& aName, const mozilla::MarkerCategory& aCategory, const mozilla::MarkerOptions& aOptions, MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) { } template void OutputMarkerSchema(void* aContext, MarkerType aMarkerType, const PayloadArguments&... aPayloadArguments) {} } // namespace ETW #endif #endif // ETWTools_h