summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/profiler/core/MicroGeckoProfiler.cpp33
-rw-r--r--tools/profiler/core/PowerCounters-android.cpp178
-rw-r--r--tools/profiler/core/PowerCounters-linux.cpp9
-rw-r--r--tools/profiler/core/PowerCounters-mac-amd64.cpp20
-rw-r--r--tools/profiler/core/PowerCounters-mac-arm64.cpp10
-rw-r--r--tools/profiler/core/PowerCounters-win.cpp8
-rw-r--r--tools/profiler/core/PowerCounters.h56
-rw-r--r--tools/profiler/core/platform-linux-android.cpp6
-rw-r--r--tools/profiler/core/platform-macos.cpp6
-rw-r--r--tools/profiler/core/platform.cpp239
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);
}