commit c1654ddf391dd0ff7263ca2deccc5219c9433267
parent 35c0a95db213a1b081f115bae1eaf4bd3e8aee8d
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date: Tue, 14 Oct 2025 21:29:31 +0000
Bug 1990870 - Add flow marker support to GeckoProfilerRuntime r=sfink,canaltinova,profiler-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D268184
Diffstat:
5 files changed, 122 insertions(+), 15 deletions(-)
diff --git a/js/public/ProfilingStack.h b/js/public/ProfilingStack.h
@@ -364,11 +364,14 @@ JS_PUBLIC_API void SetContextProfilingStack(JSContext* cx,
JS_PUBLIC_API void EnableContextProfilingStack(JSContext* cx, bool enabled);
-JS_PUBLIC_API void RegisterContextProfilingEventMarker(
+JS_PUBLIC_API void RegisterContextProfilerMarkers(
JSContext* cx,
- void (*mark)(mozilla::MarkerCategory, const char*, const char*),
- void (*interval)(mozilla::MarkerCategory, const char*, mozilla::TimeStamp,
- const char*));
+ void (*eventMarker)(mozilla::MarkerCategory, const char*, const char*),
+ void (*intervalMarker)(mozilla::MarkerCategory, const char*,
+ mozilla::TimeStamp, const char*),
+ void (*flowMarker)(mozilla::MarkerCategory, const char*, uint64_t),
+ void (*terminatingFlowMarker)(mozilla::MarkerCategory, const char*,
+ uint64_t));
} // namespace js
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
@@ -7759,11 +7759,26 @@ static void PrintProfilerIntervals_Callback(mozilla::MarkerCategory,
(TimeStamp::Now() - start).ToMilliseconds(), msg, details);
}
+static void PrintProfilerFlow_Callback(mozilla::MarkerCategory,
+ const char* markerName,
+ uint64_t flowId) {
+ fprintf(stderr, "PROFILER FLOW: %s (flowId=%" PRIu64 ")\n", markerName,
+ flowId);
+}
+
+static void PrintProfilerTerminatingFlow_Callback(mozilla::MarkerCategory,
+ const char* markerName,
+ uint64_t flowId) {
+ fprintf(stderr, "PROFILER TERMINATING FLOW: %s (flowId=%" PRIu64 ")\n",
+ markerName, flowId);
+}
+
static bool PrintProfilerEvents(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (cx->runtime()->geckoProfiler().enabled()) {
- js::RegisterContextProfilingEventMarker(cx, &PrintProfilerEvents_Callback,
- &PrintProfilerIntervals_Callback);
+ js::RegisterContextProfilerMarkers(
+ cx, &PrintProfilerEvents_Callback, &PrintProfilerIntervals_Callback,
+ &PrintProfilerFlow_Callback, &PrintProfilerTerminatingFlow_Callback);
}
args.rval().setUndefined();
return true;
diff --git a/js/src/vm/GeckoProfiler.cpp b/js/src/vm/GeckoProfiler.cpp
@@ -34,7 +34,9 @@ GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt)
slowAssertions(false),
enabled_(false),
eventMarker_(nullptr),
- intervalMarker_(nullptr) {
+ intervalMarker_(nullptr),
+ flowMarker_(nullptr),
+ terminatingFlowMarker_(nullptr) {
MOZ_ASSERT(rt != nullptr);
}
@@ -55,6 +57,16 @@ void GeckoProfilerRuntime::setIntervalMarker(void (*fn)(
intervalMarker_ = fn;
}
+void GeckoProfilerRuntime::setFlowMarker(void (*fn)(mozilla::MarkerCategory,
+ const char*, uint64_t)) {
+ flowMarker_ = fn;
+}
+
+void GeckoProfilerRuntime::setTerminatingFlowMarker(
+ void (*fn)(mozilla::MarkerCategory, const char*, uint64_t)) {
+ terminatingFlowMarker_ = fn;
+}
+
// Get a pointer to the top-most profiling frame, given the exit frame pointer.
static jit::JitFrameLayout* GetTopProfilingJitFrame(jit::JitActivation* act) {
// If there is no exit frame set, just return.
@@ -214,6 +226,28 @@ void GeckoProfilerRuntime::markInterval(const char* event,
}
}
+void GeckoProfilerRuntime::markFlow(const char* markerName, uint64_t flowId,
+ JS::ProfilingCategoryPair jsPair) {
+ MOZ_ASSERT(enabled());
+ if (flowMarker_) {
+ JS::AutoSuppressGCAnalysis nogc;
+ mozilla::MarkerCategory category(
+ static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
+ flowMarker_(category, markerName, flowId);
+ }
+}
+
+void GeckoProfilerRuntime::markTerminatingFlow(
+ const char* markerName, uint64_t flowId, JS::ProfilingCategoryPair jsPair) {
+ MOZ_ASSERT(enabled());
+ if (terminatingFlowMarker_) {
+ JS::AutoSuppressGCAnalysis nogc;
+ mozilla::MarkerCategory category(
+ static_cast<mozilla::baseprofiler::ProfilingCategoryPair>(jsPair));
+ terminatingFlowMarker_(category, markerName, flowId);
+ }
+}
+
bool GeckoProfilerThread::enter(JSContext* cx, JSScript* script) {
const char* dynamicString =
cx->runtime()->geckoProfiler().profileString(cx, script);
@@ -510,14 +544,20 @@ JS_PUBLIC_API void js::EnableContextProfilingStack(JSContext* cx,
cx->runtime()->geckoProfiler().enable(enabled);
}
-JS_PUBLIC_API void js::RegisterContextProfilingEventMarker(
+JS_PUBLIC_API void js::RegisterContextProfilerMarkers(
JSContext* cx,
- void (*mark)(mozilla::MarkerCategory, const char*, const char*),
- void (*interval)(mozilla::MarkerCategory, const char*, mozilla::TimeStamp,
- const char*)) {
+ void (*eventMarker)(mozilla::MarkerCategory, const char*, const char*),
+ void (*intervalMarker)(mozilla::MarkerCategory, const char*,
+ mozilla::TimeStamp, const char*),
+ void (*flowMarker)(mozilla::MarkerCategory, const char*, uint64_t),
+ void (*terminatingFlowMarker)(mozilla::MarkerCategory, const char*,
+ uint64_t)) {
MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
- cx->runtime()->geckoProfiler().setEventMarker(mark);
- cx->runtime()->geckoProfiler().setIntervalMarker(interval);
+ cx->runtime()->geckoProfiler().setEventMarker(eventMarker);
+ cx->runtime()->geckoProfiler().setIntervalMarker(intervalMarker);
+ cx->runtime()->geckoProfiler().setFlowMarker(flowMarker);
+ cx->runtime()->geckoProfiler().setTerminatingFlowMarker(
+ terminatingFlowMarker);
}
AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx)
diff --git a/js/src/vm/GeckoProfiler.h b/js/src/vm/GeckoProfiler.h
@@ -127,6 +127,9 @@ class GeckoProfilerRuntime {
void (*eventMarker_)(mozilla::MarkerCategory, const char*, const char*);
void (*intervalMarker_)(mozilla::MarkerCategory, const char*,
mozilla::TimeStamp, const char*);
+ void (*flowMarker_)(mozilla::MarkerCategory, const char*, uint64_t);
+ void (*terminatingFlowMarker_)(mozilla::MarkerCategory, const char*,
+ uint64_t);
public:
explicit GeckoProfilerRuntime(JSRuntime* rt);
@@ -141,6 +144,10 @@ class GeckoProfilerRuntime {
const char*));
void setIntervalMarker(void (*fn)(mozilla::MarkerCategory, const char*,
mozilla::TimeStamp, const char*));
+ void setFlowMarker(void (*fn)(mozilla::MarkerCategory, const char*,
+ uint64_t));
+ void setTerminatingFlowMarker(void (*fn)(mozilla::MarkerCategory, const char*,
+ uint64_t));
static JS::UniqueChars allocProfileString(JSContext* cx, BaseScript* script);
const char* profileString(JSContext* cx, BaseScript* script);
@@ -155,6 +162,17 @@ class GeckoProfilerRuntime {
const char* event, mozilla::TimeStamp start, const char* details,
JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
+ // Note that flowId will be added as a process-scoped id for both
+ // markFlow and markTerminatingFlow.
+ //
+ // See baseprofiler/public/Flow.h
+ void markFlow(
+ const char* markerName, uint64_t flowId,
+ JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
+ void markTerminatingFlow(
+ const char* markerName, uint64_t flowId,
+ JS::ProfilingCategoryPair jsPair = JS::ProfilingCategoryPair::JS);
+
ProfileStringMap& strings() { return strings_.ref(); }
/* meant to be used for testing, not recommended to call in normal code */
diff --git a/tools/profiler/core/ProfilerThreadRegistrationData.cpp b/tools/profiler/core/ProfilerThreadRegistrationData.cpp
@@ -7,6 +7,7 @@
#include "mozilla/ProfilerThreadRegistrationData.h"
#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/FlowMarkers.h"
#include "mozilla/FOGIPC.h"
#include "mozilla/ProfilerMarkers.h"
#include "js/AllocationRecording.h"
@@ -102,6 +103,35 @@ static void profiler_add_js_interval(mozilla::MarkerCategory aCategory,
#endif
}
+static void profiler_add_js_flow(mozilla::MarkerCategory aCategory,
+ const char* aMarkerName, uint64_t aFlowId) {
+#ifdef MOZ_GECKO_PROFILER
+ if (!profiler_feature_active(ProfilerFeature::Flows)) {
+ return;
+ }
+ AUTO_PROFILER_STATS(js_flow);
+ profiler_add_marker(
+ mozilla::ProfilerString8View::WrapNullTerminatedString(aMarkerName),
+ aCategory, {}, ::geckoprofiler::markers::FlowMarker{},
+ Flow::ProcessScoped(aFlowId));
+#endif
+}
+
+static void profiler_add_js_terminating_flow(mozilla::MarkerCategory aCategory,
+ const char* aMarkerName,
+ uint64_t aFlowId) {
+#ifdef MOZ_GECKO_PROFILER
+ if (!profiler_feature_active(ProfilerFeature::Flows)) {
+ return;
+ }
+ AUTO_PROFILER_STATS(js_terminating_flow);
+ profiler_add_marker(
+ mozilla::ProfilerString8View::WrapNullTerminatedString(aMarkerName),
+ aCategory, {}, ::geckoprofiler::markers::TerminatingFlowMarker{},
+ Flow::ProcessScoped(aFlowId));
+#endif
+}
+
static void profiler_add_js_allocation_marker(JS::RecordAllocationInfo&& info) {
if (!profiler_thread_is_being_profiled_for_markers()) {
return;
@@ -261,8 +291,9 @@ void ThreadRegistrationLockedRWOnThread::PollJSSampling() {
JS::EnableRecordingAllocations(cx, profiler_add_js_allocation_marker,
0.01);
}
- js::RegisterContextProfilingEventMarker(cx, profiler_add_js_marker,
- profiler_add_js_interval);
+ js::RegisterContextProfilerMarkers(
+ cx, profiler_add_js_marker, profiler_add_js_interval,
+ profiler_add_js_flow, profiler_add_js_terminating_flow);
} else if (mJSSampling == INACTIVE_REQUESTED) {
mJSSampling = INACTIVE;