Skip to content

Commit 93441d7

Browse files
committed
Bug 2050049 - Add a profiler API to dump a profile off the main thread after a deadline, r=profiler-reviewers,mstange.
Add profiler_schedule_dump_to_file / profiler_cancel_scheduled_dump (exposed on nsIProfiler as scheduleDumpToFile / cancelScheduledDump), backed by a one-shot deadline the SamplerThread checks each tick and serializes off the main thread via profiler_save_profile_to_file. Unlike a main-thread timer this still fires when the main thread is wedged in a long synchronous run, so a caller can get a profile from a process that would otherwise be force-killed without one. Differential Revision: https://phabricator.services.mozilla.com/D308520
1 parent a9f3aea commit 93441d7

4 files changed

Lines changed: 119 additions & 0 deletions

File tree

tools/profiler/core/platform.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,28 @@ class CorePS {
990990
PS_GET_AND_SET(const Maybe<nsCOMPtr<nsIFile>>&, AsyncSignalDumpDirectory)
991991
#endif
992992

993+
// The scheduled off-main-thread profile dump deadline
994+
// (profiler_schedule_dump_to_file). A null deadline means none is scheduled.
995+
// Read by the sampler thread.
996+
static const TimeStamp& ScheduledDumpDeadline(PSLockRef) {
997+
MOZ_ASSERT(sInstance);
998+
return sInstance->mScheduledDumpDeadline;
999+
}
1000+
static const nsACString& ScheduledDumpPath(PSLockRef) {
1001+
MOZ_ASSERT(sInstance);
1002+
return sInstance->mScheduledDumpPath;
1003+
}
1004+
static void ScheduleDumpToFile(PSLockRef, const TimeStamp& aDeadline,
1005+
const nsACString& aPath) {
1006+
MOZ_ASSERT(sInstance);
1007+
sInstance->mScheduledDumpDeadline = aDeadline;
1008+
sInstance->mScheduledDumpPath = aPath;
1009+
}
1010+
static void CancelScheduledDump(PSLockRef) {
1011+
MOZ_ASSERT(sInstance);
1012+
sInstance->mScheduledDumpDeadline = TimeStamp{};
1013+
}
1014+
9931015
static void SetBandwidthCounter(ProfilerBandwidthCounter* aBandwidthCounter) {
9941016
MOZ_ASSERT(sInstance);
9951017

@@ -1043,6 +1065,12 @@ class CorePS {
10431065
// fission)
10441066
nsAutoCString mETLDplus1;
10451067

1068+
// A scheduled off-main-thread profile dump (profiler_schedule_dump_to_file):
1069+
// the deadline at which the sampler thread should write the profile, and the
1070+
// file to write it to. Null deadline when none is scheduled.
1071+
TimeStamp mScheduledDumpDeadline;
1072+
nsAutoCString mScheduledDumpPath;
1073+
10461074
// This memory buffer is used by the MergeStacks mechanism. Previously it was
10471075
// stack allocated, but this led to a stack overflow, as it was too much
10481076
// memory. Here the buffer can be pre-allocated, and shared with the
@@ -4664,6 +4692,12 @@ void SamplerThread::Run() {
46644692
// This will be set inside the loop, before invoking callbacks outside.
46654693
SamplingState samplingState{};
46664694

4695+
// Set inside the locked scope when a scheduled off-main-thread profile dump
4696+
// (profiler_schedule_dump_to_file) is due, then acted on outside the lock
4697+
// (profiler_save_profile_to_file takes the profiler lock itself).
4698+
bool scheduledDumpDue = false;
4699+
nsAutoCString scheduledDumpPath;
4700+
46674701
const TimeDuration sampleInterval =
46684702
TimeDuration::FromMicroseconds(mIntervalMicroseconds);
46694703
const uint32_t minimumIntervalSleepUs =
@@ -4737,6 +4771,13 @@ void SamplerThread::Run() {
47374771

47384772
ActivePS::ClearExpiredExitProfiles(lock);
47394773

4774+
if (const TimeStamp& deadline = CorePS::ScheduledDumpDeadline(lock);
4775+
!deadline.IsNull() && sampleStart >= deadline) {
4776+
scheduledDumpDue = true;
4777+
scheduledDumpPath = CorePS::ScheduledDumpPath(lock);
4778+
CorePS::CancelScheduledDump(lock);
4779+
}
4780+
47404781
TimeStamp expiredMarkersCleaned = TimeStamp::Now();
47414782

47424783
if (int(gSkipSampling) <= 0 && !ActivePS::IsSamplingPaused(lock)) {
@@ -5212,6 +5253,13 @@ void SamplerThread::Run() {
52125253
InvokePostSamplingCallbacks(std::move(postSamplingCallbacks),
52135254
samplingState);
52145255

5256+
// We've hit the deadline for a scheduled profile dump; call
5257+
// profiler_save_profile_to_file outside the lock to avoid a deadlock.
5258+
if (scheduledDumpDue) {
5259+
scheduledDumpDue = false;
5260+
profiler_save_profile_to_file(scheduledDumpPath.get());
5261+
}
5262+
52155263
ProfilerChild::ProcessPendingUpdate();
52165264

52175265
if (ProfilerFeature::HasUnregisteredThreads(features)) {
@@ -6605,6 +6653,30 @@ void profiler_save_profile_to_file(const char* aFilename) {
66056653
preRecordedMetaInformation);
66066654
}
66076655

6656+
void profiler_schedule_dump_to_file(double aDelaySeconds,
6657+
const char* aFilename) {
6658+
if (!aFilename || !CorePS::Exists()) {
6659+
return;
6660+
}
6661+
6662+
// Compute the deadline before grabbing the lock, which may already be held
6663+
// by another thread for a while.
6664+
TimeStamp deadline =
6665+
TimeStamp::Now() + TimeDuration::FromSeconds(aDelaySeconds);
6666+
6667+
PSAutoLock lock;
6668+
CorePS::ScheduleDumpToFile(lock, deadline, nsDependentCString(aFilename));
6669+
}
6670+
6671+
void profiler_cancel_scheduled_dump() {
6672+
if (!CorePS::Exists()) {
6673+
return;
6674+
}
6675+
6676+
PSAutoLock lock;
6677+
CorePS::CancelScheduledDump(lock);
6678+
}
6679+
66086680
void profiler_request_dump_and_quit_for_test(const nsACString& aReason) {
66096681
if (!profiler_is_active()) {
66106682
return;

tools/profiler/gecko/nsIProfiler.idl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,24 @@ interface nsIProfiler : nsISupports
127127
*/
128128
void dumpProfileToFile(in string aFilename);
129129

130+
/**
131+
* Schedule a one-shot profile dump performed off the main thread by the
132+
* sampler thread once `aDelaySeconds` have elapsed, writing this process's
133+
* profile to `aFilename` (a no-op if the profiler stops first).
134+
*
135+
* Unlike a main-thread timer, this still fires when the main thread is stuck
136+
* in a long synchronous run, so a test that would otherwise be force-killed
137+
* by the harness without a profile still leaves one. The harness that set the
138+
* deadline is responsible for reporting the written file. Call
139+
* `cancelScheduledDump` when the test ends normally.
140+
*/
141+
void scheduleDumpToFile(in double aDelaySeconds, in string aFilename);
142+
143+
/**
144+
* Cancel a dump scheduled by `scheduleDumpToFile`.
145+
*/
146+
void cancelScheduledDump();
147+
130148
boolean IsActive();
131149

132150
/**

tools/profiler/gecko/nsProfiler.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,18 @@ nsProfiler::DumpProfileToFile(const char* aFilename) {
354354
return NS_OK;
355355
}
356356

357+
NS_IMETHODIMP
358+
nsProfiler::ScheduleDumpToFile(double aDelaySeconds, const char* aFilename) {
359+
profiler_schedule_dump_to_file(aDelaySeconds, aFilename);
360+
return NS_OK;
361+
}
362+
363+
NS_IMETHODIMP
364+
nsProfiler::CancelScheduledDump() {
365+
profiler_cancel_scheduled_dump();
366+
return NS_OK;
367+
}
368+
357369
NS_IMETHODIMP
358370
nsProfiler::GetProfileData(double aSinceTime, JSContext* aCx,
359371
JS::MutableHandle<JS::Value> aResult) {

tools/profiler/public/GeckoProfiler.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,23 @@ extern "C" {
319319
void profiler_save_profile_to_file(const char* aFilename);
320320
}
321321

322+
// Schedule a one-shot profile dump that the sampler thread performs off the
323+
// main thread once aDelaySeconds have elapsed, writing this process's profile
324+
// to aFilename (a no-op if the profiler stops first).
325+
//
326+
// This exists for the test harnesses: a test that wedges its main thread (a
327+
// long synchronous run that never returns to the event loop, or a blocking
328+
// native call) gets force-killed by the harness without leaving a profile,
329+
// because a main-thread timer can never fire. Dumping from the sampler thread
330+
// sidesteps that: profiler_save_profile_to_file only needs the profiler state
331+
// lock, and the sampled main-thread stack it captures shows where the time
332+
// went. The harness that set the deadline is responsible for reporting the
333+
// written file. Call profiler_cancel_scheduled_dump() when the test ends
334+
// normally.
335+
void profiler_schedule_dump_to_file(double aDelaySeconds,
336+
const char* aFilename);
337+
void profiler_cancel_scheduled_dump();
338+
322339
//---------------------------------------------------------------------------
323340
// RAII classes
324341
//---------------------------------------------------------------------------

0 commit comments

Comments
 (0)