diff options
Diffstat (limited to 'tools/profiler/core')
-rw-r--r-- | tools/profiler/core/MicroGeckoProfiler.cpp | 33 | ||||
-rw-r--r-- | tools/profiler/core/PowerCounters-android.cpp | 178 | ||||
-rw-r--r-- | tools/profiler/core/PowerCounters-linux.cpp | 9 | ||||
-rw-r--r-- | tools/profiler/core/PowerCounters-mac-amd64.cpp | 20 | ||||
-rw-r--r-- | tools/profiler/core/PowerCounters-mac-arm64.cpp | 10 | ||||
-rw-r--r-- | tools/profiler/core/PowerCounters-win.cpp | 8 | ||||
-rw-r--r-- | tools/profiler/core/PowerCounters.h | 56 | ||||
-rw-r--r-- | tools/profiler/core/platform-linux-android.cpp | 6 | ||||
-rw-r--r-- | tools/profiler/core/platform-macos.cpp | 6 | ||||
-rw-r--r-- | tools/profiler/core/platform.cpp | 239 |
10 files changed, 487 insertions, 78 deletions
diff --git a/tools/profiler/core/MicroGeckoProfiler.cpp b/tools/profiler/core/MicroGeckoProfiler.cpp index bedb755742..6c384aeb41 100644 --- a/tools/profiler/core/MicroGeckoProfiler.cpp +++ b/tools/profiler/core/MicroGeckoProfiler.cpp @@ -133,10 +133,10 @@ struct ProfileBufferEntryReader::Deserializer<TraceOption> { } // namespace mozilla #endif // MOZ_GECKO_PROFILER -void uprofiler_simple_event_marker(const char* name, char phase, int num_args, - const char** arg_names, - const unsigned char* arg_types, - const unsigned long long* arg_values) { +void uprofiler_simple_event_marker_internal( + const char* name, char phase, int num_args, const char** arg_names, + const unsigned char* arg_types, const unsigned long long* arg_values, + bool full_stack) { #ifdef MOZ_GECKO_PROFILER if (!profiler_thread_is_being_profiled_for_markers()) { return; @@ -196,8 +196,27 @@ void uprofiler_simple_event_marker(const char* name, char phase, int num_args, break; } } - profiler_add_marker(ProfilerString8View::WrapNullTerminatedString(name), - geckoprofiler::category::MEDIA_RT, {timing.extract()}, - TraceMarker{}, tuple); + profiler_add_marker( + ProfilerString8View::WrapNullTerminatedString(name), + geckoprofiler::category::MEDIA_RT, + {timing.extract(), + full_stack ? MarkerStack::Capture(StackCaptureOptions::Full) + : MarkerStack::Capture(StackCaptureOptions::NoStack)}, + TraceMarker{}, tuple); #endif // MOZ_GECKO_PROFILER } + +void uprofiler_simple_event_marker_with_stack( + const char* name, char phase, int num_args, const char** arg_names, + const unsigned char* arg_types, const unsigned long long* arg_values) { + uprofiler_simple_event_marker_internal(name, phase, num_args, arg_names, + arg_types, arg_values, true); +} + +void uprofiler_simple_event_marker(const char* name, char phase, int num_args, + const char** arg_names, + const unsigned char* arg_types, + const unsigned long long* arg_values) { + uprofiler_simple_event_marker_internal(name, phase, num_args, arg_names, + arg_types, arg_values, false); +} diff --git a/tools/profiler/core/PowerCounters-android.cpp b/tools/profiler/core/PowerCounters-android.cpp new file mode 100644 index 0000000000..5e784952b5 --- /dev/null +++ b/tools/profiler/core/PowerCounters-android.cpp @@ -0,0 +1,178 @@ +/* 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 "PowerCounters.h" +#include "nsXULAppAPI.h" // for XRE_IsParentProcess +#include <dlfcn.h> + +#define ALOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "GeckoProfiler", ##args) + +/* + * The following declarations come from the dlext.h header (not in the ndk). + * https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/include/android/dlext.h;drc=655e430b28d7404f763e7ebefe84fba5a387666d + */ +struct android_namespace_t; +typedef struct { + /** A bitmask of `ANDROID_DLEXT_` enum values. */ + uint64_t flags; + + /** Used by `ANDROID_DLEXT_RESERVED_ADDRESS` and + * `ANDROID_DLEXT_RESERVED_ADDRESS_HINT`. */ + void* _Nullable reserved_addr; + /** Used by `ANDROID_DLEXT_RESERVED_ADDRESS` and + * `ANDROID_DLEXT_RESERVED_ADDRESS_HINT`. */ + size_t reserved_size; + + /** Used by `ANDROID_DLEXT_WRITE_RELRO` and `ANDROID_DLEXT_USE_RELRO`. */ + int relro_fd; + + /** Used by `ANDROID_DLEXT_USE_LIBRARY_FD`. */ + int library_fd; + /** Used by `ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET` */ + off64_t library_fd_offset; + + /** Used by `ANDROID_DLEXT_USE_NAMESPACE`. */ + struct android_namespace_t* _Nullable library_namespace; +} android_dlextinfo; +enum { ANDROID_DLEXT_USE_NAMESPACE = 0x200 }; +extern "C" + __attribute__((visibility("default"))) void* _Nullable android_dlopen_ext( + const char* _Nullable __filename, int __flags, + const android_dlextinfo* _Nullable __info); + +// See also documentation at +// https://developer.android.com/studio/profile/power-profiler#power-rails +bool GetAvailableRails(RailDescriptor*, size_t* size_of_arr); + +class RailEnergy final : public BaseProfilerCount { + public: + explicit RailEnergy(RailEnergyData* data, const char* aRailName, + const char* aSubsystemName) + : BaseProfilerCount(aSubsystemName, nullptr, nullptr, "power", aRailName), + mDataPtr(data), + mLastTimestamp(0) {} + + ~RailEnergy() {} + + RailEnergy(const RailEnergy&) = delete; + RailEnergy& operator=(const RailEnergy&) = delete; + + CountSample Sample() override { + CountSample result = { + // RailEnergyData.energy is in microwatt-seconds (uWs) + // we need to return values in picowatt-hour. + .count = static_cast<int64_t>(mDataPtr->energy * 1e3 / 3.6), + .number = 0, + .isSampleNew = mDataPtr->timestamp != mLastTimestamp, + }; + mLastTimestamp = mDataPtr->timestamp; + return result; + } + + private: + RailEnergyData* mDataPtr; + uint64_t mLastTimestamp; +}; + +PowerCounters::PowerCounters() { + if (!XRE_IsParentProcess()) { + // Energy meters are global, so only sample them on the parent. + return; + } + + // A direct dlopen call on libperfetto_android_internal.so fails with a + // namespace error because libperfetto_android_internal.so is missing in + // /etc/public.libraries.txt + // Instead, use android_dlopen_ext with the "default" namespace. + void* libcHandle = dlopen("libc.so", RTLD_LAZY); + if (!libcHandle) { + ALOG("failed to dlopen libc: %s", dlerror()); + return; + } + + struct android_namespace_t* (*android_get_exported_namespace)(const char*) = + reinterpret_cast<struct android_namespace_t* (*)(const char*)>( + dlsym(libcHandle, "__loader_android_get_exported_namespace")); + if (!android_get_exported_namespace) { + ALOG("failed to get __loader_android_get_exported_namespace: %s", + dlerror()); + return; + } + + struct android_namespace_t* ns = android_get_exported_namespace("default"); + const android_dlextinfo dlextinfo = { + .flags = ANDROID_DLEXT_USE_NAMESPACE, + .library_namespace = ns, + }; + + mLibperfettoModule = android_dlopen_ext("libperfetto_android_internal.so", + RTLD_LOCAL | RTLD_LAZY, &dlextinfo); + MOZ_ASSERT(mLibperfettoModule); + if (!mLibperfettoModule) { + ALOG("failed to get libperfetto handle: %s", dlerror()); + return; + } + + decltype(&GetAvailableRails) getAvailableRails = + reinterpret_cast<decltype(&GetAvailableRails)>( + dlsym(mLibperfettoModule, "GetAvailableRails")); + if (!getAvailableRails) { + ALOG("failed to get GetAvailableRails pointer: %s", dlerror()); + return; + } + + constexpr size_t kMaxNumRails = 32; + if (!mRailDescriptors.resize(kMaxNumRails)) { + ALOG("failed to grow mRailDescriptors"); + return; + } + size_t numRails = mRailDescriptors.length(); + getAvailableRails(&mRailDescriptors[0], &numRails); + mRailDescriptors.shrinkTo(numRails); + ALOG("found %zu rails", numRails); + if (numRails == 0) { + // We will see 0 rails either if the device has no support for power + // profiling or if the SELinux policy blocks access (ie. on a non-rooted + // device). + return; + } + + if (!mRailEnergyData.resize(numRails)) { + ALOG("failed to grow mRailEnergyData"); + return; + } + for (size_t i = 0; i < numRails; ++i) { + RailDescriptor& rail = mRailDescriptors[i]; + ALOG("rail %zu, name: %s, subsystem: %s", i, rail.rail_name, + rail.subsys_name); + RailEnergy* railEnergy = + new RailEnergy(&mRailEnergyData[i], rail.rail_name, rail.subsys_name); + if (!mCounters.emplaceBack(railEnergy)) { + delete railEnergy; + } + } + + mGetRailEnergyData = reinterpret_cast<decltype(&GetRailEnergyData)>( + dlsym(mLibperfettoModule, "GetRailEnergyData")); + if (!mGetRailEnergyData) { + ALOG("failed to get GetRailEnergyData pointer"); + return; + } +} +PowerCounters::~PowerCounters() { + if (mLibperfettoModule) { + dlclose(mLibperfettoModule); + } +} +void PowerCounters::Sample() { + // Energy meters are global, so only sample them on the parent. + // Also return early if we failed to access the GetRailEnergyData symbol. + if (!XRE_IsParentProcess() || !mGetRailEnergyData) { + return; + } + + size_t length = mRailEnergyData.length(); + mGetRailEnergyData(&mRailEnergyData[0], &length); +} diff --git a/tools/profiler/core/PowerCounters-linux.cpp b/tools/profiler/core/PowerCounters-linux.cpp index 006cea4867..a28171a6e2 100644 --- a/tools/profiler/core/PowerCounters-linux.cpp +++ b/tools/profiler/core/PowerCounters-linux.cpp @@ -276,12 +276,3 @@ PowerCounters::PowerCounters() { } } } - -PowerCounters::~PowerCounters() { - for (auto* raplEvent : mCounters) { - delete raplEvent; - } - mCounters.clear(); -} - -void PowerCounters::Sample() {} diff --git a/tools/profiler/core/PowerCounters-mac-amd64.cpp b/tools/profiler/core/PowerCounters-mac-amd64.cpp index 540cee155d..c5a82694cd 100644 --- a/tools/profiler/core/PowerCounters-mac-amd64.cpp +++ b/tools/profiler/core/PowerCounters-mac-amd64.cpp @@ -350,13 +350,7 @@ class RAPL { } } - ~RAPL() { - free(mPkes); - delete mPkg; - delete mCores; - delete mGpu; - delete mRam; - } + ~RAPL() { free(mPkes); } void Sample() { constexpr uint64_t kSupportedVersion = 1; @@ -403,14 +397,14 @@ class RAPL { PowerCounters::PowerCounters() { // RAPL values are global, so only sample them on the parent. - mRapl = XRE_IsParentProcess() ? new RAPL(mCounters) : nullptr; + if (XRE_IsParentProcess()) { + mRapl = mozilla::MakeUnique<RAPL>(mCounters); + } } -PowerCounters::~PowerCounters() { - mCounters.clear(); - delete mRapl; - mRapl = nullptr; -} +// This default destructor can not be defined in the header file as it depends +// on the full definition of RAPL which lives in this file. +PowerCounters::~PowerCounters() {} void PowerCounters::Sample() { if (mRapl) { diff --git a/tools/profiler/core/PowerCounters-mac-arm64.cpp b/tools/profiler/core/PowerCounters-mac-arm64.cpp index 3a84a479ef..76fceeca8d 100644 --- a/tools/profiler/core/PowerCounters-mac-arm64.cpp +++ b/tools/profiler/core/PowerCounters-mac-arm64.cpp @@ -36,12 +36,4 @@ class ProcessPower final : public BaseProfilerCount { } }; -PowerCounters::PowerCounters() : mProcessPower(new ProcessPower()) { - if (mProcessPower) { - (void)mCounters.append(mProcessPower.get()); - } -} - -PowerCounters::~PowerCounters() { mCounters.clear(); } - -void PowerCounters::Sample() {} +PowerCounters::PowerCounters() { (void)mCounters.append(new ProcessPower()); } diff --git a/tools/profiler/core/PowerCounters-win.cpp b/tools/profiler/core/PowerCounters-win.cpp index 6e8f492d6d..535a553867 100644 --- a/tools/profiler/core/PowerCounters-win.cpp +++ b/tools/profiler/core/PowerCounters-win.cpp @@ -212,13 +212,13 @@ class PowerMeterDevice { void AppendCountersTo(PowerCounters::CountVector& aCounters) { if (aCounters.reserve(aCounters.length() + mChannels.length())) { for (auto& channel : mChannels) { - aCounters.infallibleAppend(channel.get()); + aCounters.infallibleAppend(channel); } } } private: - Vector<UniquePtr<PowerMeterChannel>, 4> mChannels; + Vector<PowerMeterChannel*, 4> mChannels; HANDLE mHandle = INVALID_HANDLE_VALUE; UniquePtr<EMI_CHANNEL_MEASUREMENT_DATA[]> mDataBuffer; }; @@ -301,7 +301,9 @@ PowerCounters::PowerCounters() { } } -PowerCounters::~PowerCounters() { mCounters.clear(); } +// This default destructor can not be defined in the header file as it depends +// on the full definition of PowerMeterDevice which lives in this file. +PowerCounters::~PowerCounters() {} void PowerCounters::Sample() { for (auto& device : mPowerMeterDevices) { diff --git a/tools/profiler/core/PowerCounters.h b/tools/profiler/core/PowerCounters.h index 2fd8d5892c..df511e87ec 100644 --- a/tools/profiler/core/PowerCounters.h +++ b/tools/profiler/core/PowerCounters.h @@ -19,20 +19,57 @@ class ProcessPower; #if defined(GP_PLAT_amd64_darwin) class RAPL; #endif +#if defined(GP_PLAT_arm64_android) + +/* + * These declarations come from: + * https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/src/android_internal/power_stats.h;l=34-52;drc=1777bdef274bcfbccd4e6f8b6d00a1bac48a8645 + */ + +struct RailDescriptor { + // Index corresponding to the rail + uint32_t index; + // Name of the rail + char rail_name[64]; + // Name of the subsystem to which this rail belongs + char subsys_name[64]; + // Hardware sampling rate + uint32_t sampling_rate; +}; + +struct RailEnergyData { + // Index corresponding to RailDescriptor.index + uint32_t index; + // Time since device boot(CLOCK_BOOTTIME) in milli-seconds + uint64_t timestamp; + // Accumulated energy since device boot in microwatt-seconds (uWs) + uint64_t energy; +}; +bool GetRailEnergyData(RailEnergyData*, size_t* size_of_arr); +#endif class PowerCounters { public: -#if defined(_MSC_VER) || defined(GP_OS_darwin) || defined(GP_PLAT_amd64_linux) +#if defined(_MSC_VER) || defined(GP_OS_darwin) || \ + defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_arm64_android) explicit PowerCounters(); +#else + explicit PowerCounters(){}; +#endif +#if defined(_MSC_VER) || defined(GP_PLAT_amd64_darwin) || \ + defined(GP_PLAT_arm64_android) ~PowerCounters(); +#else + ~PowerCounters() = default; +#endif +#if defined(_MSC_VER) || defined(GP_PLAT_amd64_darwin) || \ + defined(GP_PLAT_arm64_android) void Sample(); #else - explicit PowerCounters(){}; - ~PowerCounters(){}; void Sample(){}; #endif - using CountVector = mozilla::Vector<BaseProfilerCount*, 4>; + using CountVector = mozilla::Vector<mozilla::UniquePtr<BaseProfilerCount>, 4>; const CountVector& GetCounters() { return mCounters; } private: @@ -41,11 +78,14 @@ class PowerCounters { #if defined(_MSC_VER) mozilla::Vector<mozilla::UniquePtr<PowerMeterDevice>> mPowerMeterDevices; #endif -#if defined(GP_PLAT_arm64_darwin) - mozilla::UniquePtr<ProcessPower> mProcessPower; -#endif #if defined(GP_PLAT_amd64_darwin) - RAPL* mRapl; + mozilla::UniquePtr<RAPL> mRapl; +#endif +#if defined(GP_PLAT_arm64_android) + void* mLibperfettoModule = nullptr; + decltype(&GetRailEnergyData) mGetRailEnergyData = nullptr; + mozilla::Vector<RailDescriptor> mRailDescriptors; + mozilla::Vector<RailEnergyData> mRailEnergyData; #endif }; diff --git a/tools/profiler/core/platform-linux-android.cpp b/tools/profiler/core/platform-linux-android.cpp index 11af93456c..8e93ca4e91 100644 --- a/tools/profiler/core/platform-linux-android.cpp +++ b/tools/profiler/core/platform-linux-android.cpp @@ -551,7 +551,11 @@ SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration, } SamplerThread::~SamplerThread() { - pthread_join(mThread, nullptr); + if (pthread_equal(mThread, pthread_self())) { + pthread_detach(mThread); + } else { + pthread_join(mThread, nullptr); + } // Just in the unlikely case some callbacks were added between the end of the // thread and now. InvokePostSamplingCallbacks(std::move(mPostSamplingCallbackList), diff --git a/tools/profiler/core/platform-macos.cpp b/tools/profiler/core/platform-macos.cpp index 78f000c470..ad0b0699f3 100644 --- a/tools/profiler/core/platform-macos.cpp +++ b/tools/profiler/core/platform-macos.cpp @@ -256,7 +256,11 @@ SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration, } SamplerThread::~SamplerThread() { - pthread_join(mThread, nullptr); + if (pthread_equal(mThread, pthread_self())) { + pthread_detach(mThread); + } else { + pthread_join(mThread, nullptr); + } // Just in the unlikely case some callbacks were added between the end of the // thread and now. InvokePostSamplingCallbacks(std::move(mPostSamplingCallbackList), diff --git a/tools/profiler/core/platform.cpp b/tools/profiler/core/platform.cpp index 8ce029402b..6b7e318f80 100644 --- a/tools/profiler/core/platform.cpp +++ b/tools/profiler/core/platform.cpp @@ -42,7 +42,12 @@ #include "ProfilerIOInterposeObserver.h" #include "ProfilerParent.h" #include "ProfilerRustBindings.h" +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" #include "mozilla/MozPromise.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsXPCOM.h" #include "shared-libraries.h" #include "VTuneProfiler.h" #include "ETWTools.h" @@ -95,6 +100,7 @@ #include "nsSystemInfo.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" +#include "nsDirectoryServiceUtils.h" #include "Tracing.h" #include "prdtoa.h" #include "prtime.h" @@ -108,6 +114,18 @@ #include <string_view> #include <type_traits> +// The signals that we use to control the profiler conflict with the signals +// used to control the code coverage tool. Therefore, if coverage is enabled, we +// need to disable our own signal handling mechanisms. +#ifndef MOZ_CODE_COVERAGE +# ifdef XP_WIN +// TODO: Add support for windows "signal"-like behaviour. See Bug 1867328. +# else +# include <signal.h> +# include <unistd.h> +# endif +#endif + #if defined(GP_OS_android) # include "JavaExceptions.h" # include "mozilla/java/GeckoJavaSamplerNatives.h" @@ -234,6 +252,10 @@ ProfileChunkedBuffer& profiler_get_core_buffer() { mozilla::Atomic<int, mozilla::MemoryOrdering::Relaxed> gSkipSampling; +// Atomic flag to stop the profiler from within the sampling loop +mozilla::Atomic<bool, mozilla::MemoryOrdering::Relaxed> gStopAndDumpFromSignal( + false); + #if defined(GP_OS_android) class GeckoJavaSampler : public java::GeckoJavaSampler::Natives<GeckoJavaSampler> { @@ -647,6 +669,9 @@ class CorePS { PS_GET_AND_SET(const nsACString&, ProcessName) PS_GET_AND_SET(const nsACString&, ETLDplus1) +#if !defined(XP_WIN) + PS_GET_AND_SET(const Maybe<nsCOMPtr<nsIFile>>&, DownloadDirectory) +#endif static void SetBandwidthCounter(ProfilerBandwidthCounter* aBandwidthCounter) { MOZ_ASSERT(sInstance); @@ -695,6 +720,11 @@ class CorePS { // lock, so it is safe to have only one instance allocated for all of the // threads. JsFrameBuffer mJsFrames; + + // Cached download directory for when we need to dump profiles to disk. +#if !defined(XP_WIN) + Maybe<nsCOMPtr<nsIFile>> mDownloadDirectory; +#endif }; CorePS* CorePS::sInstance = nullptr; @@ -839,7 +869,7 @@ class ActivePS { if (ProfilerFeature::HasPower(aFeatures)) { mMaybePowerCounters = new PowerCounters(); for (const auto& powerCounter : mMaybePowerCounters->GetCounters()) { - locked_profiler_add_sampled_counter(aLock, powerCounter); + locked_profiler_add_sampled_counter(aLock, powerCounter.get()); } } @@ -935,7 +965,7 @@ class ActivePS { if (sInstance->mMaybePowerCounters) { for (const auto& powerCounter : sInstance->mMaybePowerCounters->GetCounters()) { - locked_profiler_remove_sampled_counter(aLock, powerCounter); + locked_profiler_remove_sampled_counter(aLock, powerCounter.get()); } delete sInstance->mMaybePowerCounters; sInstance->mMaybePowerCounters = nullptr; @@ -1918,11 +1948,10 @@ static uint32_t ExtractJsFrames( // Merges the profiling stack, native stack, and JS stack, outputting the // details to aCollector. static void MergeStacks( - uint32_t aFeatures, bool aIsSynchronous, + bool aIsSynchronous, const ThreadRegistration::UnlockedReaderAndAtomicRWOnThread& aThreadData, - const Registers& aRegs, const NativeStack& aNativeStack, - ProfilerStackCollector& aCollector, JsFrame* aJsFrames, - uint32_t aJsFramesCount) { + const NativeStack& aNativeStack, ProfilerStackCollector& aCollector, + JsFrame* aJsFrames, uint32_t aJsFramesCount) { // WARNING: this function runs within the profiler's "critical section". // WARNING: this function might be called while the profiler is inactive, and // cannot rely on ActivePS. @@ -2571,13 +2600,13 @@ static inline void DoSharedSample( DoNativeBacktrace(aThreadData, aRegs, nativeStack, stackWalkControlIfSupported); - MergeStacks(aFeatures, aIsSynchronous, aThreadData, aRegs, nativeStack, - collector, aJsFrames, jsFramesCount); + MergeStacks(aIsSynchronous, aThreadData, nativeStack, collector, aJsFrames, + jsFramesCount); } else #endif { - MergeStacks(aFeatures, aIsSynchronous, aThreadData, aRegs, nativeStack, - collector, aJsFrames, jsFramesCount); + MergeStacks(aIsSynchronous, aThreadData, nativeStack, collector, aJsFrames, + jsFramesCount); // We can't walk the whole native stack, but we can record the top frame. if (aCaptureOptions == StackCaptureOptions::Full) { @@ -2933,16 +2962,6 @@ static void StreamMetaJSCustomObject( ActivePS::WriteActiveConfiguration(aLock, aWriter, MakeStringSpan("configuration")); - if (!NS_IsMainThread()) { - // Leave the rest of the properties out if we're not on the main thread. - // At the moment, the only case in which this function is called on a - // background thread is if we're in a content process and are going to - // send this profile to the parent process. In that case, the parent - // process profile's "meta" object already has the rest of the properties, - // and the parent process profile is dumped on that process's main thread. - return; - } - aWriter.DoubleProperty("interval", ActivePS::Interval(aLock)); aWriter.IntProperty("stackwalk", ActivePS::FeatureStackWalk(aLock)); @@ -3019,6 +3038,16 @@ static void StreamMetaJSCustomObject( } aWriter.EndObject(); + if (!NS_IsMainThread()) { + // Leave the rest of the properties out if we're not on the main thread. + // At the moment, the only case in which this function is called on a + // background thread is if we're in a content process and are going to + // send this profile to the parent process. In that case, the parent + // process profile's "meta" object already has the rest of the properties, + // and the parent process profile is dumped on that process's main thread. + return; + } + // We should avoid collecting extension metadata for profiler when there is no // observer service, since a ExtensionPolicyService could not be created then. if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) { @@ -4077,6 +4106,10 @@ static SamplerThread* NewSamplerThread(PSLockRef aLock, uint32_t aGeneration, return new SamplerThread(aLock, aGeneration, aInterval, aFeatures); } +// Forward declare the function to call when we need to dump + stop from within +// the sampler thread +void profiler_dump_and_stop(); + // This function is the sampler thread. This implementation is used for all // targets. void SamplerThread::Run() { @@ -4732,6 +4765,27 @@ void SamplerThread::Run() { scheduledSampleStart = beforeSleep + sampleInterval; SleepMicro(static_cast<uint32_t>(sampleInterval.ToMicroseconds())); } + + // Check to see if the hard-reset flag has been set to stop the profiler. + // This should only be used on the worst occasions when we need to stop the + // profiler from within the sampling thread (e.g. if the main thread is + // stuck) We need to do this here as it is outside of the scope of the lock. + // Otherwise we'll encounter a race condition where `profiler_stop` tries to + // get the lock that we already hold. We also need to wait until after we + // have carried out post sampling callbacks, as otherwise we may reach a + // situation where another part of the program is waiting for us to finish + // sampling, but we have ended early! + if (gStopAndDumpFromSignal) { + // Reset the flag in case we restart the profiler at a later point + gStopAndDumpFromSignal = false; + // dump the profile, and stop the profiler + profiler_dump_and_stop(); + // profiler_stop will try to destroy the active sampling thread. This will + // also destroy some data structures that are used further down this + // function, leading to invalid accesses. We therefore exit the function + // directly, rather than breaking from the loop. + return; + } } // End of `while` loop. We can only be here from a `break` inside the loop. @@ -4840,10 +4894,10 @@ void SamplerThread::SpyOnUnregisteredThreads() { /* aWindowInfo = */ nsTArray<WindowInfo>{}, /* aUtilityInfo = */ nsTArray<UtilityInfo>{}, /* aChild = */ 0 -#ifdef XP_MACOSX +#ifdef XP_DARWIN , /* aChildTask = */ MACH_PORT_NULL -#endif // XP_MACOSX +#endif // XP_DARWIN ); const ProcInfoPromise::ResolveOrRejectValue procInfoOrError = @@ -5240,6 +5294,104 @@ static const char* get_size_suffix(const char* str) { return ptr; } +#if !defined(XP_WIN) && !defined(MOZ_CODE_COVERAGE) +static void profiler_stop_signal_handler(int signal, siginfo_t* info, + void* context) { + // We cannot really do any logging here, as this is a signal handler. + // Signal handlers are limited in what functions they can call, for more + // details see: https://man7.org/linux/man-pages/man7/signal-safety.7.html + // Writing to a file is allowed, but as signal handlers are also limited in + // how long they can run, we instead set an atomic variable to true to trigger + // the sampling thread to stop and dump the data in the profiler. + gStopAndDumpFromSignal = true; +} +#endif + +// This may fail if we have previously had an issue finding the download +// directory, or if the directory has moved since we cached the path. +// This is non-ideal, but captured by Bug 1885000 +Maybe<nsAutoCString> profiler_find_dump_path() { +// Note, this is currently a posix-only implementation, as we currently have +// issues with fetching the download directory on Windows. See Bug 1890154. +#if defined(XP_WIN) + return Nothing(); +#else + Maybe<nsCOMPtr<nsIFile>> directory = Nothing(); + nsAutoCString path; + + { + // Acquire the lock so that we can get things from CorePS + PSAutoLock lock; + Maybe<nsCOMPtr<nsIFile>> downloadDir = Nothing(); + downloadDir = CorePS::DownloadDirectory(lock); + + // This needs to be done within the context of the lock, as otherwise + // another thread might modify CorePS::mDownloadDirectory while we're + // cloning the pointer. + if (downloadDir) { + nsCOMPtr<nsIFile> d; + downloadDir.value()->Clone(getter_AddRefs(d)); + directory = Some(d); + } else { + return Nothing(); + } + } + + // Now, we can check to see if we have a directory, and use it to construct + // the output file + if (directory) { + // Set up the name of our profile file + path.AppendPrintf("profile_%i_%i.json", XRE_GetProcessType(), getpid()); + + // Append it to the directory we found + nsresult rv = directory.value()->AppendNative(path); + if (NS_FAILED(rv)) { + LOG("Failed to append path to profile file"); + return Nothing(); + } + + // Write the result *back* to the original path + rv = directory.value()->GetNativePath(path); + if (NS_FAILED(rv)) { + LOG("Failed to get native path for temp path"); + return Nothing(); + } + + return Some(path); + } + + return Nothing(); +#endif +} + +void profiler_dump_and_stop() { + // pause the profiler until we are done dumping + profiler_pause(); + + // Try to save the profile to a file + if (auto path = profiler_find_dump_path()) { + profiler_save_profile_to_file(path.value().get()); + } else { + LOG("Failed to dump profile to disk"); + } + + // Stop the profiler + profiler_stop(); +} + +void profiler_init_signal_handlers() { +#if !defined(XP_WIN) && !defined(MOZ_CODE_COVERAGE) + // Set a handler to stop the profiler + struct sigaction prof_stop_sa {}; + memset(&prof_stop_sa, 0, sizeof(struct sigaction)); + prof_stop_sa.sa_sigaction = profiler_stop_signal_handler; + prof_stop_sa.sa_flags = SA_RESTART | SA_SIGINFO; + sigemptyset(&prof_stop_sa.sa_mask); + DebugOnly<int> rstop = sigaction(SIGUSR2, &prof_stop_sa, nullptr); + MOZ_ASSERT(rstop == 0, "Failed to install Profiler SIGUSR2 handler"); +#endif +} + void profiler_init(void* aStackTop) { LOG("profiler_init"); @@ -5289,10 +5441,12 @@ void profiler_init(void* aStackTop) { locked_register_thread(lock, offThreadRef); } } - // Platform-specific initialization. PlatformInit(lock); + // Initialise the signal handlers needed to start/stop the profiler + profiler_init_signal_handlers(); + #if defined(GP_OS_android) if (jni::IsAvailable()) { GeckoJavaSampler::Init(); @@ -6309,6 +6463,37 @@ bool profiler_is_paused() { return ActivePS::AppendPostSamplingCallback(lock, std::move(aCallback)); } +// See `ProfilerControl.h` for more details. +void profiler_lookup_download_directory() { +// This implementation is causing issues on Windows (see Bug 1890154) but as it +// only exists to support the posix signal handling (on non-windows platforms) +// we can remove it for now. +#if !defined(XP_WIN) + LOG("profiler_lookup_download_directory"); + + MOZ_ASSERT( + NS_IsMainThread(), + "We can only get access to the directory service from the main thread"); + + // Make sure the profiler is actually running~ + MOZ_RELEASE_ASSERT(CorePS::Exists()); + + // take the lock so that we can write to CorePS + PSAutoLock lock; + + nsCOMPtr<nsIFile> tDownloadDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_DEFAULT_DOWNLOAD_DIR, + getter_AddRefs(tDownloadDir)); + if (NS_FAILED(rv)) { + LOG("Failed to find download directory. Profiler signal handling will not " + "be able to save to disk. Error: %s", + GetStaticErrorName(rv)); + } else { + CorePS::SetDownloadDirectory(lock, Some(tDownloadDir)); + } +#endif +} + RefPtr<GenericPromise> profiler_pause() { LOG("profiler_pause"); @@ -7157,13 +7342,13 @@ static void profiler_suspend_and_sample_thread( # error "Invalid configuration" # endif - MergeStacks(aFeatures, !aLockIfAsynchronousSampling, aThreadData, aRegs, - nativeStack, aCollector, aJsFrames, jsFramesCount); + MergeStacks(!aLockIfAsynchronousSampling, aThreadData, nativeStack, + aCollector, aJsFrames, jsFramesCount); } else #endif { - MergeStacks(aFeatures, !aLockIfAsynchronousSampling, aThreadData, aRegs, - nativeStack, aCollector, aJsFrames, jsFramesCount); + MergeStacks(!aLockIfAsynchronousSampling, aThreadData, nativeStack, + aCollector, aJsFrames, jsFramesCount); aCollector.CollectNativeLeafAddr((void*)aRegs.mPC); } |