Bug 853358 - Add plugin profiling support. r=ehsan,bsmedberg
authorBenoit Girard <b56girard@gmail.com>
Thu, 21 Mar 2013 10:17:23 +0100
changeset 129864 c759d3eb1118e75caa76524a830140a7222915db
parent 129863 1a7dac116295392baded40c0ac56e7eeabfd3536
child 129865 f0f0cf98b3cdc8fce9edb59edc17b506305f93c6
push id27059
push userb56girard@gmail.com
push dateThu, 25 Apr 2013 15:24:33 +0000
treeherdermozilla-inbound@c759d3eb1118 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, bsmedberg
bugs853358
milestone23.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 853358 - Add plugin profiling support. r=ehsan,bsmedberg
dom/plugins/ipc/PPluginModule.ipdl
dom/plugins/ipc/PluginModuleChild.cpp
dom/plugins/ipc/PluginModuleChild.h
dom/plugins/ipc/PluginModuleParent.cpp
dom/plugins/ipc/PluginModuleParent.h
tools/profiler/GeckoProfilerImpl.h
tools/profiler/Makefile.in
tools/profiler/ProfileEntry.cpp
tools/profiler/SaveProfileTask.cpp
tools/profiler/SaveProfileTask.h
tools/profiler/TableTicker.cpp
tools/profiler/moz.build
tools/profiler/nsIProfileSaveEvent.idl
tools/profiler/platform.cpp
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -79,16 +79,19 @@ child:
                             nsString aDisplayName,
                             nsString aIconPath);
 
   async SetParentHangTimeout(uint32_t seconds);
 
   rpc PCrashReporter()
     returns (NativeThreadId tid, uint32_t processType);
 
+  rpc GeckoGetProfile()
+    returns (nsCString aProfile);
+
 parent:
   /**
    * This message is only used on X11 platforms.
    *
    * Send a dup of the plugin process's X socket to the parent
    * process.  In theory, this scheme keeps the plugin's X resources
    * around until after both the plugin process shuts down *and* the
    * parent process closes the dup fd.  This is used to prevent the
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -51,16 +51,18 @@
 #include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_WIDGET_COCOA
 #include "PluginInterposeOSX.h"
 #include "PluginUtilsOSX.h"
 #endif
 
+#include "GeckoProfiler.h"
+
 using namespace mozilla;
 using namespace mozilla::plugins;
 using mozilla::dom::CrashReporterChild;
 using mozilla::dom::PCrashReporterChild;
 
 #if defined(XP_WIN)
 const PRUnichar * kFlashFullscreenClass = L"ShockwaveFlashFullScreen";
 const PRUnichar * kMozillaWindowClass = L"MozillaWindowClass";
@@ -2420,8 +2422,21 @@ PluginModuleChild::RecvProcessNativeEven
 }
 
 #ifdef MOZ_WIDGET_COCOA
 void
 PluginModuleChild::ProcessNativeEvents() {
     CallProcessSomeEvents();    
 }
 #endif
+
+bool
+PluginModuleChild::AnswerGeckoGetProfile(nsCString* aProfile) {
+    char* profile = profiler_get_profile();
+    if (profile != NULL) {
+        *aProfile = nsCString(profile, strlen(profile));
+        free(profile);
+    } else {
+        *aProfile = nsCString("", 0);
+    }
+    return true;
+}
+
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -155,16 +155,19 @@ protected:
     virtual void
     ActorDestroy(ActorDestroyReason why);
 
     MOZ_NORETURN void QuickExit();
 
     virtual bool
     RecvProcessNativeEventsInRPCCall() MOZ_OVERRIDE;
 
+    virtual bool
+    AnswerGeckoGetProfile(nsCString* aProfile);
+
 public:
     PluginModuleChild();
     virtual ~PluginModuleChild();
 
     // aPluginFilename is UTF8, not native-charset!
     bool Init(const std::string& aPluginFilename,
               base::ProcessHandle aParentProcessHandle,
               MessageLoop* aIOLoop,
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -33,17 +33,19 @@
 #include "nsPrintfCString.h"
 
 #include "prsystem.h"
 
 #ifdef XP_WIN
 #include "PluginHangUIParent.h"
 #include "mozilla/widget/AudioSession.h"
 #endif
-#include "GeckoProfiler.h"
+#include "nsIProfileSaveEvent.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
 
 using base::KillProcess;
 
 using mozilla::PluginLibrary;
 using mozilla::ipc::SyncChannel;
 using mozilla::dom::PCrashReporterParent;
 using mozilla::dom::CrashReporterParent;
 
@@ -133,22 +135,26 @@ PluginModuleParent::PluginModuleParent(c
     mIdentifiers.Init();
 
     Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this);
     Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this);
 #ifdef XP_WIN
     Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this);
     Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this);
 #endif
+
+    InitPluginProfiling();
 }
 
 PluginModuleParent::~PluginModuleParent()
 {
     NS_ASSERTION(OkToCleanup(), "unsafe destruction");
 
+    ShutdownPluginProfiling();
+
     if (!mShutdown) {
         NS_WARNING("Plugin host deleted the module without shutting down.");
         NPError err;
         NP_Shutdown(&err);
     }
     NS_ASSERTION(mShutdown, "NP_Shutdown didn't");
 
     if (mSubprocess) {
@@ -1705,8 +1711,61 @@ PluginModuleParent::OnCrash(DWORD proces
 {
     if (!mShutdown) {
         GetIPCChannel()->CloseWithError();
         KillProcess(OtherProcess(), 1, false);
     }
 }
 
 #endif // MOZ_CRASHREPORTER_INJECTOR
+
+class PluginProfilerObserver MOZ_FINAL : public nsIObserver,
+                                         public nsSupportsWeakReference
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+
+    explicit PluginProfilerObserver(PluginModuleParent* pmp)
+      : mPmp(pmp)
+    {}
+
+private:
+    PluginModuleParent* mPmp;
+};
+
+NS_IMPL_ISUPPORTS2(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference)
+
+NS_IMETHODIMP
+PluginProfilerObserver::Observe(nsISupports *aSubject,
+                                const char *aTopic,
+                                const PRUnichar *aData)
+{
+    nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
+    if (pse) {
+      nsCString result;
+      bool success = mPmp->CallGeckoGetProfile(&result);
+      if (success && !result.IsEmpty()) {
+          pse->AddSubProfile(result.get());
+      }
+    }
+    return NS_OK;
+}
+
+void
+PluginModuleParent::InitPluginProfiling()
+{
+    nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+    if (observerService) {
+        mProfilerObserver = new PluginProfilerObserver(this);
+        observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false);
+    }
+}
+
+void
+PluginModuleParent::ShutdownPluginProfiling()
+{
+    nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+    if (observerService) {
+        observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess");
+    }
+}
+
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -296,29 +296,33 @@ private:
     void ProcessFirstMinidump();
     void WriteExtraDataForMinidump(CrashReporter::AnnotationTable& notes);
 #endif
     void CleanupFromTimeout(const bool aByHangUI);
     void SetChildTimeout(const int32_t aChildTimeout);
     static int TimeoutChanged(const char* aPref, void* aModule);
     void NotifyPluginCrashed();
 
+    void InitPluginProfiling();
+    void ShutdownPluginProfiling();
+
     PluginProcessParent* mSubprocess;
     // the plugin thread in mSubprocess
     NativeThreadId mPluginThread;
     bool mShutdown;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     const NPNetscapeFuncs* mNPNIface;
     nsDataHashtable<nsPtrHashKey<void>, PluginIdentifierParent*> mIdentifiers;
     nsNPAPIPlugin* mPlugin;
     ScopedRunnableMethodFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
     nsString mHangID;
+    nsRefPtr<nsIObserver> mProfilerObserver;
 #ifdef XP_WIN
     InfallibleTArray<float> mPluginCpuUsageOnHang;
     PluginHangUIParent *mHangUIParent;
     bool mHangUIEnabled;
     bool mIsTimerReset;
 
     void
     EvaluateHangUIState(const bool aReset);
--- a/tools/profiler/GeckoProfilerImpl.h
+++ b/tools/profiler/GeckoProfilerImpl.h
@@ -14,17 +14,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Util.h"
 #include "nsAlgorithm.h"
 #include "nscore.h"
 #include "jsfriendapi.h"
 #include "GeckoProfilerFunc.h"
 #include "PseudoStack.h"
-
+#include "nsISupports.h"
 
 /* QT has a #define for the word "slots" and jsfriendapi.h has a struct with
  * this variable name, causing compilation problems. Alleviate this for now by
  * removing this #define */
 #ifdef MOZ_WIDGET_QT
 #undef slots
 #endif
 
--- a/tools/profiler/Makefile.in
+++ b/tools/profiler/Makefile.in
@@ -37,16 +37,17 @@ endif # !_MSC_VER
 # Uncomment for better debugging in opt builds
 #MOZ_OPTIMIZE_FLAGS += -O0 -g
 
 CPPSRCS		= \
   platform.cpp \
   nsProfilerFactory.cpp \
   nsProfiler.cpp \
   TableTicker.cpp \
+  SaveProfileTask.cpp \
   BreakpadSampler.cpp \
   UnwinderThread2.cpp \
   ProfileEntry.cpp \
   local_debug_info_symbolizer.cc \
   JSObjectBuilder.cpp \
   JSCustomObjectBuilder.cpp \
   $(NULL)
 
--- a/tools/profiler/ProfileEntry.cpp
+++ b/tools/profiler/ProfileEntry.cpp
@@ -2,16 +2,17 @@
 /* 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 <iostream>
 #include "GeckoProfilerImpl.h"
 #include "platform.h"
 #include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
 
 // JSON
 #include "JSObjectBuilder.h"
 #include "JSCustomObjectBuilder.h"
 
 // Self
 #include "ProfileEntry.h"
 
@@ -302,18 +303,25 @@ JSCustomObject* ThreadProfile::ToJSObjec
   JSObjectBuilder b(aCx);
   JSCustomObject *profile = b.CreateObject();
   BuildJSObject(b, profile);
 
   return profile;
 }
 
 void ThreadProfile::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) {
+
   // Thread meta data
-  b.DefineProperty(profile, "name", mName);
+  if (XRE_GetProcessType() == GeckoProcessType_Plugin) {
+    // TODO Add the proper plugin name
+    b.DefineProperty(profile, "name", "Plugin");
+  } else {
+    b.DefineProperty(profile, "name", mName);
+  }
+
   b.DefineProperty(profile, "tid", mThreadId);
 
   JSCustomArray *samples = b.CreateArray();
   b.DefineProperty(profile, "samples", samples);
 
   JSCustomObject *sample = nullptr;
   JSCustomArray *frames = nullptr;
   JSCustomArray *marker = nullptr;
new file mode 100644
--- /dev/null
+++ b/tools/profiler/SaveProfileTask.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SaveProfileTask.h"
+#include "GeckoProfilerImpl.h"
+
+static JSBool
+WriteCallback(const jschar *buf, uint32_t len, void *data)
+{
+  std::ofstream& stream = *static_cast<std::ofstream*>(data);
+  nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
+  stream << profile.Data();
+  return JS_TRUE;
+}
+
+nsresult
+SaveProfileTask::Run() {
+  // Get file path
+#if defined(SPS_PLAT_arm_android) && !defined(MOZ_WIDGET_GONK)
+  nsCString tmpPath;
+  tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
+#else
+  nsCOMPtr<nsIFile> tmpFile;
+  nsAutoCString tmpPath;
+  if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
+    LOG("Failed to find temporary directory.");
+    return NS_ERROR_FAILURE;
+  }
+  tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
+
+  nsresult rv = tmpFile->AppendNative(tmpPath);
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = tmpFile->GetNativePath(tmpPath);
+  if (NS_FAILED(rv))
+    return rv;
+#endif
+
+  // Create a JSContext to run a JSObjectBuilder :(
+  // Based on XPCShellEnvironment
+  JSRuntime *rt;
+  JSContext *cx;
+  nsCOMPtr<nsIJSRuntimeService> rtsvc
+    = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+  if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
+    LOG("failed to get RuntimeService");
+    return NS_ERROR_FAILURE;;
+  }
+
+  cx = JS_NewContext(rt, 8192);
+  if (!cx) {
+    LOG("Failed to get context");
+    return NS_ERROR_FAILURE;
+  }
+
+  {
+    JSAutoRequest ar(cx);
+    static JSClass c = {
+      "global", JSCLASS_GLOBAL_FLAGS,
+      JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
+      JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
+    };
+    JSObject *obj = JS_NewGlobalObject(cx, &c, NULL);
+
+    std::ofstream stream;
+    stream.open(tmpPath.get());
+    if (stream.is_open()) {
+      JSAutoCompartment autoComp(cx, obj);
+      JSObject* profileObj = profiler_get_profile_jsobject(cx);
+      jsval val = OBJECT_TO_JSVAL(profileObj);
+      JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
+      stream.close();
+      LOGF("Saved to %s", tmpPath.get());
+    } else {
+      LOG("Fail to open profile log file.");
+    }
+  }
+  JS_EndRequest(cx);
+  JS_DestroyContext(cx);
+
+  return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS1(ProfileSaveEvent, nsIProfileSaveEvent)
+
+nsresult
+ProfileSaveEvent::AddSubProfile(const char* aProfile) {
+  mFunc(aProfile, mClosure);
+  return NS_OK;
+}
+
--- a/tools/profiler/SaveProfileTask.h
+++ b/tools/profiler/SaveProfileTask.h
@@ -7,105 +7,53 @@
 #define PROFILER_SAVETASK_H_
 
 #include "platform.h"
 #include "nsThreadUtils.h"
 #include "nsIXULRuntime.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
+#include "jsfriendapi.h"
 #include "nsIJSRuntimeService.h"
+#include "nsIProfileSaveEvent.h"
+
+#include <iostream>
+#include <fstream>
 
 #ifdef XP_WIN
  #include <windows.h>
  #define getpid GetCurrentProcessId
 #else
  #include <unistd.h>
 #endif
 
-static JSBool
-WriteCallback(const jschar *buf, uint32_t len, void *data)
-{
-  std::ofstream& stream = *static_cast<std::ofstream*>(data);
-  nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
-  stream << profile.Data();
-  return JS_TRUE;
-}
-
 /**
  * This is an event used to save the profile on the main thread
  * to be sure that it is not being modified while saving.
  */
 class SaveProfileTask : public nsRunnable {
 public:
   SaveProfileTask() {}
 
-  NS_IMETHOD Run() {
-    // Get file path
-#   if defined(SPS_PLAT_arm_android) && !defined(MOZ_WIDGET_GONK)
-    nsCString tmpPath;
-    tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
-#   else
-    nsCOMPtr<nsIFile> tmpFile;
-    nsAutoCString tmpPath;
-    if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
-      LOG("Failed to find temporary directory.");
-      return NS_ERROR_FAILURE;
-    }
-    tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
+  NS_IMETHOD Run();
+};
 
-    nsresult rv = tmpFile->AppendNative(tmpPath);
-    if (NS_FAILED(rv))
-      return rv;
-
-    rv = tmpFile->GetNativePath(tmpPath);
-    if (NS_FAILED(rv))
-      return rv;
-#   endif
-
-    // Create a JSContext to run a JSObjectBuilder :(
-    // Based on XPCShellEnvironment
-    JSRuntime *rt;
-    JSContext *cx;
-    nsCOMPtr<nsIJSRuntimeService> rtsvc 
-      = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
-    if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
-      LOG("failed to get RuntimeService");
-      return NS_ERROR_FAILURE;;
-    }
+class ProfileSaveEvent MOZ_FINAL : public nsIProfileSaveEvent {
+public:
+  typedef void (*AddSubProfileFunc)(const char* aProfile, void* aClosure);
+  NS_DECL_ISUPPORTS
 
-    cx = JS_NewContext(rt, 8192);
-    if (!cx) {
-      LOG("Failed to get context");
-      return NS_ERROR_FAILURE;
-    }
-
-    {
-      JSAutoRequest ar(cx);
-      static JSClass c = {
-          "global", JSCLASS_GLOBAL_FLAGS,
-          JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
-          JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
-      };
-      JSObject *obj = JS_NewGlobalObject(cx, &c, NULL);
+  ProfileSaveEvent(AddSubProfileFunc aFunc, void* aClosure)
+    : mFunc(aFunc)
+    , mClosure(aClosure)
+  {}
 
-      std::ofstream stream;
-      stream.open(tmpPath.get());
-      if (stream.is_open()) {
-        JSAutoCompartment autoComp(cx, obj);
-        JSObject* profileObj = profiler_get_profile_jsobject(cx);
-        jsval val = OBJECT_TO_JSVAL(profileObj);
-        JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
-        stream.close();
-        LOGF("Saved to %s", tmpPath.get());
-      } else {
-        LOGF("Fail to open profile log file '%s'.", tmpPath.get());
-      }
-    }
-    JS_EndRequest(cx);
-    JS_DestroyContext(cx);
+  ~ProfileSaveEvent() {}
 
-    return NS_OK;
-  }
+  NS_IMETHOD AddSubProfile(const char* aProfile);
+private:
+  AddSubProfileFunc mFunc;
+  void* mClosure;
 };
 
 #endif
 
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -11,16 +11,17 @@
 #include "SaveProfileTask.h"
 #include "ProfileEntry.h"
 #include "platform.h"
 #include "nsThreadUtils.h"
 #include "prenv.h"
 #include "shared-libraries.h"
 #include "mozilla/StackWalk.h"
 #include "TableTicker.h"
+#include "nsXULAppAPI.h"
 
 // JSON
 #include "JSObjectBuilder.h"
 #include "JSCustomObjectBuilder.h"
 
 // Meta
 #include "nsXPCOM.h"
 #include "nsXPCOMCID.h"
@@ -159,16 +160,30 @@ JSObject* TableTicker::ToJSObject(JSCont
   JSObjectBuilder b(aCx);
   JSCustomObject* profile = b.CreateObject();
   BuildJSObject(b, profile);
   JSObject* jsProfile = b.GetJSObject(profile);
 
   return jsProfile;
 }
 
+struct SubprocessClosure {
+  JSAObjectBuilder* mBuilder;
+  JSCustomArray* mThreads;
+};
+
+void SubProcessCallback(const char* aProfile, void* aClosure)
+{
+  // Called by the observer to get their profile data included
+  // as a sub profile
+  SubprocessClosure* closure = (SubprocessClosure*)aClosure;
+
+  closure->mBuilder->ArrayPush(closure->mThreads, aProfile);
+}
+
 #if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK)
 static
 JSCustomObject* BuildJavaThreadJSObject(JSAObjectBuilder& b)
 {
   JSCustomObject* javaThread = b.CreateObject();
   b.DefineProperty(javaThread, "name", "Java Main Thread");
 
   JSCustomArray *samples = b.CreateArray();
@@ -250,16 +265,27 @@ void TableTicker::BuildJSObject(JSAObjec
     JSCustomObject* javaThread = BuildJavaThreadJSObject(b);
     b.ArrayPush(threads, javaThread);
 
     AndroidBridge::Bridge()->UnpauseJavaProfiling();
   }
 #endif
 
   SetPaused(false);
+
+  // Send a event asking any subprocesses (plugins) to
+  // give us their information
+  SubprocessClosure closure;
+  closure.mBuilder = &b;
+  closure.mThreads = threads;
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os) {
+    nsRefPtr<ProfileSaveEvent> pse = new ProfileSaveEvent(SubProcessCallback, &closure);
+    os->NotifyObservers(pse, "profiler-subprocess", nullptr);
+  }
 }
 
 // END SaveProfileTask et al
 ////////////////////////////////////////////////////////////////////////
 
 static
 void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
 {
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
     MODULE = 'profiler'
     XPIDL_SOURCES += [
+        'nsIProfileSaveEvent.idl',
         'nsIProfiler.idl',
     ]
     EXPORTS += [
         'GeckoProfilerImpl.h',
         'GeckoProfilerFunc.h',
         'PseudoStack.h',
         'shared-libraries.h',
     ]
new file mode 100644
--- /dev/null
+++ b/tools/profiler/nsIProfileSaveEvent.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 
+#include "nsISupports.idl"
+
+[uuid(f5ad0830-e178-41f9-b253-db9b4fae4cb3)]
+interface nsIProfileSaveEvent : nsISupports
+{
+  /**
+   * Call this method when observing this event to include
+   * a sub profile origining from an external source such
+   * as a non native thread or another process.
+   */
+  void AddSubProfile(in string aMarker);
+};
+
+
--- a/tools/profiler/platform.cpp
+++ b/tools/profiler/platform.cpp
@@ -345,25 +345,20 @@ void mozilla_sampler_save()
 
 char* mozilla_sampler_get_profile()
 {
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return NULL;
   }
 
-  std::stringstream profile;
-  t->SetPaused(true);
-  profile << *(t->GetPrimaryThreadProfile());
-  t->SetPaused(false);
-
-  std::string profileString = profile.str();
-  char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
-  strcpy(rtn, profileString.c_str());
-  return rtn;
+  std::stringstream stream;
+  t->ToStreamAsJSON(stream);
+  char* profile = strdup(stream.str().c_str());
+  return profile;
 }
 
 JSObject *mozilla_sampler_get_profile_data(JSContext *aCx)
 {
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return NULL;
   }