summaryrefslogtreecommitdiffstats
path: root/tools/profiler
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:34:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:34:42 +0000
commitda4c7e7ed675c3bf405668739c3012d140856109 (patch)
treecdd868dba063fecba609a1d819de271f0d51b23e /tools/profiler
parentAdding upstream version 125.0.3. (diff)
downloadfirefox-da4c7e7ed675c3bf405668739c3012d140856109.tar.xz
firefox-da4c7e7ed675c3bf405668739c3012d140856109.zip
Adding upstream version 126.0.upstream/126.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/profiler')
-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
-rw-r--r--tools/profiler/moz.build4
-rw-r--r--tools/profiler/public/MicroGeckoProfiler.h65
-rw-r--r--tools/profiler/public/ProfilerControl.h14
-rw-r--r--tools/profiler/tests/xpcshell/test_feature_posix_signals.js194
-rw-r--r--tools/profiler/tests/xpcshell/xpcshell.toml12
15 files changed, 746 insertions, 108 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);
}
diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build
index ddb2ce5fff..4d2bf3628f 100644
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -85,6 +85,10 @@ if CONFIG["MOZ_GECKO_PROFILER"]:
UNIFIED_SOURCES += [
"core/PowerCounters-linux.cpp",
]
+ elif CONFIG["TARGET_CPU"] == "aarch64" and CONFIG["OS_TARGET"] == "Android":
+ SOURCES += [
+ "core/PowerCounters-android.cpp",
+ ]
if CONFIG["TARGET_CPU"] == "arm" and CONFIG["OS_TARGET"] != "FreeBSD":
SOURCES += [
"core/EHABIStackWalk.cpp",
diff --git a/tools/profiler/public/MicroGeckoProfiler.h b/tools/profiler/public/MicroGeckoProfiler.h
index 7b735e1eec..c23142f07f 100644
--- a/tools/profiler/public/MicroGeckoProfiler.h
+++ b/tools/profiler/public/MicroGeckoProfiler.h
@@ -34,6 +34,10 @@ extern MOZ_EXPORT void uprofiler_unregister_thread();
extern MOZ_EXPORT 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);
+
+extern MOZ_EXPORT 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);
#ifdef __cplusplus
}
@@ -60,6 +64,10 @@ struct UprofilerFuncPtrs {
const char** arg_names,
const unsigned char* arg_types,
const unsigned long long* arg_values);
+ void (*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);
};
#pragma GCC diagnostic push
@@ -77,6 +85,12 @@ static void simple_event_marker_noop(const char* name, char phase, int num_args,
/* no-op */
}
+static void simple_event_marker_with_stack_noop(
+ const char* name, char phase, int num_args, const char** arg_names,
+ const unsigned char* arg_types, const unsigned long long* arg_values) {
+ /* no-op */
+}
+
#pragma GCC diagnostic pop
#if defined(_WIN32)
@@ -88,7 +102,7 @@ static void simple_event_marker_noop(const char* name, char phase, int num_args,
#if defined(_WIN32)
# define UPROFILER_GET_SYM(handle, sym) GetProcAddress(handle, sym)
#else
-# define UPROFILER_GET_SYM(handle, sym) dlsym(handle, sym)
+# define UPROFILER_GET_SYM(handle, sym) (typeof(sym)*)(dlsym(handle, #sym))
#endif
#if defined(_WIN32)
@@ -98,33 +112,30 @@ static void simple_event_marker_noop(const char* name, char phase, int num_args,
fprintf(stderr, "%s error: %s\n", #func, dlerror());
#endif
+#define FETCH(func) \
+ uprofiler.func = UPROFILER_GET_SYM(handle, uprofiler_##func); \
+ if (!uprofiler.func) { \
+ UPROFILER_PRINT_ERROR(uprofiler_##func); \
+ uprofiler.func = func##_noop; \
+ }
+
+#define UPROFILER_VISIT() \
+ FETCH(register_thread) \
+ FETCH(unregister_thread) \
+ FETCH(simple_event_marker) \
+ FETCH(simple_event_marker_with_stack)
+
// Assumes that a variable of type UprofilerFuncPtrs, named uprofiler
// is accessible in the scope
-#define UPROFILER_GET_FUNCTIONS() \
- void* handle = UPROFILER_OPENLIB(); \
- if (!handle) { \
- UPROFILER_PRINT_ERROR(UPROFILER_OPENLIB); \
- uprofiler.register_thread = register_thread_noop; \
- uprofiler.unregister_thread = unregister_thread_noop; \
- uprofiler.simple_event_marker = simple_event_marker_noop; \
- } \
- uprofiler.register_thread = \
- UPROFILER_GET_SYM(handle, "uprofiler_register_thread"); \
- if (!uprofiler.register_thread) { \
- UPROFILER_PRINT_ERROR(uprofiler_unregister_thread); \
- uprofiler.register_thread = register_thread_noop; \
- } \
- uprofiler.unregister_thread = \
- UPROFILER_GET_SYM(handle, "uprofiler_unregister_thread"); \
- if (!uprofiler.unregister_thread) { \
- UPROFILER_PRINT_ERROR(uprofiler_unregister_thread); \
- uprofiler.unregister_thread = unregister_thread_noop; \
- } \
- uprofiler.simple_event_marker = \
- UPROFILER_GET_SYM(handle, "uprofiler_simple_event_marker"); \
- if (!uprofiler.simple_event_marker) { \
- UPROFILER_PRINT_ERROR(uprofiler_simple_event_marker); \
- uprofiler.simple_event_marker = simple_event_marker_noop; \
- }
+#define UPROFILER_GET_FUNCTIONS() \
+ void* handle = UPROFILER_OPENLIB(); \
+ if (!handle) { \
+ UPROFILER_PRINT_ERROR(UPROFILER_OPENLIB); \
+ uprofiler.register_thread = register_thread_noop; \
+ uprofiler.unregister_thread = unregister_thread_noop; \
+ uprofiler.simple_event_marker = simple_event_marker_noop; \
+ uprofiler.simple_event_marker_with_stack = simple_event_with_stack_noop; \
+ } \
+ UPROFILER_VISIT()
#endif // MICRO_GECKO_PROFILER
diff --git a/tools/profiler/public/ProfilerControl.h b/tools/profiler/public/ProfilerControl.h
index 466d15eb69..ac145fac00 100644
--- a/tools/profiler/public/ProfilerControl.h
+++ b/tools/profiler/public/ProfilerControl.h
@@ -40,6 +40,8 @@ static inline void profiler_init(void* stackTop) {}
static inline void profiler_shutdown(
IsFastShutdown aIsFastShutdown = IsFastShutdown::No) {}
+static inline void profiler_lookup_download_directory() {}
+
#else // !MOZ_GECKO_PROFILER
# include "BaseProfiler.h"
@@ -123,6 +125,18 @@ void profiler_ensure_started(
const char** aFilters, uint32_t aFilterCount, uint64_t aActiveTabID,
const mozilla::Maybe<double>& aDuration = mozilla::Nothing());
+// Tell the profiler to look up the download directory for writing profiles.
+// With some features, such as signal control, we need to know the location of
+// a directory where we can save profiles to disk. Because we start the
+// profiler before we start the directory service, we can't access the
+// download directory at profiler startup. Similarly, when we need to get the
+// directory, we often can't, as we're running in non-main-thread contexts
+// that don't have access to the directory service. This function gives us a
+// third option, by giving us a hook to look for the download directory when
+// the time is right. This might be triggered internally (e.g. when we start
+// profiling), or externally, e.g. after the directory service is initialised.
+void profiler_lookup_download_directory();
+
//---------------------------------------------------------------------------
// Control the profiler
//---------------------------------------------------------------------------
diff --git a/tools/profiler/tests/xpcshell/test_feature_posix_signals.js b/tools/profiler/tests/xpcshell/test_feature_posix_signals.js
new file mode 100644
index 0000000000..28fbf890e8
--- /dev/null
+++ b/tools/profiler/tests/xpcshell/test_feature_posix_signals.js
@@ -0,0 +1,194 @@
+/* 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/. */
+
+ChromeUtils.defineESModuleGetters(this, {
+ Downloads: "resource://gre/modules/Downloads.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ BrowserTestUtils: "resource://testing-common/BrowserTestUtils.sys.mjs",
+});
+
+const { ctypes } = ChromeUtils.importESModule(
+ "resource://gre/modules/ctypes.sys.mjs"
+);
+
+// Derived from functionality in js/src/devtools/rootAnalysis/utility.js
+function openLibrary(names) {
+ for (const name of names) {
+ try {
+ return ctypes.open(name);
+ } catch (e) {}
+ }
+ return undefined;
+}
+
+// Derived heavily from equivalent sandbox testing code.
+// For more details see:
+// https://searchfox.org/mozilla-central/rev/1aaacaeb4fa3aca6837ecc157e43e947229ba8ce/security/sandbox/test/browser_content_sandbox_utils.js#89
+function raiseSignal(pid, sig) {
+ try {
+ const libc = openLibrary([
+ "libc.so.6",
+ "libc.so",
+ "libc.dylib",
+ "libSystem.B.dylib",
+ ]);
+ if (!libc) {
+ info("Failed to open any libc shared object");
+ return { ok: false };
+ }
+
+ // c.f. https://man7.org/linux/man-pages/man2/kill.2.html
+ // This choice of typing for `pid` is complex, and brittle, as it's
+ // platform dependent. Getting it wrong can result in incoreect
+ // generation/calling of the `kill` function. Unfortunately, as it's
+ // defined as `pid_t` in a header, we can't easily get access to it.
+ // For now, we just use an integer, and hope that the system int size
+ // aligns with the `pid_t` size.
+ const kill = libc.declare(
+ "kill",
+ ctypes.default_abi,
+ ctypes.int, // return value
+ ctypes.int32_t, // pid
+ ctypes.int // sig
+ );
+
+ let kres = kill(pid, sig);
+ if (kres != 0) {
+ info(`Kill returned a non-zero result ${kres}.`);
+ return { ok: false };
+ }
+
+ libc.close();
+ } catch (e) {
+ info(`Exception ${e} thrown while trying to call kill`);
+ return { ok: false };
+ }
+
+ return { ok: true };
+}
+
+// We would like to use the following to wait for a stop signal to actually be
+// handled:
+// await Services.profiler.waitOnePeriodicSampling();
+// However, as we are trying to shut down the profiler using the sampler
+// thread, this can cause complications between the callback waiting for the
+// sampling to be over, and the sampler thread actually finishing.
+// Instead, we use the BrowserTestUtils.waitForCondition to wait until the
+// profiler is no longer active.
+async function waitUntilProfilerStopped(interval = 1000, maxTries = 100) {
+ await BrowserTestUtils.waitForCondition(
+ () => !Services.profiler.IsActive(),
+ "the profiler should be inactive",
+ interval,
+ maxTries
+ );
+}
+
+async function cleanupAfterTest() {
+ // We need to cleanup written profiles after a test
+ // Get the system downloads directory, and use it to build a profile file
+ let profile = FileUtils.File(await Downloads.getSystemDownloadsDirectory());
+
+ // Get the process ID
+ let pid = Services.appinfo.processID;
+
+ // write it to the profile file name
+ profile.append(`profile_0_${pid}.json`);
+
+ // remove the file!
+ await IOUtils.remove(profile.path, { ignoreAbsent: true });
+
+ // Make sure the profiler is fully stopped, even if the test failed
+ await Services.profiler.StopProfiler();
+}
+
+// Hardcode the constants SIGUSR1 and SIGUSR2.
+// This is an absolutely terrible idea, as they are implementation defined!
+// However, it turns out that for 99% of the platforms we care about, and for
+// 99.999% of the platforms we test, these constants are, well, constant.
+// Additionally, these constants are only for _testing_ the signal handling
+// feature - the actual feature relies on platform specific definitions. This
+// may cause a mismatch if we test on on, say, a gnu hurd kernel, or on a
+// linux kernel running on sparc, but the feature will not break - only
+// the testing.
+// const SIGUSR1 = Services.appinfo.OS === "Darwin" ? 30 : 10;
+const SIGUSR2 = Services.appinfo.OS === "Darwin" ? 31 : 12;
+
+add_task(async () => {
+ info("Test that stopping the profiler with a posix signal works.");
+ registerCleanupFunction(cleanupAfterTest);
+
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler should not begin the test active."
+ );
+
+ const entries = 100;
+ const interval = 1;
+ const threads = [];
+ const features = [];
+
+ // Start the profiler, and ensure that it's active
+ await Services.profiler.StartProfiler(entries, interval, threads, features);
+ Assert.ok(Services.profiler.IsActive(), "The profiler should now be active.");
+
+ // Get the process ID
+ let pid = Services.appinfo.processID;
+
+ // Try and stop the profiler using a signal.
+ let result = raiseSignal(pid, SIGUSR2);
+ Assert.ok(result, "Raising a SIGUSR2 signal should succeed.");
+
+ await waitUntilProfilerStopped();
+
+ do_test_finished();
+});
+
+add_task(async () => {
+ info(
+ "Test that stopping the profiler with a posix signal writes a profile file to the system download directory."
+ );
+ registerCleanupFunction(cleanupAfterTest);
+
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler should not begin the test active."
+ );
+
+ const entries = 100;
+ const interval = 1;
+ const threads = [];
+ const features = [];
+
+ // Get the system downloads directory, and use it to build a profile file
+ let profile = FileUtils.File(await Downloads.getSystemDownloadsDirectory());
+
+ // Get the process ID
+ let pid = Services.appinfo.processID;
+
+ // use the pid to construct the name of the profile, and resulting file
+ profile.append(`profile_0_${pid}.json`);
+
+ // Start the profiler, and ensure that it's active
+ await Services.profiler.StartProfiler(entries, interval, threads, features);
+ Assert.ok(Services.profiler.IsActive(), "The profiler should now be active.");
+
+ // Try and stop the profiler using a signal.
+ let result = raiseSignal(pid, SIGUSR2);
+ Assert.ok(result, "Raising a SIGUSR2 signal should succeed.");
+
+ // Wait for the file to exist
+ await BrowserTestUtils.waitForCondition(
+ async () => await IOUtils.exists(profile.path),
+ "Waiting for a profile file to be written to disk."
+ );
+
+ await waitUntilProfilerStopped();
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler should now be inactive."
+ );
+
+ do_test_finished();
+});
diff --git a/tools/profiler/tests/xpcshell/xpcshell.toml b/tools/profiler/tests/xpcshell/xpcshell.toml
index 5c094899a4..2cde39d09f 100644
--- a/tools/profiler/tests/xpcshell/xpcshell.toml
+++ b/tools/profiler/tests/xpcshell/xpcshell.toml
@@ -24,9 +24,6 @@ skip-if = ["!debug"]
["test_feature_fileioall.js"]
skip-if = ["release_or_beta"]
-# The sanitizer checks appears to overwrite our own memory hooks in xpcshell tests,
-# and no allocation markers are gathered. Skip this test in that configuration.
-
["test_feature_java.js"]
skip-if = ["os != 'android'"]
@@ -50,6 +47,12 @@ skip-if = [
"socketprocess_networking",
]
+["test_feature_posix_signals.js"]
+skip-if = [
+ "ccov",
+ "os == 'win'",
+]
+
# Native stackwalking is somewhat unreliable depending on the platform.
#
# We don't have frame pointers on macOS release and beta, so stack walking does not
@@ -61,6 +64,9 @@ skip-if = [
# For sanitizer builds, there were many intermittents, and we're not getting much
# additional coverage there, so it's better to be a bit more reliable.
+# The sanitizer checks appears to overwrite our own memory hooks in xpcshell tests,
+# and no allocation markers are gathered. Skip this test in that configuration.
+
["test_feature_stackwalking.js"]
skip-if = [
"os == 'mac' && release_or_beta",