diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /tools/profiler/core/ProfilerThreadRegistrationData.cpp | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/profiler/core/ProfilerThreadRegistrationData.cpp')
-rw-r--r-- | tools/profiler/core/ProfilerThreadRegistrationData.cpp | 303 |
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 |