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:
Gerald Squelart 2020-07-02 01:36:27 +00:00
parent d1626da38c
commit 56736f31c3
29 changed files with 410 additions and 83 deletions

View File

@ -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()

View File

@ -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) {

View File

@ -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 {

View File

@ -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;

View File

@ -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:

View File

@ -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*)) \

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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();

View File

@ -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) {

View File

@ -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();

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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:

View File

@ -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)) \

View File

@ -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);
}
}

View File

@ -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.

View File

@ -22,6 +22,8 @@ child:
async Stop();
async Pause();
async Resume();
async PauseSampling();
async ResumeSampling();
async AwaitNextChunkManagerUpdate() returns (ProfileBufferChunkManagerUpdate update);
async DestroyReleasedChunksAtOrBefore(TimeStamp timeStamp);

View File

@ -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();

View File

@ -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()) {

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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(

View File

@ -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.

View File

@ -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;

View File

@ -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();

View File

@ -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();
}