summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/ProfilerThreadRegistrationData.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/core/ProfilerThreadRegistrationData.cpp')
-rw-r--r--tools/profiler/core/ProfilerThreadRegistrationData.cpp303
1 files changed, 303 insertions, 0 deletions
diff --git a/tools/profiler/core/ProfilerThreadRegistrationData.cpp b/tools/profiler/core/ProfilerThreadRegistrationData.cpp
new file mode 100644
index 0000000000..e70f9e749a
--- /dev/null
+++ b/tools/profiler/core/ProfilerThreadRegistrationData.cpp
@@ -0,0 +1,303 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/ProfilerThreadRegistrationData.h"
+
+#include "mozilla/FOGIPC.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "js/AllocationRecording.h"
+#include "js/ProfilingStack.h"
+
+#if defined(XP_WIN)
+# include <windows.h>
+#elif defined(XP_DARWIN)
+# include <pthread.h>
+#endif
+
+#ifdef NIGHTLY_BUILD
+namespace geckoprofiler::markers {
+
+using namespace mozilla;
+
+struct ThreadCpuUseMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("ThreadCpuUse");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ ProfilerThreadId aThreadId,
+ int64_t aCpuTimeMs, int64_t aWakeUps,
+ const ProfilerString8View& aThreadName) {
+ aWriter.IntProperty("threadId", static_cast<int64_t>(aThreadId.ToNumber()));
+ aWriter.IntProperty("time", aCpuTimeMs);
+ aWriter.IntProperty("wakeups", aWakeUps);
+ aWriter.StringProperty("label", aThreadName);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormat("time", "CPU Time", MS::Format::Milliseconds);
+ schema.AddKeyLabelFormat("wakeups", "Wake ups", MS::Format::Integer);
+ schema.SetTooltipLabel("{marker.name} - {marker.data.label}");
+ schema.SetTableLabel(
+ "{marker.name} - {marker.data.label}: {marker.data.time} of CPU time, "
+ "{marker.data.wakeups} wake ups");
+ return schema;
+ }
+};
+
+} // namespace geckoprofiler::markers
+#endif
+
+namespace mozilla::profiler {
+
+ThreadRegistrationData::ThreadRegistrationData(const char* aName,
+ const void* aStackTop)
+ : mInfo(aName),
+ mPlatformData(mInfo.ThreadId()),
+ mStackTop(
+#if defined(XP_WIN)
+ // We don't have to guess on Windows.
+ reinterpret_cast<const void*>(
+ reinterpret_cast<PNT_TIB>(NtCurrentTeb())->StackBase)
+#elif defined(XP_DARWIN)
+ // We don't have to guess on Mac/Darwin.
+ reinterpret_cast<const void*>(
+ pthread_get_stackaddr_np(pthread_self()))
+#else
+ // Otherwise use the given guess.
+ aStackTop
+#endif
+ ) {
+}
+
+// This is a simplified version of profiler_add_marker that can be easily passed
+// into the JS engine.
+static void profiler_add_js_marker(const char* aMarkerName,
+ const char* aMarkerText) {
+ PROFILER_MARKER_TEXT(
+ mozilla::ProfilerString8View::WrapNullTerminatedString(aMarkerName), JS,
+ {}, mozilla::ProfilerString8View::WrapNullTerminatedString(aMarkerText));
+}
+
+static void profiler_add_js_allocation_marker(JS::RecordAllocationInfo&& info) {
+ if (!profiler_thread_is_being_profiled_for_markers()) {
+ return;
+ }
+
+ struct JsAllocationMarker {
+ static constexpr mozilla::Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("JS allocation");
+ }
+ static void StreamJSONMarkerData(
+ mozilla::baseprofiler::SpliceableJSONWriter& aWriter,
+ const mozilla::ProfilerString16View& aTypeName,
+ const mozilla::ProfilerString8View& aClassName,
+ const mozilla::ProfilerString16View& aDescriptiveTypeName,
+ const mozilla::ProfilerString8View& aCoarseType, uint64_t aSize,
+ bool aInNursery) {
+ if (aClassName.Length() != 0) {
+ aWriter.StringProperty("className", aClassName);
+ }
+ if (aTypeName.Length() != 0) {
+ aWriter.StringProperty("typeName", NS_ConvertUTF16toUTF8(aTypeName));
+ }
+ if (aDescriptiveTypeName.Length() != 0) {
+ aWriter.StringProperty("descriptiveTypeName",
+ NS_ConvertUTF16toUTF8(aDescriptiveTypeName));
+ }
+ aWriter.StringProperty("coarseType", aCoarseType);
+ aWriter.IntProperty("size", aSize);
+ aWriter.BoolProperty("inNursery", aInNursery);
+ }
+ static mozilla::MarkerSchema MarkerTypeDisplay() {
+ return mozilla::MarkerSchema::SpecialFrontendLocation{};
+ }
+ };
+
+ profiler_add_marker(
+ "JS allocation", geckoprofiler::category::JS,
+ mozilla::MarkerStack::Capture(), JsAllocationMarker{},
+ mozilla::ProfilerString16View::WrapNullTerminatedString(info.typeName),
+ mozilla::ProfilerString8View::WrapNullTerminatedString(info.className),
+ mozilla::ProfilerString16View::WrapNullTerminatedString(
+ info.descriptiveTypeName),
+ mozilla::ProfilerString8View::WrapNullTerminatedString(info.coarseType),
+ info.size, info.inNursery);
+}
+
+void ThreadRegistrationLockedRWFromAnyThread::SetProfilingFeaturesAndData(
+ ThreadProfilingFeatures aProfilingFeatures,
+ ProfiledThreadData* aProfiledThreadData, const PSAutoLock&) {
+ MOZ_ASSERT(mProfilingFeatures == ThreadProfilingFeatures::NotProfiled);
+ mProfilingFeatures = aProfilingFeatures;
+
+ MOZ_ASSERT(!mProfiledThreadData);
+ MOZ_ASSERT(aProfiledThreadData);
+ mProfiledThreadData = aProfiledThreadData;
+
+ if (mJSContext) {
+ // The thread is now being profiled, and we already have a JSContext,
+ // allocate a JsFramesBuffer to allow profiler-unlocked on-thread sampling.
+ MOZ_ASSERT(!mJsFrameBuffer);
+ mJsFrameBuffer = new JsFrame[MAX_JS_FRAMES];
+ }
+
+ // Check invariants.
+ MOZ_ASSERT((mProfilingFeatures != ThreadProfilingFeatures::NotProfiled) ==
+ !!mProfiledThreadData);
+ MOZ_ASSERT((mJSContext &&
+ (mProfilingFeatures != ThreadProfilingFeatures::NotProfiled)) ==
+ !!mJsFrameBuffer);
+}
+
+void ThreadRegistrationLockedRWFromAnyThread::ClearProfilingFeaturesAndData(
+ const PSAutoLock&) {
+ mProfilingFeatures = ThreadProfilingFeatures::NotProfiled;
+ mProfiledThreadData = nullptr;
+
+ if (mJsFrameBuffer) {
+ delete[] mJsFrameBuffer;
+ mJsFrameBuffer = nullptr;
+ }
+
+ // Check invariants.
+ MOZ_ASSERT((mProfilingFeatures != ThreadProfilingFeatures::NotProfiled) ==
+ !!mProfiledThreadData);
+ MOZ_ASSERT((mJSContext &&
+ (mProfilingFeatures != ThreadProfilingFeatures::NotProfiled)) ==
+ !!mJsFrameBuffer);
+}
+
+void ThreadRegistrationLockedRWOnThread::SetJSContext(JSContext* aJSContext) {
+ MOZ_ASSERT(aJSContext && !mJSContext);
+
+ mJSContext = aJSContext;
+
+ if (mProfiledThreadData) {
+ MOZ_ASSERT((mProfilingFeatures != ThreadProfilingFeatures::NotProfiled) ==
+ !!mProfiledThreadData);
+ // We now have a JSContext, and the thread is already being profiled,
+ // allocate a JsFramesBuffer to allow profiler-unlocked on-thread sampling.
+ MOZ_ASSERT(!mJsFrameBuffer);
+ mJsFrameBuffer = new JsFrame[MAX_JS_FRAMES];
+ }
+
+ // We give the JS engine a non-owning reference to the ProfilingStack. It's
+ // important that the JS engine doesn't touch this once the thread dies.
+ js::SetContextProfilingStack(aJSContext, &ProfilingStackRef());
+
+ // Check invariants.
+ MOZ_ASSERT((mJSContext &&
+ (mProfilingFeatures != ThreadProfilingFeatures::NotProfiled)) ==
+ !!mJsFrameBuffer);
+}
+
+void ThreadRegistrationLockedRWOnThread::ClearJSContext() {
+ mJSContext = nullptr;
+
+ if (mJsFrameBuffer) {
+ delete[] mJsFrameBuffer;
+ mJsFrameBuffer = nullptr;
+ }
+
+ // Check invariants.
+ MOZ_ASSERT((mJSContext &&
+ (mProfilingFeatures != ThreadProfilingFeatures::NotProfiled)) ==
+ !!mJsFrameBuffer);
+}
+
+void ThreadRegistrationLockedRWOnThread::PollJSSampling() {
+ // We can't start/stop profiling until we have the thread's JSContext.
+ if (mJSContext) {
+ // It is possible for mJSSampling to go through the following sequences.
+ //
+ // - INACTIVE, ACTIVE_REQUESTED, INACTIVE_REQUESTED, INACTIVE
+ //
+ // - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
+ //
+ // Therefore, the if and else branches here aren't always interleaved.
+ // This is ok because the JS engine can handle that.
+ //
+ if (mJSSampling == ACTIVE_REQUESTED) {
+ mJSSampling = ACTIVE;
+ js::EnableContextProfilingStack(mJSContext, true);
+
+ if (JSAllocationsEnabled()) {
+ // TODO - This probability should not be hardcoded. See Bug 1547284.
+ JS::EnableRecordingAllocations(mJSContext,
+ profiler_add_js_allocation_marker, 0.01);
+ }
+ js::RegisterContextProfilingEventMarker(mJSContext,
+ profiler_add_js_marker);
+
+ } else if (mJSSampling == INACTIVE_REQUESTED) {
+ mJSSampling = INACTIVE;
+ js::EnableContextProfilingStack(mJSContext, false);
+
+ if (JSAllocationsEnabled()) {
+ JS::DisableRecordingAllocations(mJSContext);
+ }
+ }
+ }
+}
+
+#ifdef NIGHTLY_BUILD
+void ThreadRegistrationUnlockedConstReaderAndAtomicRW::RecordWakeCount() const {
+ baseprofiler::detail::BaseProfilerAutoLock lock(mRecordWakeCountMutex);
+
+ uint64_t newWakeCount = mWakeCount - mAlreadyRecordedWakeCount;
+ if (newWakeCount == 0 && mSleep != AWAKE) {
+ // If no new wake-up was counted, and the thread is not marked awake,
+ // we can be pretty sure there is no CPU activity to record.
+ // Threads that are never annotated as asleep/awake (typically rust threads)
+ // start as awake.
+ return;
+ }
+
+ uint64_t cpuTimeNs;
+ if (!GetCpuTimeSinceThreadStartInNs(&cpuTimeNs, PlatformDataCRef())) {
+ cpuTimeNs = 0;
+ }
+
+ constexpr uint64_t NS_PER_MS = 1'000'000;
+ uint64_t cpuTimeMs = cpuTimeNs / NS_PER_MS;
+
+ uint64_t newCpuTimeMs = MOZ_LIKELY(cpuTimeMs > mAlreadyRecordedCpuTimeInMs)
+ ? cpuTimeMs - mAlreadyRecordedCpuTimeInMs
+ : 0;
+
+ if (!newWakeCount && !newCpuTimeMs) {
+ // Nothing to report, avoid computing the Glean friendly thread name.
+ return;
+ }
+
+ nsAutoCString threadName(mInfo.Name());
+ // Trim the trailing number of threads that are part of a thread pool.
+ for (size_t length = threadName.Length(); length > 0; --length) {
+ const char c = threadName.CharAt(length - 1);
+ if ((c < '0' || c > '9') && c != '#' && c != ' ') {
+ if (length != threadName.Length()) {
+ threadName.SetLength(length);
+ }
+ break;
+ }
+ }
+
+ mozilla::glean::RecordThreadCpuUse(threadName, newCpuTimeMs, newWakeCount);
+
+ // The thread id is provided as part of the payload because this call is
+ // inside a ThreadRegistration data function, which could be invoked with
+ // the ThreadRegistry locked. We cannot call any function/option that could
+ // attempt to lock the ThreadRegistry again, like MarkerThreadId.
+ PROFILER_MARKER("Thread CPU use", OTHER, {}, ThreadCpuUseMarker,
+ mInfo.ThreadId(), newCpuTimeMs, newWakeCount, threadName);
+ mAlreadyRecordedCpuTimeInMs = cpuTimeMs;
+ mAlreadyRecordedWakeCount += newWakeCount;
+}
+#endif
+
+} // namespace mozilla::profiler