/* 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 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(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 filename16; aObservation.Filename(filename16); nsAutoCStringN 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 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 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()); } }