1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
/* 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.AddKeyLabelFormat("operation", "Operation", MS::Format::String,
MS::PayloadFlags::Searchable);
schema.AddKeyLabelFormat("source", "Source", MS::Format::String,
MS::PayloadFlags::Searchable);
schema.AddKeyLabelFormat("filename", "Filename", MS::Format::FilePath,
MS::PayloadFlags::Searchable);
schema.AddKeyLabelFormat("threadId", "Thread ID", MS::Format::String,
MS::PayloadFlags::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());
}
}
|