--- a/js/public/ProfilingFrameIterator.h
+++ b/js/public/ProfilingFrameIterator.h
@@ -49,17 +49,17 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
public:
enum class Kind : bool {
JSJit,
Wasm
};
private:
JSContext* cx_;
- uint32_t sampleBufferGen_;
+ mozilla::Maybe<uint64_t> samplePositionInProfilerBuffer_;
js::Activation* activation_;
Kind kind_;
static const unsigned StorageSpace = 8 * sizeof(void*);
alignas(void*) unsigned char storage_[StorageSpace];
void* storage() { return storage_; }
const void* storage() const { return storage_; }
@@ -85,32 +85,28 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
MOZ_ASSERT(!done());
MOZ_ASSERT(isJSJit());
return *static_cast<const js::jit::JSJitProfilingFrameIterator*>(storage());
}
void settleFrames();
void settle();
- bool hasSampleBufferGen() const {
- return sampleBufferGen_ != UINT32_MAX;
- }
-
public:
struct RegisterState
{
RegisterState() : pc(nullptr), sp(nullptr), fp(nullptr), lr(nullptr) {}
void* pc;
void* sp;
void* fp;
void* lr;
};
ProfilingFrameIterator(JSContext* cx, const RegisterState& state,
- uint32_t sampleBufferGen = UINT32_MAX);
+ const mozilla::Maybe<uint64_t>& samplePositionInProfilerBuffer = mozilla::Nothing());
~ProfilingFrameIterator();
void operator++();
bool done() const { return !activation_; }
// Assuming the stack grows down (we do), the return value:
// - always points into the stack
// - is weakly monotonically increasing (may be equal for successive frames)
// - will compare greater than newer native and psuedo-stack frame addresses
@@ -148,26 +144,25 @@ class MOZ_NON_PARAM JS_PUBLIC_API(Profil
void iteratorDestroy();
bool iteratorDone();
} JS_HAZ_GC_INVALIDATED;
JS_FRIEND_API(bool)
IsProfilingEnabledForContext(JSContext* cx);
/**
- * After each sample run, this method should be called with the latest sample
- * buffer generation, and the lapCount. It will update corresponding fields on
- * JSRuntime.
+ * After each sample run, this method should be called with the current buffer
+ * position at which the buffer contents start. This will update the
+ * corresponding field on the JSRuntime.
*
- * See fields |profilerSampleBufferGen|, |profilerSampleBufferLapCount| on
- * JSRuntime for documentation about what these values are used for.
+ * See the field |profilerSampleBufferRangeStart| on JSRuntime for documentation
+ * about what this value is used for.
*/
JS_FRIEND_API(void)
-UpdateJSContextProfilerSampleBufferGen(JSContext* cx, uint32_t generation,
- uint32_t lapCount);
+SetJSContextProfilerSampleBufferRangeStart(JSContext* cx, uint64_t rangeStart);
struct ForEachProfiledFrameOp
{
// A handle to the underlying JitcodeGlobalEntry, so as to avoid repeated
// lookups on JitcodeGlobalTable.
class MOZ_STACK_CLASS FrameHandle
{
friend JS_PUBLIC_API(void) ForEachProfiledFrame(JSContext* cx, void* addr,
--- a/js/src/jit/JitcodeMap.cpp
+++ b/js/src/jit/JitcodeMap.cpp
@@ -1,17 +1,16 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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 "jit/JitcodeMap.h"
-#include "mozilla/DebugOnly.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/Sprintf.h"
#include "jsprf.h"
#include "gc/Marking.h"
#include "gc/Statistics.h"
@@ -429,27 +428,27 @@ JitcodeGlobalTable::Enum::popFront()
void
JitcodeGlobalTable::Enum::removeFront()
{
MOZ_ASSERT(!empty());
table_.releaseEntry(*cur_, prevTower_, rt_);
}
const JitcodeGlobalEntry&
-JitcodeGlobalTable::lookupForSamplerInfallible(void* ptr, JSRuntime* rt, uint32_t sampleBufferGen)
+JitcodeGlobalTable::lookupForSamplerInfallible(void* ptr, JSRuntime* rt, uint64_t samplePosInBuffer)
{
JitcodeGlobalEntry* entry = lookupInternal(ptr);
MOZ_ASSERT(entry);
- entry->setGeneration(sampleBufferGen);
+ entry->setSamplePositionInBuffer(samplePosInBuffer);
// IonCache entries must keep their corresponding Ion entries alive.
if (entry->isIonCache()) {
JitcodeGlobalEntry& rejoinEntry = RejoinEntry(rt, entry->ionCacheEntry(), ptr);
- rejoinEntry.setGeneration(sampleBufferGen);
+ rejoinEntry.setSamplePositionInBuffer(samplePosInBuffer);
}
// JitcodeGlobalEntries are marked at the end of the mark phase. A read
// barrier is not needed. Any JS frames sampled during the sweep phase of
// the GC must be on stack, and on-stack frames must already be marked at
// the beginning of the sweep phase. It's not possible to assert this here
// as we may be off main thread when called from the gecko profiler.
@@ -565,19 +564,20 @@ JitcodeGlobalTable::removeEntry(JitcodeG
entry = JitcodeGlobalEntry();
entry.addToFreeList(&freeEntries_);
}
void
JitcodeGlobalTable::releaseEntry(JitcodeGlobalEntry& entry, JitcodeGlobalEntry** prevTower,
JSRuntime* rt)
{
- mozilla::DebugOnly<uint32_t> gen = rt->profilerSampleBufferGen();
- mozilla::DebugOnly<uint32_t> lapCount = rt->profilerSampleBufferLapCount();
- MOZ_ASSERT_IF(gen != UINT32_MAX, !entry.isSampled(gen, lapCount));
+#ifdef DEBUG
+ Maybe<uint64_t> rangeStart = rt->profilerSampleBufferRangeStart();
+ MOZ_ASSERT_IF(rangeStart, !entry.isSampled(*rangeStart));
+#endif
removeEntry(entry, prevTower, rt);
}
void
JitcodeGlobalTable::searchInternal(const JitcodeGlobalEntry& query, JitcodeGlobalEntry** towerOut)
{
JitcodeGlobalEntry* cur = nullptr;
for (int level = JitcodeSkiplistTower::MAX_HEIGHT - 1; level >= 0; level--) {
@@ -790,35 +790,33 @@ JitcodeGlobalTable::markIteratively(GCMa
// been newly pushed, and thus are already marked.
//
// The approach above obviates the need for read barriers. The assumption
// above is checked in JitcodeGlobalTable::lookupForSampler.
MOZ_ASSERT(!JS::CurrentThreadIsHeapMinorCollecting());
AutoSuppressProfilerSampling suppressSampling(TlsContext.get());
- uint32_t gen = marker->runtime()->profilerSampleBufferGen();
- uint32_t lapCount = marker->runtime()->profilerSampleBufferLapCount();
- // If the profiler is off, all entries are considered to be expired.
- if (!marker->runtime()->geckoProfiler().enabled())
- gen = UINT32_MAX;
+ // If the profiler is off, rangeStart will be Nothing() and all entries are
+ // considered to be expired.
+ Maybe<uint64_t> rangeStart = marker->runtime()->profilerSampleBufferRangeStart();
bool markedAny = false;
for (Range r(*this); !r.empty(); r.popFront()) {
JitcodeGlobalEntry* entry = r.front();
- // If an entry is not sampled, reset its generation to the invalid
- // generation, and conditionally mark the rest of the entry if its
+ // If an entry is not sampled, reset its buffer position to the invalid
+ // position, and conditionally mark the rest of the entry if its
// JitCode is not already marked. This conditional marking ensures
// that so long as the JitCode *may* be sampled, we keep any
// information that may be handed out to the sampler, like tracked
// types used by optimizations and scripts used for pc to line number
// mapping, alive as well.
- if (!entry->isSampled(gen, lapCount)) {
+ if (!rangeStart || !entry->isSampled(*rangeStart)) {
if (entry->canHoldNurseryPointers())
removeFromNurseryList(&entry->ionEntry());
entry->setAsExpired();
if (!entry->baseEntry().isJitcodeMarkedFromAnyThread(marker->runtime()))
continue;
}
// The table is runtime-wide. Not all zones may be participating in
--- a/js/src/jit/JitcodeMap.h
+++ b/js/src/jit/JitcodeMap.h
@@ -138,55 +138,59 @@ class JitcodeGlobalEntry
jsbytecode* pc;
BytecodeLocation(JSScript* script, jsbytecode* pc) : script(script), pc(pc) {}
};
typedef Vector<BytecodeLocation, 0, SystemAllocPolicy> BytecodeLocationVector;
typedef Vector<const char*, 0, SystemAllocPolicy> ProfileStringVector;
struct BaseEntry
{
+ static const uint64_t kNoSampleInBuffer = UINT64_MAX;
+
JitCode* jitcode_;
void* nativeStartAddr_;
void* nativeEndAddr_;
- uint32_t gen_;
+ // If this entry is referenced from the profiler buffer, this is the
+ // position where the most recent sample that references it starts.
+ // Otherwise set to kNoSampleInBuffer.
+ uint64_t samplePositionInBuffer_;
Kind kind_ : 7;
void init() {
jitcode_ = nullptr;
nativeStartAddr_ = nullptr;
nativeEndAddr_ = nullptr;
- gen_ = UINT32_MAX;
+ samplePositionInBuffer_ = kNoSampleInBuffer;
kind_ = INVALID;
}
void init(Kind kind, JitCode* code,
void* nativeStartAddr, void* nativeEndAddr)
{
MOZ_ASSERT_IF(kind != Query, code);
MOZ_ASSERT(nativeStartAddr);
MOZ_ASSERT(nativeEndAddr);
MOZ_ASSERT(kind > INVALID && kind < LIMIT);
jitcode_ = code;
nativeStartAddr_ = nativeStartAddr;
nativeEndAddr_ = nativeEndAddr;
- gen_ = UINT32_MAX;
+ samplePositionInBuffer_ = kNoSampleInBuffer;
kind_ = kind;
}
- uint32_t generation() const {
- return gen_;
- }
- void setGeneration(uint32_t gen) {
- gen_ = gen;
+ void setSamplePositionInBuffer(uint64_t bufferWritePos) {
+ samplePositionInBuffer_ = bufferWritePos;
}
- bool isSampled(uint32_t currentGen, uint32_t lapCount) {
- if (gen_ == UINT32_MAX || currentGen == UINT32_MAX)
+ void setAsExpired() {
+ samplePositionInBuffer_ = kNoSampleInBuffer;
+ }
+ bool isSampled(uint64_t bufferRangeStart) {
+ if (samplePositionInBuffer_ == kNoSampleInBuffer)
return false;
- MOZ_ASSERT(currentGen >= gen_);
- return (currentGen - gen_) <= lapCount;
+ return bufferRangeStart <= samplePositionInBuffer_;
}
Kind kind() const {
return kind_;
}
JitCode* jitcode() const {
return jitcode_;
}
@@ -632,27 +636,24 @@ class JitcodeGlobalEntry
}
void* nativeStartAddr() const {
return base_.nativeStartAddr();
}
void* nativeEndAddr() const {
return base_.nativeEndAddr();
}
- uint32_t generation() const {
- return baseEntry().generation();
- }
- void setGeneration(uint32_t gen) {
- baseEntry().setGeneration(gen);
+ void setSamplePositionInBuffer(uint64_t samplePositionInBuffer) {
+ baseEntry().setSamplePositionInBuffer(samplePositionInBuffer);
}
void setAsExpired() {
- baseEntry().setGeneration(UINT32_MAX);
+ baseEntry().setAsExpired();
}
- bool isSampled(uint32_t currentGen, uint32_t lapCount) {
- return baseEntry().isSampled(currentGen, lapCount);
+ bool isSampled(uint64_t bufferRangeStart) {
+ return baseEntry().isSampled(bufferRangeStart);
}
bool startsBelowPointer(void* ptr) const {
return base_.startsBelowPointer(ptr);
}
bool endsAbovePointer(void* ptr) const {
return base_.endsAbovePointer(ptr);
}
@@ -1048,17 +1049,17 @@ class JitcodeGlobalTable
JitcodeGlobalEntry& lookupInfallible(void* ptr) {
JitcodeGlobalEntry* entry = lookupInternal(ptr);
MOZ_ASSERT(entry);
return *entry;
}
const JitcodeGlobalEntry& lookupForSamplerInfallible(void* ptr, JSRuntime* rt,
- uint32_t sampleBufferGen);
+ uint64_t samplePosInBuffer);
MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::IonEntry& entry, JSRuntime* rt) {
return addEntry(JitcodeGlobalEntry(entry), rt);
}
MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::BaselineEntry& entry, JSRuntime* rt) {
return addEntry(JitcodeGlobalEntry(entry), rt);
}
MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::IonCacheEntry& entry, JSRuntime* rt) {
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -105,22 +105,20 @@ GeckoProfilerRuntime::enable(bool enable
/*
* Ensure all future generated code will be instrumented, or that all
* currently instrumented code is discarded
*/
ReleaseAllJITCode(rt->defaultFreeOp());
// This function is called when the Gecko profiler makes a new Sampler
// (and thus, a new circular buffer). Set all current entries in the
- // JitcodeGlobalTable as expired and reset the buffer generation and lap
- // count.
+ // JitcodeGlobalTable as expired and reset the buffer range start.
if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable())
rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired(rt);
- rt->resetProfilerSampleBufferGen();
- rt->resetProfilerSampleBufferLapCount();
+ rt->setProfilerSampleBufferRangeStart(0);
// Ensure that lastProfilingFrame is null for all threads before 'enabled' becomes true.
for (const CooperatingContext& target : rt->cooperatingContexts()) {
if (target.context()->jitActivation) {
target.context()->jitActivation->setLastProfilingFrame(nullptr);
target.context()->jitActivation->setLastProfilingCallSite(nullptr);
}
}
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -96,18 +96,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
initialized_(false),
#endif
activeContext_(nullptr),
activeContextChangeProhibited_(0),
singleThreadedExecutionRequired_(0),
startingSingleThreadedExecution_(false),
beginSingleThreadedExecutionCallback(nullptr),
endSingleThreadedExecutionCallback(nullptr),
- profilerSampleBufferGen_(0),
- profilerSampleBufferLapCount_(1),
+ profilerSampleBufferRangeStart_(0),
telemetryCallback(nullptr),
consumeStreamCallback(nullptr),
readableStreamDataRequestCallback(nullptr),
readableStreamWriteIntoReadRequestCallback(nullptr),
readableStreamCancelCallback(nullptr),
readableStreamClosedCallback(nullptr),
readableStreamErroredCallback(nullptr),
readableStreamFinalizeCallback(nullptr),
@@ -311,19 +310,16 @@ JSRuntime::destroyRuntime()
* Flag us as being destroyed. This allows the GC to free things like
* interned atoms and Ion trampolines.
*/
beingDestroyed_ = true;
/* Allow the GC to release scripts that were being profiled. */
profilingScripts = false;
- /* Set the profiler sampler buffer generation to invalid. */
- profilerSampleBufferGen_ = UINT32_MAX;
-
JS::PrepareForFullGC(cx);
gc.gc(GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
}
AutoNoteSingleThreadedRegion anstr;
MOZ_ASSERT(!hasHelperThreadZones());
@@ -924,21 +920,19 @@ js::CurrentThreadCanAccessZone(Zone* zon
bool
js::CurrentThreadIsPerformingGC()
{
return TlsContext.get()->performingGC;
}
#endif
JS_FRIEND_API(void)
-JS::UpdateJSContextProfilerSampleBufferGen(JSContext* cx, uint32_t generation,
- uint32_t lapCount)
+JS::SetJSContextProfilerSampleBufferRangeStart(JSContext* cx, uint64_t rangeStart)
{
- cx->runtime()->setProfilerSampleBufferGen(generation);
- cx->runtime()->updateProfilerSampleBufferLapCount(lapCount);
+ cx->runtime()->setProfilerSampleBufferRangeStart(rangeStart);
}
JS_FRIEND_API(bool)
JS::IsProfilingEnabledForContext(JSContext* cx)
{
MOZ_ASSERT(cx);
return cx->runtime()->geckoProfiler().enabled();
}
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -6,16 +6,17 @@
#ifndef vm_Runtime_h
#define vm_Runtime_h
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/DoublyLinkedList.h"
#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
#include "mozilla/MaybeOneOf.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Scoped.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/Vector.h"
#include <setjmp.h>
@@ -343,69 +344,40 @@ struct JSRuntime : public js::MallocProv
// Ensure there is only a single thread interacting with this runtime.
// beginSingleThreadedExecution() returns false if some context has already
// started forcing this runtime to be single threaded. Calls to these
// functions must be balanced.
bool beginSingleThreadedExecution(JSContext* cx);
void endSingleThreadedExecution(JSContext* cx);
/*
- * The profiler sampler generation after the latest sample.
- *
- * The lapCount indicates the number of largest number of 'laps'
- * (wrapping from high to low) that occurred when writing entries
- * into the sample buffer. All JitcodeGlobalMap entries referenced
- * from a given sample are assigned the generation of the sample buffer
- * at the START of the run. If multiple laps occur, then some entries
- * (towards the end) will be written out with the "wrong" generation.
- * The lapCount indicates the required fudge factor to use to compare
- * entry generations with the sample buffer generation.
+ * The start of the range stored in the profiler sample buffer, as measured
+ * after the most recent sample.
+ * All JitcodeGlobalMap entries referenced from a given sample are assigned
+ * the buffer position of the START of the sample. The buffer entries that
+ * reference the JitcodeGlobalMap entries will only ever be read from the
+ * buffer while the entire sample is still inside the buffer; if some
+ * buffer entries at the start of the sample have left the buffer, the
+ * entire sample will be considered inaccessible.
+ * This means that, once profilerSampleBufferRangeStart_ advances beyond
+ * the sample position that's stored on a JitcodeGlobalMap entry, the buffer
+ * entries that reference this JitcodeGlobalMap entry will be considered
+ * inaccessible, and those JitcodeGlobalMap entry can be disposed of.
*/
- mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> profilerSampleBufferGen_;
- mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> profilerSampleBufferLapCount_;
+ mozilla::Atomic<uint64_t, mozilla::ReleaseAcquire> profilerSampleBufferRangeStart_;
- uint32_t profilerSampleBufferGen() {
- return profilerSampleBufferGen_;
- }
- void resetProfilerSampleBufferGen() {
- profilerSampleBufferGen_ = 0;
+ mozilla::Maybe<uint64_t> profilerSampleBufferRangeStart() {
+ if (beingDestroyed_ || !geckoProfiler().enabled()) {
+ return mozilla::Nothing();
+ }
+ uint64_t rangeStart = profilerSampleBufferRangeStart_;
+ return mozilla::Some(rangeStart);
}
- void setProfilerSampleBufferGen(uint32_t gen) {
- // May be called from sampler thread or signal handler; use
- // compareExchange to make sure we have monotonic increase.
- for (;;) {
- uint32_t curGen = profilerSampleBufferGen_;
- if (curGen >= gen)
- break;
-
- if (profilerSampleBufferGen_.compareExchange(curGen, gen))
- break;
- }
- }
-
- uint32_t profilerSampleBufferLapCount() {
- MOZ_ASSERT(profilerSampleBufferLapCount_ > 0);
- return profilerSampleBufferLapCount_;
- }
- void resetProfilerSampleBufferLapCount() {
- profilerSampleBufferLapCount_ = 1;
- }
- void updateProfilerSampleBufferLapCount(uint32_t lapCount) {
- MOZ_ASSERT(profilerSampleBufferLapCount_ > 0);
-
- // May be called from sampler thread or signal handler; use
- // compareExchange to make sure we have monotonic increase.
- for (;;) {
- uint32_t curLapCount = profilerSampleBufferLapCount_;
- if (curLapCount >= lapCount)
- break;
-
- if (profilerSampleBufferLapCount_.compareExchange(curLapCount, lapCount))
- break;
- }
+ void setProfilerSampleBufferRangeStart(uint64_t rangeStart) {
+ profilerSampleBufferRangeStart_ = rangeStart;
}
/* Call this to accumulate telemetry data. */
js::ActiveThreadData<JSAccumulateTelemetryDataCallback> telemetryCallback;
/* Call this to accumulate use counter data. */
js::ActiveThreadData<JSSetUseCounterCallback> useCounterCallback;
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1896,19 +1896,19 @@ ActivationIterator&
ActivationIterator::operator++()
{
MOZ_ASSERT(activation_);
activation_ = activation_->prev();
return *this;
}
JS::ProfilingFrameIterator::ProfilingFrameIterator(JSContext* cx, const RegisterState& state,
- uint32_t sampleBufferGen)
+ const Maybe<uint64_t>& samplePositionInProfilerBuffer)
: cx_(cx),
- sampleBufferGen_(sampleBufferGen),
+ samplePositionInProfilerBuffer_(samplePositionInProfilerBuffer),
activation_(nullptr)
{
if (!cx->runtime()->geckoProfiler().enabled())
MOZ_CRASH("ProfilingFrameIterator called when geckoProfiler not enabled for runtime.");
if (!cx->profilingActivation())
return;
@@ -2077,18 +2077,19 @@ JS::ProfilingFrameIterator::getPhysicalF
return mozilla::Some(frame);
}
MOZ_ASSERT(isJSJit());
// Look up an entry for the return address.
void* returnAddr = jsJitIter().returnAddressToFp();
jit::JitcodeGlobalTable* table = cx_->runtime()->jitRuntime()->getJitcodeGlobalTable();
- if (hasSampleBufferGen())
- *entry = table->lookupForSamplerInfallible(returnAddr, cx_->runtime(), sampleBufferGen_);
+ if (samplePositionInProfilerBuffer_)
+ *entry = table->lookupForSamplerInfallible(returnAddr, cx_->runtime(),
+ *samplePositionInProfilerBuffer_);
else
*entry = table->lookupInfallible(returnAddr);
MOZ_ASSERT(entry->isIon() || entry->isIonCache() || entry->isBaseline() || entry->isDummy());
// Dummy frames produce no stack frames.
if (entry->isDummy())
return mozilla::Nothing();
--- a/tools/profiler/core/ProfileBuffer.cpp
+++ b/tools/profiler/core/ProfileBuffer.cpp
@@ -1,74 +1,73 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ProfileBuffer.h"
+#include "mozilla/MathAlgorithms.h"
+
#include "ProfilerMarker.h"
#include "jsfriendapi.h"
#include "nsScriptSecurityManager.h"
#include "nsJSPrincipals.h"
using namespace mozilla;
ProfileBuffer::ProfileBuffer(uint32_t aEntrySize)
- : mEntries(mozilla::MakeUnique<ProfileBufferEntry[]>(aEntrySize))
- , mWritePos(0)
- , mReadPos(0)
- , mEntrySize(aEntrySize)
- , mGeneration(0)
+ : mEntryIndexMask(0)
+ , mRangeStart(0)
+ , mRangeEnd(0)
+ , mEntrySize(0)
{
+ // Round aEntrySize up to the nearest power of two, so that we can index
+ // mEntries with a simple mask and don't need to do a slow modulo operation.
+ const uint32_t UINT32_MAX_POWER_OF_TWO = 1 << 31;
+ MOZ_RELEASE_ASSERT(aEntrySize <= UINT32_MAX_POWER_OF_TWO,
+ "aEntrySize is larger than what we support");
+ mEntrySize = RoundUpPow2(aEntrySize);
+ mEntryIndexMask = mEntrySize - 1;
+ mEntries = MakeUnique<ProfileBufferEntry[]>(mEntrySize);
}
ProfileBuffer::~ProfileBuffer()
{
while (mStoredMarkers.peek()) {
delete mStoredMarkers.popHead();
}
}
// Called from signal, call only reentrant functions
void
ProfileBuffer::AddEntry(const ProfileBufferEntry& aEntry)
{
- mEntries[mWritePos++] = aEntry;
- if (mWritePos == mEntrySize) {
- // Wrapping around may result in things referenced in the buffer (e.g.,
- // JIT code addresses and markers) being incorrectly collected.
- MOZ_ASSERT(mGeneration != UINT32_MAX);
- mGeneration++;
- mWritePos = 0;
- }
+ GetEntry(mRangeEnd++) = aEntry;
- if (mWritePos == mReadPos) {
- // Keep one slot open.
- mEntries[mReadPos] = ProfileBufferEntry();
- mReadPos = (mReadPos + 1) % mEntrySize;
+ // The distance between mRangeStart and mRangeEnd must never exceed
+ // mEntrySize, so advance mRangeStart if necessary.
+ if (mRangeEnd - mRangeStart > mEntrySize) {
+ mRangeStart++;
}
}
-void
-ProfileBuffer::AddThreadIdEntry(int aThreadId, LastSample* aLS)
+uint64_t
+ProfileBuffer::AddThreadIdEntry(int aThreadId)
{
- if (aLS) {
- // This is the start of a sample, so make a note of its location in |aLS|.
- aLS->mGeneration = mGeneration;
- aLS->mPos = Some(mWritePos);
- }
+ uint64_t pos = mRangeEnd;
AddEntry(ProfileBufferEntry::ThreadId(aThreadId));
+ return pos;
}
void
ProfileBuffer::AddStoredMarker(ProfilerMarker *aStoredMarker)
{
- aStoredMarker->SetGeneration(mGeneration);
+ aStoredMarker->SetPositionInBuffer(mRangeEnd);
mStoredMarkers.insert(aStoredMarker);
}
void
ProfileBuffer::CollectCodeLocation(
const char* aLabel, const char* aStr, int aLineNumber,
const Maybe<js::ProfileEntry::Category>& aCategory)
{
@@ -100,28 +99,31 @@ ProfileBuffer::CollectCodeLocation(
}
}
void
ProfileBuffer::DeleteExpiredStoredMarkers()
{
// Delete markers of samples that have been overwritten due to circular
// buffer wraparound.
- uint32_t generation = mGeneration;
while (mStoredMarkers.peek() &&
- mStoredMarkers.peek()->HasExpired(generation)) {
+ mStoredMarkers.peek()->HasExpired(mRangeStart)) {
delete mStoredMarkers.popHead();
}
}
void
ProfileBuffer::Reset()
{
- mGeneration += 2;
- mReadPos = mWritePos = 0;
+ // Empty the buffer (make sure that mRangeEnd - mRangeStart == 0) and
+ // increase the positions by twice the buffer size for good measure.
+ // This method isn't really intended to stay around for long; the only user
+ // wants to flush only one thread's data and ends up accidentally flushing
+ // the whole buffer which holds data for all threads, see bug 1429904.
+ mRangeStart = mRangeEnd = mRangeEnd + 2 * mEntrySize;
}
size_t
ProfileBuffer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t n = aMallocSizeOf(this);
n += aMallocSizeOf(mEntries.get());
@@ -143,22 +145,16 @@ IsChromeJSScript(JSScript* aScript)
nsIScriptSecurityManager* const secman =
nsScriptSecurityManager::GetScriptSecurityManager();
NS_ENSURE_TRUE(secman, false);
JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
}
-Maybe<uint32_t>
-ProfileBufferCollector::Generation()
-{
- return Some(mBuf.mGeneration);
-}
-
void
ProfileBufferCollector::CollectNativeLeafAddr(void* aAddr)
{
mBuf.AddEntry(ProfileBufferEntry::NativeLeafAddr(aAddr));
}
void
ProfileBufferCollector::CollectJitReturnAddr(void* aAddr)
--- a/tools/profiler/core/ProfileBuffer.h
+++ b/tools/profiler/core/ProfileBuffer.h
@@ -8,44 +8,45 @@
#include "platform.h"
#include "ProfileBufferEntry.h"
#include "ProfilerMarker.h"
#include "ProfileJSONWriter.h"
#include "mozilla/RefPtr.h"
#include "mozilla/RefCounted.h"
+// A fixed-capacity circular buffer.
+// This class is used as a queue of entries which, after construction, never
+// allocates. This makes it safe to use in the profiler's "critical section".
+// Entries are appended at the end. Once the queue capacity has been reached,
+// adding a new entry will evict an old entry from the start of the queue.
+// Positions in the queue are represented as 64-bit unsigned integers which
+// only increase and never wrap around.
+// mRangeStart and mRangeEnd describe the range in that uint64_t space which is
+// covered by the queue contents.
+// Internally, the buffer uses a fixed-size storage and applies a modulo
+// operation when accessing entries in that storage buffer. "Evicting" an entry
+// really just means that an existing entry in the storage buffer gets
+// overwritten and that mRangeStart gets incremented.
class ProfileBuffer final
{
public:
+ // ProfileBuffer constructor
+ // @param aEntrySize The minimum capacity of the buffer. The actual buffer
+ // capacity will be rounded up to the next power of two.
explicit ProfileBuffer(uint32_t aEntrySize);
~ProfileBuffer();
- // LastSample is used to record the buffer location of the most recent
- // sample for each thread. It is used for periodic samples stored in the
- // global ProfileBuffer, but *not* for synchronous samples.
- struct LastSample {
- LastSample()
- : mGeneration(0)
- , mPos()
- {}
-
- // The profiler-buffer generation number at which the sample was created.
- uint32_t mGeneration;
- // And its position in the buffer.
- mozilla::Maybe<uint32_t> mPos;
- };
-
// Add |aEntry| to the buffer, ignoring what kind of entry it is.
void AddEntry(const ProfileBufferEntry& aEntry);
- // Add to the buffer a sample start (ThreadId) entry for aThreadId. Also,
- // record the resulting generation and index in |aLS| if it's non-null.
- void AddThreadIdEntry(int aThreadId, LastSample* aLS = nullptr);
+ // Add to the buffer a sample start (ThreadId) entry for aThreadId.
+ // Returns the position of the entry.
+ uint64_t AddThreadIdEntry(int aThreadId);
void CollectCodeLocation(
const char* aLabel, const char* aStr, int aLineNumber,
const mozilla::Maybe<js::ProfileEntry::Category>& aCategory);
// Maximum size of a frameKey string that we'll handle.
static const size_t kMaxFrameKeyLength = 512;
@@ -55,72 +56,104 @@ public:
UniqueStacks& aUniqueStacks) const;
bool StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
const mozilla::TimeStamp& aProcessStartTime,
double aSinceTime,
UniqueStacks& aUniqueStacks) const;
void StreamPausedRangesToJSON(SpliceableJSONWriter& aWriter,
double aSinceTime) const;
- // Find (via |aLS|) the most recent sample for the thread denoted by
- // |aThreadId| and clone it, patching in |aProcessStartTime| as appropriate.
+ // Find (via |aLastSample|) the most recent sample for the thread denoted by
+ // |aThreadId| and clone it, patching in the current time as appropriate.
+ // Mutate |aLastSample| to point to the newly inserted sample.
+ // Returns whether duplication was successful.
bool DuplicateLastSample(int aThreadId,
const mozilla::TimeStamp& aProcessStartTime,
- LastSample& aLS);
+ mozilla::Maybe<uint64_t>& aLastSample);
void AddStoredMarker(ProfilerMarker* aStoredMarker);
// The following two methods are not signal safe! They delete markers.
void DeleteExpiredStoredMarkers();
void Reset();
+ // Access an entry in the buffer.
+ ProfileBufferEntry& GetEntry(uint64_t aPosition) const
+ {
+ return mEntries[aPosition & mEntryIndexMask];
+ }
+
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
private:
- mozilla::Maybe<uint32_t> FindLastSampleOfThread(int aThreadId, const LastSample& aLS) const;
-
-public:
- // Circular buffer 'Keep One Slot Open' implementation for simplicity
+ // The storage that backs our buffer. Holds mEntrySize entries.
+ // All accesses to entries in mEntries need to go through GetEntry(), which
+ // translates the given buffer position from the near-infinite uint64_t space
+ // into the entry storage space.
mozilla::UniquePtr<ProfileBufferEntry[]> mEntries;
- // Points to the next entry we will write to, which is also the one at which
- // we need to stop reading.
- uint32_t mWritePos;
+ // A mask such that pos & mEntryIndexMask == pos % mEntrySize.
+ uint32_t mEntryIndexMask;
- // Points to the entry at which we can start reading.
- uint32_t mReadPos;
+public:
+ // mRangeStart and mRangeEnd are uint64_t values that strictly advance and
+ // never wrap around. mRangeEnd is always greater than or equal to
+ // mRangeStart, but never gets more than mEntrySize steps ahead of
+ // mRangeStart, because we can only store a fixed number of entries in the
+ // buffer. Once the entire buffer is in use, adding a new entry will evict an
+ // entry from the front of the buffer (and increase mRangeStart).
+ // In other words, the following conditions hold true at all times:
+ // (1) mRangeStart <= mRangeEnd
+ // (2) mRangeEnd - mRangeStart <= mEntrySize
+ //
+ // If there are no live entries, then mRangeStart == mRangeEnd.
+ // Otherwise, mRangeStart is the first live entry and mRangeEnd is one past
+ // the last live entry, and also the position at which the next entry will be
+ // added.
+ // (mRangeEnd - mRangeStart) always gives the number of live entries.
+ uint64_t mRangeStart;
+ uint64_t mRangeEnd;
- // The number of entries in our buffer.
+ // The number of entries in our buffer. Always a power of two.
uint32_t mEntrySize;
- // How many times mWritePos has wrapped around.
- uint32_t mGeneration;
-
// Markers that marker entries in the buffer might refer to.
ProfilerMarkerLinkedList mStoredMarkers;
};
/**
* Helper type used to implement ProfilerStackCollector. This type is used as
* the collector for MergeStacks by ProfileBuffer. It holds a reference to the
* buffer, as well as additional feature flags which are needed to control the
* data collection strategy
*/
class ProfileBufferCollector final : public ProfilerStackCollector
{
public:
- ProfileBufferCollector(ProfileBuffer& aBuf, uint32_t aFeatures)
+ ProfileBufferCollector(ProfileBuffer& aBuf, uint32_t aFeatures,
+ uint64_t aSamplePos)
: mBuf(aBuf)
+ , mSamplePositionInBuffer(aSamplePos)
, mFeatures(aFeatures)
{}
- virtual mozilla::Maybe<uint32_t> Generation() override;
+ mozilla::Maybe<uint64_t> SamplePositionInBuffer() override
+ {
+ return mozilla::Some(mSamplePositionInBuffer);
+ }
+
+ mozilla::Maybe<uint64_t> BufferRangeStart() override
+ {
+ return mozilla::Some(mBuf.mRangeStart);
+ }
+
virtual void CollectNativeLeafAddr(void* aAddr) override;
virtual void CollectJitReturnAddr(void* aAddr) override;
virtual void CollectWasmFrame(const char* aLabel) override;
virtual void CollectPseudoEntry(const js::ProfileEntry& aEntry) override;
private:
ProfileBuffer& mBuf;
+ uint64_t mSamplePositionInBuffer;
uint32_t mFeatures;
};
#endif
--- a/tools/profiler/core/ProfileBufferEntry.cpp
+++ b/tools/profiler/core/ProfileBufferEntry.cpp
@@ -568,32 +568,35 @@ static void WriteSample(SpliceableJSONWr
if (aSample.mUSS.isSome()) {
writer.DoubleElement(USS, *aSample.mUSS);
}
}
class EntryGetter
{
public:
- explicit EntryGetter(const ProfileBuffer& aBuffer)
- : mEntries(aBuffer.mEntries.get())
- , mReadPos(aBuffer.mReadPos)
- , mWritePos(aBuffer.mWritePos)
- , mEntrySize(aBuffer.mEntrySize)
- {}
+ explicit EntryGetter(const ProfileBuffer& aBuffer,
+ uint64_t aInitialReadPos = 0)
+ : mBuffer(aBuffer)
+ , mReadPos(aBuffer.mRangeStart)
+ {
+ if (aInitialReadPos != 0) {
+ MOZ_RELEASE_ASSERT(aInitialReadPos >= aBuffer.mRangeStart &&
+ aInitialReadPos <= aBuffer.mRangeEnd);
+ mReadPos = aInitialReadPos;
+ }
+ }
- bool Has() const { return mReadPos != mWritePos; }
- const ProfileBufferEntry& Get() const { return mEntries[mReadPos]; }
- void Next() { mReadPos = (mReadPos + 1) % mEntrySize; }
+ bool Has() const { return mReadPos != mBuffer.mRangeEnd; }
+ const ProfileBufferEntry& Get() const { return mBuffer.GetEntry(mReadPos); }
+ void Next() { mReadPos++; }
private:
- const ProfileBufferEntry* const mEntries;
- uint32_t mReadPos;
- const uint32_t mWritePos;
- const uint32_t mEntrySize;
+ const ProfileBuffer& mBuffer;
+ uint64_t mReadPos;
};
// The following grammar shows legal sequences of profile buffer entries.
// The sequences beginning with a ThreadId entry are known as "samples".
//
// (
// (
// ThreadId
@@ -995,90 +998,65 @@ ProfileBuffer::StreamPausedRangesToJSON(
currentPauseStartTime, Nothing());
}
if (currentCollectionStartTime) {
AddPausedRange(aWriter, "collecting",
currentCollectionStartTime, Nothing());
}
}
-Maybe<uint32_t>
-ProfileBuffer::FindLastSampleOfThread(int aThreadId, const LastSample& aLS)
- const
-{
- // |aLS| has a valid generation number if either it matches the buffer's
- // generation, or is one behind the buffer's generation, since the buffer's
- // generation is incremented on wraparound. There's no ambiguity relative to
- // ProfileBuffer::reset, since that increments mGeneration by two.
- if (aLS.mGeneration == mGeneration ||
- (mGeneration > 0 && aLS.mGeneration == mGeneration - 1)) {
- if (!aLS.mPos) {
- // There's no record of |aLS|'s thread ever having recorded a sample in
- // the buffer.
- return Nothing();
- }
-
- uint32_t ix = *aLS.mPos;
-
- // It might be that the sample has since been overwritten, so check that it
- // is still valid.
- MOZ_RELEASE_ASSERT(0 <= ix && ix < mEntrySize);
- ProfileBufferEntry& entry = mEntries[ix];
- bool isStillValid = entry.IsThreadId() && entry.u.mInt == aThreadId;
- return isStillValid ? Some(ix) : Nothing();
- }
-
- // |aLS| denotes a sample which is older than either two wraparounds or one
- // call to ProfileBuffer::reset. In either case it is no longer valid.
- MOZ_ASSERT(aLS.mGeneration <= mGeneration - 2);
- return Nothing();
-}
-
bool
ProfileBuffer::DuplicateLastSample(int aThreadId,
const TimeStamp& aProcessStartTime,
- LastSample& aLS)
+ Maybe<uint64_t>& aLastSample)
{
- Maybe<uint32_t> maybeLastSampleStartPos =
- FindLastSampleOfThread(aThreadId, aLS);
- if (!maybeLastSampleStartPos) {
+ if (aLastSample && *aLastSample < mRangeStart) {
+ // The last sample is no longer within the buffer range, so we cannot use
+ // it. Reset the stored buffer position to Nothing().
+ aLastSample.reset();
+ }
+
+ if (!aLastSample) {
return false;
}
- uint32_t lastSampleStartPos = *maybeLastSampleStartPos;
+ uint64_t lastSampleStartPos = *aLastSample;
- MOZ_ASSERT(mEntries[lastSampleStartPos].IsThreadId() &&
- mEntries[lastSampleStartPos].u.mInt == aThreadId);
+ MOZ_RELEASE_ASSERT(GetEntry(lastSampleStartPos).IsThreadId() &&
+ GetEntry(lastSampleStartPos).u.mInt == aThreadId);
- AddThreadIdEntry(aThreadId, &aLS);
+ aLastSample = Some(AddThreadIdEntry(aThreadId));
+
+ EntryGetter e(*this, lastSampleStartPos + 1);
// Go through the whole entry and duplicate it, until we find the next one.
- for (uint32_t readPos = (lastSampleStartPos + 1) % mEntrySize;
- readPos != mWritePos;
- readPos = (readPos + 1) % mEntrySize) {
- switch (mEntries[readPos].GetKind()) {
+ while (e.Has()) {
+ switch (e.Get().GetKind()) {
case ProfileBufferEntry::Kind::Pause:
case ProfileBufferEntry::Kind::Resume:
case ProfileBufferEntry::Kind::CollectionStart:
case ProfileBufferEntry::Kind::CollectionEnd:
case ProfileBufferEntry::Kind::ThreadId:
// We're done.
return true;
case ProfileBufferEntry::Kind::Time:
// Copy with new time
AddEntry(ProfileBufferEntry::Time(
(TimeStamp::Now() - aProcessStartTime).ToMilliseconds()));
break;
case ProfileBufferEntry::Kind::Marker:
// Don't copy markers
break;
- default:
+ default: {
// Copy anything else we don't know about.
- AddEntry(mEntries[readPos]);
+ ProfileBufferEntry entry = e.Get();
+ AddEntry(entry);
break;
+ }
}
+ e.Next();
}
return true;
}
// END ProfileBuffer
////////////////////////////////////////////////////////////////////////
--- a/tools/profiler/core/ProfilerMarker.h
+++ b/tools/profiler/core/ProfilerMarker.h
@@ -25,23 +25,26 @@ public:
int aThreadId,
mozilla::UniquePtr<ProfilerMarkerPayload>
aPayload = nullptr,
double aTime = 0)
: mMarkerName(strdup(aMarkerName))
, mPayload(Move(aPayload))
, mNext{nullptr}
, mTime(aTime)
- , mGenID{0}
+ , mPositionInBuffer{0}
, mThreadId{aThreadId}
{}
- void SetGeneration(uint32_t aGenID) { mGenID = aGenID; }
+ void SetPositionInBuffer(uint64_t aPosition) { mPositionInBuffer = aPosition; }
- bool HasExpired(uint32_t aGenID) const { return mGenID + 2 <= aGenID; }
+ bool HasExpired(uint64_t aBufferRangeStart) const
+ {
+ return mPositionInBuffer < aBufferRangeStart;
+ }
double GetTime() const { return mTime; }
int GetThreadId() const { return mThreadId; }
void StreamJSON(SpliceableJSONWriter& aWriter,
const mozilla::TimeStamp& aProcessStartTime,
UniqueStacks& aUniqueStacks) const
@@ -67,17 +70,17 @@ public:
aWriter.EndArray();
}
private:
mozilla::UniqueFreePtr<char> mMarkerName;
mozilla::UniquePtr<ProfilerMarkerPayload> mPayload;
ProfilerMarker* mNext;
double mTime;
- uint32_t mGenID;
+ uint64_t mPositionInBuffer;
int mThreadId;
};
template<typename T>
class ProfilerLinkedList
{
public:
ProfilerLinkedList()
--- a/tools/profiler/core/ThreadInfo.h
+++ b/tools/profiler/core/ThreadInfo.h
@@ -195,17 +195,17 @@ public:
void NotifyUnregistered() { mUnregisterTime = TimeStamp::Now(); }
PlatformData* GetPlatformData() const { return mPlatformData.get(); }
void* StackTop() const { return mStackTop; }
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
- ProfileBuffer::LastSample& LastSample() { return mLastSample; }
+ mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
private:
mozilla::UniqueFreePtr<char> mName;
mozilla::TimeStamp mRegisterTime;
mozilla::TimeStamp mUnregisterTime;
const bool mIsMainThread;
nsCOMPtr<nsIEventTarget> mThread;
@@ -375,19 +375,20 @@ private:
//
enum {
INACTIVE = 0,
ACTIVE_REQUESTED = 1,
ACTIVE = 2,
INACTIVE_REQUESTED = 3,
} mJSSampling;
- // When sampling, this holds the generation number and offset in
- // ActivePS::mBuffer of the most recent sample for this thread.
- ProfileBuffer::LastSample mLastSample;
+ // When sampling, this holds the position in ActivePS::mBuffer of the most
+ // recent sample for this thread, or Nothing() if there is no sample for this
+ // thread in the buffer.
+ mozilla::Maybe<uint64_t> mLastSample;
};
void
StreamSamplesAndMarkers(const char* aName, int aThreadId,
const ProfileBuffer& aBuffer,
SpliceableJSONWriter& aWriter,
const mozilla::TimeStamp& aProcessStartTime,
const TimeStamp& aRegisterTime,
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -44,16 +44,17 @@
#include "ProfilerIOInterposeObserver.h"
#include "mozilla/AutoProfilerLabel.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/Scheduler.h"
#include "mozilla/StackWalk.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/TimeStamp.h"
+#include "mozilla/Tuple.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "ThreadInfo.h"
#include "nsIHttpProtocolHandler.h"
#include "nsIObserverService.h"
#include "nsIXULAppInfo.h"
#include "nsIXULRuntime.h"
#include "nsDirectoryServiceUtils.h"
@@ -772,43 +773,43 @@ MergeStacks(uint32_t aFeatures, bool aIs
js::ProfileEntry* pseudoEntries = racyInfo->entries;
uint32_t pseudoCount = racyInfo->stackSize();
JSContext* context = aThreadInfo.mContext;
// Make a copy of the JS stack into a JSFrame array. This is necessary since,
// like the native stack, the JS stack is iterated youngest-to-oldest and we
// need to iterate oldest-to-youngest when adding entries to aInfo.
- // Synchronous sampling reports an invalid buffer generation to
- // ProfilingFrameIterator to avoid incorrectly resetting the generation of
- // sampled JIT entries inside the JS engine. See note below concerning 'J'
- // entries.
- uint32_t startBufferGen = UINT32_MAX;
- if (!aIsSynchronous && aCollector.Generation().isSome()) {
- startBufferGen = *aCollector.Generation();
+ // Non-periodic sampling passes Nothing() as the buffer write position to
+ // ProfilingFrameIterator to avoid incorrectly resetting the buffer position
+ // of sampled JIT entries inside the JS engine.
+ Maybe<uint64_t> samplePosInBuffer;
+ if (!aIsSynchronous) {
+ // aCollector.SamplePositionInBuffer() will return Nothing() when
+ // profiler_suspend_and_sample_thread is called from the background hang
+ // reporter.
+ samplePosInBuffer = aCollector.SamplePositionInBuffer();
}
uint32_t jsCount = 0;
JS::ProfilingFrameIterator::Frame jsFrames[MAX_JS_FRAMES];
// Only walk jit stack if profiling frame iterator is turned on.
if (context && JS::IsProfilingEnabledForContext(context)) {
AutoWalkJSStack autoWalkJSStack;
const uint32_t maxFrames = ArrayLength(jsFrames);
if (autoWalkJSStack.walkAllowed) {
JS::ProfilingFrameIterator::RegisterState registerState;
registerState.pc = aRegs.mPC;
registerState.sp = aRegs.mSP;
registerState.lr = aRegs.mLR;
registerState.fp = aRegs.mFP;
- JS::ProfilingFrameIterator jsIter(context, registerState,
- startBufferGen);
+ JS::ProfilingFrameIterator jsIter(context, registerState, samplePosInBuffer);
for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) {
- // See note below regarding 'J' entries.
if (aIsSynchronous || jsIter.isWasm()) {
uint32_t extracted =
jsIter.extractStack(jsFrames, jsCount, maxFrames);
jsCount += extracted;
if (jsCount == maxFrames) {
break;
}
} else {
@@ -945,24 +946,23 @@ MergeStacks(uint32_t aFeatures, bool aIs
}
if (nativeIndex >= 0) {
nativeIndex--;
}
}
// Update the JS context with the current profile sample buffer generation.
//
- // Do not do this for synchronous samples, which use their own
- // ProfileBuffers instead of the global one in CorePS.
- if (!aIsSynchronous && context && aCollector.Generation().isSome()) {
- MOZ_ASSERT(*aCollector.Generation() >= startBufferGen);
- uint32_t lapCount = *aCollector.Generation() - startBufferGen;
- JS::UpdateJSContextProfilerSampleBufferGen(context,
- *aCollector.Generation(),
- lapCount);
+ // Only do this for periodic samples. We don't want to do this for
+ // synchronous samples, and we also don't want to do it for calls to
+ // profiler_suspend_and_sample_thread() from the background hang reporter -
+ // in that case, aCollector.BufferRangeStart() will return Nothing().
+ if (!aIsSynchronous && context && aCollector.BufferRangeStart()) {
+ uint64_t bufferRangeStart = *aCollector.BufferRangeStart();
+ JS::SetJSContextProfilerSampleBufferRangeStart(context, bufferRangeStart);
}
}
#if defined(GP_OS_windows)
static HANDLE GetThreadHandle(PlatformData* aData);
#endif
#if defined(USE_FRAME_POINTER_STACK_WALK) || defined(USE_MOZ_STACK_WALK)
@@ -1270,29 +1270,33 @@ DoNativeBacktrace(PSLockRef aLock, const
// ActivePS's ProfileBuffer. (This should only be called from DoSyncSample()
// and DoPeriodicSample().)
//
// The grammar for entry sequences is in a comment above
// ProfileBuffer::StreamSamplesToJSON.
static inline void
DoSharedSample(PSLockRef aLock, bool aIsSynchronous,
ThreadInfo& aThreadInfo, const TimeStamp& aNow,
- const Registers& aRegs, ProfileBuffer::LastSample* aLS,
+ const Registers& aRegs, Maybe<uint64_t>* aLastSample,
ProfileBuffer& aBuffer)
{
// WARNING: this function runs within the profiler's "critical section".
MOZ_RELEASE_ASSERT(ActivePS::Exists(aLock));
- aBuffer.AddThreadIdEntry(aThreadInfo.ThreadId(), aLS);
+ uint64_t samplePos = aBuffer.AddThreadIdEntry(aThreadInfo.ThreadId());
+ if (aLastSample) {
+ *aLastSample = Some(samplePos);
+ }
TimeDuration delta = aNow - CorePS::ProcessStartTime();
aBuffer.AddEntry(ProfileBufferEntry::Time(delta.ToMilliseconds()));
- ProfileBufferCollector collector(aBuffer, ActivePS::Features(aLock));
+ ProfileBufferCollector collector(aBuffer, ActivePS::Features(aLock),
+ samplePos);
NativeStack nativeStack;
#if defined(HAVE_NATIVE_UNWIND)
if (ActivePS::FeatureStackWalk(aLock)) {
DoNativeBacktrace(aLock, aThreadInfo, aRegs, nativeStack);
MergeStacks(ActivePS::Features(aLock), aIsSynchronous, aThreadInfo, aRegs,
nativeStack, collector);
} else
@@ -2691,19 +2695,18 @@ profiler_get_buffer_info()
PSAutoLock lock(gPSMutex);
if (!ActivePS::Exists(lock)) {
return Nothing();
}
return Some(ProfilerBufferInfo {
- ActivePS::Buffer(lock).mWritePos,
- ActivePS::Buffer(lock).mReadPos,
- ActivePS::Buffer(lock).mGeneration,
+ ActivePS::Buffer(lock).mRangeStart,
+ ActivePS::Buffer(lock).mRangeEnd,
ActivePS::Entries(lock)
});
}
static void
PollJSSamplingForCurrentThread()
{
MOZ_RELEASE_ASSERT(CorePS::Exists());
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -491,19 +491,19 @@ NS_IMETHODIMP
nsProfiler::GetBufferInfo(uint32_t* aCurrentPosition, uint32_t* aTotalSize,
uint32_t* aGeneration)
{
MOZ_ASSERT(aCurrentPosition);
MOZ_ASSERT(aTotalSize);
MOZ_ASSERT(aGeneration);
Maybe<ProfilerBufferInfo> info = profiler_get_buffer_info();
if (info) {
- *aCurrentPosition = info->mWritePosition;
+ *aCurrentPosition = info->mRangeEnd % info->mEntryCount;
*aTotalSize = info->mEntryCount;
- *aGeneration = info->mGeneration;
+ *aGeneration = info->mRangeEnd / info->mEntryCount;
} else {
*aCurrentPosition = 0;
*aTotalSize = 0;
*aGeneration = 0;
}
return NS_OK;
}
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -302,17 +302,18 @@ int profiler_current_thread_id();
// An object of this class is passed to profiler_suspend_and_sample_thread().
// For each stack frame, one of the Collect methods will be called.
class ProfilerStackCollector
{
public:
// Some collectors need to worry about possibly overwriting previous
// generations of data. If that's not an issue, this can return Nothing,
// which is the default behaviour.
- virtual mozilla::Maybe<uint32_t> Generation() { return mozilla::Nothing(); }
+ virtual mozilla::Maybe<uint64_t> SamplePositionInBuffer() { return mozilla::Nothing(); }
+ virtual mozilla::Maybe<uint64_t> BufferRangeStart() { return mozilla::Nothing(); }
// This method will be called once if the thread being suspended is the main
// thread. Default behaviour is to do nothing.
virtual void SetIsMainThread() {}
// WARNING: The target thread is suspended when the Collect methods are
// called. Do not try to allocate or acquire any locks, or you could
// deadlock. The target thread will have resumed by the time this function
@@ -344,19 +345,18 @@ using UniqueProfilerBacktrace =
mozilla::UniquePtr<ProfilerBacktrace, ProfilerBacktraceDestructor>;
// Immediately capture the current thread's call stack and return it. A no-op
// if the profiler is inactive or in privacy mode.
UniqueProfilerBacktrace profiler_get_backtrace();
struct ProfilerBufferInfo
{
- uint32_t mWritePosition;
- uint32_t mReadPosition;
- uint32_t mGeneration;
+ uint64_t mRangeStart;
+ uint64_t mRangeEnd;
uint32_t mEntryCount;
};
// Get information about the current buffer status.
// Returns Nothing() if the profiler is inactive.
//
// This information may be useful to a user-interface displaying the current
// status of the profiler, allowing the user to get a sense for how fast the
--- a/tools/profiler/tests/gtest/GeckoProfiler.cpp
+++ b/tools/profiler/tests/gtest/GeckoProfiler.cpp
@@ -189,32 +189,30 @@ TEST(GeckoProfiler, EnsureStarted)
{
// Active -> Active with same settings
// First, write some samples into the buffer.
PR_Sleep(PR_MillisecondsToInterval(500));
Maybe<ProfilerBufferInfo> info1 = profiler_get_buffer_info();
- ASSERT_TRUE(info1->mGeneration > 0 || info1->mWritePosition > 0);
+ ASSERT_TRUE(info1->mRangeEnd > 0);
// Call profiler_ensure_started with the same settings as before.
// This operation must not clear our buffer!
profiler_ensure_started(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
features, filters, MOZ_ARRAY_LENGTH(filters));
ActiveParamsCheck(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
features, filters, MOZ_ARRAY_LENGTH(filters));
// Check that our position in the buffer stayed the same or advanced.
// In particular, it shouldn't have reverted to the start.
Maybe<ProfilerBufferInfo> info2 = profiler_get_buffer_info();
- ASSERT_TRUE(info2->mGeneration >= info1->mGeneration);
- ASSERT_TRUE(info2->mGeneration > info1->mGeneration ||
- info2->mWritePosition >= info1->mWritePosition);
+ ASSERT_TRUE(info2->mRangeEnd >= info1->mRangeEnd);
}
{
// Active -> Active with *different* settings
Maybe<ProfilerBufferInfo> info1 = profiler_get_buffer_info();
// Call profiler_ensure_started with a different feature set than the one it's
@@ -224,19 +222,17 @@ TEST(GeckoProfiler, EnsureStarted)
profiler_ensure_started(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
differentFeatures,
filters, MOZ_ARRAY_LENGTH(filters));
ActiveParamsCheck(PROFILER_DEFAULT_ENTRIES, PROFILER_DEFAULT_INTERVAL,
differentFeatures, filters, MOZ_ARRAY_LENGTH(filters));
Maybe<ProfilerBufferInfo> info2 = profiler_get_buffer_info();
- ASSERT_TRUE(info2->mGeneration <= info1->mGeneration);
- ASSERT_TRUE(info2->mGeneration < info1->mGeneration ||
- info2->mWritePosition < info1->mWritePosition);
+ ASSERT_TRUE(info2->mRangeEnd < info1->mRangeEnd);
}
{
// Active -> Inactive
profiler_stop();
InactiveFeaturesAndParamsCheck();
@@ -379,27 +375,27 @@ TEST(GeckoProfiler, Pause)
features, filters, MOZ_ARRAY_LENGTH(filters));
ASSERT_TRUE(!profiler_is_paused());
// Check that we are writing samples while not paused.
Maybe<ProfilerBufferInfo> info1 = profiler_get_buffer_info();
PR_Sleep(PR_MillisecondsToInterval(500));
Maybe<ProfilerBufferInfo> info2 = profiler_get_buffer_info();
- ASSERT_TRUE(info1->mWritePosition != info2->mWritePosition);
+ ASSERT_TRUE(info1->mRangeEnd != info2->mRangeEnd);
profiler_pause();
ASSERT_TRUE(profiler_is_paused());
// Check that we are not writing samples while paused.
info1 = profiler_get_buffer_info();
PR_Sleep(PR_MillisecondsToInterval(500));
info2 = profiler_get_buffer_info();
- ASSERT_TRUE(info1->mWritePosition == info2->mWritePosition);
+ ASSERT_TRUE(info1->mRangeEnd == info2->mRangeEnd);
profiler_resume();
ASSERT_TRUE(!profiler_is_paused());
profiler_stop();
ASSERT_TRUE(!profiler_is_paused());
--- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp
+++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp
@@ -21,59 +21,56 @@ TEST(ThreadProfile, Initialization) {
// Make sure we can record one entry and read it
TEST(ThreadProfile, InsertOneEntry) {
int tid = 1000;
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
auto pb = MakeUnique<ProfileBuffer>(10);
pb->AddEntry(ProfileBufferEntry::Time(123.1));
- ASSERT_TRUE(pb->mEntries != nullptr);
- ASSERT_TRUE(pb->mEntries[pb->mReadPos].IsTime());
- ASSERT_TRUE(pb->mEntries[pb->mReadPos].u.mDouble == 123.1);
+ ASSERT_TRUE(pb->GetEntry(pb->mRangeStart).IsTime());
+ ASSERT_TRUE(pb->GetEntry(pb->mRangeStart).u.mDouble == 123.1);
}
// See if we can insert some entries
TEST(ThreadProfile, InsertEntriesNoWrap) {
int tid = 1000;
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
auto pb = MakeUnique<ProfileBuffer>(100);
int test_size = 50;
for (int i = 0; i < test_size; i++) {
pb->AddEntry(ProfileBufferEntry::Time(i));
}
- ASSERT_TRUE(pb->mEntries != nullptr);
- uint32_t readPos = pb->mReadPos;
- while (readPos != pb->mWritePos) {
- ASSERT_TRUE(pb->mEntries[readPos].IsTime());
- ASSERT_TRUE(pb->mEntries[readPos].u.mDouble == readPos);
- readPos = (readPos + 1) % pb->mEntrySize;
+ uint64_t readPos = pb->mRangeStart;
+ while (readPos != pb->mRangeEnd) {
+ ASSERT_TRUE(pb->GetEntry(readPos).IsTime());
+ ASSERT_TRUE(pb->GetEntry(readPos).u.mDouble == readPos);
+ readPos++;
}
}
-// See if wrapping works as it should in the basic case
+// See if evicting works as it should in the basic case
TEST(ThreadProfile, InsertEntriesWrap) {
int tid = 1000;
- // we can fit only 24 entries in this buffer because of the empty slot
- int entries = 24;
- int buffer_size = entries + 1;
+ int entries = 32;
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
ThreadInfo info("testThread", tid, true, mainThread, nullptr);
- auto pb = MakeUnique<ProfileBuffer>(buffer_size);
+ auto pb = MakeUnique<ProfileBuffer>(entries);
+ ASSERT_TRUE(pb->mRangeStart == 0);
+ ASSERT_TRUE(pb->mRangeEnd == 0);
int test_size = 43;
for (int i = 0; i < test_size; i++) {
pb->AddEntry(ProfileBufferEntry::Time(i));
}
- ASSERT_TRUE(pb->mEntries != nullptr);
- uint32_t readPos = pb->mReadPos;
- int ctr = 0;
- while (readPos != pb->mWritePos) {
- ASSERT_TRUE(pb->mEntries[readPos].IsTime());
- // the first few entries were discarded when we wrapped
- ASSERT_TRUE(pb->mEntries[readPos].u.mDouble == ctr + (test_size - entries));
- ctr++;
- readPos = (readPos + 1) % pb->mEntrySize;
+ // We inserted 11 more entries than fit in the buffer, so the first 11 entries
+ // should have been evicted, and the range start should have increased to 11.
+ ASSERT_TRUE(pb->mRangeStart == 11);
+ uint64_t readPos = pb->mRangeStart;
+ while (readPos != pb->mRangeEnd) {
+ ASSERT_TRUE(pb->GetEntry(readPos).IsTime());
+ ASSERT_TRUE(pb->GetEntry(readPos).u.mDouble == readPos);
+ readPos++;
}
}