ProfilerMarkers.cpp (15983B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "mozilla/BaseProfilerMarkers.h" 7 8 #include "mozilla/BaseProfilerUtils.h" 9 10 #include <limits> 11 12 namespace mozilla { 13 namespace base_profiler_markers_detail { 14 15 // We need an atomic type that can hold a `DeserializerTag`. (Atomic doesn't 16 // work with too-small types.) 17 using DeserializerTagAtomic = unsigned; 18 19 // The atomic sDeserializerCount still also include bits that act as a "RWLock": 20 // Whoever can set this bit gets exclusive access to the count and the whole 21 // sMarkerTypeFunctions1Based array, guaranteeing that it cannot be modified. 22 static constexpr DeserializerTagAtomic scExclusiveLock = 0x80'00'00'00u; 23 // Code that wants shared access can add this value, then ensure there is no 24 // exclusive lock, after which it's guaranteed that no exclusive lock can be 25 // taken until the shared lock count goes back to zero. 26 static constexpr DeserializerTagAtomic scSharedLockUnit = 0x00'01'00'00u; 27 // This mask isolates the actual count value from the lock bits. 28 static constexpr DeserializerTagAtomic scTagMask = 0x00'00'FF'FFu; 29 30 // Number of currently-registered deserializers and other marker type functions. 31 // The high bits contain lock bits, see above. 32 static Atomic<DeserializerTagAtomic, MemoryOrdering::ReleaseAcquire> 33 sDeserializerCount{0}; 34 35 // This needs to be big enough to handle all possible marker types. If one day 36 // this needs to be higher, the underlying DeserializerTag type will have to be 37 // changed. 38 static constexpr DeserializerTagAtomic DeserializerMax = 250; 39 static_assert(DeserializerMax <= scTagMask, 40 "DeserializerMax doesn't fit in scTagMask"); 41 42 static_assert( 43 DeserializerMax <= std::numeric_limits<Streaming::DeserializerTag>::max(), 44 "The maximum number of deserializers must fit in the DeserializerTag type"); 45 46 // Array of marker type functions. 47 // 1-based, i.e.: [0] -> tag 1, [DeserializerMax - 1] -> tag DeserializerMax. 48 // Elements are added at the next available atomically-incremented 49 // `sDeserializerCount` (minus 1) whenever a new marker type is used in a 50 // Firefox session; the content is kept between profiler runs in that session. 51 // There is theoretically a race between the increment and the time the entry is 52 // fully written, but in practice all new elements are written (during 53 // profiling, using a marker type for the first time) long before they are read 54 // (after profiling is paused). 55 static Streaming::MarkerTypeFunctions 56 sMarkerTypeFunctions1Based[DeserializerMax]; 57 58 /* static */ Streaming::DeserializerTag Streaming::TagForMarkerTypeFunctions( 59 Streaming::MarkerDataDeserializer aDeserializer, 60 Streaming::MarkerTypeNameFunction aMarkerTypeNameFunction, 61 Streaming::MarkerSchemaFunction aMarkerSchemaFunction) { 62 MOZ_RELEASE_ASSERT(!!aDeserializer); 63 MOZ_RELEASE_ASSERT(!!aMarkerTypeNameFunction); 64 MOZ_RELEASE_ASSERT(!!aMarkerSchemaFunction); 65 66 // Add a shared lock request, which will prevent future exclusive locking. 67 DeserializerTagAtomic tagWithLock = (sDeserializerCount += scSharedLockUnit); 68 69 // An exclusive locker may have arrived before us, just wait for it to finish. 70 while ((tagWithLock & scExclusiveLock) != 0u) { 71 tagWithLock = sDeserializerCount; 72 } 73 74 MOZ_ASSERT( 75 // This is equivalent to shifting right to only keep the lock counts. 76 tagWithLock / scSharedLockUnit < 77 // This is effectively half of the permissible shared lock range, 78 // that would mean way too many threads doing this work here! 79 scExclusiveLock / scSharedLockUnit / 2, 80 "The shared lock count is getting unexpectedly high, verify the " 81 "algorithm, and tweak constants if needed"); 82 83 // Reserve a tag. Even if there are multiple shared-lock holders here, each 84 // one will get a different value, and therefore will access a different part 85 // of the sMarkerTypeFunctions1Based array. 86 const DeserializerTagAtomic tag = ++sDeserializerCount & scTagMask; 87 88 MOZ_RELEASE_ASSERT( 89 tag <= DeserializerMax, 90 "Too many deserializers, consider increasing DeserializerMax. " 91 "Or is a deserializer stored again and again?"); 92 sMarkerTypeFunctions1Based[tag - 1] = {aDeserializer, aMarkerTypeNameFunction, 93 aMarkerSchemaFunction}; 94 95 // And release our shared lock, to allow exclusive readers. 96 sDeserializerCount -= scSharedLockUnit; 97 98 return static_cast<DeserializerTag>(tag); 99 } 100 101 /* static */ Streaming::MarkerDataDeserializer Streaming::DeserializerForTag( 102 Streaming::DeserializerTag aTag) { 103 MOZ_RELEASE_ASSERT( 104 aTag > 0 && static_cast<DeserializerTagAtomic>(aTag) <= 105 static_cast<DeserializerTagAtomic>(sDeserializerCount), 106 "Out-of-range tag value"); 107 return sMarkerTypeFunctions1Based[aTag - 1].mMarkerDataDeserializer; 108 } 109 110 Streaming::LockedMarkerTypeFunctionsList::LockedMarkerTypeFunctionsList() { 111 for (;;) { 112 const DeserializerTagAtomic count = sDeserializerCount; 113 if ((count & scTagMask) != count) { 114 // Someone already has a lock, loop around. 115 continue; 116 } 117 118 // There are currently no locks, try to add our exclusive lock. 119 if (!sDeserializerCount.compareExchange(count, count | scExclusiveLock)) { 120 // Someone else modified sDeserializerCount since our read, loop around. 121 continue; 122 } 123 124 // We applied our exclusive lock, we can now read the list of functions, 125 // without interference until ~LockedMarkerTypeFunctionsList(). 126 // (Note that sDeserializerCount may receive shared lock requests, but the 127 // count won't change.) 128 mMarkerTypeFunctionsSpan = {sMarkerTypeFunctions1Based, count}; 129 break; 130 } 131 } 132 133 Streaming::LockedMarkerTypeFunctionsList::~LockedMarkerTypeFunctionsList() { 134 MOZ_ASSERT( 135 (sDeserializerCount & scExclusiveLock) == scExclusiveLock, 136 "sDeserializerCount should still have the the exclusive lock bit set"); 137 MOZ_ASSERT( 138 (sDeserializerCount & scTagMask) == 139 DeserializerTagAtomic(mMarkerTypeFunctionsSpan.size()), 140 "sDeserializerCount should have the same count since construction"); 141 sDeserializerCount &= ~scExclusiveLock; 142 } 143 144 // Only accessed on the main thread. 145 // Both profilers (Base and Gecko) could be active at the same time, so keep a 146 // ref-count to only allocate at most one buffer at any time. 147 static int sBufferForMainThreadAddMarkerRefCount = 0; 148 static ProfileChunkedBuffer* sBufferForMainThreadAddMarker = nullptr; 149 150 ProfileChunkedBuffer* GetClearedBufferForMainThreadAddMarker() { 151 if (!mozilla::baseprofiler::profiler_is_main_thread()) { 152 return nullptr; 153 } 154 155 if (sBufferForMainThreadAddMarker) { 156 MOZ_ASSERT(sBufferForMainThreadAddMarker->IsInSession(), 157 "sBufferForMainThreadAddMarker should always be in-session"); 158 sBufferForMainThreadAddMarker->Clear(); 159 MOZ_ASSERT( 160 sBufferForMainThreadAddMarker->IsInSession(), 161 "Cleared sBufferForMainThreadAddMarker should still be in-session"); 162 } 163 164 return sBufferForMainThreadAddMarker; 165 } 166 167 MFBT_API void EnsureBufferForMainThreadAddMarker() { 168 if (!mozilla::baseprofiler::profiler_is_main_thread()) { 169 return; 170 } 171 172 if (sBufferForMainThreadAddMarkerRefCount++ == 0) { 173 // First `Ensure`, allocate the buffer. 174 MOZ_ASSERT(!sBufferForMainThreadAddMarker); 175 sBufferForMainThreadAddMarker = new ProfileChunkedBuffer( 176 ProfileChunkedBuffer::ThreadSafety::WithoutMutex, 177 MakeUnique<ProfileBufferChunkManagerSingle>( 178 ProfileBufferChunkManager::scExpectedMaximumStackSize)); 179 MOZ_ASSERT(sBufferForMainThreadAddMarker); 180 MOZ_ASSERT(sBufferForMainThreadAddMarker->IsInSession()); 181 } 182 } 183 184 MFBT_API void ReleaseBufferForMainThreadAddMarker() { 185 if (!mozilla::baseprofiler::profiler_is_main_thread()) { 186 return; 187 } 188 189 if (sBufferForMainThreadAddMarkerRefCount == 0) { 190 // Unexpected Release! This should not normally happen, but it's harmless in 191 // practice, it means the buffer is not alive anyway. 192 return; 193 } 194 195 MOZ_ASSERT(sBufferForMainThreadAddMarker); 196 MOZ_ASSERT(sBufferForMainThreadAddMarker->IsInSession()); 197 if (--sBufferForMainThreadAddMarkerRefCount == 0) { 198 // Last `Release`, destroy the buffer. 199 delete sBufferForMainThreadAddMarker; 200 sBufferForMainThreadAddMarker = nullptr; 201 } 202 } 203 204 } // namespace base_profiler_markers_detail 205 206 void MarkerSchema::Stream(JSONWriter& aWriter, 207 const Span<const char>& aName) && { 208 // The caller should have started a JSON array, in which we can add an object 209 // that defines a marker schema. 210 211 if (mLocations.empty()) { 212 // SpecialFrontendLocation case, don't output anything for this type. 213 return; 214 } 215 216 aWriter.StartObjectElement(); 217 { 218 aWriter.StringProperty("name", aName); 219 220 if (!mChartLabel.empty()) { 221 aWriter.StringProperty("chartLabel", mChartLabel); 222 } 223 224 if (!mTooltipLabel.empty()) { 225 aWriter.StringProperty("tooltipLabel", mTooltipLabel); 226 } 227 228 if (!mTableLabel.empty()) { 229 aWriter.StringProperty("tableLabel", mTableLabel); 230 } 231 232 if (mIsStackBased) { 233 aWriter.BoolProperty("isStackBased", true); 234 } 235 236 aWriter.StartArrayProperty("display"); 237 { 238 for (Location location : mLocations) { 239 aWriter.StringElement(LocationToStringSpan(location)); 240 } 241 } 242 aWriter.EndArray(); 243 244 aWriter.StartArrayProperty("data"); 245 { 246 for (const DataRow& row : mData) { 247 aWriter.StartObjectElement(); 248 { 249 row.match( 250 [&aWriter](const DynamicData& aData) { 251 aWriter.StringProperty("key", aData.mKey); 252 if (aData.mLabel) { 253 aWriter.StringProperty("label", *aData.mLabel); 254 } 255 aWriter.StringProperty("format", 256 FormatToStringSpan(aData.mFormat)); 257 258 if (uint32_t(aData.mPayloadFlags) & 259 uint32_t(PayloadFlags::Searchable)) { 260 aWriter.BoolProperty("searchable", true); 261 } 262 263 if (uint32_t(aData.mPayloadFlags) & 264 uint32_t(PayloadFlags::Hidden)) { 265 aWriter.BoolProperty("hidden", true); 266 } 267 }, 268 [&aWriter](const StaticData& aStaticData) { 269 aWriter.StringProperty("label", aStaticData.mLabel); 270 aWriter.StringProperty("value", aStaticData.mValue); 271 }); 272 } 273 aWriter.EndObject(); 274 } 275 } 276 aWriter.EndArray(); 277 278 if (!mGraphs.empty()) { 279 aWriter.StartArrayProperty("graphs"); 280 { 281 for (const GraphData& graph : mGraphs) { 282 aWriter.StartObjectElement(); 283 { 284 aWriter.StringProperty("key", graph.mKey); 285 aWriter.StringProperty("type", GraphTypeToStringSpan(graph.mType)); 286 if (graph.mColor) { 287 aWriter.StringProperty("color", 288 GraphColorToStringSpan(*graph.mColor)); 289 } 290 } 291 aWriter.EndObject(); 292 } 293 } 294 aWriter.EndArray(); 295 } 296 } 297 aWriter.EndObject(); 298 } 299 300 /* static */ 301 Span<const char> MarkerSchema::LocationToStringSpan( 302 MarkerSchema::Location aLocation) { 303 switch (aLocation) { 304 case Location::MarkerChart: 305 return mozilla::MakeStringSpan("marker-chart"); 306 case Location::MarkerTable: 307 return mozilla::MakeStringSpan("marker-table"); 308 case Location::TimelineOverview: 309 return mozilla::MakeStringSpan("timeline-overview"); 310 case Location::TimelineMemory: 311 return mozilla::MakeStringSpan("timeline-memory"); 312 case Location::TimelineIPC: 313 return mozilla::MakeStringSpan("timeline-ipc"); 314 case Location::TimelineFileIO: 315 return mozilla::MakeStringSpan("timeline-fileio"); 316 case Location::StackChart: 317 return mozilla::MakeStringSpan("stack-chart"); 318 default: 319 MOZ_CRASH("Unexpected Location enum"); 320 return {}; 321 } 322 } 323 324 /* static */ 325 Span<const char> MarkerSchema::FormatToStringSpan( 326 MarkerSchema::Format aFormat) { 327 switch (aFormat) { 328 case Format::Url: 329 return mozilla::MakeStringSpan("url"); 330 case Format::FilePath: 331 return mozilla::MakeStringSpan("file-path"); 332 case Format::SanitizedString: 333 return mozilla::MakeStringSpan("sanitized-string"); 334 case Format::String: 335 return mozilla::MakeStringSpan("string"); 336 case Format::UniqueString: 337 return mozilla::MakeStringSpan("unique-string"); 338 case Format::Duration: 339 return mozilla::MakeStringSpan("duration"); 340 case Format::Time: 341 return mozilla::MakeStringSpan("time"); 342 case Format::Seconds: 343 return mozilla::MakeStringSpan("seconds"); 344 case Format::Milliseconds: 345 return mozilla::MakeStringSpan("milliseconds"); 346 case Format::Microseconds: 347 return mozilla::MakeStringSpan("microseconds"); 348 case Format::Nanoseconds: 349 return mozilla::MakeStringSpan("nanoseconds"); 350 case Format::Bytes: 351 return mozilla::MakeStringSpan("bytes"); 352 case Format::Percentage: 353 return mozilla::MakeStringSpan("percentage"); 354 case Format::Integer: 355 return mozilla::MakeStringSpan("integer"); 356 case Format::Decimal: 357 return mozilla::MakeStringSpan("decimal"); 358 case Format::Flow: 359 return mozilla::MakeStringSpan("flow-id"); 360 case Format::TerminatingFlow: 361 return mozilla::MakeStringSpan("terminating-flow-id"); 362 default: 363 MOZ_CRASH("Unexpected Format enum"); 364 return {}; 365 } 366 } 367 368 /* static */ 369 Span<const char> MarkerSchema::GraphTypeToStringSpan( 370 MarkerSchema::GraphType aType) { 371 switch (aType) { 372 case GraphType::Line: 373 return mozilla::MakeStringSpan("line"); 374 case GraphType::Bar: 375 return mozilla::MakeStringSpan("bar"); 376 case GraphType::FilledLine: 377 return mozilla::MakeStringSpan("line-filled"); 378 default: 379 MOZ_CRASH("Unexpected GraphType enum"); 380 return {}; 381 } 382 } 383 384 /* static */ 385 Span<const char> MarkerSchema::GraphColorToStringSpan( 386 MarkerSchema::GraphColor aColor) { 387 switch (aColor) { 388 case GraphColor::Blue: 389 return mozilla::MakeStringSpan("blue"); 390 case GraphColor::Green: 391 return mozilla::MakeStringSpan("green"); 392 case GraphColor::Grey: 393 return mozilla::MakeStringSpan("grey"); 394 case GraphColor::Ink: 395 return mozilla::MakeStringSpan("ink"); 396 case GraphColor::Magenta: 397 return mozilla::MakeStringSpan("magenta"); 398 case GraphColor::Orange: 399 return mozilla::MakeStringSpan("orange"); 400 case GraphColor::Purple: 401 return mozilla::MakeStringSpan("purple"); 402 case GraphColor::Red: 403 return mozilla::MakeStringSpan("red"); 404 case GraphColor::Teal: 405 return mozilla::MakeStringSpan("teal"); 406 case GraphColor::Yellow: 407 return mozilla::MakeStringSpan("yellow"); 408 default: 409 MOZ_CRASH("Unexpected GraphColor enum"); 410 return {}; 411 } 412 } 413 414 } // namespace mozilla 415 416 namespace mozilla::baseprofiler { 417 template MFBT_API ProfileBufferBlockIndex AddMarker(const ProfilerString8View&, 418 const MarkerCategory&, 419 MarkerOptions&&, 420 markers::TextMarker, 421 const std::string&); 422 423 template MFBT_API ProfileBufferBlockIndex 424 AddMarkerToBuffer(ProfileChunkedBuffer&, const ProfilerString8View&, 425 const MarkerCategory&, MarkerOptions&&, markers::NoPayload); 426 427 template MFBT_API ProfileBufferBlockIndex AddMarkerToBuffer( 428 ProfileChunkedBuffer&, const ProfilerString8View&, const MarkerCategory&, 429 MarkerOptions&&, markers::TextMarker, const std::string&); 430 } // namespace mozilla::baseprofiler