Bug 867757 - Part 1: Allow markers to carry payload. r=aklotz

--HG--
extra : rebase_source : 31904aa5f084283bfc23ecfc53762d275967e34b
This commit is contained in:
Benoit Girard 2013-07-11 00:27:04 -04:00
parent 3a1e811e43
commit c2c963e37e
13 changed files with 388 additions and 73 deletions

View File

@ -181,10 +181,13 @@ void TableTicker::UnwinderTick(TickSample* sample)
// Marker(s) come before the sample
PseudoStack* stack = currThreadProfile.GetPseudoStack();
for (int i = 0; stack->getMarker(i) != NULL; i++) {
utb__addEntry( utb, ProfileEntry('m', stack->getMarker(i)) );
ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
while (pendingMarkersList && pendingMarkersList->peek()) {
ProfilerMarker* marker = pendingMarkersList->popHead();
stack->addStoredMarker(marker);
utb__addEntry( utb, ProfileEntry('m', marker) );
}
stack->mQueueClearMarker = true;
stack->updateGeneration(currThreadProfile.GetGenerationID());
bool recordSample = true;
if (mJankOnly) {

View File

@ -76,6 +76,7 @@ class TimeStamp;
// only recorded if a sample is collected while it is active, marker will always
// be collected.
#define PROFILER_MARKER(info) do {} while (0)
#define PROFILER_MARKER_PAYLOAD(info, payload) do {} while (0)
// Main thread specilization to avoid TLS lookup for performance critical use.
#define PROFILER_MAIN_THREAD_LABEL(name_space, info) do {} while (0)

View File

@ -18,12 +18,14 @@ class TimeStamp;
using mozilla::TimeStamp;
using mozilla::TimeDuration;
class ProfilerMarkerPayload;
// Returns a handle to pass on exit. This can check that we are popping the
// correct callstack.
inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress = NULL,
bool aCopy = false, uint32_t line = 0);
inline void mozilla_sampler_call_exit(void* handle);
inline void mozilla_sampler_add_marker(const char *aInfo);
inline void mozilla_sampler_add_marker(const char *aInfo, ProfilerMarkerPayload *aPayload = nullptr);
void mozilla_sampler_start(int aEntries, double aInterval,
const char** aFeatures, uint32_t aFeatureCount,

View File

@ -193,6 +193,7 @@ bool profiler_in_privacy_mode()
#define PROFILER_LABEL(name_space, info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__)
#define PROFILER_LABEL_PRINTF(name_space, info, ...) mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__, __VA_ARGS__)
#define PROFILER_MARKER(info) mozilla_sampler_add_marker(info)
#define PROFILER_MARKER_PAYLOAD(info, payload) mozilla_sampler_add_marker(info, payload)
#define PROFILER_MAIN_THREAD_LABEL(name_space, info) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__)
#define PROFILER_MAIN_THREAD_LABEL_PRINTF(name_space, info, ...) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__, __VA_ARGS__)
#define PROFILER_MAIN_THREAD_MARKER(info) MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla_sampler_add_marker(info)
@ -327,7 +328,7 @@ inline void mozilla_sampler_call_exit(void *aHandle)
stack->pop();
}
inline void mozilla_sampler_add_marker(const char *aMarker)
inline void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPayload)
{
if (!stack_key_initialized)
return;
@ -347,7 +348,7 @@ inline void mozilla_sampler_add_marker(const char *aMarker)
if (!stack) {
return;
}
stack->addMarker(aMarker);
stack->addMarker(aMarker, aPayload);
}
#endif /* ndef TOOLS_SPS_SAMPLER_H_ */

View File

@ -34,6 +34,11 @@ ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
, mTagName(aTagName)
{ }
ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
: mTagMarker(aTagMarker)
, mTagName(aTagName)
{ }
ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
: mTagPtr(aTagPtr)
, mTagName(aTagName)
@ -92,7 +97,9 @@ void ProfileEntry::log()
// mTagChar (char) h
// mTagFloat (double) r,t
switch (mTagName) {
case 'm': case 'c': case 's':
case 'm':
LOGF("%c \"%s\"", mTagName, mTagMarker->GetMarkerName()); break;
case 'c': case 's':
LOGF("%c \"%s\"", mTagName, mTagData); break;
case 'd': case 'l': case 'L': case 'S':
LOGF("%c %p", mTagName, mTagPtr); break;
@ -152,6 +159,7 @@ ThreadProfile::ThreadProfile(const char* aName, int aEntrySize,
, mStackTop(aStackTop)
{
mEntries = new ProfileEntry[mEntrySize];
mGeneration = 0;
}
ThreadProfile::~ThreadProfile()
@ -164,7 +172,11 @@ void ThreadProfile::addTag(ProfileEntry aTag)
{
// Called from signal, call only reentrant functions
mEntries[mWritePos] = aTag;
mWritePos = (mWritePos + 1) % mEntrySize;
mWritePos = mWritePos + 1;
if (mWritePos >= mEntrySize) {
mPendingGenerationFlush++;
mWritePos = mWritePos % mEntrySize;
}
if (mWritePos == mReadPos) {
// Keep one slot open
mEntries[mReadPos] = ProfileEntry();
@ -181,6 +193,8 @@ void ThreadProfile::addTag(ProfileEntry aTag)
void ThreadProfile::flush()
{
mLastFlushPos = mWritePos;
mGeneration += mPendingGenerationFlush;
mPendingGenerationFlush = 0;
}
// discards all of the entries since the last flush()
@ -236,6 +250,7 @@ void ThreadProfile::flush()
void ThreadProfile::erase()
{
mWritePos = mLastFlushPos;
mPendingGenerationFlush = 0;
}
char* ThreadProfile::processDynamicTag(int readPos,
@ -325,7 +340,7 @@ void ThreadProfile::BuildJSObject(Builder& b, typename Builder::ObjectHandle pro
typename Builder::RootedObject sample(b.context());
typename Builder::RootedArray frames(b.context());
typename Builder::RootedArray marker(b.context());
typename Builder::RootedArray markers(b.context());
int readPos = mReadPos;
while (readPos != mLastFlushPos) {
@ -353,16 +368,16 @@ void ThreadProfile::BuildJSObject(Builder& b, typename Builder::ObjectHandle pro
b.DefineProperty(sample, "frames", frames);
b.ArrayPush(samples, sample);
// Created lazily
marker = nullptr;
markers = nullptr;
break;
case 'm':
{
if (sample) {
if (!marker) {
marker = b.CreateArray();
b.DefineProperty(sample, "marker", marker);
if (!markers) {
markers = b.CreateArray();
b.DefineProperty(sample, "marker", markers);
}
b.ArrayPush(marker, tagStringData);
entry.getMarker()->BuildJSObject(b, markers);
}
}
break;

View File

@ -21,6 +21,7 @@ public:
// 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 aTagFloat);
ProfileEntry(char aTagName, uintptr_t aTagOffset);
ProfileEntry(char aTagName, Address aTagAddress);
@ -32,6 +33,10 @@ public:
bool is_ent(char tagName);
void* get_tagPtr();
void log();
const ProfilerMarker* getMarker() {
MOZ_ASSERT(mTagName == 'm');
return mTagMarker;
}
char getTagName() const { return mTagName; }
@ -41,6 +46,7 @@ private:
const char* mTagData;
char mTagChars[sizeof(void*)];
void* mTagPtr;
ProfilerMarker* mTagMarker;
double mTagFloat;
Address mTagAddress;
uintptr_t mTagOffset;
@ -77,6 +83,10 @@ public:
int ThreadId() const { return mThreadId; }
PlatformData* GetPlatformData() { return mPlatformData; }
int GetGenerationID() const { return mGeneration; }
bool HasGenerationExpired(int aGenID) {
return aGenID + 2 <= mGeneration;
}
void* GetStackTop() const { return mStackTop; }
private:
// Circular buffer 'Keep One Slot Open' implementation
@ -92,6 +102,8 @@ private:
int mThreadId;
bool mIsMainThread;
PlatformData* mPlatformData; // Platform specific data.
int mGeneration;
int mPendingGenerationFlush;
void* const mStackTop;
};

View File

@ -0,0 +1,28 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "ProfilerMarkers.h"
#include "gfxASurface.h"
ProfilerMarkerImagePayload::ProfilerMarkerImagePayload(gfxASurface *aImg)
: mImg(aImg)
{}
template<typename Builder>
typename Builder::Object
ProfilerMarkerImagePayload::preparePayloadImp(Builder& b)
{
typename Builder::RootedObject data(b.context(), b.CreateObject());
b.DefineProperty(data, "type", "innerHTML");
// TODO: Finish me
//b.DefineProperty(data, "innerHTML", "<img src=''/>");
return data;
}
template JSCustomObjectBuilder::Object
ProfilerMarkerImagePayload::preparePayloadImp<JSCustomObjectBuilder>(JSCustomObjectBuilder& b);
template JSObjectBuilder::Object
ProfilerMarkerImagePayload::preparePayloadImp<JSObjectBuilder>(JSObjectBuilder& b);

View File

@ -0,0 +1,75 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#ifndef PROFILER_MARKERS_H
#define PROFILER_MARKERS_H
#include "JSCustomObjectBuilder.h"
#include "JSObjectBuilder.h"
#include "nsAutoPtr.h"
/**
* This is an abstract object that can be implied to supply
* data to be attached with a profiler marker. Most data inserted
* into a profile is stored in a circular buffer. This buffer
* typically wraps around and overwrites most entries. Because
* of this, this structure is designed to defer the work of
* prepare the payload only when 'preparePayload' is called.
*
* Note when implementing that this object is typically constructed
* on a particular thread but 'preparePayload' and the destructor
* is called from the main thread.
*/
class ProfilerMarkerPayload {
public:
ProfilerMarkerPayload() {}
/**
* Called from the main thread
*/
virtual ~ProfilerMarkerPayload() {}
/**
* Called from the main thread
*/
template<typename Builder>
typename Builder::Object PreparePayload(Builder& b)
{
return preparePayload(b);
}
protected:
/**
* Called from the main thread
*/
virtual JSCustomObjectBuilder::Object
preparePayload(JSCustomObjectBuilder& b) = 0;
/**
* Called from the main thread
*/
virtual JSObjectBuilder::Object
preparePayload(JSObjectBuilder& b) = 0;
};
class gfxASurface;
class ProfilerMarkerImagePayload : public ProfilerMarkerPayload {
public:
ProfilerMarkerImagePayload(gfxASurface *aImg);
protected:
virtual JSCustomObjectBuilder::Object
preparePayload(JSCustomObjectBuilder& b) { return preparePayloadImp(b); }
virtual JSObjectBuilder::Object
preparePayload(JSObjectBuilder& b) { return preparePayloadImp(b); }
private:
template<typename Builder>
typename Builder::Object preparePayloadImp(Builder& b);
nsRefPtr<gfxASurface> mImg;
};
#endif // PROFILER_MARKERS_H

View File

@ -100,6 +100,112 @@ public:
}
};
class ProfilerMarkerPayload;
class ProfilerMarkerLinkedList;
class JSAObjectBuilder;
class JSCustomArray;
class ThreadProfile;
class ProfilerMarker {
friend class ProfilerMarkerLinkedList;
public:
ProfilerMarker(const char* aMarkerName,
ProfilerMarkerPayload* aPayload = nullptr);
~ProfilerMarker();
const char* GetMarkerName() const {
return mMarkerName;
}
template<typename Builder> void
BuildJSObject(Builder& b, typename Builder::ArrayHandle markers) const;
void SetGeneration(int aGenID);
bool HasExpired(int aGenID) const {
return mGenID + 2 <= aGenID;
}
private:
char* mMarkerName;
ProfilerMarkerPayload* mPayload;
ProfilerMarker* mNext;
int mGenID;
};
class ProfilerMarkerLinkedList {
public:
ProfilerMarkerLinkedList()
: mHead(nullptr)
, mTail(nullptr)
{}
void insert(ProfilerMarker* elem);
ProfilerMarker* popHead();
const ProfilerMarker* peek() {
return mHead;
}
private:
ProfilerMarker* mHead;
ProfilerMarker* mTail;
};
class PendingMarkers {
public:
PendingMarkers()
: mSignalLock(false)
{}
~PendingMarkers();
void addMarker(ProfilerMarker *aMarker);
void updateGeneration(int aGenID);
/**
* Track a marker which has been inserted into the ThreadProfile.
* This marker can safely be deleted once the generation has
* expired.
*/
void addStoredMarker(ProfilerMarker *aStoredMarker);
// called within signal. Function must be reentrant
ProfilerMarkerLinkedList* getPendingMarkers()
{
// if mSignalLock then the stack is inconsistent because it's being
// modified by the profiled thread. Post pone these markers
// for the next sample. The odds of a livelock are nearly impossible
// and would show up in a profile as many sample in 'addMarker' thus
// we ignore this scenario.
if (mSignalLock) {
return nullptr;
}
return &mPendingMarkers;
}
void clearMarkers()
{
while (mPendingMarkers.peek()) {
delete mPendingMarkers.popHead();
}
while (mStoredMarkers.peek()) {
delete mStoredMarkers.popHead();
}
}
private:
// Keep a list of active markers to be applied to the next sample taken
ProfilerMarkerLinkedList mPendingMarkers;
ProfilerMarkerLinkedList mStoredMarkers;
// If this is set then it's not safe to read mStackPointer from the signal handler
volatile bool mSignalLock;
// We don't want to modify _markers from within the signal so we allow
// it to queue a clear operation.
volatile mozilla::sig_safe_t mGenID;
};
// the PseudoStack members are read by signal
// handlers, so the mutation of them needs to be signal-safe.
struct PseudoStack
@ -107,18 +213,13 @@ struct PseudoStack
public:
PseudoStack()
: mStackPointer(0)
, mSignalLock(false)
, mMarkerPointer(0)
, mQueueClearMarker(false)
, mRuntime(nullptr)
, mStartJSSampling(false)
, mPrivacyMode(false)
{ }
~PseudoStack() {
clearMarkers();
if (mStackPointer != 0 || mSignalLock != false ||
mMarkerPointer != 0) {
if (mStackPointer != 0) {
// We're releasing the pseudostack while it's still in use.
// The label macros keep a non ref counted reference to the
// stack to avoid a TLS. If these are not all cleared we will
@ -127,53 +228,24 @@ public:
}
}
void addMarker(const char *aMarker)
void addMarker(const char *aMarkerStr, ProfilerMarkerPayload *aPayload)
{
char* markerCopy = strdup(aMarker);
mSignalLock = true;
STORE_SEQUENCER();
ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload);
mPendingMarkers.addMarker(marker);
}
if (mQueueClearMarker) {
clearMarkers();
}
if (!aMarker) {
return; //discard
}
if (size_t(mMarkerPointer) == mozilla::ArrayLength(mMarkers)) {
return; //array full, silently drop
}
mMarkers[mMarkerPointer] = markerCopy;
mMarkerPointer++;
void addStoredMarker(ProfilerMarker *aStoredMarker) {
mPendingMarkers.addStoredMarker(aStoredMarker);
}
mSignalLock = false;
STORE_SEQUENCER();
void updateGeneration(int aGenID) {
mPendingMarkers.updateGeneration(aGenID);
}
// called within signal. Function must be reentrant
const char* getMarker(int aMarkerId)
ProfilerMarkerLinkedList* getPendingMarkers()
{
// if mSignalLock then the stack is inconsistent because it's being
// modified by the profiled thread. Post pone these markers
// for the next sample. The odds of a livelock are nearly impossible
// and would show up in a profile as many sample in 'addMarker' thus
// we ignore this scenario.
// if mQueueClearMarker then we've the sampler thread has already
// thread the markers then they are pending deletion.
if (mSignalLock || mQueueClearMarker || aMarkerId < 0 ||
static_cast<mozilla::sig_safe_t>(aMarkerId) >= mMarkerPointer) {
return nullptr;
}
return mMarkers[aMarkerId];
}
// called within signal. Function must be reentrant
void clearMarkers()
{
for (mozilla::sig_safe_t i = 0; i < mMarkerPointer; i++) {
free(mMarkers[i]);
}
mMarkerPointer = 0;
mQueueClearMarker = false;
return mPendingMarkers.getPendingMarkers();
}
void push(const char *aName, uint32_t line)
@ -246,19 +318,14 @@ public:
// Keep a list of active checkpoints
StackEntry volatile mStack[1024];
// Keep a list of active markers to be applied to the next sample taken
char* mMarkers[1024];
private:
// Keep a list of pending markers that must be moved
// to the circular buffer
PendingMarkers mPendingMarkers;
// This may exceed the length of mStack, so instead use the stackSize() method
// to determine the number of valid samples in mStack
mozilla::sig_safe_t mStackPointer;
// If this is set then it's not safe to read mStackPointer from the signal handler
volatile bool mSignalLock;
public:
volatile mozilla::sig_safe_t mMarkerPointer;
// We don't want to modify _markers from within the signal so we allow
// it to queue a clear operation.
volatile mozilla::sig_safe_t mQueueClearMarker;
// The runtime which is being sampled
JSRuntime *mRuntime;
// Start JS Profiling when possible

View File

@ -511,10 +511,13 @@ void TableTicker::InplaceTick(TickSample* sample)
// Marker(s) come before the sample
PseudoStack* stack = currThreadProfile.GetPseudoStack();
for (int i = 0; stack->getMarker(i) != NULL; i++) {
addDynamicTag(currThreadProfile, 'm', stack->getMarker(i));
ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers();
while (pendingMarkersList && pendingMarkersList->peek()) {
ProfilerMarker* marker = pendingMarkersList->popHead();
stack->addStoredMarker(marker);
currThreadProfile.addTag(ProfileEntry('m', marker));
}
stack->mQueueClearMarker = true;
stack->updateGeneration(currThreadProfile.GetGenerationID());
bool recordSample = true;
if (mJankOnly) {

View File

@ -122,8 +122,6 @@ class TableTicker: public Sampler {
aInfo->GetPlatformData(),
aInfo->IsMainThread(),
aInfo->StackTop());
profile->addTag(ProfileEntry('m', "Start"));
aInfo->SetProfile(profile);
}

View File

@ -18,6 +18,7 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
EXPORTS += [
'GeckoProfilerFunc.h',
'GeckoProfilerImpl.h',
'ProfilerMarkers.h',
'PseudoStack.h',
'shared-libraries.h',
]
@ -39,6 +40,7 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
'IOInterposer.cpp',
'NSPRInterposer.cpp',
'ProfilerIOInterposeObserver.cpp',
'ProfilerMarkers.cpp',
]
if CONFIG['OS_TARGET'] in ('Android', 'Linux'):

View File

@ -23,6 +23,7 @@
#include "nsDirectoryServiceDefs.h"
#include "mozilla/Services.h"
#include "nsThreadUtils.h"
#include "ProfilerMarkers.h"
#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
#include "AndroidBridge.h"
@ -92,6 +93,113 @@ ThreadInfo::~ThreadInfo() {
Sampler::FreePlatformData(mPlatformData);
}
ProfilerMarker::ProfilerMarker(const char* aMarkerName,
ProfilerMarkerPayload* aPayload)
: mMarkerName(strdup(aMarkerName))
, mPayload(aPayload)
{
}
ProfilerMarker::~ProfilerMarker() {
free(mMarkerName);
delete mPayload;
}
void
ProfilerMarker::SetGeneration(int aGenID) {
mGenID = aGenID;
}
template<typename Builder> void
ProfilerMarker::BuildJSObject(Builder& b, typename Builder::ArrayHandle markers) const {
typename Builder::RootedObject marker(b.context(), b.CreateObject());
b.DefineProperty(marker, "name", GetMarkerName());
// TODO: Store the callsite for this marker if available:
// if have location data
// b.DefineProperty(marker, "location", ...);
if (mPayload) {
typename Builder::RootedObject markerData(b.context(),
mPayload->PreparePayload(b));
b.DefineProperty(marker, "data", markerData);
}
b.ArrayPush(markers, marker);
}
template void
ProfilerMarker::BuildJSObject<JSCustomObjectBuilder>(JSCustomObjectBuilder& b,
JSCustomObjectBuilder::ArrayHandle markers) const;
template void
ProfilerMarker::BuildJSObject<JSObjectBuilder>(JSObjectBuilder& b,
JSObjectBuilder::ArrayHandle markers) const;
void
ProfilerMarkerLinkedList::insert(ProfilerMarker* elem) {
if (!mTail) {
mHead = elem;
mTail = elem;
} else {
mTail->mNext = elem;
mTail = elem;
}
elem->mNext = nullptr;
}
ProfilerMarker*
ProfilerMarkerLinkedList::popHead() {
if (!mHead) {
MOZ_ASSERT(false);
return nullptr;
}
ProfilerMarker* head = mHead;
mHead = head->mNext;
if (!mHead) {
mTail = nullptr;
}
return head;
}
PendingMarkers::~PendingMarkers() {
clearMarkers();
if (mSignalLock != false) {
// We're releasing the pseudostack while it's still in use.
// The label macros keep a non ref counted reference to the
// stack to avoid a TLS. If these are not all cleared we will
// get a use-after-free so better to crash now.
abort();
}
}
void
PendingMarkers::addMarker(ProfilerMarker *aMarker) {
mSignalLock = true;
STORE_SEQUENCER();
MOZ_ASSERT(aMarker);
mPendingMarkers.insert(aMarker);
// Clear markers that have been overwritten
while (mStoredMarkers.peek() &&
mStoredMarkers.peek()->HasExpired(mGenID)) {
delete mStoredMarkers.popHead();
}
STORE_SEQUENCER();
mSignalLock = false;
}
void
PendingMarkers::updateGeneration(int aGenID) {
mGenID = aGenID;
}
void
PendingMarkers::addStoredMarker(ProfilerMarker *aStoredMarker) {
aStoredMarker->SetGeneration(mGenID);
mStoredMarkers.insert(aStoredMarker);
}
bool sps_version2()
{
static int version = 0; // Raced on, potentially