Bug 1328385 - Replace the profile entry tag with an enum. r=kvijayan.

--HG--
extra : rebase_source : 4e8de98fc4e89a772e8fdc2261e0ebb8d30e2642
This commit is contained in:
Julian Seward 2017-02-07 16:47:28 +01:00
Родитель 2835870c9c
Коммит 11b9557938
4 изменённых файлов: 135 добавлений и 115 удалений

Просмотреть файл

@ -31,67 +31,50 @@ using mozilla::JSONWriter;
ProfileEntry::ProfileEntry()
: mTagData(nullptr)
, mTagName(0)
, mKind(Kind::INVALID)
{ }
// aTagData must not need release (i.e. be a string from the text segment)
ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
ProfileEntry::ProfileEntry(Kind aKind, const char *aTagData)
: mTagData(aTagData)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
ProfileEntry::ProfileEntry(Kind aKind, ProfilerMarker *aTagMarker)
: mTagMarker(aTagMarker)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
ProfileEntry::ProfileEntry(Kind aKind, void *aTagPtr)
: mTagPtr(aTagPtr)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, double aTagDouble)
ProfileEntry::ProfileEntry(Kind aKind, double aTagDouble)
: mTagDouble(aTagDouble)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
ProfileEntry::ProfileEntry(Kind aKind, uintptr_t aTagOffset)
: mTagOffset(aTagOffset)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
ProfileEntry::ProfileEntry(Kind aKind, Address aTagAddress)
: mTagAddress(aTagAddress)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, int aTagInt)
ProfileEntry::ProfileEntry(Kind aKind, int aTagInt)
: mTagInt(aTagInt)
, mTagName(aTagName)
, mKind(aKind)
{ }
ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
ProfileEntry::ProfileEntry(Kind aKind, char aTagChar)
: mTagChar(aTagChar)
, mTagName(aTagName)
, mKind(aKind)
{ }
bool ProfileEntry::is_ent_hint(char hintChar) {
return mTagName == 'h' && mTagChar == hintChar;
}
bool ProfileEntry::is_ent_hint() {
return mTagName == 'h';
}
bool ProfileEntry::is_ent(char tagChar) {
return mTagName == tagChar;
}
void* ProfileEntry::get_tagPtr() {
// No consistency checking. Oh well.
return mTagPtr;
}
// END ProfileEntry
////////////////////////////////////////////////////////////////////////
@ -607,40 +590,40 @@ void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThre
while (readPos != mWritePos) {
ProfileEntry entry = mEntries[readPos];
if (entry.mTagName == 'T') {
if (entry.isThreadId()) {
currentThreadID = entry.mTagInt;
currentTime.reset();
int readAheadPos = (readPos + 1) % mEntrySize;
if (readAheadPos != mWritePos) {
ProfileEntry readAheadEntry = mEntries[readAheadPos];
if (readAheadEntry.mTagName == 't') {
if (readAheadEntry.isTime()) {
currentTime = Some(readAheadEntry.mTagDouble);
}
}
}
if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) {
switch (entry.mTagName) {
case 'r':
switch (entry.kind()) {
case ProfileEntry::Kind::Responsiveness:
if (sample.isSome()) {
sample->mResponsiveness = Some(entry.mTagDouble);
}
break;
case 'R':
case ProfileEntry::Kind::ResidentMemory:
if (sample.isSome()) {
sample->mRSS = Some(entry.mTagDouble);
}
break;
case 'U':
case ProfileEntry::Kind::UnsharedMemory:
if (sample.isSome()) {
sample->mUSS = Some(entry.mTagDouble);
}
break;
case 'f':
case ProfileEntry::Kind::FrameNumber:
if (sample.isSome()) {
sample->mFrameNumber = Some(entry.mTagInt);
}
break;
case 's':
case ProfileEntry::Kind::Sample:
{
// end the previous sample if there was one
if (sample.isSome()) {
@ -660,49 +643,51 @@ void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThre
int framePos = (readPos + 1) % mEntrySize;
ProfileEntry frame = mEntries[framePos];
while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') {
while (framePos != mWritePos && !frame.isSample() && !frame.isThreadId()) {
int incBy = 1;
frame = mEntries[framePos];
// Read ahead to the next tag, if it's a 'd' tag process it now
// Read ahead to the next tag, if it's an EmbeddedString
// tag process it now
const char* tagStringData = frame.mTagData;
int readAheadPos = (framePos + 1) % mEntrySize;
// Make sure the string is always null terminated if it fills up
// DYNAMIC_MAX_STRING-2
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') {
if (readAheadPos != mWritePos && mEntries[readAheadPos].isEmbeddedString()) {
tagStringData = processDynamicTag(framePos, &incBy, tagBuff.get());
}
// Write one frame. It can have either
// 1. only location - 'l' containing a memory address
// 2. location and line number - 'c' followed by 'd's,
// an optional 'n' and an optional 'y'
// 3. a JIT return address - 'j' containing native code address
if (frame.mTagName == 'l') {
// 1. only location - a NativeLeafAddr containing a memory address
// 2. location and line number - a CodeLocation followed by
// EmbeddedStrings, an optional LineNumber and an
// optional Category
// 3. a JitReturnAddress containing a native code address
if (frame.isNativeLeafAddr()) {
// Bug 753041
// We need a double cast here to tell GCC that we don't want to sign
// extend 32-bit addresses starting with 0xFXXXXXX.
unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
snprintf(tagBuff.get(), DYNAMIC_MAX_STRING, "%#llx", pc);
stack.AppendFrame(UniqueStacks::OnStackFrameKey(tagBuff.get()));
} else if (frame.mTagName == 'c') {
} else if (frame.isCodeLocation()) {
UniqueStacks::OnStackFrameKey frameKey(tagStringData);
readAheadPos = (framePos + incBy) % mEntrySize;
if (readAheadPos != mWritePos &&
mEntries[readAheadPos].mTagName == 'n') {
mEntries[readAheadPos].isLineNumber()) {
frameKey.mLine = Some((unsigned) mEntries[readAheadPos].mTagInt);
incBy++;
}
readAheadPos = (framePos + incBy) % mEntrySize;
if (readAheadPos != mWritePos &&
mEntries[readAheadPos].mTagName == 'y') {
mEntries[readAheadPos].isCategory()) {
frameKey.mCategory = Some((unsigned) mEntries[readAheadPos].mTagInt);
incBy++;
}
stack.AppendFrame(frameKey);
} else if (frame.mTagName == 'J') {
} else if (frame.isJitReturnAddr()) {
// A JIT frame may expand to multiple frames due to inlining.
void* pc = frame.mTagPtr;
unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
@ -723,7 +708,9 @@ void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThre
sample->mStack = stack.GetOrAddIndex();
break;
}
}
default:
break;
} /* switch (entry.kind()) */
}
readPos = (readPos + 1) % mEntrySize;
}
@ -739,9 +726,9 @@ void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThre
int currentThreadID = -1;
while (readPos != mWritePos) {
ProfileEntry entry = mEntries[readPos];
if (entry.mTagName == 'T') {
if (entry.isThreadId()) {
currentThreadID = entry.mTagInt;
} else if (currentThreadID == aThreadId && entry.mTagName == 'm') {
} else if (currentThreadID == aThreadId && entry.isMarker()) {
const ProfilerMarker* marker = entry.getMarker();
if (marker->GetTime() >= aSinceTime) {
entry.getMarker()->StreamJSON(aWriter, aUniqueStacks);
@ -759,7 +746,7 @@ int ProfileBuffer::FindLastSampleOfThread(int aThreadId)
readPos != (mReadPos + mEntrySize - 1) % mEntrySize;
readPos = (readPos + mEntrySize - 1) % mEntrySize) {
ProfileEntry entry = mEntries[readPos];
if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) {
if (entry.isThreadId() && entry.mTagInt == aThreadId) {
return readPos;
}
}
@ -774,7 +761,7 @@ void ProfileBuffer::DuplicateLastSample(int aThreadId)
return;
}
MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T');
MOZ_ASSERT(mEntries[lastSampleStartPos].isThreadId());
addTag(mEntries[lastSampleStartPos]);
@ -782,20 +769,19 @@ void ProfileBuffer::DuplicateLastSample(int aThreadId)
for (int readPos = (lastSampleStartPos + 1) % mEntrySize;
readPos != mWritePos;
readPos = (readPos + 1) % mEntrySize) {
switch (mEntries[readPos].mTagName) {
case 'T':
switch (mEntries[readPos].kind()) {
case ProfileEntry::Kind::ThreadId:
// We're done.
return;
case 't':
case ProfileEntry::Kind::Time:
// Copy with new time
addTag(ProfileEntry('t', (mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()));
addTag(ProfileEntry::Time((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()));
break;
case 'm':
case ProfileEntry::Kind::Marker:
// Don't copy markers
break;
// Copy anything else we don't know about
// L, B, S, c, s, d, l, f, h, r, t, p
default:
// Copy anything else we don't know about
addTag(mEntries[readPos]);
break;
}

Просмотреть файл

@ -25,6 +25,22 @@
#include "mozilla/HashFunctions.h"
#include "mozilla/UniquePtr.h"
#define PROFILE_ENTRY_KIND_LIST(_) \
_(Category, int) \
_(CodeLocation, const char *) \
_(EmbeddedString, void *) \
_(FrameNumber, int) \
_(JitReturnAddr, void *) \
_(LineNumber, int) \
_(NativeLeafAddr, void *) \
_(Marker, ProfilerMarker *) \
_(ResidentMemory, double) \
_(Responsiveness, double) \
_(Sample, const char *) \
_(ThreadId, int) \
_(Time, double) \
_(UnsharedMemory, double)
// NB: Packing this structure has been shown to cause SIGBUS issues on ARM.
#ifndef __arm__
#pragma pack(push, 1)
@ -33,28 +49,46 @@
class ProfileEntry
{
public:
enum class Kind : uint8_t {
INVALID = 0,
# define DEF_ENUM_(k, t) k,
PROFILE_ENTRY_KIND_LIST(DEF_ENUM_)
# undef DEF_ENUM_
LIMIT
};
ProfileEntry();
private:
// aTagData must not need release (i.e. be a string from the text segment)
ProfileEntry(char aTagName, const char *aTagData);
ProfileEntry(char aTagName, void *aTagPtr);
ProfileEntry(char aTagName, ProfilerMarker *aTagMarker);
ProfileEntry(char aTagName, double aTagDouble);
ProfileEntry(char aTagName, uintptr_t aTagOffset);
ProfileEntry(char aTagName, Address aTagAddress);
ProfileEntry(char aTagName, int aTagLine);
ProfileEntry(char aTagName, char aTagChar);
bool is_ent_hint(char hintChar);
bool is_ent_hint();
bool is_ent(char tagName);
void* get_tagPtr();
ProfileEntry(Kind aKind, const char *aTagData);
ProfileEntry(Kind aKind, void *aTagPtr);
ProfileEntry(Kind aKind, ProfilerMarker *aTagMarker);
ProfileEntry(Kind aKind, double aTagDouble);
ProfileEntry(Kind aKind, uintptr_t aTagOffset);
ProfileEntry(Kind aKind, Address aTagAddress);
ProfileEntry(Kind aKind, int aTagLine);
ProfileEntry(Kind aKind, char aTagChar);
public:
# define DEF_MAKE_(k, t) \
static ProfileEntry k(t val) { return ProfileEntry(Kind::k, val); }
PROFILE_ENTRY_KIND_LIST(DEF_MAKE_)
# undef DEF_MAKE_
Kind kind() const { return mKind; }
bool hasKind(Kind k) const { return kind() == k; }
# define DEF_METHODS_(k, t) \
bool is##k() const { return hasKind(Kind::k); }
PROFILE_ENTRY_KIND_LIST(DEF_METHODS_)
# undef DEF_METHODS_
const ProfilerMarker* getMarker() {
MOZ_ASSERT(mTagName == 'm');
MOZ_ASSERT(isMarker());
return mTagMarker;
}
char getTagName() const { return mTagName; }
private:
FRIEND_TEST(ThreadProfile, InsertOneTag);
FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer);
@ -73,7 +107,7 @@ private:
int mTagInt;
char mTagChar;
};
char mTagName;
Kind mKind;
};
#ifndef __arm__

Просмотреть файл

@ -767,9 +767,9 @@ void PseudoStack::flushSamplerOnJSShutdown()
////////////////////////////////////////////////////////////////////////
static
void addDynamicTag(ThreadInfo& aInfo, char aTagName, const char* aStr)
void addDynamicCodeLocationTag(ThreadInfo& aInfo, const char* aStr)
{
aInfo.addTag(ProfileEntry(aTagName, ""));
aInfo.addTag(ProfileEntry::CodeLocation(""));
// Add one to store the null termination
size_t strLen = strlen(aStr) + 1;
for (size_t j = 0; j < strLen;) {
@ -782,7 +782,7 @@ void addDynamicTag(ThreadInfo& aInfo, char aTagName, const char* aStr)
memcpy(text, &aStr[j], len);
j += sizeof(void*)/sizeof(char);
// Cast to *((void**) to pass the text data to a void*
aInfo.addTag(ProfileEntry('d', *((void**)(&text[0]))));
aInfo.addTag(ProfileEntry::EmbeddedString(*((void**)(&text[0]))));
}
}
@ -797,14 +797,14 @@ void addPseudoEntry(volatile StackEntry& entry, ThreadInfo& aInfo,
int lineno = -1;
// First entry has tagName 's' (start)
// First entry has kind CodeLocation
// Check for magic pointer bit 1 to indicate copy
const char* sampleLabel = entry.label();
if (entry.isCopyLabel()) {
// Store the string using 1 or more 'd' (dynamic) tags
// Store the string using 1 or more EmbeddedString tags
// that will happen to the preceding tag
addDynamicTag(aInfo, 'c', sampleLabel);
addDynamicCodeLocationTag(aInfo, sampleLabel);
if (entry.isJs()) {
JSScript* script = entry.script();
if (script) {
@ -827,7 +827,7 @@ void addPseudoEntry(volatile StackEntry& entry, ThreadInfo& aInfo,
lineno = entry.line();
}
} else {
aInfo.addTag(ProfileEntry('c', sampleLabel));
aInfo.addTag(ProfileEntry::CodeLocation(sampleLabel));
// XXX: Bug 1010578. Don't assume a CPP entry and try to get the
// line for js entries as well.
@ -837,7 +837,7 @@ void addPseudoEntry(volatile StackEntry& entry, ThreadInfo& aInfo,
}
if (lineno != -1) {
aInfo.addTag(ProfileEntry('n', lineno));
aInfo.addTag(ProfileEntry::LineNumber(lineno));
}
uint32_t category = entry.category();
@ -845,7 +845,7 @@ void addPseudoEntry(volatile StackEntry& entry, ThreadInfo& aInfo,
MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY));
if (category) {
aInfo.addTag(ProfileEntry('y', (int)category));
aInfo.addTag(ProfileEntry::Category((int)category));
}
}
@ -928,7 +928,7 @@ mergeStacksIntoProfile(ThreadInfo& aInfo, TickSample* aSample,
}
// Start the sample with a root entry.
aInfo.addTag(ProfileEntry('s', "(root)"));
aInfo.addTag(ProfileEntry::Sample("(root)"));
// While the pseudo-stack array is ordered oldest-to-youngest, the JS and
// native arrays are ordered youngest-to-oldest. We must add frames to
@ -1012,7 +1012,7 @@ mergeStacksIntoProfile(ThreadInfo& aInfo, TickSample* aSample,
// Stringifying non-wasm JIT frames is delayed until streaming
// time. To re-lookup the entry in the JitcodeGlobalTable, we need to
// store the JIT code address ('J') in the circular buffer.
// store the JIT code address (OptInfoAddr) in the circular buffer.
//
// Note that we cannot do this when we are sychronously sampling the
// current thread; that is, when called from profiler_get_backtrace. The
@ -1020,16 +1020,16 @@ mergeStacksIntoProfile(ThreadInfo& aInfo, TickSample* aSample,
// amount of time, such as in nsRefreshDriver. Problematically, the
// stored backtrace may be alive across a GC during which the profiler
// itself is disabled. In that case, the JS engine is free to discard
// its JIT code. This means that if we inserted such 'J' entries into
// the buffer, nsRefreshDriver would now be holding on to a backtrace
// with stale JIT code return addresses.
// its JIT code. This means that if we inserted such OptInfoAddr entries
// into the buffer, nsRefreshDriver would now be holding on to a
// backtrace with stale JIT code return addresses.
if (aSample->isSamplingCurrentThread ||
jsFrame.kind == JS::ProfilingFrameIterator::Frame_Wasm) {
addDynamicTag(aInfo, 'c', jsFrame.label);
addDynamicCodeLocationTag(aInfo, jsFrame.label);
} else {
MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion ||
jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline);
aInfo.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress));
aInfo.addTag(ProfileEntry::JitReturnAddr(jsFrames[jsIndex].returnAddress));
}
jsIndex--;
@ -1041,7 +1041,7 @@ mergeStacksIntoProfile(ThreadInfo& aInfo, TickSample* aSample,
if (nativeStackAddr) {
MOZ_ASSERT(nativeIndex >= 0);
aInfo
.addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex]));
.addTag(ProfileEntry::NativeLeafAddr((void*)aNativeStack.pc_array[nativeIndex]));
}
if (nativeIndex >= 0) {
nativeIndex--;
@ -1299,7 +1299,7 @@ doSampleStackTrace(ThreadInfo& aInfo, TickSample* aSample,
#ifdef ENABLE_LEAF_DATA
if (aSample && aAddLeafAddresses) {
aInfo.addTag(ProfileEntry('l', (void*)aSample->pc));
aInfo.addTag(ProfileEntry::NativeLeafAddr((void*)aSample->pc));
}
#endif
}
@ -1316,10 +1316,10 @@ Sampler::InplaceTick(TickSample* sample)
{
ThreadInfo& currThreadInfo = *sample->threadInfo;
currThreadInfo.addTag(ProfileEntry('T', currThreadInfo.ThreadId()));
currThreadInfo.addTag(ProfileEntry::ThreadId(currThreadInfo.ThreadId()));
mozilla::TimeDuration delta = sample->timestamp - sStartTime;
currThreadInfo.addTag(ProfileEntry('t', delta.ToMilliseconds()));
currThreadInfo.addTag(ProfileEntry::Time(delta.ToMilliseconds()));
PseudoStack* stack = currThreadInfo.Stack();
@ -1341,27 +1341,27 @@ Sampler::InplaceTick(TickSample* sample)
while (pendingMarkersList && pendingMarkersList->peek()) {
ProfilerMarker* marker = pendingMarkersList->popHead();
currThreadInfo.addStoredMarker(marker);
currThreadInfo.addTag(ProfileEntry('m', marker));
currThreadInfo.addTag(ProfileEntry::Marker(marker));
}
}
if (currThreadInfo.GetThreadResponsiveness()->HasData()) {
mozilla::TimeDuration delta = currThreadInfo.GetThreadResponsiveness()->GetUnresponsiveDuration(sample->timestamp);
currThreadInfo.addTag(ProfileEntry('r', delta.ToMilliseconds()));
currThreadInfo.addTag(ProfileEntry::Responsiveness(delta.ToMilliseconds()));
}
// rssMemory is equal to 0 when we are not recording.
if (sample->rssMemory != 0) {
currThreadInfo.addTag(ProfileEntry('R', static_cast<double>(sample->rssMemory)));
currThreadInfo.addTag(ProfileEntry::ResidentMemory(static_cast<double>(sample->rssMemory)));
}
// ussMemory is equal to 0 when we are not recording.
if (sample->ussMemory != 0) {
currThreadInfo.addTag(ProfileEntry('U', static_cast<double>(sample->ussMemory)));
currThreadInfo.addTag(ProfileEntry::UnsharedMemory(static_cast<double>(sample->ussMemory)));
}
if (sLastFrameNumber != sFrameNumber) {
currThreadInfo.addTag(ProfileEntry('f', sFrameNumber));
currThreadInfo.addTag(ProfileEntry::FrameNumber(sFrameNumber));
sLastFrameNumber = sFrameNumber;
}
}

Просмотреть файл

@ -23,9 +23,9 @@ TEST(ThreadProfile, InsertOneTag) {
Thread::tid_t tid = 1000;
ThreadInfo info("testThread", tid, true, stack, nullptr);
RefPtr<ProfileBuffer> pb = new ProfileBuffer(10);
pb->addTag(ProfileEntry('t', 123.1));
pb->addTag(ProfileEntry::Time(123.1));
ASSERT_TRUE(pb->mEntries != nullptr);
ASSERT_TRUE(pb->mEntries[pb->mReadPos].mTagName == 't');
ASSERT_TRUE(pb->mEntries[pb->mReadPos].kind() == ProfileEntry::Kind::Time);
ASSERT_TRUE(pb->mEntries[pb->mReadPos].mTagDouble == 123.1);
}
@ -37,13 +37,13 @@ TEST(ThreadProfile, InsertTagsNoWrap) {
RefPtr<ProfileBuffer> pb = new ProfileBuffer(100);
int test_size = 50;
for (int i = 0; i < test_size; i++) {
pb->addTag(ProfileEntry('t', i));
pb->addTag(ProfileEntry::Time(i));
}
ASSERT_TRUE(pb->mEntries != nullptr);
int readPos = pb->mReadPos;
while (readPos != pb->mWritePos) {
ASSERT_TRUE(pb->mEntries[readPos].mTagName == 't');
ASSERT_TRUE(pb->mEntries[readPos].mTagInt == readPos);
ASSERT_TRUE(pb->mEntries[readPos].kind() == ProfileEntry::Kind::Time);
ASSERT_TRUE(pb->mEntries[readPos].mTagDouble == readPos);
readPos = (readPos + 1) % pb->mEntrySize;
}
}
@ -59,15 +59,15 @@ TEST(ThreadProfile, InsertTagsWrap) {
RefPtr<ProfileBuffer> pb = new ProfileBuffer(buffer_size);
int test_size = 43;
for (int i = 0; i < test_size; i++) {
pb->addTag(ProfileEntry('t', i));
pb->addTag(ProfileEntry::Time(i));
}
ASSERT_TRUE(pb->mEntries != nullptr);
int readPos = pb->mReadPos;
int ctr = 0;
while (readPos != pb->mWritePos) {
ASSERT_TRUE(pb->mEntries[readPos].mTagName == 't');
ASSERT_TRUE(pb->mEntries[readPos].kind() == ProfileEntry::Kind::Time);
// the first few tags were discarded when we wrapped
ASSERT_TRUE(pb->mEntries[readPos].mTagInt == ctr + (test_size - tags));
ASSERT_TRUE(pb->mEntries[readPos].mTagDouble == ctr + (test_size - tags));
ctr++;
readPos = (readPos + 1) % pb->mEntrySize;
}