Bug 729182 - Implement visual event tracer, part2 - event tracer service, r=ehsan

This commit is contained in:
Honza Bambas 2013-03-28 18:38:05 +01:00
parent edca9830f3
commit a22e7ccfea
6 changed files with 470 additions and 180 deletions

View File

@ -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);
}
// static
void
RecordBatch::GCLog(const TimeStamp& aUntil)
{
// Garbage collect all unreferenced and old batches
gMonitor->AssertCurrentThreadOwns();
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;
}
if (!gLogHead)
gLogHead = threadLogPrivate;
else // gLogTail is non-null
gLogTail->mNextBatch = threadLogPrivate;
gLogTail = threadLogPrivate;
RecordBatch::Delete(batch);
}
mon.Notify();
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;
gInitialized = true;
gProfilerStart = new mozilla::TimeStamp();
gMaxBacklogTime = new mozilla::TimeDuration();
MOZ_EVENT_TRACER_NAME_OBJECT(gFlushingThread, "Profiler");
MOZ_EVENT_TRACER_MARK(gFlushingThread, "Profiling Start");
gInitialized = true;
#endif
}
@ -374,25 +432,10 @@ void Init()
void Shutdown()
{
#ifdef MOZ_VISUAL_EVENT_TRACER
MOZ_EVENT_TRACER_MARK(gFlushingThread, "Profiling End");
gCapture = false;
gInitialized = false;
// 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);
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

View File

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

View File

@ -25,6 +25,7 @@ XPIDL_SOURCES += [
'nsITraceRefcnt.idl',
'nsIUUIDGenerator.idl',
'nsIVersionComparator.idl',
'nsIVisualEventTracer.idl',
'nsIWeakReference.idl',
'nsrootidl.idl',
]

View 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;
};

View File

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

View File

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