mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 729182 - Implement visual event tracer, part2 - event tracer service, r=ehsan
This commit is contained in:
parent
edca9830f3
commit
a22e7ccfea
@ -19,7 +19,7 @@ namespace mozilla { namespace eventtracer {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kBatchSize = 0x1000;
|
||||
const uint32_t kBatchSize = 256;
|
||||
const char kTypeChars[eventtracer::eLast] = {' ','N','S','W','E','D'};
|
||||
|
||||
// Flushing thread and records queue monitor
|
||||
@ -27,29 +27,49 @@ mozilla::Monitor * gMonitor = nullptr;
|
||||
|
||||
// Accessed concurently but since this flag is not functionally critical
|
||||
// for optimization purposes is not accessed under a lock
|
||||
bool gInitialized = false;
|
||||
bool volatile gInitialized = false;
|
||||
|
||||
// Flag to allow capturing
|
||||
bool volatile gCapture = false;
|
||||
|
||||
// Time stamp of the epoch we have started to capture
|
||||
mozilla::TimeStamp * gProfilerStart;
|
||||
|
||||
// Duration of the log to keep up to
|
||||
mozilla::TimeDuration * gMaxBacklogTime;
|
||||
|
||||
|
||||
// Record of a single event
|
||||
class Record {
|
||||
public:
|
||||
Record()
|
||||
: mType(::mozilla::eventtracer::eNone)
|
||||
, mTime(0)
|
||||
, mItem(nullptr)
|
||||
, mText(nullptr)
|
||||
, mText2(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(Record);
|
||||
}
|
||||
|
||||
Record& operator=(const Record & aOther)
|
||||
{
|
||||
mType = aOther.mType;
|
||||
mTime = aOther.mTime;
|
||||
mItem = aOther.mItem;
|
||||
mText = PL_strdup(aOther.mText);
|
||||
mText2 = aOther.mText2 ? PL_strdup(aOther.mText2) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~Record()
|
||||
{
|
||||
PL_strfree(mText);
|
||||
PL_strfree(mText2);
|
||||
PL_strfree(mText);
|
||||
MOZ_COUNT_DTOR(Record);
|
||||
}
|
||||
|
||||
uint32_t mType;
|
||||
double mTime;
|
||||
TimeStamp mTime;
|
||||
void * mItem;
|
||||
char * mText;
|
||||
char * mText2;
|
||||
@ -74,12 +94,14 @@ char * DupCurrentThreadName()
|
||||
// An array of events, each thread keeps its own private instance
|
||||
class RecordBatch {
|
||||
public:
|
||||
RecordBatch()
|
||||
: mRecordsHead(new Record[kBatchSize])
|
||||
, mRecordsTail(mRecordsHead + kBatchSize)
|
||||
RecordBatch(size_t aLength = kBatchSize,
|
||||
char * aThreadName = DupCurrentThreadName())
|
||||
: mRecordsHead(new Record[aLength])
|
||||
, mRecordsTail(mRecordsHead + aLength)
|
||||
, mNextRecord(mRecordsHead)
|
||||
, mNextBatch(nullptr)
|
||||
, mThreadNameCopy(DupCurrentThreadName())
|
||||
, mThreadNameCopy(aThreadName)
|
||||
, mClosed(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(RecordBatch);
|
||||
}
|
||||
@ -91,41 +113,203 @@ public:
|
||||
MOZ_COUNT_DTOR(RecordBatch);
|
||||
}
|
||||
|
||||
static void FlushBatch(void * aData);
|
||||
void Close() { mClosed = true; }
|
||||
|
||||
size_t Length() const { return mNextRecord - mRecordsHead; }
|
||||
bool CanBeDeleted(const TimeStamp& aUntil) const;
|
||||
|
||||
static RecordBatch * Register();
|
||||
static void Close(void * data); // Registered on freeing thread data
|
||||
static RecordBatch * Clone(RecordBatch * aLog, const TimeStamp& aSince);
|
||||
static void Delete(RecordBatch * aLog);
|
||||
|
||||
static RecordBatch * CloneLog();
|
||||
static void GCLog(const TimeStamp& aUntil);
|
||||
static void DeleteLog();
|
||||
|
||||
Record * mRecordsHead;
|
||||
Record * mRecordsTail;
|
||||
Record * mNextRecord;
|
||||
Record * volatile mNextRecord;
|
||||
|
||||
RecordBatch * mNextBatch;
|
||||
char * mThreadNameCopy;
|
||||
bool mClosed;
|
||||
};
|
||||
|
||||
// Protected by gMonitor, accessed concurently
|
||||
// Linked list of batches threads want to flush on disk
|
||||
RecordBatch * gLogHead = nullptr;
|
||||
RecordBatch * gLogTail = nullptr;
|
||||
RecordBatch * volatile gLogHead = nullptr;
|
||||
RecordBatch * volatile gLogTail = nullptr;
|
||||
|
||||
// Registered as thread private data destructor
|
||||
void
|
||||
RecordBatch::FlushBatch(void * aData)
|
||||
// Registers the batch in the linked list
|
||||
// static
|
||||
RecordBatch *
|
||||
RecordBatch::Register()
|
||||
{
|
||||
RecordBatch * threadLogPrivate = static_cast<RecordBatch *>(aData);
|
||||
MonitorAutoLock mon(*gMonitor);
|
||||
|
||||
if (!gInitialized)
|
||||
return nullptr;
|
||||
|
||||
if (gLogHead)
|
||||
RecordBatch::GCLog(TimeStamp::Now() - *gMaxBacklogTime);
|
||||
|
||||
RecordBatch * batch = new RecordBatch();
|
||||
if (!gLogHead)
|
||||
gLogHead = batch;
|
||||
else // gLogTail is non-null
|
||||
gLogTail->mNextBatch = batch;
|
||||
gLogTail = batch;
|
||||
|
||||
mon.Notify();
|
||||
return batch;
|
||||
}
|
||||
|
||||
void
|
||||
RecordBatch::Close(void * data)
|
||||
{
|
||||
RecordBatch * batch = static_cast<RecordBatch*>(data);
|
||||
batch->Close();
|
||||
}
|
||||
|
||||
// static
|
||||
RecordBatch *
|
||||
RecordBatch::Clone(RecordBatch * aOther, const TimeStamp& aSince)
|
||||
{
|
||||
if (!aOther)
|
||||
return nullptr;
|
||||
|
||||
size_t length = aOther->Length();
|
||||
size_t min = 0;
|
||||
size_t max = length;
|
||||
Record * record = nullptr;
|
||||
|
||||
// Binary search for record with time >= aSince
|
||||
size_t i;
|
||||
while (min < max) {
|
||||
i = (max + min) / 2;
|
||||
|
||||
record = aOther->mRecordsHead + i;
|
||||
if (record->mTime >= aSince)
|
||||
max = i;
|
||||
else
|
||||
min = i+1;
|
||||
}
|
||||
i = (max + min) / 2;
|
||||
|
||||
// How many Record's to copy?
|
||||
size_t toCopy = length - i;
|
||||
if (!toCopy)
|
||||
return RecordBatch::Clone(aOther->mNextBatch, aSince);
|
||||
|
||||
// Clone
|
||||
RecordBatch * clone = new RecordBatch(toCopy, PL_strdup(aOther->mThreadNameCopy));
|
||||
for (; i < length; ++i) {
|
||||
record = aOther->mRecordsHead + i;
|
||||
*clone->mNextRecord = *record;
|
||||
++clone->mNextRecord;
|
||||
}
|
||||
clone->mNextBatch = RecordBatch::Clone(aOther->mNextBatch, aSince);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
RecordBatch::Delete(RecordBatch * aLog)
|
||||
{
|
||||
while (aLog) {
|
||||
RecordBatch * batch = aLog;
|
||||
aLog = aLog->mNextBatch;
|
||||
delete batch;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
RecordBatch *
|
||||
RecordBatch::CloneLog()
|
||||
{
|
||||
TimeStamp startEpoch = *gProfilerStart;
|
||||
TimeStamp backlogEpoch = TimeStamp::Now() - *gMaxBacklogTime;
|
||||
|
||||
TimeStamp since = (startEpoch > backlogEpoch) ? startEpoch : backlogEpoch;
|
||||
|
||||
MonitorAutoLock mon(*gMonitor);
|
||||
|
||||
if (!gInitialized) {
|
||||
delete threadLogPrivate;
|
||||
return;
|
||||
return RecordBatch::Clone(gLogHead, since);
|
||||
}
|
||||
|
||||
if (!gLogHead)
|
||||
gLogHead = threadLogPrivate;
|
||||
else // gLogTail is non-null
|
||||
gLogTail->mNextBatch = threadLogPrivate;
|
||||
gLogTail = threadLogPrivate;
|
||||
// static
|
||||
void
|
||||
RecordBatch::GCLog(const TimeStamp& aUntil)
|
||||
{
|
||||
// Garbage collect all unreferenced and old batches
|
||||
gMonitor->AssertCurrentThreadOwns();
|
||||
|
||||
mon.Notify();
|
||||
RecordBatch *volatile * referer = &gLogHead;
|
||||
gLogTail = nullptr;
|
||||
|
||||
RecordBatch * batch = *referer;
|
||||
while (batch) {
|
||||
if (batch->CanBeDeleted(aUntil)) {
|
||||
// The batch is completed and thus unreferenced by the thread
|
||||
// and the most recent record has time older then the time
|
||||
// we want to save records for, hence delete it.
|
||||
*referer = batch->mNextBatch;
|
||||
delete batch;
|
||||
batch = *referer;
|
||||
}
|
||||
else {
|
||||
// We walk the whole list, so this will end up filled with
|
||||
// the very last valid element of it.
|
||||
gLogTail = batch;
|
||||
// The current batch is active, examine the next in the list.
|
||||
batch = batch->mNextBatch;
|
||||
// When the next batch is found expired, we must extract it
|
||||
// from the list, shift the referer.
|
||||
referer = &((*referer)->mNextBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
RecordBatch::DeleteLog()
|
||||
{
|
||||
RecordBatch * batch;
|
||||
{
|
||||
MonitorAutoLock mon(*gMonitor);
|
||||
batch = gLogHead;
|
||||
gLogHead = nullptr;
|
||||
gLogTail = nullptr;
|
||||
}
|
||||
|
||||
RecordBatch::Delete(batch);
|
||||
}
|
||||
|
||||
bool
|
||||
RecordBatch::CanBeDeleted(const TimeStamp& aUntil) const
|
||||
{
|
||||
if (mClosed) {
|
||||
// This flag is set when a thread releases this batch as
|
||||
// its private data. It happens when the list is full or
|
||||
// when the thread ends its job. We must not delete this
|
||||
// batch from memory while it's held by a thread.
|
||||
|
||||
if (!Length()) {
|
||||
// There are no records, just get rid of this empty batch.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((mNextRecord-1)->mTime <= aUntil) {
|
||||
// Is the last record older then the time we demand records
|
||||
// for? If not, this batch has expired.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Not all conditions to close the batch met, keep it.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper class for filtering events by MOZ_PROFILING_EVENTS
|
||||
@ -203,111 +387,7 @@ bool gStopFlushingThread = false;
|
||||
// State and control variables, initialized in Init() method, after it
|
||||
// immutable and read concurently.
|
||||
EventFilter * gEventFilter = nullptr;
|
||||
const char * gLogFilePath = nullptr;
|
||||
PRThread * gFlushingThread = nullptr;
|
||||
unsigned gThreadPrivateIndex;
|
||||
mozilla::TimeStamp gProfilerStart;
|
||||
|
||||
// To prevent any major I/O blockade caused by call to eventtracer::Mark()
|
||||
// we buffer the data produced by each thread and write it to disk
|
||||
// in a separate low-priority thread.
|
||||
|
||||
// static
|
||||
void FlushingThread(void * aArg)
|
||||
{
|
||||
PRFileDesc * logFile = PR_Open(gLogFilePath,
|
||||
PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE,
|
||||
0644);
|
||||
|
||||
MonitorAutoLock mon(*gMonitor);
|
||||
|
||||
if (!logFile) {
|
||||
gInitialized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t rv;
|
||||
bool ioError = false;
|
||||
|
||||
const char logHead[] = "{\n\"version\": 1,\n\"records\":[\n";
|
||||
rv = PR_Write(logFile, logHead, sizeof(logHead) - 1);
|
||||
ioError |= (rv < 0);
|
||||
|
||||
bool firstBatch = true;
|
||||
while (!gStopFlushingThread || gLogHead) {
|
||||
if (ioError) {
|
||||
gInitialized = false;
|
||||
break;
|
||||
}
|
||||
|
||||
mon.Wait();
|
||||
|
||||
// Grab the current log list head and start a new blank global list
|
||||
RecordBatch * batch = gLogHead;
|
||||
gLogHead = nullptr;
|
||||
gLogTail = nullptr;
|
||||
|
||||
MonitorAutoUnlock unlock(*gMonitor); // no need to block on I/O :-)
|
||||
|
||||
while (batch) {
|
||||
if (!firstBatch) {
|
||||
const char threadDelimiter[] = ",\n";
|
||||
rv = PR_Write(logFile, threadDelimiter, sizeof(threadDelimiter) - 1);
|
||||
ioError |= (rv < 0);
|
||||
}
|
||||
firstBatch = false;
|
||||
|
||||
static const int kBufferSize = 2048;
|
||||
char buf[kBufferSize];
|
||||
|
||||
PR_snprintf(buf, kBufferSize, "{\"thread\":\"%s\",\"log\":[\n",
|
||||
batch->mThreadNameCopy);
|
||||
|
||||
rv = PR_Write(logFile, buf, strlen(buf));
|
||||
ioError |= (rv < 0);
|
||||
|
||||
for (Record * record = batch->mRecordsHead;
|
||||
record < batch->mNextRecord && !ioError;
|
||||
++record) {
|
||||
|
||||
// mType carries both type and flags, separate type
|
||||
// as lower 16 bits and flags as higher 16 bits.
|
||||
// The json format expects this separated.
|
||||
uint32_t type = record->mType & 0xffffUL;
|
||||
uint32_t flags = record->mType >> 16;
|
||||
PR_snprintf(buf, kBufferSize,
|
||||
"{\"e\":\"%c\",\"t\":%f,\"f\":%d,\"i\":\"%p\",\"n\":\"%s%s\"}%s\n",
|
||||
kTypeChars[type],
|
||||
record->mTime,
|
||||
flags,
|
||||
record->mItem,
|
||||
record->mText,
|
||||
record->mText2 ? record->mText2 : "",
|
||||
(record == batch->mNextRecord - 1) ? "" : ",");
|
||||
|
||||
rv = PR_Write(logFile, buf, strlen(buf));
|
||||
ioError |= (rv < 0);
|
||||
}
|
||||
|
||||
const char threadTail[] = "]}\n";
|
||||
rv = PR_Write(logFile, threadTail, sizeof(threadTail) - 1);
|
||||
ioError |= (rv < 0);
|
||||
|
||||
RecordBatch * next = batch->mNextBatch;
|
||||
delete batch;
|
||||
batch = next;
|
||||
}
|
||||
}
|
||||
|
||||
const char logTail[] = "]}\n";
|
||||
rv = PR_Write(logFile, logTail, sizeof(logTail) - 1);
|
||||
ioError |= (rv < 0);
|
||||
|
||||
PR_Close(logFile);
|
||||
|
||||
if (ioError)
|
||||
PR_Delete(gLogFilePath);
|
||||
}
|
||||
|
||||
// static
|
||||
bool CheckEventFilters(uint32_t aType, void * aItem, const char * aText)
|
||||
@ -318,9 +398,6 @@ bool CheckEventFilters(uint32_t aType, void * aItem, const char * aText)
|
||||
if (aType == eName)
|
||||
return true;
|
||||
|
||||
if (aItem == gFlushingThread) // Pass events coming from the tracer
|
||||
return true;
|
||||
|
||||
return gEventFilter->EventPasses(aText);
|
||||
}
|
||||
|
||||
@ -332,20 +409,11 @@ bool CheckEventFilters(uint32_t aType, void * aItem, const char * aText)
|
||||
void Init()
|
||||
{
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
const char * logFile = PR_GetEnv("MOZ_PROFILING_FILE");
|
||||
if (!logFile || !*logFile)
|
||||
return;
|
||||
|
||||
gLogFilePath = logFile;
|
||||
|
||||
const char * logEvents = PR_GetEnv("MOZ_PROFILING_EVENTS");
|
||||
if (logEvents && *logEvents)
|
||||
gEventFilter = EventFilter::Build(logEvents);
|
||||
|
||||
gProfilerStart = mozilla::TimeStamp::Now();
|
||||
|
||||
PRStatus status = PR_NewThreadPrivateIndex(&gThreadPrivateIndex,
|
||||
&RecordBatch::FlushBatch);
|
||||
PRStatus status = PR_NewThreadPrivateIndex(&gThreadPrivateIndex, &RecordBatch::Close);
|
||||
if (status != PR_SUCCESS)
|
||||
return;
|
||||
|
||||
@ -353,20 +421,10 @@ void Init()
|
||||
if (!gMonitor)
|
||||
return;
|
||||
|
||||
gFlushingThread = PR_CreateThread(PR_USER_THREAD,
|
||||
&FlushingThread,
|
||||
nullptr,
|
||||
PR_PRIORITY_LOW,
|
||||
PR_LOCAL_THREAD,
|
||||
PR_JOINABLE_THREAD,
|
||||
32768);
|
||||
if (!gFlushingThread)
|
||||
return;
|
||||
gProfilerStart = new mozilla::TimeStamp();
|
||||
gMaxBacklogTime = new mozilla::TimeDuration();
|
||||
|
||||
gInitialized = true;
|
||||
|
||||
MOZ_EVENT_TRACER_NAME_OBJECT(gFlushingThread, "Profiler");
|
||||
MOZ_EVENT_TRACER_MARK(gFlushingThread, "Profiling Start");
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -374,25 +432,10 @@ void Init()
|
||||
void Shutdown()
|
||||
{
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
MOZ_EVENT_TRACER_MARK(gFlushingThread, "Profiling End");
|
||||
|
||||
// This must be called after all other threads had been shut down
|
||||
// (i.e. their private data had been 'released').
|
||||
|
||||
// Release the private data of this thread to flush all the remaning writes.
|
||||
PR_SetThreadPrivate(gThreadPrivateIndex, nullptr);
|
||||
|
||||
if (gFlushingThread) {
|
||||
{
|
||||
MonitorAutoLock mon(*gMonitor);
|
||||
gCapture = false;
|
||||
gInitialized = false;
|
||||
gStopFlushingThread = true;
|
||||
mon.Notify();
|
||||
}
|
||||
|
||||
PR_JoinThread(gFlushingThread);
|
||||
gFlushingThread = nullptr;
|
||||
}
|
||||
RecordBatch::DeleteLog();
|
||||
|
||||
if (gMonitor) {
|
||||
delete gMonitor;
|
||||
@ -403,6 +446,16 @@ void Shutdown()
|
||||
delete gEventFilter;
|
||||
gEventFilter = nullptr;
|
||||
}
|
||||
|
||||
if (gProfilerStart) {
|
||||
delete gProfilerStart;
|
||||
gProfilerStart = nullptr;
|
||||
}
|
||||
|
||||
if (gMaxBacklogTime) {
|
||||
delete gMaxBacklogTime;
|
||||
gMaxBacklogTime = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -410,7 +463,7 @@ void Shutdown()
|
||||
void Mark(uint32_t aType, void * aItem, const char * aText, const char * aText2)
|
||||
{
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
if (!gInitialized)
|
||||
if (!gInitialized || !gCapture)
|
||||
return;
|
||||
|
||||
if (aType == eNone)
|
||||
@ -422,25 +475,173 @@ void Mark(uint32_t aType, void * aItem, const char * aText, const char * aText2)
|
||||
RecordBatch * threadLogPrivate = static_cast<RecordBatch *>(
|
||||
PR_GetThreadPrivate(gThreadPrivateIndex));
|
||||
if (!threadLogPrivate) {
|
||||
// Deletion is made by the flushing thread
|
||||
threadLogPrivate = new RecordBatch();
|
||||
threadLogPrivate = RecordBatch::Register();
|
||||
if (!threadLogPrivate)
|
||||
return;
|
||||
|
||||
PR_SetThreadPrivate(gThreadPrivateIndex, threadLogPrivate);
|
||||
}
|
||||
|
||||
Record * record = threadLogPrivate->mNextRecord;
|
||||
record->mType = aType;
|
||||
record->mTime = (mozilla::TimeStamp::Now() - gProfilerStart).ToMilliseconds();
|
||||
record->mTime = mozilla::TimeStamp::Now();
|
||||
record->mItem = aItem;
|
||||
record->mText = PL_strdup(aText);
|
||||
record->mText2 = aText2 ? PL_strdup(aText2) : nullptr;
|
||||
|
||||
++threadLogPrivate->mNextRecord;
|
||||
if (threadLogPrivate->mNextRecord == threadLogPrivate->mRecordsTail) {
|
||||
// This calls RecordBatch::FlushBatch(threadLogPrivate)
|
||||
// Calls RecordBatch::Close(threadLogPrivate) that marks
|
||||
// the batch as OK to be deleted from memory when no longer needed.
|
||||
PR_SetThreadPrivate(gThreadPrivateIndex, nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
|
||||
// The scriptable classes
|
||||
|
||||
class VisualEventTracerLog : public nsIVisualEventTracerLog
|
||||
{
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIVISUALEVENTTRACERLOG
|
||||
|
||||
VisualEventTracerLog(RecordBatch* aBatch)
|
||||
: mBatch(aBatch)
|
||||
, mProfilerStart(*gProfilerStart)
|
||||
{}
|
||||
|
||||
virtual ~VisualEventTracerLog();
|
||||
|
||||
protected:
|
||||
RecordBatch * mBatch;
|
||||
TimeStamp mProfilerStart;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS1(VisualEventTracerLog, nsIVisualEventTracerLog)
|
||||
|
||||
VisualEventTracerLog::~VisualEventTracerLog()
|
||||
{
|
||||
RecordBatch::Delete(mBatch);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
VisualEventTracerLog::GetJSONString(nsACString & _retval)
|
||||
{
|
||||
nsCString buffer;
|
||||
|
||||
buffer.Assign(NS_LITERAL_CSTRING("{\n\"version\": 1,\n\"records\":[\n"));
|
||||
|
||||
RecordBatch * batch = mBatch;
|
||||
while (batch) {
|
||||
if (batch != mBatch) {
|
||||
// This is not the first batch we are writting, add comma
|
||||
buffer.Append(NS_LITERAL_CSTRING(",\n"));
|
||||
}
|
||||
|
||||
buffer.Append(NS_LITERAL_CSTRING("{\"thread\":\""));
|
||||
buffer.Append(batch->mThreadNameCopy);
|
||||
buffer.Append(NS_LITERAL_CSTRING("\",\"log\":[\n"));
|
||||
|
||||
static const int kBufferSize = 2048;
|
||||
char buf[kBufferSize];
|
||||
|
||||
for (Record * record = batch->mRecordsHead;
|
||||
record < batch->mNextRecord;
|
||||
++record) {
|
||||
|
||||
// mType carries both type and flags, separate type
|
||||
// as lower 16 bits and flags as higher 16 bits.
|
||||
// The json format expects this separated.
|
||||
uint32_t type = record->mType & 0xffffUL;
|
||||
uint32_t flags = record->mType >> 16;
|
||||
PR_snprintf(buf, kBufferSize,
|
||||
"{\"e\":\"%c\",\"t\":%f,\"f\":%d,\"i\":\"%p\",\"n\":\"%s%s\"}%s\n",
|
||||
kTypeChars[type],
|
||||
(record->mTime - mProfilerStart).ToMilliseconds(),
|
||||
flags,
|
||||
record->mItem,
|
||||
record->mText,
|
||||
record->mText2 ? record->mText2 : "",
|
||||
(record == batch->mNextRecord - 1) ? "" : ",");
|
||||
|
||||
buffer.Append(buf);
|
||||
}
|
||||
|
||||
buffer.Append(NS_LITERAL_CSTRING("]}\n"));
|
||||
|
||||
RecordBatch * next = batch->mNextBatch;
|
||||
batch = next;
|
||||
}
|
||||
|
||||
buffer.Append(NS_LITERAL_CSTRING("]}\n"));
|
||||
_retval.Assign(buffer);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(VisualEventTracer, nsIVisualEventTracer)
|
||||
|
||||
NS_IMETHODIMP
|
||||
VisualEventTracer::Start(const uint32_t aMaxBacklogSeconds)
|
||||
{
|
||||
if (!gInitialized)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
if (gCapture) {
|
||||
NS_WARNING("VisualEventTracer has already been started");
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
*gMaxBacklogTime = TimeDuration::FromMilliseconds(aMaxBacklogSeconds * 1000);
|
||||
|
||||
*gProfilerStart = mozilla::TimeStamp::Now();
|
||||
{
|
||||
MonitorAutoLock mon(*gMonitor);
|
||||
RecordBatch::GCLog(*gProfilerStart);
|
||||
}
|
||||
gCapture = true;
|
||||
|
||||
MOZ_EVENT_TRACER_MARK(this, "trace::start");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
VisualEventTracer::Stop()
|
||||
{
|
||||
if (!gInitialized)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
if (!gCapture) {
|
||||
NS_WARNING("VisualEventTracer is not runing");
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
MOZ_EVENT_TRACER_MARK(this, "trace::stop");
|
||||
|
||||
gCapture = false;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
VisualEventTracer::Snapshot(nsIVisualEventTracerLog ** _result)
|
||||
{
|
||||
if (!gInitialized)
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
||||
RecordBatch * batch = RecordBatch::CloneLog();
|
||||
|
||||
nsRefPtr<VisualEventTracerLog> log = new VisualEventTracerLog(batch);
|
||||
log.forget(_result);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // eventtracer
|
||||
} // mozilla
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "nscore.h"
|
||||
#include "mozilla/GuardObjects.h"
|
||||
#include "nsIVisualEventTracer.h"
|
||||
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
|
||||
@ -115,7 +116,8 @@
|
||||
#endif
|
||||
|
||||
|
||||
namespace mozilla { namespace eventtracer {
|
||||
namespace mozilla {
|
||||
namespace eventtracer {
|
||||
|
||||
// Initialize the event tracer engine, called automatically on XPCOM startup.
|
||||
void Init();
|
||||
@ -202,5 +204,23 @@ private:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
};
|
||||
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
|
||||
// The scriptable class that drives the event tracer
|
||||
|
||||
class VisualEventTracer : public nsIVisualEventTracer
|
||||
{
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIVISUALEVENTTRACER
|
||||
};
|
||||
|
||||
#define NS_VISUALEVENTTRACER_CID \
|
||||
{ 0xb9e5e102, 0xc2f4, 0x497a, \
|
||||
{ 0xa6, 0xe4, 0x54, 0xde, 0xf3, 0x71, 0xf3, 0x9d } }
|
||||
#define NS_VISUALEVENTTRACER_CONTRACTID "@mozilla.org/base/visual-event-tracer;1"
|
||||
#define NS_VISUALEVENTTRACER_CLASSNAME "Visual Event Tracer"
|
||||
|
||||
#endif
|
||||
|
||||
} // eventtracer
|
||||
} // mozilla
|
||||
|
@ -25,6 +25,7 @@ XPIDL_SOURCES += [
|
||||
'nsITraceRefcnt.idl',
|
||||
'nsIUUIDGenerator.idl',
|
||||
'nsIVersionComparator.idl',
|
||||
'nsIVisualEventTracer.idl',
|
||||
'nsIWeakReference.idl',
|
||||
'nsrootidl.idl',
|
||||
]
|
||||
|
53
xpcom/base/nsIVisualEventTracer.idl
Normal file
53
xpcom/base/nsIVisualEventTracer.idl
Normal file
@ -0,0 +1,53 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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 "nsISupports.idl"
|
||||
|
||||
interface nsIVisualEventTracerLog;
|
||||
|
||||
/**
|
||||
* Interface to control the visual event tracer feature. The result
|
||||
* is a log of various events that are monitored by a custom code
|
||||
* instrumentation around the mozilla code base.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(D51F7867-42F3-4029-8C4E-C00676253A8E)]
|
||||
interface nsIVisualEventTracer : nsISupports
|
||||
{
|
||||
/**
|
||||
* Start the logging now. No affect if already started.
|
||||
* Current backlog is deleted by this call otherwise.
|
||||
*
|
||||
* @param minBacklogSeconds
|
||||
* Manimum time to keep the backlog. Entries of the log are discarded
|
||||
* when their age is more then value of this argument.
|
||||
*/
|
||||
void start(in unsigned long minBacklogSeconds);
|
||||
|
||||
/**
|
||||
* Stop the logging now. Backlog is kept in memory.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Obtain the log. This can be called whenever you want.
|
||||
*
|
||||
* @return
|
||||
* Result is an object that keeps snaphot of the log from
|
||||
* time this method has been called. You can then access
|
||||
* the log using the object. Calling stop() on the tracer
|
||||
* doesn't delete this log.
|
||||
*/
|
||||
nsIVisualEventTracerLog snapshot();
|
||||
};
|
||||
|
||||
[scriptable, uuid(52EC8962-F67C-4f49-A9D6-89B8EBDA2649)]
|
||||
interface nsIVisualEventTracerLog : nsISupports
|
||||
{
|
||||
/**
|
||||
* JSON string of the log. Use JSON.parse to get it as an object.
|
||||
*/
|
||||
readonly attribute ACString JSONString;
|
||||
};
|
@ -23,6 +23,9 @@
|
||||
COMPONENT(EXCEPTIONSERVICE, nsExceptionServiceConstructor)
|
||||
COMPONENT(ATOMSERVICE, nsAtomServiceConstructor)
|
||||
COMPONENT(OBSERVERSERVICE, nsObserverService::Create)
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
COMPONENT(VISUALEVENTTRACER, VisualEventTracerConstructor)
|
||||
#endif
|
||||
|
||||
COMPONENT(TIMER, nsTimerImplConstructor)
|
||||
|
||||
|
@ -120,12 +120,17 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **)
|
||||
#include "mozilla/AvailableMemoryTracker.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
#include "mozilla/VisualEventTracer.h"
|
||||
#endif
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
using base::AtExitManager;
|
||||
using mozilla::ipc::BrowserProcessSubThread;
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
using mozilla::eventtracer::VisualEventTracer;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
@ -177,6 +182,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryInputStream)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsStorageStream)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsVersionComparatorImpl)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptableBase64Encoder)
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(VisualEventTracer)
|
||||
#endif
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsVariant)
|
||||
|
||||
@ -486,7 +494,9 @@ NS_InitXPCOM2(nsIServiceManager* *result,
|
||||
|
||||
mozilla::HangMonitor::Startup();
|
||||
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
mozilla::eventtracer::Init();
|
||||
#endif
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -715,7 +725,9 @@ ShutdownXPCOM(nsIServiceManager* servMgr)
|
||||
|
||||
HangMonitor::Shutdown();
|
||||
|
||||
#ifdef MOZ_VISUAL_EVENT_TRACER
|
||||
eventtracer::Shutdown();
|
||||
#endif
|
||||
|
||||
NS_LogTerm();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user