Bug 1497016 - Add an API to extract tracelogger data and use this within the gecko profiler r=mstange,djvj
authorDenis Palmeiro <dpalmeiro@mozilla.com>
Tue, 04 Dec 2018 21:43:38 +0000
changeset 508574 7dc99db92ea4dee6bd7a02b20067fd3b64e1f352
parent 508573 36c4c80ddc1a12665af8cbcaef716e384caab99e
child 508575 c28a868a45f5b368f687f2f6e9b32914a9d560c0
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange, djvj
bugs1497016
milestone65.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 1497016 - Add an API to extract tracelogger data and use this within the gecko profiler r=mstange,djvj Add a new class to extract tracelogger data using chunked buffers and use this to write the data out to the profiler JSON output. Copying the data in chunks lets us minimize our memory overhead when writing out to the profiler so a large array of millions of elements does not need to be allocated ahead of time. Differential Revision: https://phabricator.services.mozilla.com/D11791
browser/components/extensions/schemas/geckoProfiler.json
devtools/client/performance-new/components/Settings.js
js/public/TraceLoggerAPI.h
js/src/vm/TraceLogging.cpp
js/src/vm/TraceLogging.h
js/src/vm/TraceLoggingTypes.h
tools/profiler/core/ProfiledThreadData.cpp
tools/profiler/core/ProfiledThreadData.h
tools/profiler/core/RegisteredThread.cpp
tools/profiler/core/RegisteredThread.h
tools/profiler/core/platform.cpp
tools/profiler/core/platform.h
tools/profiler/public/GeckoProfiler.h
--- a/browser/components/extensions/schemas/geckoProfiler.json
+++ b/browser/components/extensions/schemas/geckoProfiler.json
@@ -30,17 +30,18 @@
           "memory",
           "privacy",
           "responsiveness",
           "screenshots",
           "seqstyle",
           "stackwalk",
           "tasktracer",
           "threads",
-          "trackopts"
+          "trackopts",
+          "jstracer"
         ]
       },
       {
         "id": "supports",
         "type": "string",
         "enum": [
           "windowLength"
         ]
--- a/devtools/client/performance-new/components/Settings.js
+++ b/devtools/client/performance-new/components/Settings.js
@@ -143,16 +143,21 @@ const featureCheckboxes = [
     value: "tasktracer",
     title: "Enable TaskTracer (Experimental, requires custom build.)",
   },
   {
     name: "Screenshots",
     value: "screenshots",
     title: "Record screenshots of all browser windows.",
   },
+  {
+    name: "JSTracer",
+    value: "jstracer",
+    title: "Trace JS engine (Experimental, requires custom build.)",
+  },
 ];
 
 /**
  * This component manages the settings for recording a performance profile.
  */
 class Settings extends PureComponent {
   static get propTypes() {
     return {
--- a/js/public/TraceLoggerAPI.h
+++ b/js/public/TraceLoggerAPI.h
@@ -5,37 +5,195 @@
 
 /* SpiderMonkey TraceLogger APIs. */
 
 #ifndef js_TraceLoggerAPI_h
 #define js_TraceLoggerAPI_h
 
 #include "jstypes.h"
 
+namespace mozilla {
+class JSONWriteFunc;
+class TimeStamp;
+};  // namespace mozilla
+
 namespace JS {
+
+// Used to lock any tracelogger activities, and consequently, will also block
+// any further JS execution when a thread hits an atomic tracelogger activity
+// such as payload creation.
+class AutoTraceLoggerLockGuard {
+ public:
+  AutoTraceLoggerLockGuard();
+  ~AutoTraceLoggerLockGuard();
+};
+
+// An implementation type must be defined in order to gather data using the
+// TraceLoggerCollectorBuffer. Each implementation must define the type that is
+// being collected in the buffer, along with a static method that is used to
+// actually write into the buffer from the tracelogger.
+struct TraceLoggerDictionaryImpl {
+  using ImplType = char;
+  static size_t NextChunk(JSContext* cx, size_t* dataIndex, ImplType buffer[],
+                          size_t bufferSize);
+};
+
+struct TraceLoggerIdImpl {
+  using ImplType = uint32_t;
+  static size_t NextChunk(JSContext* cx, size_t* dataIndex, ImplType buffer[],
+                          size_t bufferSize);
+};
+
+struct TraceLoggerLineNoImpl {
+  using ImplType = int32_t;
+  static size_t NextChunk(JSContext* cx, size_t* dataIndex, ImplType buffer[],
+                          size_t bufferSize);
+};
+
+struct TraceLoggerColNoImpl {
+  using ImplType = int32_t;
+  static size_t NextChunk(JSContext* cx, size_t* dataIndex, ImplType buffer[],
+                          size_t bufferSize);
+};
+
+struct TraceLoggerTimeStampImpl {
+  using ImplType = mozilla::TimeStamp;
+  static size_t NextChunk(JSContext* cx, size_t* dataIndex, ImplType buffer[],
+                          size_t bufferSize);
+};
+
+struct TraceLoggerDurationImpl {
+  using ImplType = double;
+  static size_t NextChunk(JSContext* cx, size_t* dataIndex, ImplType buffer[],
+                          size_t bufferSize);
+};
+
+// Buffer that is used to retrieve tracelogger data in fixed size chunks so that
+// allocation of a large array is not necessary.  The TraceLoggerCollectorBuffer
+// class will manage an internal state which points to the next data index being
+// collected.  Each call to NextChunk will also clobber the internal buffer used
+// to store the data.
+template <class T>
+class TraceLoggerCollectorBuffer {
+  using ImplType = typename T::ImplType;
+
+ public:
+  class Iterator {
+   public:
+    Iterator(ImplType* buffer, size_t index)
+        : iteratorIndex(index), buf(buffer) {}
+
+    Iterator operator++() {
+      iteratorIndex++;
+      return *this;
+    }
+
+    bool operator!=(const Iterator& other) const {
+      return iteratorIndex != other.iteratorIndex;
+    }
+
+    ImplType operator*() const { return buf[iteratorIndex]; }
+
+   private:
+    size_t iteratorIndex;
+    ImplType* buf;
+  };
+
+  explicit TraceLoggerCollectorBuffer(AutoTraceLoggerLockGuard& lockGuard,
+                                      JSContext* cx = nullptr,
+                                      size_t length = 4096)
+      : cx_(cx), length_(length), dataIndex_(0), bufferIndex_(0) {
+    buffer_ = js_pod_malloc<ImplType>(length);
+  }
+
+  ~TraceLoggerCollectorBuffer() { js_free(buffer_); }
+
+  Iterator begin() const { return Iterator(buffer_, 0); }
+
+  Iterator end() const { return Iterator(buffer_, bufferIndex_); }
+
+  ImplType* internalBuffer() const { return buffer_; }
+
+  bool NextChunk() {
+    bufferIndex_ = T::NextChunk(cx_, &dataIndex_, buffer_, length_);
+    return (bufferIndex_ != 0) ? true : false;
+  }
+
+ private:
+  JSContext* cx_;
+  size_t length_;
+  size_t dataIndex_;
+  size_t bufferIndex_;
+  ImplType* buffer_;
+};
+
 #ifdef JS_TRACE_LOGGING
-
 // Begin trace logging events.  This will activate some of the
 // textId's for various events and set the global option
 // JSJITCOMPILER_ENABLE_TRACELOGGER to true.
 // This does nothing except return if the trace logger is already active.
-extern JS_PUBLIC_API void StartTraceLogger(JSContext *cx,
-                                           mozilla::TimeStamp profilerStart);
+extern JS_PUBLIC_API void StartTraceLogger(JSContext* cx);
 
 // Stop trace logging events.  All textId's will be set to false, and the
 // global JSJITCOMPILER_ENABLE_TRACELOGGER will be set to false.
 // This does nothing except return if the trace logger is not active.
-extern JS_PUBLIC_API void StopTraceLogger(JSContext *cx);
+extern JS_PUBLIC_API void StopTraceLogger(JSContext* cx);
 
 // Clear and free any event data that was recorded by the trace logger.
 extern JS_PUBLIC_API void ResetTraceLogger(void);
 
 #else
 // Define empty inline functions for when trace logging compilation is not
 // enabled.  TraceLogging.cpp will not be built in that case so we need to
 // provide something for any routines that reference these.
-inline void StartTraceLogger(JSContext *cx, mozilla::TimeStamp profilerStart) {}
-inline void StopTraceLogger(JSContext *cx) {}
+inline void StartTraceLogger(JSContext* cx) {}
+inline void StopTraceLogger(JSContext* cx) {}
 inline void ResetTraceLogger(void) {}
+inline size_t TraceLoggerDictionaryImpl::NextChunk(JSContext* cx,
+                                                   size_t* dataIndex,
+                                                   ImplType buffer[],
+                                                   size_t bufferSize) {
+  return 0;
+}
+inline size_t TraceLoggerIdImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                           ImplType buffer[],
+                                           size_t bufferSize) {
+  return 0;
+}
+inline size_t TraceLoggerTimeStampImpl::NextChunk(JSContext* cx,
+                                                  size_t* dataIndex,
+                                                  ImplType buffer[],
+                                                  size_t bufferSize) {
+  return 0;
+}
+inline size_t TraceLoggerDurationImpl::NextChunk(JSContext* cx,
+                                                 size_t* dataIndex,
+                                                 ImplType buffer[],
+                                                 size_t bufferSize) {
+  return 0;
+}
+inline size_t TraceLoggerLineNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                               ImplType buffer[],
+                                               size_t bufferSize) {
+  return 0;
+}
+inline size_t TraceLoggerColNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                              ImplType buffer[],
+                                              size_t bufferSize) {
+  return 0;
+}
+inline AutoTraceLoggerLockGuard::AutoTraceLoggerLockGuard() {}
+inline AutoTraceLoggerLockGuard::~AutoTraceLoggerLockGuard() {}
 #endif
+using TraceLoggerDictionaryBuffer =
+    TraceLoggerCollectorBuffer<JS::TraceLoggerDictionaryImpl>;
+using TraceLoggerIdBuffer = TraceLoggerCollectorBuffer<JS::TraceLoggerIdImpl>;
+using TraceLoggerTimeStampBuffer =
+    TraceLoggerCollectorBuffer<JS::TraceLoggerTimeStampImpl>;
+using TraceLoggerDurationBuffer =
+    TraceLoggerCollectorBuffer<JS::TraceLoggerDurationImpl>;
+using TraceLoggerLineNoBuffer =
+    TraceLoggerCollectorBuffer<JS::TraceLoggerLineNoImpl>;
+using TraceLoggerColNoBuffer =
+    TraceLoggerCollectorBuffer<JS::TraceLoggerColNoImpl>;
 };  // namespace JS
 
 #endif /* js_TraceLoggerAPI_h */
--- a/js/src/vm/TraceLogging.cpp
+++ b/js/src/vm/TraceLogging.cpp
@@ -631,16 +631,288 @@ void TraceLoggerThread::stopEvent(uint32
       MOZ_ASSERT(id == prev);
     }
   }
 #endif
 
   log(TraceLogger_Stop);
 }
 
+JS::AutoTraceLoggerLockGuard::AutoTraceLoggerLockGuard() {
+  traceLoggerState->lock.lock();
+}
+
+JS::AutoTraceLoggerLockGuard::~AutoTraceLoggerLockGuard() {
+  traceLoggerState->lock.unlock();
+}
+
+size_t JS::TraceLoggerDictionaryImpl::NextChunk(JSContext* cx,
+                                                size_t* dataIndex,
+                                                char buffer[],
+                                                size_t bufferSize) {
+  MOZ_ASSERT(dataIndex != nullptr);
+  if (!traceLoggerState || bufferSize == 0 || !buffer ||
+      !jit::JitOptions.enableTraceLogger) {
+    return 0;
+  }
+
+  size_t bufferIndex = 0;
+
+  const char* eventString = nullptr;
+  if (*dataIndex < TraceLogger_Last) {
+    eventString = TLTextIdString(static_cast<TraceLoggerTextId>(*dataIndex));
+  } else {
+    uint32_t dictId = *dataIndex - TraceLogger_Last;
+    if (dictId < traceLoggerState->dictionaryData.length()) {
+      eventString = traceLoggerState->dictionaryData[dictId].get();
+      MOZ_ASSERT(eventString);
+    }
+  }
+
+  if (eventString) {
+    size_t length = strlen(eventString);
+    if (length < bufferSize - 1) {
+      memcpy(buffer, eventString, length);
+      buffer[length] = '\0';
+      bufferIndex = length;
+    } else {
+      memcpy(buffer, eventString, bufferSize);
+      buffer[bufferSize - 1] = '\0';
+      bufferIndex = bufferSize - 1;
+    }
+  }
+
+  (*dataIndex)++;
+  return bufferIndex;
+}
+
+size_t JS::TraceLoggerIdImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                        uint32_t buffer[], size_t bufferSize) {
+  MOZ_ASSERT(dataIndex != nullptr);
+  if (!cx || !cx->traceLogger) {
+    return 0;
+  }
+
+  if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
+    return 0;
+  }
+
+  size_t bufferIndex = 0;
+  ContinuousSpace<EventEntry>& events = cx->traceLogger->events;
+
+  for (; *dataIndex < events.size(); (*dataIndex)++) {
+    if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
+      continue;
+    }
+
+    if (events[*dataIndex].textId >= TraceLogger_Last) {
+      TraceLoggerEventPayload* payload =
+          traceLoggerState->getPayload(events[*dataIndex].textId);
+      MOZ_ASSERT(payload);
+      // Write the index of this event into the jsTracerDictionary array
+      // property
+      uint32_t dictId = TraceLogger_Last + payload->dictionaryId();
+      buffer[bufferIndex++] = dictId;
+      payload->release();
+    } else {
+      buffer[bufferIndex++] = events[*dataIndex].textId;
+      ;
+    }
+
+    if (bufferIndex == bufferSize) {
+      break;
+    }
+  }
+
+  return bufferIndex;
+}
+
+size_t JS::TraceLoggerLineNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                            int32_t buffer[],
+                                            size_t bufferSize) {
+  MOZ_ASSERT(dataIndex != nullptr);
+  if (!cx || !cx->traceLogger) {
+    return 0;
+  }
+
+  if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
+    return 0;
+  }
+
+  size_t bufferIndex = 0;
+  ContinuousSpace<EventEntry>& events = cx->traceLogger->events;
+
+  for (; *dataIndex < events.size(); (*dataIndex)++) {
+    if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
+      continue;
+    }
+
+    if (events[*dataIndex].textId >= TraceLogger_Last) {
+      TraceLoggerEventPayload* payload =
+          traceLoggerState->getPayload(events[*dataIndex].textId);
+      MOZ_ASSERT(payload);
+      mozilla::Maybe<uint32_t> line = payload->line();
+      payload->release();
+      if (line) {
+        buffer[bufferIndex++] = *line;
+      } else {
+        buffer[bufferIndex++] = -1;
+      }
+    } else {
+      buffer[bufferIndex++] = -1;
+    }
+    if (bufferIndex == bufferSize) {
+      break;
+    }
+  }
+
+  return bufferIndex;
+}
+
+size_t JS::TraceLoggerColNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                           int32_t buffer[],
+                                           size_t bufferSize) {
+  MOZ_ASSERT(dataIndex != nullptr);
+  if (!cx || !cx->traceLogger) {
+    return 0;
+  }
+
+  if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
+    return 0;
+  }
+
+  size_t bufferIndex = 0;
+  ContinuousSpace<EventEntry>& events = cx->traceLogger->events;
+
+  for (; *dataIndex < events.size(); (*dataIndex)++) {
+    if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
+      continue;
+    }
+
+    if (events[*dataIndex].textId >= TraceLogger_Last) {
+      TraceLoggerEventPayload* payload =
+          traceLoggerState->getPayload(events[*dataIndex].textId);
+      MOZ_ASSERT(payload);
+      mozilla::Maybe<uint32_t> column = payload->column();
+      payload->release();
+      if (column) {
+        buffer[bufferIndex++] = *column;
+      } else {
+        buffer[bufferIndex++] = -1;
+      }
+    } else {
+      buffer[bufferIndex++] = -1;
+    }
+    if (bufferIndex == bufferSize) {
+      break;
+    }
+  }
+
+  return bufferIndex;
+}
+
+size_t JS::TraceLoggerTimeStampImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                               mozilla::TimeStamp buffer[],
+                                               size_t bufferSize) {
+  MOZ_ASSERT(dataIndex != nullptr);
+  if (!cx || !cx->traceLogger) {
+    return 0;
+  }
+
+  if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
+    return 0;
+  }
+
+  size_t bufferIndex = 0;
+  ContinuousSpace<EventEntry>& events = cx->traceLogger->events;
+
+  for (; *dataIndex < events.size(); (*dataIndex)++) {
+    if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
+      continue;
+    }
+    buffer[bufferIndex++] = events[*dataIndex].time;
+    if (bufferIndex == bufferSize) {
+      break;
+    }
+  }
+
+  return bufferIndex;
+}
+
+size_t JS::TraceLoggerDurationImpl::NextChunk(JSContext* cx, size_t* dataIndex,
+                                              double buffer[],
+                                              size_t bufferSize) {
+  MOZ_ASSERT(dataIndex != nullptr);
+  if (!cx || !cx->traceLogger) {
+    return 0;
+  }
+
+  if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
+    return 0;
+  }
+
+  ContinuousSpace<EventEntry>& events = cx->traceLogger->events;
+  Vector<size_t, 0, js::SystemAllocPolicy> eventStack;
+  using EventDurationMap =
+      HashMap<size_t, double, DefaultHasher<size_t>, SystemAllocPolicy>;
+  EventDurationMap eventMap;
+
+  size_t bufferIndex = 0;
+  for (; *dataIndex < events.size(); (*dataIndex)++) {
+    if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
+      continue;
+    }
+    double duration = 0;
+    if (TLTextIdIsLogEvent(events[*dataIndex].textId)) {
+      // log events are snapshot events with no start & stop
+      duration = -1;
+    } else if (EventDurationMap::Ptr p = eventMap.lookup(*dataIndex)) {
+      // value has already been cached
+      duration = p->value();
+    } else {
+      MOZ_ASSERT(eventStack.empty());
+      if (!eventStack.append(*dataIndex)) {
+        return 0;
+      }
+
+      // Search through the events array to find the matching stop event in
+      // order to calculate the duration time.  Cache all other durations we
+      // calculate in the meantime.
+      for (size_t j = *dataIndex + 1; j < events.size(); j++) {
+        uint32_t id = events[j].textId;
+        if (id == TraceLogger_Stop) {
+          uint32_t prev = eventStack.popCopy();
+          double delta = (events[j].time - events[prev].time).ToMicroseconds();
+          if (prev == *dataIndex) {
+            MOZ_ASSERT(eventStack.empty());
+            duration = delta;
+            break;
+          }
+
+          if (!eventMap.putNew(prev, delta)) {
+            return 0;
+          }
+
+        } else if (TLTextIdIsTreeEvent(id)) {
+          if (!eventStack.append(j)) {
+            return 0;
+          }
+        }
+      }
+    }
+
+    buffer[bufferIndex++] = duration;
+    if (bufferIndex == bufferSize) {
+      break;
+    }
+  }
+
+  return bufferIndex;
+}
+
 void TraceLoggerThread::logTimestamp(TraceLoggerTextId id) {
   logTimestamp(uint32_t(id));
 }
 
 void TraceLoggerThread::logTimestamp(uint32_t id) {
   MOZ_ASSERT(id > TraceLogger_TreeItemEnd && id < TraceLogger_Last);
   log(id);
 }
@@ -1104,38 +1376,41 @@ TraceLoggerEvent::TraceLoggerEvent(const
     : payload_(other.payload_) {
   if (hasExtPayload()) {
     extPayload()->use();
   }
 }
 
 JS_PUBLIC_API void JS::ResetTraceLogger(void) { js::ResetTraceLogger(); }
 
-JS_PUBLIC_API void JS::StartTraceLogger(JSContext* cx,
-                                        mozilla::TimeStamp profilerStart) {
-  if (jit::JitOptions.enableTraceLogger || !traceLoggerState) {
+JS_PUBLIC_API void JS::StartTraceLogger(JSContext* cx) {
+  if (!EnsureTraceLoggerState()) {
     return;
   }
 
-  LockGuard<Mutex> guard(traceLoggerState->lock);
-  traceLoggerState->enableTextIdsForProfiler();
-  jit::JitOptions.enableTraceLogger = true;
+  if (!jit::JitOptions.enableTraceLogger) {
+    LockGuard<Mutex> guard(traceLoggerState->lock);
+    traceLoggerState->enableTextIdsForProfiler();
+    jit::JitOptions.enableTraceLogger = true;
+  }
 
-  // Reset the start time to profile start so it aligns with sampling.
-  traceLoggerState->startTime = profilerStart;
-
-  if (cx->traceLogger) {
-    cx->traceLogger->enable();
+  TraceLoggerThread* logger = traceLoggerState->forCurrentThread(cx);
+  if (logger) {
+    logger->enable();
   }
 }
 
 JS_PUBLIC_API void JS::StopTraceLogger(JSContext* cx) {
-  if (!jit::JitOptions.enableTraceLogger || !traceLoggerState) {
+  if (!traceLoggerState) {
     return;
   }
 
-  LockGuard<Mutex> guard(traceLoggerState->lock);
-  traceLoggerState->disableTextIdsForProfiler();
-  jit::JitOptions.enableTraceLogger = false;
-  if (cx->traceLogger) {
-    cx->traceLogger->disable();
+  if (jit::JitOptions.enableTraceLogger) {
+    LockGuard<Mutex> guard(traceLoggerState->lock);
+    traceLoggerState->disableTextIdsForProfiler();
+    jit::JitOptions.enableTraceLogger = false;
+  }
+
+  TraceLoggerThread* logger = traceLoggerState->forCurrentThread(cx);
+  if (logger) {
+    logger->disable();
   }
 }
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -14,16 +14,17 @@
 #include "mozilla/Vector.h"
 
 #include <utility>
 
 #include "jsapi.h"
 
 #include "js/AllocPolicy.h"
 #include "js/HashTable.h"
+#include "js/TraceLoggerAPI.h"
 #include "js/TypeDecls.h"
 #include "js/Vector.h"
 #include "threading/LockGuard.h"
 #include "vm/MutexIDs.h"
 #include "vm/TraceLoggingGraph.h"
 #include "vm/TraceLoggingTypes.h"
 
 namespace js {
@@ -213,16 +214,22 @@ class TraceLoggerEventPayload {
   size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
     return mallocSizeOf(this);
   }
 };
 
 // Per thread trace logger state.
 class TraceLoggerThread : public mozilla::LinkedListElement<TraceLoggerThread> {
 #ifdef JS_TRACE_LOGGING
+  friend JS::TraceLoggerIdImpl;
+  friend JS::TraceLoggerTimeStampImpl;
+  friend JS::TraceLoggerDurationImpl;
+  friend JS::TraceLoggerLineNoImpl;
+  friend JS::TraceLoggerColNoImpl;
+
  private:
   uint32_t enabled_;
   bool failed;
 
   UniquePtr<TraceLoggerGraph> graph;
 
   ContinuousSpace<EventEntry> events;
 
@@ -340,16 +347,17 @@ class TraceLoggerThread : public mozilla
     return offsetof(TraceLoggerThread, enabled_);
   }
 #endif
 };
 
 // Process wide trace logger state.
 class TraceLoggerThreadState {
 #ifdef JS_TRACE_LOGGING
+  friend JS::TraceLoggerDictionaryImpl;
 #ifdef DEBUG
   bool initialized;
 #endif
 
   bool enabledTextIds[TraceLogger_Last];
   bool mainThreadEnabled;
   bool helperThreadEnabled;
   bool graphEnabled;
--- a/js/src/vm/TraceLoggingTypes.h
+++ b/js/src/vm/TraceLoggingTypes.h
@@ -99,17 +99,18 @@ enum TraceLoggerTextId {
       TraceLogger_Last
 };
 
 inline const char* TLTextIdString(TraceLoggerTextId id) {
   switch (id) {
     case TraceLogger_Error:
       return "TraceLogger failed to process text";
     case TraceLogger_Internal:
-      return "TraceLogger overhead";
+    case TraceLogger_TreeItemEnd:
+      return "TraceLogger internal event";
 #define NAME(textId)         \
   case TraceLogger_##textId: \
     return #textId;
       TRACELOGGER_TREE_ITEMS(NAME)
       TRACELOGGER_LOG_ITEMS(NAME)
 #undef NAME
     default:
       MOZ_CRASH();
--- a/tools/profiler/core/ProfiledThreadData.cpp
+++ b/tools/profiler/core/ProfiledThreadData.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "ProfiledThreadData.h"
+#include "js/TraceLoggerAPI.h"
 
 #include "mozilla/dom/ContentChild.h"
 
 #if defined(GP_OS_darwin)
 #include <pthread.h>
 #endif
 
 #ifdef XP_WIN
@@ -32,17 +33,17 @@ ProfiledThreadData::ProfiledThreadData(T
 ProfiledThreadData::~ProfiledThreadData() {
   MOZ_COUNT_DTOR(ProfiledThreadData);
 }
 
 void ProfiledThreadData::StreamJSON(const ProfileBuffer& aBuffer,
                                     JSContext* aCx,
                                     SpliceableJSONWriter& aWriter,
                                     const TimeStamp& aProcessStartTime,
-                                    double aSinceTime) {
+                                    double aSinceTime, bool JSTracerEnabled) {
   if (mJITFrameInfoForPreviousJSContexts &&
       mJITFrameInfoForPreviousJSContexts->HasExpired(aBuffer.mRangeStart)) {
     mJITFrameInfoForPreviousJSContexts = nullptr;
   }
 
   // If we have an existing JITFrameInfo in mJITFrameInfoForPreviousJSContexts,
   // copy the data from it.
   JITFrameInfo jitFrameInfo =
@@ -97,19 +98,111 @@ void ProfiledThreadData::StreamJSON(cons
     }
     aWriter.EndObject();
 
     aWriter.StartArrayProperty("stringTable");
     { uniqueStacks.mUniqueStrings->SpliceStringTableElements(aWriter); }
     aWriter.EndArray();
   }
 
+  if (aCx && JSTracerEnabled) {
+    StreamTraceLoggerJSON(aCx, aWriter, aProcessStartTime);
+  }
+
   aWriter.End();
 }
 
+void ProfiledThreadData::StreamTraceLoggerJSON(
+    JSContext* aCx, SpliceableJSONWriter& aWriter,
+    const TimeStamp& aProcessStartTime) {
+  aWriter.StartObjectProperty("jsTracerEvents");
+  {
+    JS::AutoTraceLoggerLockGuard lockGuard;
+    uint32_t length = 0;
+
+    // Collect Event Ids
+    aWriter.StartArrayProperty("events", mozilla::JSONWriter::SingleLineStyle);
+    {
+      JS::TraceLoggerIdBuffer collectionBuffer(lockGuard, aCx);
+      while (collectionBuffer.NextChunk()) {
+        for (uint32_t val : collectionBuffer) {
+          aWriter.IntElement(val);
+          length++;
+        }
+      }
+    }
+    aWriter.EndArray();
+
+    // Collect Event Timestamps
+    aWriter.StartArrayProperty("timestamps",
+                               mozilla::JSONWriter::SingleLineStyle);
+    {
+      JS::TraceLoggerTimeStampBuffer collectionBuffer(lockGuard, aCx);
+      while (collectionBuffer.NextChunk()) {
+        for (TimeStamp val : collectionBuffer) {
+          aWriter.DoubleElement((val - aProcessStartTime).ToMicroseconds());
+        }
+      }
+    }
+    aWriter.EndArray();
+
+    // Collect Event Durations
+    aWriter.StartArrayProperty("durations",
+                               mozilla::JSONWriter::SingleLineStyle);
+    {
+      JS::TraceLoggerDurationBuffer collectionBuffer(lockGuard, aCx);
+      while (collectionBuffer.NextChunk()) {
+        for (double val : collectionBuffer) {
+          if (val == -1) {
+            aWriter.NullElement();
+          } else {
+            aWriter.DoubleElement(val);
+          }
+        }
+      }
+    }
+    aWriter.EndArray();
+
+    // Collect Event LineNo
+    aWriter.StartArrayProperty("line", mozilla::JSONWriter::SingleLineStyle);
+    {
+      JS::TraceLoggerLineNoBuffer collectionBuffer(lockGuard, aCx);
+      while (collectionBuffer.NextChunk()) {
+        for (int32_t val : collectionBuffer) {
+          if (val == -1) {
+            aWriter.NullElement();
+          } else {
+            aWriter.IntElement(val);
+          }
+        }
+      }
+    }
+    aWriter.EndArray();
+
+    // Collect Event ColNo
+    aWriter.StartArrayProperty("column", mozilla::JSONWriter::SingleLineStyle);
+    {
+      JS::TraceLoggerColNoBuffer collectionBuffer(lockGuard, aCx);
+      while (collectionBuffer.NextChunk()) {
+        for (int32_t val : collectionBuffer) {
+          if (val == -1) {
+            aWriter.NullElement();
+          } else {
+            aWriter.IntElement(val);
+          }
+        }
+      }
+    }
+    aWriter.EndArray();
+
+    aWriter.IntProperty("length", length);
+  }
+  aWriter.EndObject();
+}
+
 void StreamSamplesAndMarkers(const char* aName, int aThreadId,
                              const ProfileBuffer& aBuffer,
                              SpliceableJSONWriter& aWriter,
                              const TimeStamp& aProcessStartTime,
                              const TimeStamp& aRegisterTime,
                              const TimeStamp& aUnregisterTime,
                              double aSinceTime, UniqueStacks& aUniqueStacks) {
   aWriter.StringProperty("processType",
--- a/tools/profiler/core/ProfiledThreadData.h
+++ b/tools/profiler/core/ProfiledThreadData.h
@@ -58,17 +58,20 @@ class ProfiledThreadData final {
     return mBufferPositionWhenUnregistered;
   }
 
   mozilla::Maybe<uint64_t>& LastSample() { return mLastSample; }
 
   void StreamJSON(const ProfileBuffer& aBuffer, JSContext* aCx,
                   SpliceableJSONWriter& aWriter,
                   const mozilla::TimeStamp& aProcessStartTime,
-                  double aSinceTime);
+                  double aSinceTime, bool aJSTracerEnabled);
+
+  void StreamTraceLoggerJSON(JSContext* aCx, SpliceableJSONWriter& aWriter,
+                             const TimeStamp& aProcessStartTime);
 
   // Returns nullptr if this is not the main thread, the responsiveness
   // feature is not turned on, or if this thread is not being profiled.
   ThreadResponsiveness* GetThreadResponsiveness() {
     ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
     return responsiveness;
   }
 
--- a/tools/profiler/core/RegisteredThread.cpp
+++ b/tools/profiler/core/RegisteredThread.cpp
@@ -10,17 +10,17 @@ RegisteredThread::RegisteredThread(Threa
                                    void* aStackTop)
     : mRacyRegisteredThread(aInfo->ThreadId()),
       mPlatformData(AllocPlatformData(aInfo->ThreadId())),
       mStackTop(aStackTop),
       mThreadInfo(aInfo),
       mThread(aThread),
       mContext(nullptr),
       mJSSampling(INACTIVE),
-      mJSTrackOptimizations(false) {
+      mJSFlags(0) {
   MOZ_COUNT_CTOR(RegisteredThread);
 
   // We don't have to guess on mac
 #if defined(GP_OS_darwin)
   pthread_t self = pthread_self();
   mStackTop = pthread_get_stackaddr_np(self);
 #endif
 }
--- a/tools/profiler/core/RegisteredThread.h
+++ b/tools/profiler/core/RegisteredThread.h
@@ -6,16 +6,17 @@
 
 #ifndef RegisteredThread_h
 #define RegisteredThread_h
 
 #include "mozilla/UniquePtrExtensions.h"
 
 #include "platform.h"
 #include "ThreadInfo.h"
+#include "js/TraceLoggerAPI.h"
 
 // This class contains the state for a single thread that is accessible without
 // protection from gPSMutex in platform.cpp. Because there is no external
 // protection against data races, it must provide internal protection. Hence
 // the "Racy" prefix.
 //
 class RacyRegisteredThread final {
  public:
@@ -181,40 +182,38 @@ class RegisteredThread final {
     MOZ_ASSERT(aContext && !mContext);
 
     mContext = aContext;
 
     // We give the JS engine a non-owning reference to the ProfilingStack. It's
     // important that the JS engine doesn't touch this once the thread dies.
     js::SetContextProfilingStack(aContext,
                                  &RacyRegisteredThread().ProfilingStack());
-
-    PollJSSampling();
   }
 
   void ClearJSContext() {
     // This function runs on-thread.
     mContext = nullptr;
   }
 
   JSContext* GetJSContext() const { return mContext; }
 
   const RefPtr<ThreadInfo> Info() const { return mThreadInfo; }
   const nsCOMPtr<nsIEventTarget> GetEventTarget() const { return mThread; }
 
   // Request that this thread start JS sampling. JS sampling won't actually
   // start until a subsequent PollJSSampling() call occurs *and* mContext has
   // been set.
-  void StartJSSampling(bool aTrackOptimizations) {
+  void StartJSSampling(uint32_t aJSFlags) {
     // This function runs on-thread or off-thread.
 
     MOZ_RELEASE_ASSERT(mJSSampling == INACTIVE ||
                        mJSSampling == INACTIVE_REQUESTED);
     mJSSampling = ACTIVE_REQUESTED;
-    mJSTrackOptimizations = aTrackOptimizations;
+    mJSFlags = aJSFlags;
   }
 
   // Request that this thread stop JS sampling. JS sampling won't actually stop
   // until a subsequent PollJSSampling() call occurs.
   void StopJSSampling() {
     // This function runs on-thread or off-thread.
 
     MOZ_RELEASE_ASSERT(mJSSampling == ACTIVE ||
@@ -235,23 +234,30 @@ class RegisteredThread final {
       // - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
       //
       // Therefore, the if and else branches here aren't always interleaved.
       // This is ok because the JS engine can handle that.
       //
       if (mJSSampling == ACTIVE_REQUESTED) {
         mJSSampling = ACTIVE;
         js::EnableContextProfilingStack(mContext, true);
-        JS_SetGlobalJitCompilerOption(
-            mContext, JSJITCOMPILER_TRACK_OPTIMIZATIONS, mJSTrackOptimizations);
+        JS_SetGlobalJitCompilerOption(mContext,
+                                      JSJITCOMPILER_TRACK_OPTIMIZATIONS,
+                                      TrackOptimizationsEnabled());
+        if (JSTracerEnabled()) {
+          JS::StartTraceLogger(mContext);
+        }
         js::RegisterContextProfilingEventMarker(mContext, profiler_add_marker);
 
       } else if (mJSSampling == INACTIVE_REQUESTED) {
         mJSSampling = INACTIVE;
         js::EnableContextProfilingStack(mContext, false);
+        if (JSTracerEnabled()) {
+          JS::StopTraceLogger(mContext);
+        }
       }
     }
   }
 
  private:
   class RacyRegisteredThread mRacyRegisteredThread;
 
   const UniquePlatformData mPlatformData;
@@ -306,12 +312,20 @@ class RegisteredThread final {
   //
   enum {
     INACTIVE = 0,
     ACTIVE_REQUESTED = 1,
     ACTIVE = 2,
     INACTIVE_REQUESTED = 3,
   } mJSSampling;
 
-  bool mJSTrackOptimizations;
+  uint32_t mJSFlags;
+
+  bool TrackOptimizationsEnabled() {
+    return mJSFlags & uint32_t(JSSamplingFlags::TrackOptimizations);
+  }
+
+  bool JSTracerEnabled() {
+    return mJSFlags & uint32_t(JSSamplingFlags::TraceLogging);
+  }
 };
 
 #endif  // RegisteredThread_h
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -68,16 +68,17 @@
 #include "ProfilerParent.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 #include "ProfilerMarkerPayload.h"
 #include "memory_hooks.h"
 #include "shared-libraries.h"
 #include "prdtoa.h"
 #include "prtime.h"
+#include "js/TraceLoggerAPI.h"
 
 #if defined(XP_WIN)
 #include <processthreadsapi.h>  // for GetCurrentProcessId()
 #else
 #include <unistd.h>  // for getpid()
 #endif               // defined(XP_WIN)
 
 #ifdef MOZ_TASK_TRACER
@@ -585,16 +586,27 @@ class ActivePS {
   static bool Feature##Name_(PSLockRef) {                     \
     return ProfilerFeature::Has##Name_(sInstance->mFeatures); \
   }
 
   PROFILER_FOR_EACH_FEATURE(PS_GET_FEATURE)
 
 #undef PS_GET_FEATURE
 
+  static uint32_t JSFlags(PSLockRef aLock) {
+    uint32_t Flags = 0;
+    Flags |= FeatureJS(aLock) ? uint32_t(JSSamplingFlags::StackSampling) : 0;
+    Flags |= FeatureTrackOptimizations(aLock)
+                 ? uint32_t(JSSamplingFlags::TrackOptimizations)
+                 : 0;
+    Flags |=
+        FeatureJSTracer(aLock) ? uint32_t(JSSamplingFlags::TraceLogging) : 0;
+    return Flags;
+  }
+
   PS_GET(const Vector<std::string>&, Filters)
 
   static ProfileBuffer& Buffer(PSLockRef) { return *sInstance->mBuffer.get(); }
 
   static const nsTArray<LiveProfiledThreadData>& LiveProfiledThreads(
       PSLockRef) {
     return sInstance->mLiveProfiledThreads;
   }
@@ -1959,40 +1971,55 @@ static void locked_profiler_stream_json_
     nsTArray<Pair<RegisteredThread*, ProfiledThreadData*>> threads =
         ActivePS::ProfiledThreads(aLock);
     for (auto& thread : threads) {
       RegisteredThread* registeredThread = thread.first();
       JSContext* cx =
           registeredThread ? registeredThread->GetJSContext() : nullptr;
       ProfiledThreadData* profiledThreadData = thread.second();
       profiledThreadData->StreamJSON(buffer, cx, aWriter,
-                                     CorePS::ProcessStartTime(), aSinceTime);
+                                     CorePS::ProcessStartTime(), aSinceTime,
+                                     ActivePS::FeatureJSTracer(aLock));
     }
 
 #if defined(GP_OS_android)
     if (ActivePS::FeatureJava(aLock)) {
       java::GeckoJavaSampler::Pause();
 
       UniquePtr<ProfileBuffer> javaBuffer = CollectJavaThreadProfileData();
 
       // Thread id of java Main thread is 0, if we support profiling of other
       // java thread, we have to get thread id and name via JNI.
       RefPtr<ThreadInfo> threadInfo = new ThreadInfo(
           "Java Main Thread", 0, false, CorePS::ProcessStartTime());
       ProfiledThreadData profiledThreadData(
           threadInfo, nullptr, ActivePS::FeatureResponsiveness(aLock));
       profiledThreadData.StreamJSON(*javaBuffer.get(), nullptr, aWriter,
-                                    CorePS::ProcessStartTime(), aSinceTime);
+                                    CorePS::ProcessStartTime(), aSinceTime,
+                                    ActivePS::FeatureJSTracer(aLock));
 
       java::GeckoJavaSampler::Unpause();
     }
 #endif
   }
   aWriter.EndArray();
 
+  if (ActivePS::FeatureJSTracer(aLock)) {
+    aWriter.StartArrayProperty("jsTracerDictionary");
+    {
+      JS::AutoTraceLoggerLockGuard lockGuard;
+      // Collect Event Dictionary
+      JS::TraceLoggerDictionaryBuffer collectionBuffer(lockGuard);
+      while (collectionBuffer.NextChunk()) {
+        aWriter.StringElement(collectionBuffer.internalBuffer());
+      }
+    }
+    aWriter.EndArray();
+  }
+
   aWriter.StartArrayProperty("pausedRanges");
   { buffer.StreamPausedRangesToJSON(aWriter, aSinceTime); }
   aWriter.EndArray();
 
   double collectionEnd = profiler_time();
 
   // Record timestamps for the collection into the buffer, so that consumers
   // know why we didn't collect any samples for its duration.
@@ -2487,18 +2514,17 @@ static ProfilingStack* locked_register_t
     ProfiledThreadData* profiledThreadData = ActivePS::AddLiveProfiledThread(
         aLock, registeredThread.get(),
         MakeUnique<ProfiledThreadData>(info, eventTarget,
                                        ActivePS::FeatureResponsiveness(aLock)));
 
     if (ActivePS::FeatureJS(aLock)) {
       // This StartJSSampling() call is on-thread, so we can poll manually to
       // start JS sampling immediately.
-      registeredThread->StartJSSampling(
-          ActivePS::FeatureTrackOptimizations(aLock));
+      registeredThread->StartJSSampling(ActivePS::JSFlags(aLock));
       registeredThread->PollJSSampling();
       if (registeredThread->GetJSContext()) {
         profiledThreadData->NotifyReceivedJSContext(
             ActivePS::Buffer(aLock).mRangeEnd);
       }
     }
   }
 
@@ -3110,18 +3136,17 @@ static void locked_profiler_start(PSLock
     if (ActivePS::ShouldProfileThread(aLock, info)) {
       registeredThread->RacyRegisteredThread().SetIsBeingProfiled(true);
       nsCOMPtr<nsIEventTarget> eventTarget = registeredThread->GetEventTarget();
       ProfiledThreadData* profiledThreadData = ActivePS::AddLiveProfiledThread(
           aLock, registeredThread.get(),
           MakeUnique<ProfiledThreadData>(
               info, eventTarget, ActivePS::FeatureResponsiveness(aLock)));
       if (ActivePS::FeatureJS(aLock)) {
-        registeredThread->StartJSSampling(
-            ActivePS::FeatureTrackOptimizations(aLock));
+        registeredThread->StartJSSampling(ActivePS::JSFlags(aLock));
         if (info->ThreadId() == tid) {
           // We can manually poll the current thread so it starts sampling
           // immediately.
           registeredThread->PollJSSampling();
         } else if (info->IsMainThread()) {
           // Dispatch a runnable to the main thread to call PollJSSampling(),
           // so that we don't have wait for the next JS interrupt callback in
           // order to start profiling JS.
@@ -3820,18 +3845,17 @@ void profiler_clear_js_context() {
       // nulling out the JSContext.
       registeredThread->StopJSSampling();
       registeredThread->PollJSSampling();
 
       registeredThread->ClearJSContext();
 
       // Tell the thread that we'd like to have JS sampling on this
       // thread again, once it gets a new JSContext (if ever).
-      registeredThread->StartJSSampling(
-          ActivePS::FeatureTrackOptimizations(lock));
+      registeredThread->StartJSSampling(ActivePS::JSFlags(lock));
       return;
     }
   }
 
   registeredThread->ClearJSContext();
 }
 
 int profiler_current_thread_id() { return Thread::GetCurrentId(); }
--- a/tools/profiler/core/platform.h
+++ b/tools/profiler/core/platform.h
@@ -111,9 +111,16 @@ namespace mozilla {
 class JSONWriter;
 }
 void AppendSharedLibraries(mozilla::JSONWriter& aWriter);
 
 // Convert the array of strings to a bitfield.
 uint32_t ParseFeaturesFromStringArray(const char** aFeatures,
                                       uint32_t aFeatureCount);
 
+// Flags to conveniently track various JS features.
+enum class JSSamplingFlags {
+  StackSampling = 0x1,
+  TrackOptimizations = 0x2,
+  TraceLogging = 0x4
+};
+
 #endif /* ndef TOOLS_PLATFORM_H_ */
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -155,17 +155,20 @@ class TimeStamp;
                                                                  \
   /* Start profiling with feature TaskTracer. */                 \
   MACRO(10, "tasktracer", TaskTracer)                            \
                                                                  \
   /* Profile the registered secondary threads. */                \
   MACRO(11, "threads", Threads)                                  \
                                                                  \
   /* Have the JavaScript engine track JIT optimizations. */      \
-  MACRO(12, "trackopts", TrackOptimizations)
+  MACRO(12, "trackopts", TrackOptimizations)                     \
+                                                                 \
+  /* Enable tracing of the JavaScript engine. */                 \
+  MACRO(13, "jstracer", JSTracer)
 
 struct ProfilerFeature {
 #define DECLARE(n_, str_, Name_)                                           \
   static const uint32_t Name_ = (1u << n_);                                \
   static bool Has##Name_(uint32_t aFeatures) { return aFeatures & Name_; } \
   static void Set##Name_(uint32_t& aFeatures) { aFeatures |= Name_; }      \
   static void Clear##Name_(uint32_t& aFeatures) { aFeatures &= ~Name_; }