Bug 723711. Return the profile data as JS objects. r=bgirard
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Thu, 02 Feb 2012 16:57:20 -0500
changeset 88687 2ee344ca2759454690970c2402b5689f2f1646ba
parent 88686 6692fb9f8f2127d876758843a04075510de6078f
child 88688 7c071cef1797fc97c6d806a80fcddda01a8bf1d2
child 88708 05a2c3b187298abc97c09f95aaef7184ace0da6f
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,153 @@
+/* -*- 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 ***** */
+
+#include "jsapi.h"
+
+/* 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);
+    uint32_t 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);
+    uint32_t 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.