tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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