mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
Bug 1648507 - Distinguish pausing sampling only from pausing the whole profiler - r=canaltinova,perftest-reviewers,geckoview-reviewers,agi
The profiler can be "paused", which stops sampling, and since bug 1578329 stops markers as well. Some test suites use pausing between tests (to better differentiate the tests, to keep the profiler ready to run, and to lower the amount of recorded data). But this causes problems with some tracing markers, as their matching ends have not been recorded (e.g., an end marker is missing), which show up as very loooong markers. To solve this, we need to be able to pause sampling only, but keep recording markers. But we still need to be able to pause the whole profiler, in particular before capturing, to avoid recording anything around that time. This big patch is mostly mechanical changes: Wherever there are "Pause" and "Unpause/Resume" profiler functions, we add matching "PauseSampling" and "UnpauseSampling/ResumeSampling" functions that only impact the periodic sampling loop; And existing "Pause/Unpause/Resume" imply pausing sampling as well. Exceptions and extra work: - nsIProfiler (the JS API) already had `Pause/ResumeSampling()`, which misleadingly paused everything! Now they do the right thing, and we have `Pause/Resume()` as well. - All tests using `Pause/ResumeSampling()` now use `Pause/Resume()`, except for Talos tests that only pause sampling between tests; Added some extra `Pause()` calls to pause everything before capturing profiles. - GeckoJavaSampler doesn't handle pausing/resuming everything, this should be done in a follow-up bug. - Sampling-only pauses are not streamed into JSON. If needed, we should follow-up, with potential work on the front-end to deal with these. Differential Revision: https://phabricator.services.mozilla.com/D81492
This commit is contained in:
parent
d1626da38c
commit
56736f31c3
@ -191,7 +191,7 @@ async function captureProfile(pageContext) {
|
||||
|
||||
// Pause profiler before we collect the profile, so that we don't capture
|
||||
// more samples while the parent process waits for subprocess profiles.
|
||||
Services.profiler.PauseSampling();
|
||||
Services.profiler.Pause();
|
||||
|
||||
const profile = await Services.profiler
|
||||
.getProfileDataAsGzippedArrayBuffer()
|
||||
|
@ -453,7 +453,7 @@ function getActiveConfiguration() {
|
||||
|
||||
// Immediately pause the sampling, to make sure the test runs fast. The profiler
|
||||
// only needs to be started to initialize the configuration.
|
||||
Services.profiler.PauseSampling();
|
||||
Services.profiler.Pause();
|
||||
|
||||
const { activeConfiguration } = Services.profiler;
|
||||
if (!activeConfiguration) {
|
||||
|
@ -152,7 +152,7 @@ class ActorReadyGeckoProfilerInterface {
|
||||
|
||||
// Pause profiler before we collect the profile, so that we don't capture
|
||||
// more samples while the parent process or android threads wait for subprocess profiles.
|
||||
Services.profiler.PauseSampling();
|
||||
Services.profiler.Pause();
|
||||
|
||||
let profile;
|
||||
try {
|
||||
|
@ -408,7 +408,7 @@ public class GeckoJavaSampler {
|
||||
}
|
||||
|
||||
@WrapForJNI
|
||||
public static void pause() {
|
||||
public static void pauseSampling() {
|
||||
synchronized (GeckoJavaSampler.class) {
|
||||
sSamplingFuture.cancel(false /* mayInterruptIfRunning */ );
|
||||
sSamplingFuture = null;
|
||||
@ -416,7 +416,7 @@ public class GeckoJavaSampler {
|
||||
}
|
||||
|
||||
@WrapForJNI
|
||||
public static void unpause() {
|
||||
public static void unpauseSampling() {
|
||||
synchronized (GeckoJavaSampler.class) {
|
||||
if (sSamplingFuture != null) {
|
||||
return;
|
||||
|
@ -1251,6 +1251,8 @@ bool ProfileBuffer::DuplicateLastSample(int aThreadId,
|
||||
switch (e.Get().GetKind()) {
|
||||
case ProfileBufferEntry::Kind::Pause:
|
||||
case ProfileBufferEntry::Kind::Resume:
|
||||
case ProfileBufferEntry::Kind::PauseSampling:
|
||||
case ProfileBufferEntry::Kind::ResumeSampling:
|
||||
case ProfileBufferEntry::Kind::CollectionStart:
|
||||
case ProfileBufferEntry::Kind::CollectionEnd:
|
||||
case ProfileBufferEntry::Kind::ThreadId:
|
||||
|
@ -39,8 +39,10 @@ namespace baseprofiler {
|
||||
MACRO(ColumnNumber, int, sizeof(int)) \
|
||||
MACRO(NativeLeafAddr, void*, sizeof(void*)) \
|
||||
MACRO(Pause, double, sizeof(double)) \
|
||||
MACRO(Responsiveness, double, sizeof(double)) \
|
||||
MACRO(Resume, double, sizeof(double)) \
|
||||
MACRO(PauseSampling, double, sizeof(double)) \
|
||||
MACRO(ResumeSampling, double, sizeof(double)) \
|
||||
MACRO(Responsiveness, double, sizeof(double)) \
|
||||
MACRO(ThreadId, int, sizeof(int)) \
|
||||
MACRO(Time, double, sizeof(double)) \
|
||||
MACRO(CounterId, void*, sizeof(void*)) \
|
||||
|
@ -495,27 +495,27 @@ void SamplerThread::Stop(PSLockRef aLock) {
|
||||
// Unfortunately all this is only doable on non-Android because Bionic doesn't
|
||||
// have pthread_atfork.
|
||||
|
||||
// In the parent, before the fork, record IsPaused, and then pause.
|
||||
// In the parent, before the fork, record IsSamplingPaused, and then pause.
|
||||
static void paf_prepare() {
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
PSAutoLock lock;
|
||||
|
||||
if (ActivePS::Exists(lock)) {
|
||||
ActivePS::SetWasPaused(lock, ActivePS::IsPaused(lock));
|
||||
ActivePS::SetIsPaused(lock, true);
|
||||
ActivePS::SetWasSamplingPaused(lock, ActivePS::IsSamplingPaused(lock));
|
||||
ActivePS::SetIsSamplingPaused(lock, true);
|
||||
}
|
||||
}
|
||||
|
||||
// In the parent, after the fork, return IsPaused to the pre-fork state.
|
||||
// In the parent, after the fork, return IsSamplingPaused to the pre-fork state.
|
||||
static void paf_parent() {
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
PSAutoLock lock;
|
||||
|
||||
if (ActivePS::Exists(lock)) {
|
||||
ActivePS::SetIsPaused(lock, ActivePS::WasPaused(lock));
|
||||
ActivePS::SetWasPaused(lock, false);
|
||||
ActivePS::SetIsSamplingPaused(lock, ActivePS::WasSamplingPaused(lock));
|
||||
ActivePS::SetWasSamplingPaused(lock, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -671,13 +671,12 @@ class ActivePS {
|
||||
// The new sampler thread doesn't start sampling immediately because the
|
||||
// main loop within Run() is blocked until this function's caller
|
||||
// unlocks gPSMutex.
|
||||
mSamplerThread(NewSamplerThread(aLock, mGeneration, aInterval))
|
||||
#undef HAS_FEATURE
|
||||
,
|
||||
mIsPaused(false)
|
||||
mSamplerThread(NewSamplerThread(aLock, mGeneration, aInterval)),
|
||||
mIsPaused(false),
|
||||
mIsSamplingPaused(false)
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_freebsd)
|
||||
,
|
||||
mWasPaused(false)
|
||||
mWasSamplingPaused(false)
|
||||
#endif
|
||||
{
|
||||
// Deep copy aFilters.
|
||||
@ -923,8 +922,20 @@ class ActivePS {
|
||||
|
||||
PS_GET_AND_SET(bool, IsPaused)
|
||||
|
||||
// True if sampling is paused (though generic `SetIsPaused()` or specific
|
||||
// `SetIsSamplingPaused()`).
|
||||
static bool IsSamplingPaused(PSLockRef lock) {
|
||||
MOZ_ASSERT(sInstance);
|
||||
return IsPaused(lock) || sInstance->mIsSamplingPaused;
|
||||
}
|
||||
|
||||
static void SetIsSamplingPaused(PSLockRef, bool aIsSamplingPaused) {
|
||||
MOZ_ASSERT(sInstance);
|
||||
sInstance->mIsSamplingPaused = aIsSamplingPaused;
|
||||
}
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_freebsd)
|
||||
PS_GET_AND_SET(bool, WasPaused)
|
||||
PS_GET_AND_SET(bool, WasSamplingPaused)
|
||||
#endif
|
||||
|
||||
static void DiscardExpiredDeadProfiledThreads(PSLockRef) {
|
||||
@ -1075,13 +1086,16 @@ class ActivePS {
|
||||
// can destroy it.
|
||||
SamplerThread* const mSamplerThread;
|
||||
|
||||
// Is the profiler paused?
|
||||
// Is the profiler fully paused?
|
||||
bool mIsPaused;
|
||||
|
||||
// Is the profiler periodic sampling paused?
|
||||
bool mIsSamplingPaused;
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_freebsd)
|
||||
// Used to record whether the profiler was paused just before forking. False
|
||||
// Used to record whether the sampler was paused just before forking. False
|
||||
// at all times except just before/after forking.
|
||||
bool mWasPaused;
|
||||
bool mWasSamplingPaused;
|
||||
#endif
|
||||
|
||||
struct ExitProfile {
|
||||
@ -1117,6 +1131,14 @@ void RacyFeatures::SetPaused() { sActiveAndFeatures |= Paused; }
|
||||
/* static */
|
||||
void RacyFeatures::SetUnpaused() { sActiveAndFeatures &= ~Paused; }
|
||||
|
||||
/* static */
|
||||
void RacyFeatures::SetSamplingPaused() { sActiveAndFeatures |= SamplingPaused; }
|
||||
|
||||
/* static */
|
||||
void RacyFeatures::SetSamplingUnpaused() {
|
||||
sActiveAndFeatures &= ~SamplingPaused;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool RacyFeatures::IsActiveWithFeature(uint32_t aFeature) {
|
||||
uint32_t af = sActiveAndFeatures; // copy it first
|
||||
@ -1129,6 +1151,12 @@ bool RacyFeatures::IsActiveAndUnpaused() {
|
||||
return (af & Active) && !(af & Paused);
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool RacyFeatures::IsActiveAndSamplingUnpaused() {
|
||||
uint32_t af = sActiveAndFeatures; // copy it first
|
||||
return (af & Active) && !(af & (Paused | SamplingPaused));
|
||||
}
|
||||
|
||||
// Each live thread has a RegisteredThread, and we store a reference to it in
|
||||
// TLS. This class encapsulates that TLS.
|
||||
class TLSRegisteredThread {
|
||||
@ -2231,7 +2259,7 @@ void SamplerThread::Run() {
|
||||
|
||||
TimeStamp expiredMarkersCleaned = TimeStamp::NowUnfuzzed();
|
||||
|
||||
if (!ActivePS::IsPaused(lock)) {
|
||||
if (!ActivePS::IsSamplingPaused(lock)) {
|
||||
TimeDuration delta = sampleStart - CorePS::ProcessStartTime();
|
||||
ProfileBuffer& buffer = ActivePS::Buffer(lock);
|
||||
|
||||
@ -3206,6 +3234,56 @@ void profiler_resume() {
|
||||
}
|
||||
}
|
||||
|
||||
bool profiler_is_sampling_paused() {
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
PSAutoLock lock;
|
||||
|
||||
if (!ActivePS::Exists(lock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ActivePS::IsSamplingPaused(lock);
|
||||
}
|
||||
|
||||
void profiler_pause_sampling() {
|
||||
LOG("profiler_pause_sampling");
|
||||
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
{
|
||||
PSAutoLock lock;
|
||||
|
||||
if (!ActivePS::Exists(lock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RacyFeatures::SetSamplingPaused();
|
||||
ActivePS::SetIsSamplingPaused(lock, true);
|
||||
ActivePS::Buffer(lock).AddEntry(
|
||||
ProfileBufferEntry::PauseSampling(profiler_time()));
|
||||
}
|
||||
}
|
||||
|
||||
void profiler_resume_sampling() {
|
||||
LOG("profiler_resume_sampling");
|
||||
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
{
|
||||
PSAutoLock lock;
|
||||
|
||||
if (!ActivePS::Exists(lock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActivePS::Buffer(lock).AddEntry(
|
||||
ProfileBufferEntry::ResumeSampling(profiler_time()));
|
||||
ActivePS::SetIsSamplingPaused(lock, false);
|
||||
RacyFeatures::SetSamplingUnpaused();
|
||||
}
|
||||
}
|
||||
|
||||
bool profiler_feature_active(uint32_t aFeature) {
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
|
@ -197,19 +197,31 @@ class RacyFeatures {
|
||||
|
||||
MFBT_API static void SetUnpaused();
|
||||
|
||||
MFBT_API static void SetSamplingPaused();
|
||||
|
||||
MFBT_API static void SetSamplingUnpaused();
|
||||
|
||||
MFBT_API static bool IsActive();
|
||||
|
||||
MFBT_API static bool IsActiveWithFeature(uint32_t aFeature);
|
||||
|
||||
// True if profiler is active, and not fully paused.
|
||||
// Note that periodic sampling *could* be paused!
|
||||
MFBT_API static bool IsActiveAndUnpaused();
|
||||
|
||||
// True if profiler is active, and sampling is not paused (though generic
|
||||
// `SetPaused()` or specific `SetSamplingPaused()`).
|
||||
MFBT_API static bool IsActiveAndSamplingUnpaused();
|
||||
|
||||
private:
|
||||
static constexpr uint32_t Active = 1u << 31;
|
||||
static constexpr uint32_t Paused = 1u << 30;
|
||||
static constexpr uint32_t SamplingPaused = 1u << 29;
|
||||
|
||||
// Ensure Active/Paused don't overlap with any of the feature bits.
|
||||
# define NO_OVERLAP(n_, str_, Name_, desc_) \
|
||||
static_assert(ProfilerFeature::Name_ != Paused, "bad feature value");
|
||||
# define NO_OVERLAP(n_, str_, Name_, desc_) \
|
||||
static_assert(ProfilerFeature::Name_ != SamplingPaused, \
|
||||
"bad feature value");
|
||||
|
||||
BASE_PROFILER_FOR_EACH_FEATURE(NO_OVERLAP);
|
||||
|
||||
@ -357,12 +369,16 @@ MFBT_API void profiler_remove_sampled_counter(BaseProfilerCount* aCounter);
|
||||
// Pause and resume the profiler. No-ops if the profiler is inactive. While
|
||||
// paused the profile will not take any samples and will not record any data
|
||||
// into its buffers. The profiler remains fully initialized in this state.
|
||||
// Timeline markers will still be stored. This feature will keep JavaScript
|
||||
// profiling enabled, thus allowing toggling the profiler without invalidating
|
||||
// the JIT.
|
||||
// This feature will keep JavaScript profiling enabled, thus allowing toggling
|
||||
// the profiler without invalidating the JIT.
|
||||
MFBT_API void profiler_pause();
|
||||
MFBT_API void profiler_resume();
|
||||
|
||||
// Only pause and resume the periodic sampling loop, including stack sampling,
|
||||
// counters, and profiling overheads.
|
||||
MFBT_API void profiler_pause_sampling();
|
||||
MFBT_API void profiler_resume_sampling();
|
||||
|
||||
// These functions tell the profiler that a thread went to sleep so that we can
|
||||
// avoid sampling it while it's sleeping. Calling profiler_thread_sleep()
|
||||
// twice without an intervening profiler_thread_wake() is an error. All three
|
||||
@ -421,6 +437,10 @@ inline bool profiler_thread_is_being_profiled() {
|
||||
// Is the profiler active and paused? Returns false if the profiler is inactive.
|
||||
MFBT_API bool profiler_is_paused();
|
||||
|
||||
// Is the profiler active and sampling is paused? Returns false if the profiler
|
||||
// is inactive.
|
||||
MFBT_API bool profiler_is_sampling_paused();
|
||||
|
||||
// Is the current thread sleeping?
|
||||
MFBT_API bool profiler_thread_is_sleeping();
|
||||
|
||||
|
@ -98,13 +98,12 @@ var Profiler;
|
||||
["js", "leaf", "stackwalk", "threads"],
|
||||
profiler_threadsArray
|
||||
);
|
||||
if (_profiler.PauseSampling) {
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
},
|
||||
finishTest: function Profiler__finishTest() {
|
||||
if (_profiler && enabled) {
|
||||
_profiler.Pause();
|
||||
_profiler.dumpProfileToFile(
|
||||
profiler_dir + "/" + currentTest + ".profile"
|
||||
);
|
||||
@ -159,6 +158,7 @@ var Profiler;
|
||||
},
|
||||
finishStartupProfiling: function Profiler__finishStartupProfiling() {
|
||||
if (_profiler && enabled) {
|
||||
_profiler.Pause();
|
||||
_profiler.dumpProfileToFile(profiler_dir + "/startup.profile");
|
||||
_profiler.StopProfiler();
|
||||
}
|
||||
@ -178,9 +178,7 @@ var Profiler;
|
||||
ChromeUtils.addProfilerMarker(
|
||||
explicit ? name : 'End of test "' + (name || test_name) + '"'
|
||||
);
|
||||
if (_profiler.PauseSampling) {
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
},
|
||||
mark: function Profiler__mark(marker, explicit) {
|
||||
|
@ -139,6 +139,7 @@ TalosPowersService.prototype = {
|
||||
*/
|
||||
profilerFinish(profileFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Services.profiler.Pause();
|
||||
Services.profiler.getProfileDataAsync().then(
|
||||
profile => {
|
||||
let encoder = new TextEncoder();
|
||||
|
@ -97,13 +97,12 @@ var Profiler;
|
||||
["js", "leaf", "stackwalk", "threads"],
|
||||
profiler_threadsArray
|
||||
);
|
||||
if (_profiler.PauseSampling) {
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
},
|
||||
finishTest: function Profiler__finishTest() {
|
||||
if (_profiler && enabled) {
|
||||
_profiler.Pause();
|
||||
_profiler.dumpProfileToFile(
|
||||
profiler_dir + "/" + currentTest + ".profile"
|
||||
);
|
||||
@ -118,9 +117,7 @@ var Profiler;
|
||||
},
|
||||
resume: function Profiler__resume(name, explicit) {
|
||||
if (_profiler) {
|
||||
if (_profiler.ResumeSampling) {
|
||||
_profiler.ResumeSampling();
|
||||
}
|
||||
_profiler.ResumeSampling();
|
||||
ChromeUtils.addProfilerMarker(
|
||||
explicit ? name : 'Start of test "' + (name || test_name) + '"'
|
||||
);
|
||||
@ -131,9 +128,7 @@ var Profiler;
|
||||
ChromeUtils.addProfilerMarker(
|
||||
explicit ? name : 'End of test "' + (name || test_name) + '"'
|
||||
);
|
||||
if (_profiler.PauseSampling) {
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
},
|
||||
mark: function Profiler__mark(marker, explicit) {
|
||||
|
@ -97,13 +97,12 @@ var Profiler;
|
||||
["js", "leaf", "stackwalk", "threads"],
|
||||
profiler_threadsArray
|
||||
);
|
||||
if (_profiler.PauseSampling) {
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
},
|
||||
finishTest: function Profiler__finishTest() {
|
||||
if (_profiler && enabled) {
|
||||
_profiler.Pause();
|
||||
_profiler.dumpProfileToFile(
|
||||
profiler_dir + "/" + currentTest + ".profile"
|
||||
);
|
||||
@ -112,15 +111,14 @@ var Profiler;
|
||||
},
|
||||
finishStartupProfiling: function Profiler__finishStartupProfiling() {
|
||||
if (_profiler && enabled) {
|
||||
_profiler.Pause();
|
||||
_profiler.dumpProfileToFile(profiler_dir + "/startup.profile");
|
||||
_profiler.StopProfiler();
|
||||
}
|
||||
},
|
||||
resume: function Profiler__resume(name, explicit) {
|
||||
if (_profiler) {
|
||||
if (_profiler.ResumeSampling) {
|
||||
_profiler.ResumeSampling();
|
||||
}
|
||||
_profiler.ResumeSampling();
|
||||
ChromeUtils.addProfilerMarker(
|
||||
explicit ? name : 'Start of test "' + (name || test_name) + '"'
|
||||
);
|
||||
@ -131,9 +129,7 @@ var Profiler;
|
||||
ChromeUtils.addProfilerMarker(
|
||||
explicit ? name : 'End of test "' + (name || test_name) + '"'
|
||||
);
|
||||
if (_profiler.PauseSampling) {
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
_profiler.PauseSampling();
|
||||
}
|
||||
},
|
||||
mark: function Profiler__mark(marker, explicit) {
|
||||
|
@ -122,11 +122,11 @@ this.geckoProfiler = class extends ExtensionAPI {
|
||||
},
|
||||
|
||||
async pause() {
|
||||
Services.profiler.PauseSampling();
|
||||
Services.profiler.Pause();
|
||||
},
|
||||
|
||||
async resume() {
|
||||
Services.profiler.ResumeSampling();
|
||||
Services.profiler.Resume();
|
||||
},
|
||||
|
||||
async dumpProfileToFile(fileName) {
|
||||
|
@ -1627,6 +1627,8 @@ bool ProfileBuffer::DuplicateLastSample(int aThreadId,
|
||||
switch (e.Get().GetKind()) {
|
||||
case ProfileBufferEntry::Kind::Pause:
|
||||
case ProfileBufferEntry::Kind::Resume:
|
||||
case ProfileBufferEntry::Kind::PauseSampling:
|
||||
case ProfileBufferEntry::Kind::ResumeSampling:
|
||||
case ProfileBufferEntry::Kind::CollectionStart:
|
||||
case ProfileBufferEntry::Kind::CollectionEnd:
|
||||
case ProfileBufferEntry::Kind::ThreadId:
|
||||
|
@ -39,6 +39,8 @@ class ProfilerCodeAddressService;
|
||||
MACRO(NativeLeafAddr, void*, sizeof(void*)) \
|
||||
MACRO(Pause, double, sizeof(double)) \
|
||||
MACRO(Resume, double, sizeof(double)) \
|
||||
MACRO(PauseSampling, double, sizeof(double)) \
|
||||
MACRO(ResumeSampling, double, sizeof(double)) \
|
||||
MACRO(ThreadId, int, sizeof(int)) \
|
||||
MACRO(Time, double, sizeof(double)) \
|
||||
MACRO(TimeBeforeCompactStack, double, sizeof(double)) \
|
||||
|
@ -503,27 +503,27 @@ void SamplerThread::Stop(PSLockRef aLock) {
|
||||
// Unfortunately all this is only doable on non-Android because Bionic doesn't
|
||||
// have pthread_atfork.
|
||||
|
||||
// In the parent, before the fork, record IsPaused, and then pause.
|
||||
// In the parent, before the fork, record IsSamplingPaused, and then pause.
|
||||
static void paf_prepare() {
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (ActivePS::Exists(lock)) {
|
||||
ActivePS::SetWasPaused(lock, ActivePS::IsPaused(lock));
|
||||
ActivePS::SetIsPaused(lock, true);
|
||||
ActivePS::SetWasSamplingPaused(lock, ActivePS::IsSamplingPaused(lock));
|
||||
ActivePS::SetIsSamplingPaused(lock, true);
|
||||
}
|
||||
}
|
||||
|
||||
// In the parent, after the fork, return IsPaused to the pre-fork state.
|
||||
// In the parent, after the fork, return IsSamplingPaused to the pre-fork state.
|
||||
static void paf_parent() {
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (ActivePS::Exists(lock)) {
|
||||
ActivePS::SetIsPaused(lock, ActivePS::WasPaused(lock));
|
||||
ActivePS::SetWasPaused(lock, false);
|
||||
ActivePS::SetIsSamplingPaused(lock, ActivePS::WasSamplingPaused(lock));
|
||||
ActivePS::SetWasSamplingPaused(lock, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -812,10 +812,11 @@ class ActivePS {
|
||||
ProfilerFeature::HasFileIOAll(aFeatures))
|
||||
? new ProfilerIOInterposeObserver()
|
||||
: nullptr),
|
||||
mIsPaused(false)
|
||||
mIsPaused(false),
|
||||
mIsSamplingPaused(false)
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_freebsd)
|
||||
,
|
||||
mWasPaused(false)
|
||||
mWasSamplingPaused(false)
|
||||
#endif
|
||||
{
|
||||
// Deep copy aFilters.
|
||||
@ -1182,8 +1183,20 @@ class ActivePS {
|
||||
|
||||
PS_GET_AND_SET(bool, IsPaused)
|
||||
|
||||
// True if sampling is paused (though generic `SetIsPaused()` or specific
|
||||
// `SetIsSamplingPaused()`).
|
||||
static bool IsSamplingPaused(PSLockRef lock) {
|
||||
MOZ_ASSERT(sInstance);
|
||||
return IsPaused(lock) || sInstance->mIsSamplingPaused;
|
||||
}
|
||||
|
||||
static void SetIsSamplingPaused(PSLockRef, bool aIsSamplingPaused) {
|
||||
MOZ_ASSERT(sInstance);
|
||||
sInstance->mIsSamplingPaused = aIsSamplingPaused;
|
||||
}
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_freebsd)
|
||||
PS_GET_AND_SET(bool, WasPaused)
|
||||
PS_GET_AND_SET(bool, WasSamplingPaused)
|
||||
#endif
|
||||
|
||||
static void DiscardExpiredDeadProfiledThreads(PSLockRef) {
|
||||
@ -1388,13 +1401,16 @@ class ActivePS {
|
||||
// The interposer that records main thread I/O.
|
||||
RefPtr<ProfilerIOInterposeObserver> mInterposeObserver;
|
||||
|
||||
// Is the profiler paused?
|
||||
// Is the profiler fully paused?
|
||||
bool mIsPaused;
|
||||
|
||||
// Is the profiler periodic sampling paused?
|
||||
bool mIsSamplingPaused;
|
||||
|
||||
#if defined(GP_OS_linux) || defined(GP_OS_freebsd)
|
||||
// Used to record whether the profiler was paused just before forking. False
|
||||
// Used to record whether the sampler was paused just before forking. False
|
||||
// at all times except just before/after forking.
|
||||
bool mWasPaused;
|
||||
bool mWasSamplingPaused;
|
||||
#endif
|
||||
|
||||
// Optional startup profile thread array from BaseProfiler.
|
||||
@ -3210,7 +3226,7 @@ void SamplerThread::Run() {
|
||||
|
||||
TimeStamp expiredMarkersCleaned = TimeStamp::NowUnfuzzed();
|
||||
|
||||
if (!ActivePS::IsPaused(lock)) {
|
||||
if (!ActivePS::IsSamplingPaused(lock)) {
|
||||
TimeDuration delta = sampleStart - CorePS::ProcessStartTime();
|
||||
ProfileBuffer& buffer = ActivePS::Buffer(lock);
|
||||
|
||||
@ -4762,15 +4778,17 @@ void profiler_pause() {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
if (ActivePS::FeatureJava(lock) && !ActivePS::IsSamplingPaused(lock)) {
|
||||
// Not paused yet, so this is the first pause, let Java know.
|
||||
// TODO: Distinguish Pause and PauseSampling in Java.
|
||||
java::GeckoJavaSampler::PauseSampling();
|
||||
}
|
||||
#endif
|
||||
|
||||
RacyFeatures::SetPaused();
|
||||
ActivePS::SetIsPaused(lock, true);
|
||||
ActivePS::Buffer(lock).AddEntry(ProfileBufferEntry::Pause(profiler_time()));
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
if (ActivePS::FeatureJava(lock)) {
|
||||
java::GeckoJavaSampler::Pause();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// gPSMutex must be unlocked when we notify, to avoid potential deadlocks.
|
||||
@ -4796,8 +4814,10 @@ void profiler_resume() {
|
||||
RacyFeatures::SetUnpaused();
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
if (ActivePS::FeatureJava(lock)) {
|
||||
java::GeckoJavaSampler::Unpause();
|
||||
if (ActivePS::FeatureJava(lock) && !ActivePS::IsSamplingPaused(lock)) {
|
||||
// Not paused anymore, so this is the last unpause, let Java know.
|
||||
// TODO: Distinguish Unpause and UnpauseSampling in Java.
|
||||
java::GeckoJavaSampler::UnpauseSampling();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -4807,6 +4827,80 @@ void profiler_resume() {
|
||||
NotifyObservers("profiler-resumed");
|
||||
}
|
||||
|
||||
bool profiler_is_sampling_paused() {
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!ActivePS::Exists(lock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ActivePS::IsSamplingPaused(lock);
|
||||
}
|
||||
|
||||
void profiler_pause_sampling() {
|
||||
LOG("profiler_pause_sampling");
|
||||
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
{
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!ActivePS::Exists(lock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
if (ActivePS::FeatureJava(lock) && !ActivePS::IsSamplingPaused(lock)) {
|
||||
// Not paused yet, so this is the first pause, let Java know.
|
||||
// TODO: Distinguish Pause and PauseSampling in Java.
|
||||
java::GeckoJavaSampler::PauseSampling();
|
||||
}
|
||||
#endif
|
||||
|
||||
RacyFeatures::SetSamplingPaused();
|
||||
ActivePS::SetIsSamplingPaused(lock, true);
|
||||
ActivePS::Buffer(lock).AddEntry(
|
||||
ProfileBufferEntry::PauseSampling(profiler_time()));
|
||||
}
|
||||
|
||||
// gPSMutex must be unlocked when we notify, to avoid potential deadlocks.
|
||||
ProfilerParent::ProfilerPausedSampling();
|
||||
NotifyObservers("profiler-paused-sampling");
|
||||
}
|
||||
|
||||
void profiler_resume_sampling() {
|
||||
LOG("profiler_resume_sampling");
|
||||
|
||||
MOZ_RELEASE_ASSERT(CorePS::Exists());
|
||||
|
||||
{
|
||||
PSAutoLock lock(gPSMutex);
|
||||
|
||||
if (!ActivePS::Exists(lock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ActivePS::Buffer(lock).AddEntry(
|
||||
ProfileBufferEntry::ResumeSampling(profiler_time()));
|
||||
ActivePS::SetIsSamplingPaused(lock, false);
|
||||
RacyFeatures::SetSamplingUnpaused();
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
if (ActivePS::FeatureJava(lock) && !ActivePS::IsSamplingPaused(lock)) {
|
||||
// Not paused anymore, so this is the last unpause, let Java know.
|
||||
// TODO: Distinguish Unpause and UnpauseSampling in Java.
|
||||
java::GeckoJavaSampler::UnpauseSampling();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// gPSMutex must be unlocked when we notify, to avoid potential deadlocks.
|
||||
ProfilerParent::ProfilerResumedSampling();
|
||||
NotifyObservers("profiler-resumed-sampling");
|
||||
}
|
||||
|
||||
bool profiler_feature_active(uint32_t aFeature) {
|
||||
// This function runs both on and off the main thread.
|
||||
|
||||
|
@ -22,6 +22,8 @@ child:
|
||||
async Stop();
|
||||
async Pause();
|
||||
async Resume();
|
||||
async PauseSampling();
|
||||
async ResumeSampling();
|
||||
|
||||
async AwaitNextChunkManagerUpdate() returns (ProfileBufferChunkManagerUpdate update);
|
||||
async DestroyReleasedChunksAtOrBefore(TimeStamp timeStamp);
|
||||
|
@ -190,6 +190,16 @@ mozilla::ipc::IPCResult ProfilerChild::RecvResume() {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ProfilerChild::RecvPauseSampling() {
|
||||
profiler_pause_sampling();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ProfilerChild::RecvResumeSampling() {
|
||||
profiler_resume_sampling();
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ProfilerChild::RecvClearAllPages() {
|
||||
profiler_clear_all_pages();
|
||||
return IPC_OK();
|
||||
|
@ -671,6 +671,28 @@ void ProfilerParent::ProfilerResumed() {
|
||||
});
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ProfilerParent::ProfilerPausedSampling() {
|
||||
if (!NS_IsMainThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
|
||||
Unused << profilerParent->SendPauseSampling();
|
||||
});
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ProfilerParent::ProfilerResumedSampling() {
|
||||
if (!NS_IsMainThread()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
|
||||
Unused << profilerParent->SendResumeSampling();
|
||||
});
|
||||
}
|
||||
|
||||
/* static */
|
||||
void ProfilerParent::ClearAllPages() {
|
||||
if (!NS_IsMainThread()) {
|
||||
|
@ -42,6 +42,9 @@ interface nsIProfiler : nsISupports
|
||||
[optional] in double aDuration);
|
||||
void StopProfiler();
|
||||
boolean IsPaused();
|
||||
void Pause();
|
||||
void Resume();
|
||||
boolean IsSamplingPaused();
|
||||
void PauseSampling();
|
||||
void ResumeSampling();
|
||||
void AddMarker(in string aMarker);
|
||||
|
@ -158,17 +158,35 @@ nsProfiler::IsPaused(bool* aIsPaused) {
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::PauseSampling() {
|
||||
nsProfiler::Pause() {
|
||||
profiler_pause();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::ResumeSampling() {
|
||||
nsProfiler::Resume() {
|
||||
profiler_resume();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::IsSamplingPaused(bool* aIsSamplingPaused) {
|
||||
*aIsSamplingPaused = profiler_is_sampling_paused();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::PauseSampling() {
|
||||
profiler_pause_sampling();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::ResumeSampling() {
|
||||
profiler_resume_sampling();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::AddMarker(const char* aMarker) {
|
||||
PROFILER_ADD_MARKER(aMarker, OTHER);
|
||||
|
@ -239,18 +239,23 @@ class RacyFeatures {
|
||||
|
||||
static void SetUnpaused() { sActiveAndFeatures &= ~Paused; }
|
||||
|
||||
static void SetSamplingPaused() { sActiveAndFeatures |= SamplingPaused; }
|
||||
|
||||
static void SetSamplingUnpaused() { sActiveAndFeatures &= ~SamplingPaused; }
|
||||
|
||||
static mozilla::Maybe<uint32_t> FeaturesIfActive() {
|
||||
if (uint32_t af = sActiveAndFeatures; af & Active) {
|
||||
// Active, remove the Active&Paused bits to get all features.
|
||||
return Some(af & ~(Active | Paused));
|
||||
return Some(af & ~(Active | Paused | SamplingPaused));
|
||||
}
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
static mozilla::Maybe<uint32_t> FeaturesIfActiveAndUnpaused() {
|
||||
if (uint32_t af = sActiveAndFeatures; (af & (Active | Paused)) == Active) {
|
||||
// Active but not paused, remove the Active bit to get all features.
|
||||
return Some(af & ~Active);
|
||||
// Active but not fully paused, remove the Active and sampling-paused bits
|
||||
// to get all features.
|
||||
return Some(af & ~(Active | SamplingPaused));
|
||||
}
|
||||
return Nothing();
|
||||
}
|
||||
@ -262,18 +267,29 @@ class RacyFeatures {
|
||||
return (af & Active) && (af & aFeature);
|
||||
}
|
||||
|
||||
// True if profiler is active, and not fully paused.
|
||||
// Note that periodic sampling *could* be paused!
|
||||
static bool IsActiveAndUnpaused() {
|
||||
uint32_t af = sActiveAndFeatures; // copy it first
|
||||
return (af & Active) && !(af & Paused);
|
||||
}
|
||||
|
||||
// True if profiler is active, and sampling is not paused (though generic
|
||||
// `SetPaused()` or specific `SetSamplingPaused()`).
|
||||
static bool IsActiveAndSamplingUnpaused() {
|
||||
uint32_t af = sActiveAndFeatures; // copy it first
|
||||
return (af & Active) && !(af & (Paused | SamplingPaused));
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint32_t Active = 1u << 31;
|
||||
static constexpr uint32_t Paused = 1u << 30;
|
||||
static constexpr uint32_t SamplingPaused = 1u << 29;
|
||||
|
||||
// Ensure Active/Paused don't overlap with any of the feature bits.
|
||||
# define NO_OVERLAP(n_, str_, Name_, desc_) \
|
||||
static_assert(ProfilerFeature::Name_ != Paused, "bad feature value");
|
||||
# define NO_OVERLAP(n_, str_, Name_, desc_) \
|
||||
static_assert(ProfilerFeature::Name_ != SamplingPaused, \
|
||||
"bad feature value");
|
||||
|
||||
PROFILER_FOR_EACH_FEATURE(NO_OVERLAP);
|
||||
|
||||
@ -465,6 +481,11 @@ using PostSamplingCallback = std::function<void(SamplingState)>;
|
||||
void profiler_pause();
|
||||
void profiler_resume();
|
||||
|
||||
// Only pause and resume the periodic sampling loop, including stack sampling,
|
||||
// counters, and profiling overheads.
|
||||
void profiler_pause_sampling();
|
||||
void profiler_resume_sampling();
|
||||
|
||||
// These functions tell the profiler that a thread went to sleep so that we can
|
||||
// avoid sampling it while it's sleeping. Calling profiler_thread_sleep()
|
||||
// twice without an intervening profiler_thread_wake() is an error. All three
|
||||
@ -544,6 +565,10 @@ inline bool profiler_is_active_and_thread_is_registered() {
|
||||
// Is the profiler active and paused? Returns false if the profiler is inactive.
|
||||
bool profiler_is_paused();
|
||||
|
||||
// Is the profiler active and sampling is paused? Returns false if the profiler
|
||||
// is inactive.
|
||||
bool profiler_is_sampling_paused();
|
||||
|
||||
// Is the current thread sleeping?
|
||||
bool profiler_thread_is_sleeping();
|
||||
|
||||
|
@ -42,6 +42,8 @@ class ProfilerChild final : public PProfilerChild,
|
||||
mozilla::ipc::IPCResult RecvStop() override;
|
||||
mozilla::ipc::IPCResult RecvPause() override;
|
||||
mozilla::ipc::IPCResult RecvResume() override;
|
||||
mozilla::ipc::IPCResult RecvPauseSampling() override;
|
||||
mozilla::ipc::IPCResult RecvResumeSampling() override;
|
||||
mozilla::ipc::IPCResult RecvAwaitNextChunkManagerUpdate(
|
||||
AwaitNextChunkManagerUpdateResolver&& aResolve) override;
|
||||
mozilla::ipc::IPCResult RecvDestroyReleasedChunksAtOrBefore(
|
||||
|
@ -60,6 +60,8 @@ class ProfilerParent final : public PProfilerParent {
|
||||
static void ProfilerStopped();
|
||||
static void ProfilerPaused();
|
||||
static void ProfilerResumed();
|
||||
static void ProfilerPausedSampling();
|
||||
static void ProfilerResumedSampling();
|
||||
static void ClearAllPages();
|
||||
|
||||
// Create a "Final" update that the Child can return to its Parent.
|
||||
|
@ -116,7 +116,7 @@ function captureAtLeastOneJsSample() {
|
||||
* @returns {Promise<Profile>}
|
||||
*/
|
||||
async function stopAndGetProfile() {
|
||||
Services.profiler.PauseSampling();
|
||||
Services.profiler.Pause();
|
||||
const profile = await Services.profiler.getProfileDataAsync();
|
||||
Services.profiler.StopProfiler();
|
||||
return profile;
|
||||
|
@ -109,7 +109,7 @@ async function startProfilerAndgetFileIOPayloads(features, filename) {
|
||||
|
||||
// Pause the profiler as we don't need to collect more samples as we retrieve
|
||||
// and serialize the profile.
|
||||
Services.profiler.PauseSampling();
|
||||
Services.profiler.Pause();
|
||||
|
||||
const profile = await Services.profiler.getProfileDataAsync();
|
||||
Services.profiler.StopProfiler();
|
||||
|
@ -12,18 +12,71 @@ function run_test() {
|
||||
|
||||
Services.profiler.StartProfiler(1000, 10, []);
|
||||
|
||||
// Default: Active and not paused.
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(!Services.profiler.IsPaused());
|
||||
Assert.ok(!Services.profiler.IsSamplingPaused());
|
||||
|
||||
// Pause everything, implicitly pauses sampling.
|
||||
Services.profiler.Pause();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(Services.profiler.IsPaused());
|
||||
Assert.ok(Services.profiler.IsSamplingPaused());
|
||||
|
||||
// While fully paused, pause and resume sampling only, no expected changes.
|
||||
Services.profiler.PauseSampling();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(Services.profiler.IsPaused());
|
||||
Assert.ok(Services.profiler.IsSamplingPaused());
|
||||
|
||||
Services.profiler.ResumeSampling();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(Services.profiler.IsPaused());
|
||||
Assert.ok(Services.profiler.IsSamplingPaused());
|
||||
|
||||
// Resume everything.
|
||||
Services.profiler.Resume();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(!Services.profiler.IsPaused());
|
||||
Assert.ok(!Services.profiler.IsSamplingPaused());
|
||||
|
||||
// Pause sampling only.
|
||||
Services.profiler.PauseSampling();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(!Services.profiler.IsPaused());
|
||||
Assert.ok(Services.profiler.IsSamplingPaused());
|
||||
|
||||
// While sampling is paused, pause everything.
|
||||
Services.profiler.Pause();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(Services.profiler.IsPaused());
|
||||
Assert.ok(Services.profiler.IsSamplingPaused());
|
||||
|
||||
// Resume, but sampling is still paused separately.
|
||||
Services.profiler.Resume();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(!Services.profiler.IsPaused());
|
||||
Assert.ok(Services.profiler.IsSamplingPaused());
|
||||
|
||||
// Resume sampling only.
|
||||
Services.profiler.ResumeSampling();
|
||||
|
||||
Assert.ok(Services.profiler.IsActive());
|
||||
Assert.ok(!Services.profiler.IsPaused());
|
||||
Assert.ok(!Services.profiler.IsSamplingPaused());
|
||||
|
||||
Services.profiler.StopProfiler();
|
||||
Assert.ok(!Services.profiler.IsActive());
|
||||
// Stopping is not pausing.
|
||||
Assert.ok(!Services.profiler.IsPaused());
|
||||
Assert.ok(!Services.profiler.IsSamplingPaused());
|
||||
|
||||
do_test_finished();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user