Bug 729182 - Implement visual event tracer, part2 - event tracer service, r=ehsan
authorHonza Bambas <honzab.moz@firemni.cz>
Thu, 28 Mar 2013 18:38:05 +0100
changeset 126588 85dd7094b78d0cce60b28adab74b14b9536e0846
parent 126587 260e98308023b51ad665612bf4be04bd8d0e7aa1
child 126589 636cfcab9682a63543eebf55cbd0c931d447f979
push id25533
push userhonzab.moz@firemni.cz
push dateThu, 28 Mar 2013 17:38:48 +0000
treeherdermozilla-inbound@85dd7094b78d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs729182
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 729182 - Implement visual event tracer, part2 - event tracer service, r=ehsan
xpcom/base/VisualEventTracer.cpp
xpcom/base/VisualEventTracer.h
xpcom/base/moz.build
xpcom/base/nsIVisualEventTracer.idl
xpcom/build/XPCOMModule.inc
xpcom/build/nsXPComInit.cpp
--- a/xpcom/base/VisualEventTracer.cpp
+++ b/xpcom/base/VisualEventTracer.cpp
@@ -14,47 +14,67 @@
 #include "nsThreadUtils.h"
 
 namespace mozilla { namespace eventtracer {
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
 
 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
 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(mText2);
     PL_strfree(mText); 
-    PL_strfree(mText2);
     MOZ_COUNT_DTOR(Record);
   }
 
   uint32_t mType;
-  double mTime;
+  TimeStamp mTime;
   void * mItem;
   char * mText;
   char * mText2;
 };
 
 char * DupCurrentThreadName()
 {
   if (NS_IsMainThread())
@@ -69,68 +89,232 @@ char * DupCurrentThreadName()
   PR_snprintf(buffer, 127, "Nameless %p", currentThread);
 
   return PL_strdup(buffer);
 }
 
 // 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);
   }
 
   ~RecordBatch()
   {
     delete [] mRecordsHead;
     PL_strfree(mThreadNameCopy);
     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;
+
+// Registers the batch in the linked list
+// static
+RecordBatch *
+RecordBatch::Register()
+{
+  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;
 
-// Registered as thread private data destructor
+  // 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::FlushBatch(void * aData)
+RecordBatch::Delete(RecordBatch * aLog)
 {
-  RecordBatch * threadLogPrivate = static_cast<RecordBatch *>(aData);
+  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);
+}
+
+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.
 
-  mon.Notify();  
+    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
 class EventFilter
 {
 public:
   static EventFilter * Build(const char * filterVar);
   bool EventPasses(const char * eventName);
@@ -198,249 +382,266 @@ EventFilter::EventPasses(const char * ev
 }
 
 // State var to stop the flushing thread
 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)
 {
   if (!gEventFilter)
     return true;
 
   if (aType == eName)
     return true;
 
-  if (aItem == gFlushingThread) // Pass events coming from the tracer
-    return true;
-
   return gEventFilter->EventPasses(aText);
 }
 
 } // anon namespace
 
 #endif //MOZ_VISUAL_EVENT_TRACER
 
 // static 
 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;
 
   gMonitor = new mozilla::Monitor("Profiler");
   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
 }
 
 // static 
 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);
+  gCapture = false;
+  gInitialized = false;
 
-  if (gFlushingThread) {
-    {
-      MonitorAutoLock mon(*gMonitor);
-      gInitialized = false;
-      gStopFlushingThread = true;
-      mon.Notify();
-    }
-
-    PR_JoinThread(gFlushingThread);
-    gFlushingThread = nullptr;
-  }
+  RecordBatch::DeleteLog();
 
   if (gMonitor) {
     delete gMonitor;
     gMonitor = nullptr;
   }
 
   if (gEventFilter) {
     delete gEventFilter;
     gEventFilter = nullptr;
   }
+
+  if (gProfilerStart) {
+    delete gProfilerStart;
+    gProfilerStart = nullptr;
+  }
+
+  if (gMaxBacklogTime) {
+    delete gMaxBacklogTime;
+    gMaxBacklogTime = nullptr;
+  }
 #endif
 }
 
 // static 
 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)
     return;
 
   if (!CheckEventFilters(aType, aItem, aText)) // Events use just aText
     return;
 
   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
--- a/xpcom/base/VisualEventTracer.h
+++ b/xpcom/base/VisualEventTracer.h
@@ -17,16 +17,17 @@
  *
  * To let the event tracer log only some events to save disk space, export 
  * MOZ_PROFILING_EVENTS with comma separated list of event names you want 
  * to record in the log.
  */
 
 #include "nscore.h"
 #include "mozilla/GuardObjects.h"
+#include "nsIVisualEventTracer.h"
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
 
 // Bind an object instance, usually |this|, to a name, usually URL or 
 // host name, the instance deals with for its lifetime.  The name string 
 // is duplicated.
 // IMPORTANT: it is up to the caller to pass the correct static_cast
 // of the |instance| pointer to all these macros ; otherwise the linking
@@ -110,17 +111,18 @@
 #define MOZ_EVENT_TRACER_DONE(instance, name) (void)0
 #define MOZ_EVENT_TRACER_WAIT_THREADSAFE(instance, name) (void)0
 #define MOZ_EVENT_TRACER_EXEC_THREADSAFE(instance, name) (void)0
 #define MOZ_EVENT_TRACER_DONE_THREASAFE(instance, name) (void)0
 
 #endif
 
 
-namespace mozilla { namespace eventtracer {
+namespace mozilla {
+namespace eventtracer {
 
 // Initialize the event tracer engine, called automatically on XPCOM startup.
 void Init();
 
 // Shuts the event tracer engine down and closes the log file, called 
 // automatically during XPCOM shutdown.
 void Shutdown();
 
@@ -197,10 +199,28 @@ private:
   const char * mName;
   const char * mName2;
   uint32_t mTypeOn;
   uint32_t mTypeOff;
 
   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
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -20,16 +20,17 @@ XPIDL_SOURCES += [
     'nsIMemoryReporter.idl',
     'nsIMessageLoop.idl',
     'nsIMutable.idl',
     'nsIProgrammingLanguage.idl',
     'nsISupports.idl',
     'nsITraceRefcnt.idl',
     'nsIUUIDGenerator.idl',
     'nsIVersionComparator.idl',
+		'nsIVisualEventTracer.idl',
     'nsIWeakReference.idl',
     'nsrootidl.idl',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     XPIDL_SOURCES += [
         'nsIMacUtils.idl',
     ]
new file mode 100644
--- /dev/null
+++ b/xpcom/base/nsIVisualEventTracer.idl
@@ -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;
+};
--- a/xpcom/build/XPCOMModule.inc
+++ b/xpcom/build/XPCOMModule.inc
@@ -18,16 +18,19 @@
     COMPONENT(PERSISTENTPROPERTIES, nsPersistentProperties::Create)
 
     COMPONENT(SUPPORTSARRAY, nsSupportsArray::Create)
     COMPONENT(ARRAY, nsArray::XPCOMConstructor)
     COMPONENT(CONSOLESERVICE, nsConsoleServiceConstructor)
     COMPONENT(EXCEPTIONSERVICE, nsExceptionServiceConstructor)
     COMPONENT(ATOMSERVICE, nsAtomServiceConstructor)
     COMPONENT(OBSERVERSERVICE, nsObserverService::Create)
+#ifdef MOZ_VISUAL_EVENT_TRACER
+    COMPONENT(VISUALEVENTTRACER, VisualEventTracerConstructor)
+#endif
 
     COMPONENT(TIMER, nsTimerImplConstructor)
 
 #define COMPONENT_SUPPORTS(TYPE, Type)                                         \
   COMPONENT(SUPPORTS_##TYPE, nsSupports##Type##ImplConstructor)
 
     COMPONENT_SUPPORTS(ID, ID)
     COMPONENT_SUPPORTS(STRING, String)
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -115,22 +115,27 @@ extern nsresult nsStringInputStreamConst
 #include "base/command_line.h"
 #include "base/message_loop.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/MapsMemoryReporter.h"
 #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 {
 
 static AtExitManager* sExitManager;
 static MessageLoop* sMessageLoop;
 static bool sCommandLineWasInitialized;
 static BrowserProcessSubThread* sIOThread;
 
@@ -172,16 +177,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsCo
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsAtomService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsExceptionService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsTimerImpl)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBinaryOutputStream)
 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)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHashPropertyBag, Init)
 
 NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsProperties, Init)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsUUIDGenerator, Init)
@@ -481,17 +489,19 @@ NS_InitXPCOM2(nsIServiceManager* *result
 #endif
 
     mozilla::MapsMemoryReporter::Init();
 
     mozilla::Telemetry::Init();
 
     mozilla::HangMonitor::Startup();
 
+#ifdef MOZ_VISUAL_EVENT_TRACER
     mozilla::eventtracer::Init();
+#endif
 
     return NS_OK;
 }
 
 
 //
 // NS_ShutdownXPCOM()
 //
@@ -710,16 +720,18 @@ ShutdownXPCOM(nsIServiceManager* servMgr
         delete sExitManager;
         sExitManager = nullptr;
     }
 
     Omnijar::CleanUp();
 
     HangMonitor::Shutdown();
 
+#ifdef MOZ_VISUAL_EVENT_TRACER
     eventtracer::Shutdown();
+#endif
 
     NS_LogTerm();
 
     return NS_OK;
 }
 
 } // namespace mozilla