forked from mirrors/gecko-dev
The sanitization function for URL and FilePath cannot currently sanitize an arbitrary string in the profiler data. The expectation is that the URL starts with a scheme like http:// and that a file path contains a /, so none of them are sanitized if the contents are a domain name. This commit introduces a new 'sanitized-string' format, that the profiler can make sure to completely blank out. Differential Revision: https://phabricator.services.mozilla.com/D211171
417 lines
15 KiB
C++
417 lines
15 KiB
C++
/* -*- 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
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#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
|