Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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
#include "mozilla/BaseProfilerMarkers.h"
#include "mozilla/BaseProfilerUtils.h"
#include <limits>
namespace mozilla {
namespace base_profiler_markers_detail {
// We need an atomic type that can hold a `DeserializerTag`. (Atomic doesn't
// work with too-small types.)
using DeserializerTagAtomic = unsigned;
// The atomic sDeserializerCount still also include bits that act as a "RWLock":
// Whoever can set this bit gets exclusive access to the count and the whole
// sMarkerTypeFunctions1Based array, guaranteeing that it cannot be modified.
static constexpr DeserializerTagAtomic scExclusiveLock = 0x80'00'00'00u;
// Code that wants shared access can add this value, then ensure there is no
// exclusive lock, after which it's guaranteed that no exclusive lock can be
// taken until the shared lock count goes back to zero.
static constexpr DeserializerTagAtomic scSharedLockUnit = 0x00'01'00'00u;
// This mask isolates the actual count value from the lock bits.
static constexpr DeserializerTagAtomic scTagMask = 0x00'00'FF'FFu;
// Number of currently-registered deserializers and other marker type functions.
// The high bits contain lock bits, see above.
static Atomic<DeserializerTagAtomic, MemoryOrdering::ReleaseAcquire>
sDeserializerCount{0};
// This needs to be big enough to handle all possible marker types. If one day
// this needs to be higher, the underlying DeserializerTag type will have to be
// changed.
static constexpr DeserializerTagAtomic DeserializerMax = 250;
static_assert(DeserializerMax <= scTagMask,
"DeserializerMax doesn't fit in scTagMask");
static_assert(
DeserializerMax <= std::numeric_limits<Streaming::DeserializerTag>::max(),
"The maximum number of deserializers must fit in the DeserializerTag type");
// Array of marker type functions.
// 1-based, i.e.: [0] -> tag 1, [DeserializerMax - 1] -> tag DeserializerMax.
// Elements are added at the next available atomically-incremented
// `sDeserializerCount` (minus 1) whenever a new marker type is used in a
// Firefox session; the content is kept between profiler runs in that session.
// There is theoretically a race between the increment and the time the entry is
// fully written, but in practice all new elements are written (during
// profiling, using a marker type for the first time) long before they are read
// (after profiling is paused).
static Streaming::MarkerTypeFunctions
sMarkerTypeFunctions1Based[DeserializerMax];
/* static */ Streaming::DeserializerTag Streaming::TagForMarkerTypeFunctions(
Streaming::MarkerDataDeserializer aDeserializer,
Streaming::MarkerTypeNameFunction aMarkerTypeNameFunction,
Streaming::MarkerSchemaFunction aMarkerSchemaFunction) {
MOZ_RELEASE_ASSERT(!!aDeserializer);
MOZ_RELEASE_ASSERT(!!aMarkerTypeNameFunction);
MOZ_RELEASE_ASSERT(!!aMarkerSchemaFunction);
// Add a shared lock request, which will prevent future exclusive locking.
DeserializerTagAtomic tagWithLock = (sDeserializerCount += scSharedLockUnit);
// An exclusive locker may have arrived before us, just wait for it to finish.
while ((tagWithLock & scExclusiveLock) != 0u) {
tagWithLock = sDeserializerCount;
}
MOZ_ASSERT(
// This is equivalent to shifting right to only keep the lock counts.
tagWithLock / scSharedLockUnit <
// This is effectively half of the permissible shared lock range,
// that would mean way too many threads doing this work here!
scExclusiveLock / scSharedLockUnit / 2,
"The shared lock count is getting unexpectedly high, verify the "
"algorithm, and tweak constants if needed");
// Reserve a tag. Even if there are multiple shared-lock holders here, each
// one will get a different value, and therefore will access a different part
// of the sMarkerTypeFunctions1Based array.
const DeserializerTagAtomic tag = ++sDeserializerCount & scTagMask;
MOZ_RELEASE_ASSERT(
tag <= DeserializerMax,
"Too many deserializers, consider increasing DeserializerMax. "
"Or is a deserializer stored again and again?");
sMarkerTypeFunctions1Based[tag - 1] = {aDeserializer, aMarkerTypeNameFunction,
aMarkerSchemaFunction};
// And release our shared lock, to allow exclusive readers.
sDeserializerCount -= scSharedLockUnit;
return static_cast<DeserializerTag>(tag);
}
/* static */ Streaming::MarkerDataDeserializer Streaming::DeserializerForTag(
Streaming::DeserializerTag aTag) {
MOZ_RELEASE_ASSERT(
aTag > 0 && static_cast<DeserializerTagAtomic>(aTag) <=
static_cast<DeserializerTagAtomic>(sDeserializerCount),
"Out-of-range tag value");
return sMarkerTypeFunctions1Based[aTag - 1].mMarkerDataDeserializer;
}
Streaming::LockedMarkerTypeFunctionsList::LockedMarkerTypeFunctionsList() {
for (;;) {
const DeserializerTagAtomic count = sDeserializerCount;
if ((count & scTagMask) != count) {
// Someone already has a lock, loop around.
continue;
}
// There are currently no locks, try to add our exclusive lock.
if (!sDeserializerCount.compareExchange(count, count | scExclusiveLock)) {
// Someone else modified sDeserializerCount since our read, loop around.
continue;
}
// We applied our exclusive lock, we can now read the list of functions,
// without interference until ~LockedMarkerTypeFunctionsList().
// (Note that sDeserializerCount may receive shared lock requests, but the
// count won't change.)
mMarkerTypeFunctionsSpan = {sMarkerTypeFunctions1Based, count};
break;
}
}
Streaming::LockedMarkerTypeFunctionsList::~LockedMarkerTypeFunctionsList() {
MOZ_ASSERT(
(sDeserializerCount & scExclusiveLock) == scExclusiveLock,
"sDeserializerCount should still have the the exclusive lock bit set");
MOZ_ASSERT(
(sDeserializerCount & scTagMask) ==
DeserializerTagAtomic(mMarkerTypeFunctionsSpan.size()),
"sDeserializerCount should have the same count since construction");
sDeserializerCount &= ~scExclusiveLock;
}
// Only accessed on the main thread.
// Both profilers (Base and Gecko) could be active at the same time, so keep a
// ref-count to only allocate at most one buffer at any time.
static int sBufferForMainThreadAddMarkerRefCount = 0;
static ProfileChunkedBuffer* sBufferForMainThreadAddMarker = nullptr;
ProfileChunkedBuffer* GetClearedBufferForMainThreadAddMarker() {
if (!mozilla::baseprofiler::profiler_is_main_thread()) {
return nullptr;
}
if (sBufferForMainThreadAddMarker) {
MOZ_ASSERT(sBufferForMainThreadAddMarker->IsInSession(),
"sBufferForMainThreadAddMarker should always be in-session");
sBufferForMainThreadAddMarker->Clear();
MOZ_ASSERT(
sBufferForMainThreadAddMarker->IsInSession(),
"Cleared sBufferForMainThreadAddMarker should still be in-session");
}
return sBufferForMainThreadAddMarker;
}
MFBT_API void EnsureBufferForMainThreadAddMarker() {
if (!mozilla::baseprofiler::profiler_is_main_thread()) {
return;
}
if (sBufferForMainThreadAddMarkerRefCount++ == 0) {
// First `Ensure`, allocate the buffer.
MOZ_ASSERT(!sBufferForMainThreadAddMarker);
sBufferForMainThreadAddMarker = new ProfileChunkedBuffer(
ProfileChunkedBuffer::ThreadSafety::WithoutMutex,
MakeUnique<ProfileBufferChunkManagerSingle>(
ProfileBufferChunkManager::scExpectedMaximumStackSize));
MOZ_ASSERT(sBufferForMainThreadAddMarker);
MOZ_ASSERT(sBufferForMainThreadAddMarker->IsInSession());
}
}
MFBT_API void ReleaseBufferForMainThreadAddMarker() {
if (!mozilla::baseprofiler::profiler_is_main_thread()) {
return;
}
if (sBufferForMainThreadAddMarkerRefCount == 0) {
// Unexpected Release! This should not normally happen, but it's harmless in
// practice, it means the buffer is not alive anyway.
return;
}
MOZ_ASSERT(sBufferForMainThreadAddMarker);
MOZ_ASSERT(sBufferForMainThreadAddMarker->IsInSession());
if (--sBufferForMainThreadAddMarkerRefCount == 0) {
// Last `Release`, destroy the buffer.
delete sBufferForMainThreadAddMarker;
sBufferForMainThreadAddMarker = nullptr;
}
}
} // namespace base_profiler_markers_detail
void MarkerSchema::Stream(JSONWriter& aWriter,
const Span<const char>& aName) && {
// The caller should have started a JSON array, in which we can add an object
// that defines a marker schema.
if (mLocations.empty()) {
// SpecialFrontendLocation case, don't output anything for this type.
return;
}
aWriter.StartObjectElement();
{
aWriter.StringProperty("name", aName);
if (!mChartLabel.empty()) {
aWriter.StringProperty("chartLabel", mChartLabel);
}
if (!mTooltipLabel.empty()) {
aWriter.StringProperty("tooltipLabel", mTooltipLabel);
}
if (!mTableLabel.empty()) {
aWriter.StringProperty("tableLabel", mTableLabel);
}
aWriter.StartArrayProperty("display");
{
for (Location location : mLocations) {
aWriter.StringElement(LocationToStringSpan(location));
}
}
aWriter.EndArray();
aWriter.StartArrayProperty("data");
{
for (const DataRow& row : mData) {
aWriter.StartObjectElement();
{
row.match(
[&aWriter](const DynamicData& aData) {
aWriter.StringProperty("key", aData.mKey);
if (aData.mLabel) {
aWriter.StringProperty("label", *aData.mLabel);
}
aWriter.StringProperty("format",
FormatToStringSpan(aData.mFormat));
if (aData.mSearchable) {
aWriter.BoolProperty(
"searchable",
*aData.mSearchable == Searchable::Searchable);
}
},
[&aWriter](const StaticData& aStaticData) {
aWriter.StringProperty("label", aStaticData.mLabel);
aWriter.StringProperty("value", aStaticData.mValue);
});
}
aWriter.EndObject();
}
}
aWriter.EndArray();
if (!mGraphs.empty()) {
aWriter.StartArrayProperty("graphs");
{
for (const GraphData& graph : mGraphs) {
aWriter.StartObjectElement();
{
aWriter.StringProperty("key", graph.mKey);
aWriter.StringProperty("type", GraphTypeToStringSpan(graph.mType));
if (graph.mColor) {
aWriter.StringProperty("color",
GraphColorToStringSpan(*graph.mColor));
}
}
aWriter.EndObject();
}
}
aWriter.EndArray();
}
}
aWriter.EndObject();
}
/* static */
Span<const char> MarkerSchema::LocationToStringSpan(
MarkerSchema::Location aLocation) {
switch (aLocation) {
case Location::MarkerChart:
return mozilla::MakeStringSpan("marker-chart");
case Location::MarkerTable:
return mozilla::MakeStringSpan("marker-table");
case Location::TimelineOverview:
return mozilla::MakeStringSpan("timeline-overview");
case Location::TimelineMemory:
return mozilla::MakeStringSpan("timeline-memory");
case Location::TimelineIPC:
return mozilla::MakeStringSpan("timeline-ipc");
case Location::TimelineFileIO:
return mozilla::MakeStringSpan("timeline-fileio");
case Location::StackChart:
return mozilla::MakeStringSpan("stack-chart");
default:
MOZ_CRASH("Unexpected Location enum");
return {};
}
}
/* static */
Span<const char> MarkerSchema::FormatToStringSpan(
MarkerSchema::Format aFormat) {
switch (aFormat) {
case Format::Url:
return mozilla::MakeStringSpan("url");
case Format::FilePath:
return mozilla::MakeStringSpan("file-path");
case Format::SanitizedString:
return mozilla::MakeStringSpan("sanitized-string");
case Format::String:
return mozilla::MakeStringSpan("string");
case Format::UniqueString:
return mozilla::MakeStringSpan("unique-string");
case Format::Duration:
return mozilla::MakeStringSpan("duration");
case Format::Time:
return mozilla::MakeStringSpan("time");
case Format::Seconds:
return mozilla::MakeStringSpan("seconds");
case Format::Milliseconds:
return mozilla::MakeStringSpan("milliseconds");
case Format::Microseconds:
return mozilla::MakeStringSpan("microseconds");
case Format::Nanoseconds:
return mozilla::MakeStringSpan("nanoseconds");
case Format::Bytes:
return mozilla::MakeStringSpan("bytes");
case Format::Percentage:
return mozilla::MakeStringSpan("percentage");
case Format::Integer:
return mozilla::MakeStringSpan("integer");
case Format::Decimal:
return mozilla::MakeStringSpan("decimal");
default:
MOZ_CRASH("Unexpected Format enum");
return {};
}
}
/* static */
Span<const char> MarkerSchema::GraphTypeToStringSpan(
MarkerSchema::GraphType aType) {
switch (aType) {
case GraphType::Line:
return mozilla::MakeStringSpan("line");
case GraphType::Bar:
return mozilla::MakeStringSpan("bar");
case GraphType::FilledLine:
return mozilla::MakeStringSpan("line-filled");
default:
MOZ_CRASH("Unexpected GraphType enum");
return {};
}
}
/* static */
Span<const char> MarkerSchema::GraphColorToStringSpan(
MarkerSchema::GraphColor aColor) {
switch (aColor) {
case GraphColor::Blue:
return mozilla::MakeStringSpan("blue");
case GraphColor::Green:
return mozilla::MakeStringSpan("green");
case GraphColor::Grey:
return mozilla::MakeStringSpan("grey");
case GraphColor::Ink:
return mozilla::MakeStringSpan("ink");
case GraphColor::Magenta:
return mozilla::MakeStringSpan("magenta");
case GraphColor::Orange:
return mozilla::MakeStringSpan("orange");
case GraphColor::Purple:
return mozilla::MakeStringSpan("purple");
case GraphColor::Red:
return mozilla::MakeStringSpan("red");
case GraphColor::Teal:
return mozilla::MakeStringSpan("teal");
case GraphColor::Yellow:
return mozilla::MakeStringSpan("yellow");
default:
MOZ_CRASH("Unexpected GraphColor enum");
return {};
}
}
} // namespace mozilla
namespace mozilla::baseprofiler {
template MFBT_API ProfileBufferBlockIndex AddMarker(const ProfilerString8View&,
const MarkerCategory&,
MarkerOptions&&,
markers::TextMarker,
const std::string&);
template MFBT_API ProfileBufferBlockIndex
AddMarkerToBuffer(ProfileChunkedBuffer&, const ProfilerString8View&,
const MarkerCategory&, MarkerOptions&&, markers::NoPayload);
template MFBT_API ProfileBufferBlockIndex AddMarkerToBuffer(
ProfileChunkedBuffer&, const ProfilerString8View&, const MarkerCategory&,
MarkerOptions&&, markers::TextMarker, const std::string&);
} // namespace mozilla::baseprofiler