Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "ProfilerIOInterposeObserver.h"
#include "GeckoProfiler.h"
using namespace mozilla;
/* static */
ProfilerIOInterposeObserver& ProfilerIOInterposeObserver::GetInstance() {
static ProfilerIOInterposeObserver sProfilerIOInterposeObserver;
return sProfilerIOInterposeObserver;
}
namespace geckoprofiler::markers {
struct FileIOMarker {
static constexpr Span<const char> MarkerTypeName() {
return MakeStringSpan("FileIO");
}
static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
const ProfilerString8View& aOperation,
const ProfilerString8View& aSource,
const ProfilerString8View& aFilename,
MarkerThreadId aOperationThreadId) {
aWriter.StringProperty("operation", aOperation);
aWriter.StringProperty("source", aSource);
if (aFilename.Length() != 0) {
aWriter.StringProperty("filename", aFilename);
}
if (!aOperationThreadId.IsUnspecified()) {
// Tech note: If `ToNumber()` returns a uint64_t, the conversion to
// int64_t is "implementation-defined" before C++20. This is acceptable
// here, because this is a one-way conversion to a unique identifier
// that's used to visually separate data by thread on the front-end.
aWriter.IntProperty(
"threadId",
static_cast<int64_t>(aOperationThreadId.ThreadId().ToNumber()));
}
}
static MarkerSchema MarkerTypeDisplay() {
using MS = MarkerSchema;
MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
MS::Location::TimelineFileIO};
schema.AddKeyLabelFormatSearchable("operation", "Operation",
MS::Format::String,
MS::Searchable::Searchable);
schema.AddKeyLabelFormatSearchable("source", "Source", MS::Format::String,
MS::Searchable::Searchable);
schema.AddKeyLabelFormatSearchable("filename", "Filename",
MS::Format::FilePath,
MS::Searchable::Searchable);
schema.AddKeyLabelFormatSearchable("threadId", "Thread ID",
MS::Format::String,
MS::Searchable::Searchable);
return schema;
}
};
} // namespace geckoprofiler::markers
static auto GetFilename(IOInterposeObserver::Observation& aObservation) {
AUTO_PROFILER_STATS(IO_filename);
constexpr size_t scExpectedMaxFilename = 512;
nsAutoStringN<scExpectedMaxFilename> filename16;
aObservation.Filename(filename16);
nsAutoCStringN<scExpectedMaxFilename> filename8;
if (!filename16.IsEmpty()) {
CopyUTF16toUTF8(filename16, filename8);
}
return filename8;
}
void ProfilerIOInterposeObserver::Observe(Observation& aObservation) {
if (profiler_is_locked_on_current_thread()) {
// Don't observe I/Os originating from the profiler itself (when internally
// locked) to avoid deadlocks when calling profiler functions.
AUTO_PROFILER_STATS(IO_profiler_locked);
return;
}
Maybe<uint32_t> maybeFeatures = profiler_features_if_active_and_unpaused();
if (maybeFeatures.isNothing()) {
return;
}
uint32_t features = *maybeFeatures;
if (!profiler_thread_is_being_profiled_for_markers(
profiler_main_thread_id()) &&
!profiler_thread_is_being_profiled_for_markers()) {
return;
}
AUTO_PROFILER_LABEL("ProfilerIOInterposeObserver", PROFILER);
if (IsMainThread()) {
// This is the main thread.
// Capture a marker if any "IO" feature is on.
// If it's not being profiled, we have nowhere to store FileIO markers.
if (!profiler_thread_is_being_profiled_for_markers() ||
!(features & ProfilerFeature::MainThreadIO)) {
return;
}
AUTO_PROFILER_STATS(IO_MT);
nsAutoCString type{aObservation.FileType()};
type.AppendLiteral("IO");
// Store the marker in the current thread.
PROFILER_MARKER(
type, OTHER,
MarkerOptions(
MarkerTiming::Interval(aObservation.Start(), aObservation.End()),
MarkerStack::Capture()),
FileIOMarker,
// aOperation
ProfilerString8View::WrapNullTerminatedString(
aObservation.ObservedOperationString()),
// aSource
ProfilerString8View::WrapNullTerminatedString(aObservation.Reference()),
// aFilename
GetFilename(aObservation),
// aOperationThreadId - Do not include a thread ID, as it's the same as
// the markers. Only include this field when the marker is being sent
// from another thread.
MarkerThreadId{});
} else if (profiler_thread_is_being_profiled_for_markers()) {
// This is a non-main thread that is being profiled.
if (!(features & ProfilerFeature::FileIO)) {
return;
}
AUTO_PROFILER_STATS(IO_off_MT);
nsAutoCString type{aObservation.FileType()};
type.AppendLiteral("IO");
// Share a backtrace between the marker on this thread, and the marker on
// the main thread.
UniquePtr<ProfileChunkedBuffer> backtrace = profiler_capture_backtrace();
// Store the marker in the current thread.
PROFILER_MARKER(
type, OTHER,
MarkerOptions(
MarkerTiming::Interval(aObservation.Start(), aObservation.End()),
backtrace ? MarkerStack::UseBacktrace(*backtrace)
: MarkerStack::NoStack()),
FileIOMarker,
// aOperation
ProfilerString8View::WrapNullTerminatedString(
aObservation.ObservedOperationString()),
// aSource
ProfilerString8View::WrapNullTerminatedString(aObservation.Reference()),
// aFilename
GetFilename(aObservation),
// aOperationThreadId - Do not include a thread ID, as it's the same as
// the markers. Only include this field when the marker is being sent
// from another thread.
MarkerThreadId{});
// Store the marker in the main thread as well, with a distinct marker name
// and thread id.
type.AppendLiteral(" (non-main thread)");
PROFILER_MARKER(
type, OTHER,
MarkerOptions(
MarkerTiming::Interval(aObservation.Start(), aObservation.End()),
backtrace ? MarkerStack::UseBacktrace(*backtrace)
: MarkerStack::NoStack(),
// This is the important piece that changed.
// It will send a marker to the main thread.
MarkerThreadId::MainThread()),
FileIOMarker,
// aOperation
ProfilerString8View::WrapNullTerminatedString(
aObservation.ObservedOperationString()),
// aSource
ProfilerString8View::WrapNullTerminatedString(aObservation.Reference()),
// aFilename
GetFilename(aObservation),
// aOperationThreadId - Include the thread ID in the payload.
MarkerThreadId::CurrentThread());
} else {
// This is a thread that is not being profiled. We still want to capture
// file I/Os (to the main thread) if the "FileIOAll" feature is on.
if (!(features & ProfilerFeature::FileIOAll)) {
return;
}
AUTO_PROFILER_STATS(IO_other);
nsAutoCString type{aObservation.FileType()};
if (profiler_is_active_and_thread_is_registered()) {
type.AppendLiteral("IO (non-profiled thread)");
} else {
type.AppendLiteral("IO (unregistered thread)");
}
// Only store this marker on the main thread, as this thread was not being
// profiled.
PROFILER_MARKER(
type, OTHER,
MarkerOptions(
MarkerTiming::Interval(aObservation.Start(), aObservation.End()),
MarkerStack::Capture(),
// Store this marker on the main thread.
MarkerThreadId::MainThread()),
FileIOMarker,
// aOperation
ProfilerString8View::WrapNullTerminatedString(
aObservation.ObservedOperationString()),
// aSource
ProfilerString8View::WrapNullTerminatedString(aObservation.Reference()),
// aFilename
GetFilename(aObservation),
// aOperationThreadId - Note which thread this marker is coming from.
MarkerThreadId::CurrentThread());
}
}