216 lines
8.4 KiB
C++
216 lines
8.4 KiB
C++
/* 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());
|
|
}
|
|
}
|