Bug 723711. Return the profile data as JS objects. r=bgirard
☠☠ backed out by e51f21f21538 ☠ ☠
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Thu, 02 Feb 2012 16:57:20 -0500
changeset 88652 c433e993506bed214ab4ca82a65f777bd8bf3dd3
parent 88651 00afd602bf7211c7fd2169c1c5c45e60ded4e4c6
child 88653 1fde6c2f3082a9a5f1570c2d88c3f21ffcdd2a8e
push idunknown
push userunknown
push dateunknown
reviewersbgirard
bugs723711
milestone13.0a1
Bug 723711. Return the profile data as JS objects. r=bgirard
tools/profiler/JSObjectBuilder.h
tools/profiler/TableTicker.cpp
tools/profiler/nsIProfiler.idl
tools/profiler/nsProfiler.cpp
tools/profiler/sampler.h
tools/profiler/sps_sampler.h
new file mode 100644
--- /dev/null
+++ b/tools/profiler/JSObjectBuilder.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jeff Muizelaar <jmuizelaar@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+/* this is handy wrapper around JSAPI to make it more pleasant to use.
+ * We collect the JSAPI errors and so that callers don't need to */
+class JSObjectBuilder
+{
+  public:
+
+  void DefineProperty(JSObject *aObject, const char *name, JSObject *aValue)
+  {
+    if (!mOk)
+      return;
+
+    mOk = JS_DefineProperty(mCx, aObject, name, OBJECT_TO_JSVAL(aValue), NULL, NULL, JSPROP_ENUMERATE);
+  }
+
+  void DefineProperty(JSObject *aObject, const char *name, int value)
+  {
+    if (!mOk)
+      return;
+
+    mOk = JS_DefineProperty(mCx, aObject, name, INT_TO_JSVAL(value), NULL, NULL, JSPROP_ENUMERATE);
+  }
+
+  void DefineProperty(JSObject *aObject, const char *name, double value)
+  {
+    if (!mOk)
+      return;
+
+    mOk = JS_DefineProperty(mCx, aObject, name, DOUBLE_TO_JSVAL(value), NULL, NULL, JSPROP_ENUMERATE);
+  }
+
+  void DefineProperty(JSObject *aObject, const char *name, nsAString &value)
+  {
+    if (!mOk)
+      return;
+
+    const nsString &flat = PromiseFlatString(value);
+    JSString *string = JS_NewUCStringCopyN(mCx, static_cast<const jschar*>(flat.get()), flat.Length());
+    if (!string)
+      mOk = JS_FALSE;
+
+    if (!mOk)
+      return;
+
+    mOk = JS_DefineProperty(mCx, aObject, name, STRING_TO_JSVAL(string), NULL, NULL, JSPROP_ENUMERATE);
+  }
+
+  void DefineProperty(JSObject *aObject, const char *name, const char *value)
+  {
+    nsAutoString string = NS_ConvertASCIItoUTF16(value);
+    DefineProperty(aObject, name, string);
+  }
+
+  void ArrayPush(JSObject *aArray, int value)
+  {
+    if (!mOk)
+      return;
+
+    jsval objval = INT_TO_JSVAL(value);
+    jsuint length;
+    mOk = JS_GetArrayLength(mCx, aArray, &length);
+
+    if (!mOk)
+      return;
+
+    mOk = JS_SetElement(mCx, aArray, length, &objval);
+  }
+
+
+  void ArrayPush(JSObject *aArray, JSObject *aObject)
+  {
+    if (!mOk)
+      return;
+
+    jsval objval = OBJECT_TO_JSVAL(aObject);
+    jsuint length;
+    mOk = JS_GetArrayLength(mCx, aArray, &length);
+
+    if (!mOk)
+      return;
+
+    mOk = JS_SetElement(mCx, aArray, length, &objval);
+  }
+
+  JSObject *CreateArray() {
+    JSObject *array = JS_NewArrayObject(mCx, 0, NULL);
+    if (!array)
+      mOk = JS_FALSE;
+
+    return array;
+  }
+
+  JSObject *CreateObject() {
+    JSObject *obj = JS_NewObject(mCx, NULL, NULL, NULL);
+    if (!obj)
+      mOk = JS_FALSE;
+
+    return obj;
+  }
+
+
+  // We need to ensure that this object lives on the stack so that GC sees it properly
+  JSObjectBuilder(JSContext *aCx) : mCx(aCx), mOk(JS_TRUE)
+  {
+  }
+  private:
+  JSObjectBuilder(JSObjectBuilder&);
+
+  JSContext *mCx;
+  JSObject *mObj;
+  JSBool mOk;
+};
+
+
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -41,16 +41,17 @@
 #include "sps_sampler.h"
 #include "platform.h"
 #include "nsXULAppAPI.h"
 #include "nsThreadUtils.h"
 #include "prenv.h"
 #include "shared-libraries.h"
 #include "mozilla/StringBuilder.h"
 #include "mozilla/StackWalk.h"
+#include "JSObjectBuilder.h"
 
 // we eventually want to make this runtime switchable
 #if defined(MOZ_PROFILING) && (defined(XP_UNIX) && !defined(XP_MACOSX))
  #ifndef ANDROID
   #define USE_BACKTRACE
  #endif
 #endif
 #ifdef USE_BACKTRACE
@@ -138,16 +139,17 @@ public:
     : mTagOffset(aTagOffset)
     , mLeafAddress(0)
     , mTagName(aTagName)
   { }
 
   string TagToString(Profile *profile);
 
 private:
+  friend class Profile;
   union {
     const char* mTagData;
     double mTagFloat;
     Address mTagAddress;
     uintptr_t mTagOffset;
   };
   Address mLeafAddress;
   char mTagName;
@@ -247,24 +249,66 @@ public:
   void erase()
   {
     mWritePos = mLastFlushPos;
   }
 
   void ToString(StringBuilder &profile)
   {
     //XXX: this code is not thread safe and needs to be fixed
+    // can we just have a mutex that guards access to the circular buffer?
+    // no because the main thread can be stopped while it still has access.
+    // which will cause a deadlock
     int oldReadPos = mReadPos;
     while (mReadPos != mLastFlushPos) {
       profile.Append(mEntries[mReadPos].TagToString(this).c_str());
       mReadPos = (mReadPos + 1) % mEntrySize;
     }
     mReadPos = oldReadPos;
   }
 
+  JSObject *ToJSObject(JSContext *aCx)
+  {
+    JSObjectBuilder b(aCx);
+
+    JSObject *profile = b.CreateObject();
+    JSObject *samples = b.CreateArray();
+    b.DefineProperty(profile, "samples", samples);
+
+    JSObject *sample = NULL;
+    JSObject *frames = NULL;
+
+    int oldReadPos = mReadPos;
+    while (mReadPos != mLastFlushPos) {
+      ProfileEntry entry = mEntries[mReadPos];
+      mReadPos = (mReadPos + 1) % mEntrySize;
+      switch (entry.mTagName) {
+        case 's':
+          sample = b.CreateObject();
+          b.DefineProperty(sample, "name", entry.mTagData);
+          frames = b.CreateArray();
+          b.DefineProperty(sample, "frames", frames);
+          b.ArrayPush(samples, sample);
+          break;
+        case 'c':
+        case 'l':
+          {
+            if (sample) {
+              JSObject *frame = b.CreateObject();
+              b.DefineProperty(frame, "location", entry.mTagData);
+              b.ArrayPush(frames, frame);
+            }
+          }
+      }
+    }
+    mReadPos = oldReadPos;
+
+    return profile;
+  }
+
   void WriteProfile(FILE* stream)
   {
     //XXX: this code is not thread safe and needs to be fixed
     int oldReadPos = mReadPos;
     while (mReadPos != mLastFlushPos) {
       string tag = mEntries[mReadPos].TagToString(this);
       fwrite(tag.data(), 1, tag.length(), stream);
       mReadPos = (mReadPos + 1) % mEntrySize;
@@ -634,16 +678,27 @@ char* mozilla_sampler_get_profile()
   StringBuilder profile;
   t->GetProfile()->ToString(profile);
 
   char *rtn = (char*)malloc( (profile.Length()+1) * sizeof(char) );
   strcpy(rtn, profile.Buffer());
   return rtn;
 }
 
+JSObject *mozilla_sampler_get_profile_data(JSContext *aCx)
+{
+  TableTicker *t = mozilla::tls::get<TableTicker>(pkey_ticker);
+  if (!t) {
+    return NULL;
+  }
+
+  return t->GetProfile()->ToJSObject(aCx);
+}
+
+
 const char** mozilla_sampler_get_features()
 {
   static const char* features[] = {
 #if defined(MOZ_PROFILING) && (defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK))
     "stackwalk",
 #endif
     "jank",
     NULL
--- a/tools/profiler/nsIProfiler.idl
+++ b/tools/profiler/nsIProfiler.idl
@@ -38,16 +38,18 @@
 [scriptable, uuid(e388fded-1321-41af-a988-861a2bc5cfc3)]
 interface nsIProfiler : nsISupports
 {
   void StartProfiler(in PRUint32 aInterval, in PRUint32 aEntries,
                       [array, size_is(aFeatureCount)] in string aFeatures,
                       in PRUint32 aFeatureCount);
   void StopProfiler();
   string GetProfile();
+  [implicit_jscontext]
+  jsval getProfileData();
   boolean IsActive();
   void GetResponsivenessTimes(out PRUint32 aCount, [retval, array, size_is(aCount)] out double aResult);
   void GetFeatures(out PRUint32 aCount, [retval, array, size_is(aCount)] out string aFeatures);
 
   /**
    * Returns a JSON string of an array of shared library objects.
    * Every object has three properties: start, end, and name.
    * start and end are integers describing the address range that the library
--- a/tools/profiler/nsProfiler.cpp
+++ b/tools/profiler/nsProfiler.cpp
@@ -39,16 +39,17 @@
 #ifdef MOZ_INSTRUMENT_EVENT_LOOP
 #include "EventTracer.h"
 #endif
 #include "sampler.h"
 #include "nsProfiler.h"
 #include "nsMemory.h"
 #include "shared-libraries.h"
 #include "nsString.h"
+#include "jsapi.h"
 
 using std::string;
 
 NS_IMPL_ISUPPORTS1(nsProfiler, nsIProfiler)
 
 
 nsProfiler::nsProfiler()
 {
@@ -120,16 +121,28 @@ GetSharedLibraryInfoString()
 
 NS_IMETHODIMP
 nsProfiler::GetSharedLibraryInformation(nsAString& aOutString)
 {
   aOutString.Assign(NS_ConvertUTF8toUTF16(GetSharedLibraryInfoString().c_str()));
   return NS_OK;
 }
 
+
+
+NS_IMETHODIMP nsProfiler::GetProfileData(JSContext* aCx, jsval* aResult)
+{
+  JSObject *obj = SAMPLER_GET_PROFILE_DATA(aCx);
+  if (!obj)
+    return NS_ERROR_FAILURE;
+
+  *aResult = OBJECT_TO_JSVAL(obj);
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsProfiler::IsActive(bool *aIsActive)
 {
   *aIsActive = SAMPLER_IS_ACTIVE();
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/tools/profiler/sampler.h
+++ b/tools/profiler/sampler.h
@@ -94,16 +94,17 @@
 #define SAMPLER_INIT()
 #define SAMPLER_DEINIT()
 #define SAMPLER_START(entries, interval, features, featureCount)
 #define SAMPLER_STOP()
 #define SAMPLER_IS_ACTIVE() false
 #define SAMPLER_SAVE()
 // Returned string must be free'ed
 #define SAMPLER_GET_PROFILE() NULL
+#define SAMPLER_GET_PROFILE_DATA(ctx) NULL
 #define SAMPLER_RESPONSIVENESS(time) NULL
 #define SAMPLER_GET_RESPONSIVENESS() NULL
 #define SAMPLER_GET_FEATURES() NULL
 #define SAMPLE_LABEL(name_space, info)
 #define SAMPLE_LABEL_FN(name_space, info)
 #define SAMPLE_MARKER(info)
 
 #endif
--- a/tools/profiler/sps_sampler.h
+++ b/tools/profiler/sps_sampler.h
@@ -35,16 +35,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include <stdlib.h>
 #include <signal.h>
 #include "thread_helper.h"
 #include "nscore.h"
+#include "jsapi.h"
 #include "mozilla/TimeStamp.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
 extern mozilla::tls::key pkey_stack;
 extern mozilla::tls::key pkey_ticker;
 extern bool stack_key_initialized;
@@ -63,16 +64,17 @@ extern bool stack_key_initialized;
 #define SAMPLER_DEINIT() mozilla_sampler_deinit()
 #define SAMPLER_START(entries, interval, features, featureCount) mozilla_sampler_start(entries, interval, features, featureCount)
 #define SAMPLER_STOP() mozilla_sampler_stop()
 #define SAMPLER_IS_ACTIVE() mozilla_sampler_is_active()
 #define SAMPLER_RESPONSIVENESS(time) mozilla_sampler_responsiveness(time)
 #define SAMPLER_GET_RESPONSIVENESS() mozilla_sampler_get_responsiveness()
 #define SAMPLER_SAVE() mozilla_sampler_save()
 #define SAMPLER_GET_PROFILE() mozilla_sampler_get_profile()
+#define SAMPLER_GET_PROFILE_DATA(ctx) mozilla_sampler_get_profile_data(ctx)
 #define SAMPLER_GET_FEATURES() mozilla_sampler_get_features()
 // we want the class and function name but can't easily get that using preprocessor macros
 // __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters
 #define SAMPLE_LABEL(name_space, info) mozilla::SamplerStackFrameRAII only_one_sampleraii_per_scope(name_space "::" info)
 #define SAMPLE_MARKER(info) mozilla_sampler_add_marker(info)
 
 /* we duplicate this code here to avoid header dependencies
  * which make it more difficult to include in other places */
@@ -135,16 +137,17 @@ inline void  mozilla_sampler_add_marker(
 
 void mozilla_sampler_start(int aEntries, int aInterval, const char** aFeatures, uint32_t aFeatureCount);
 void mozilla_sampler_stop();
 bool mozilla_sampler_is_active();
 void mozilla_sampler_responsiveness(TimeStamp time);
 const double* mozilla_sampler_get_responsiveness();
 void mozilla_sampler_save();
 char* mozilla_sampler_get_profile();
+JSObject *mozilla_sampler_get_profile_data(JSContext *aCx);
 const char** mozilla_sampler_get_features();
 void mozilla_sampler_init();
 
 namespace mozilla {
 
 class NS_STACK_CLASS SamplerStackFrameRAII {
 public:
   // we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.