Bug 799640 - Part 2: Save profiles on shutdown using custom JSON encoder. r=ehsan
☠☠ backed out by fd22236ffb15 ☠ ☠
authorBenoit Girard <b56girard@gmail.com>
Fri, 30 Nov 2012 12:49:20 -0500
changeset 114607 b130bb991d84
parent 114606 e47b059493cf
child 114608 133c704dbcc6
push id18871
push userb56girard@gmail.com
push dateFri, 30 Nov 2012 17:54:51 +0000
treeherdermozilla-inbound@133c704dbcc6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs799640
milestone20.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 799640 - Part 2: Save profiles on shutdown using custom JSON encoder. r=ehsan
toolkit/xre/nsAppRunner.cpp
tools/profiler/JSAObjectBuilder.h
tools/profiler/JSCustomObjectBuilder.cpp
tools/profiler/JSCustomObjectBuilder.h
tools/profiler/JSObjectBuilder.cpp
tools/profiler/JSObjectBuilder.h
tools/profiler/Makefile.in
tools/profiler/TableTicker.cpp
tools/profiler/sps_sampler.h
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -3932,16 +3932,17 @@ XREMain::XRE_main(int argc, char* argv[]
     SaveFileToEnvIfUnset("XRE_PROFILE_PATH", mProfD);
     SaveFileToEnvIfUnset("XRE_PROFILE_LOCAL_PATH", mProfLD);
     SaveWordToEnvIfUnset("XRE_PROFILE_NAME", mProfileName);
 
 #ifdef MOZ_WIDGET_GTK
     MOZ_gdk_display_close(mGdkDisplay);
 #endif
 
+    SAMPLER_SHUTDOWN();
     rv = LaunchChild(mNativeApp, true);
 
 #ifdef MOZ_CRASHREPORTER
     if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
       CrashReporter::UnsetExceptionHandler();
 #endif
     return rv == NS_ERROR_LAUNCHED_CHILD_PROCESS ? 0 : 1;
   }
@@ -3954,16 +3955,18 @@ XREMain::XRE_main(int argc, char* argv[]
 
 #ifdef MOZ_CRASHREPORTER
   if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
       CrashReporter::UnsetExceptionHandler();
 #endif
 
   XRE_DeinitCommandLine();
 
+  SAMPLER_SHUTDOWN();
+
   return NS_FAILED(rv) ? 1 : 0;
 }
 
 #if defined(MOZ_METRO) && defined(XP_WIN)
 extern bool XRE_MetroCoreApplicationRun();
 static XREMain* xreMainPtr;
 
 // must be called by the thread we want as the main thread
@@ -4065,16 +4068,17 @@ XRE_mainMetro(int argc, char* argv[], co
   if (!XRE_MetroCoreApplicationRun()) {
     return 1;
   }
 
   // XRE_metroShutdown should have already been called on the worker
   // thread that called XRE_metroStartup.
   NS_ASSERTION(!xreMainPtr->mScopedXPCom,
                "XPCOM Shutdown hasn't occured, and we are exiting.");
+  SAMPLER_SHUTDOWN();
   return 0;
 }
 
 void SetWindowsEnvironment(WindowsEnvironmentType aEnvID);
 #endif // MOZ_METRO || !defined(XP_WIN)
 
 int
 XRE_main(int argc, char* argv[], const nsXREAppData* aAppData, uint32_t aFlags)
new file mode 100644
--- /dev/null
+++ b/tools/profiler/JSAObjectBuilder.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef JSAOBJECTBUILDER_H
+#define JSAOBJECTBUILDER_H
+
+class JSCustomObject;
+class JSCustomArray;
+class nsAString;
+
+class JSAObjectBuilder
+{
+public:
+  virtual ~JSAObjectBuilder() = 0;
+
+  virtual void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue) = 0;
+  virtual void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue) = 0;
+  virtual void DefineProperty(JSCustomObject *aObject, const char *name, int value) = 0;
+  virtual void DefineProperty(JSCustomObject *aObject, const char *name, double value) = 0;
+  virtual void DefineProperty(JSCustomObject *aObject, const char *name, const char *value) = 0;
+  virtual void ArrayPush(JSCustomArray *aArray, int value) = 0;
+  virtual void ArrayPush(JSCustomArray *aArray, const char *value) = 0;
+  virtual void ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject) = 0;
+  virtual JSCustomArray  *CreateArray() = 0;
+  virtual JSCustomObject *CreateObject() = 0;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/tools/profiler/JSCustomObjectBuilder.cpp
@@ -0,0 +1,311 @@
+/* -*- 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 "JSCustomObjectBuilder.h"
+#include "nsStringGlue.h"
+#include "nsDataHashtable.h"
+#include "nsUTF8Utils.h"
+
+#if _MSC_VER
+ #define snprintf _snprintf
+#endif
+
+// These are owned and deleted by JSCustomObject
+struct PropertyValue {
+  virtual ~PropertyValue() {}
+  virtual void SendToStream(std::ostream& stream) = 0;
+};
+
+template <typename T>
+struct finalizer_impl
+{
+  static void run(T) {}
+};
+
+template <typename T>
+struct finalizer_impl<T*>
+{
+  static void run(T* p) {
+    delete p;
+  }
+};
+
+template <>
+struct finalizer_impl<char *>
+{
+  static void run(char* p) {
+    free(p);
+  }
+};
+
+template <class T>
+class TemplatePropertyValue : public PropertyValue {
+public:
+  TemplatePropertyValue(T aValue)
+    : mValue(aValue)
+  {}
+
+  ~TemplatePropertyValue() {
+    finalizer_impl<T>::run(mValue);
+  }
+
+  virtual void SendToStream(std::ostream& stream);
+private:
+  T mValue;
+};
+
+// Escape a UTF8 string to a stream. When an illegal encoding
+// is found it will insert "INVALID" and the function will return.
+void EscapeToStream(std::ostream& stream, const char* str) {
+  stream << "\"";
+
+  size_t len = strlen(str);
+  const char* end = &str[len];
+  while (str < end) {
+    bool err;
+    const char* utf8CharStart = str;
+    uint32_t ucs4Char = UTF8CharEnumerator::NextChar(&str, end, &err);
+
+    if (err) {
+      // Encoding error
+      stream << "INVALID\"";
+      return;
+    }
+
+    // See http://www.ietf.org/rfc/rfc4627.txt?number=4627
+    // characters that must be escaped: quotation mark,
+    // reverse solidus, and the control characters
+    // (U+0000 through U+001F).
+    if (ucs4Char == '\"') {
+      stream << "\\\"";
+    } else if (ucs4Char == '\\') {
+      stream << "\\\\";
+    } else if (ucs4Char > 0xFF) {
+      PRUnichar chr[2];
+      ConvertUTF8toUTF16 encoder(chr);
+      encoder.write(utf8CharStart, str-utf8CharStart);
+      char escChar[13];
+      snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X\\u%04X", chr[0], chr[1]);
+      stream << escChar;
+    } else if (ucs4Char < 0x1F || ucs4Char > 0xFF) {
+      char escChar[7];
+      snprintf(escChar, mozilla::ArrayLength(escChar), "\\u%04X", ucs4Char);
+      stream << escChar;
+    } else {
+      stream << char(ucs4Char);
+    }
+  }
+  stream << "\"";
+}
+
+class JSCustomObject {
+public:
+  JSCustomObject() {
+    mProperties.Init();
+  }
+  ~JSCustomObject();
+
+  friend std::ostream& operator<<(std::ostream& stream, JSCustomObject* entry);
+
+  template<class T>
+  void AddProperty(const char* aName, T aValue) {
+    mProperties.Put(nsDependentCString(aName), new TemplatePropertyValue<T>(aValue));
+  }
+
+  nsDataHashtable<nsCStringHashKey, PropertyValue*> mProperties;
+};
+
+class JSCustomArray {
+public:
+  nsTArray<PropertyValue*> mValues;
+
+  friend std::ostream& operator<<(std::ostream& stream, JSCustomArray* entry);
+
+  template<class T>
+  void AppendElement(T aValue) {
+    mValues.AppendElement(new TemplatePropertyValue<T>(aValue));
+  }
+};
+
+template <typename T>
+struct SendToStreamImpl
+{
+  static void run(std::ostream& stream, const T& t) {
+    stream << t;
+  }
+};
+
+template<typename T>
+struct SendToStreamImpl<T*>
+{
+  static void run(std::ostream& stream, T* t) {
+    stream << *t;
+  }
+};
+
+template <>
+struct SendToStreamImpl<char *>
+{
+  static void run(std::ostream& stream, char* p) {
+    EscapeToStream(stream, p);
+  }
+};
+
+template <>
+struct SendToStreamImpl<JSCustomObject*>
+{
+  static void run(std::ostream& stream, JSCustomObject* p) {
+    stream << p;
+  }
+};
+
+template <>
+struct SendToStreamImpl<JSCustomArray*>
+{
+  static void run(std::ostream& stream, JSCustomArray* p) {
+    stream << p;
+  }
+};
+
+template <class T> void
+TemplatePropertyValue<T>::SendToStream(std::ostream& stream)
+{
+  SendToStreamImpl<T>::run(stream, mValue);
+}
+
+struct JSONStreamClosure {
+  std::ostream& mStream;
+  bool mNeedsComma;
+};
+
+PLDHashOperator HashTableOutput(const nsACString& aKey, PropertyValue* aValue, void* stream)
+{
+  JSONStreamClosure& streamClosure = *(JSONStreamClosure*)stream;
+  if (streamClosure.mNeedsComma) {
+    streamClosure.mStream << ",";
+  }
+  streamClosure.mNeedsComma = true;
+  EscapeToStream(streamClosure.mStream, (const char*)aKey.BeginReading());
+  streamClosure.mStream << ":";
+  aValue->SendToStream(streamClosure.mStream);
+  return PLDHashOperator::PL_DHASH_NEXT;
+}
+
+std::ostream&
+operator<<(std::ostream& stream, JSCustomObject* entry)
+{
+  JSONStreamClosure streamClosure = {stream, false};
+  stream << "{";
+  entry->mProperties.EnumerateRead(HashTableOutput, &streamClosure);
+  stream << "}";
+  return stream;
+}
+
+std::ostream&
+operator<<(std::ostream& stream, JSCustomArray* entry)
+{
+  bool needsComma = false;
+  stream << "[";
+  for (int i = 0; i < entry->mValues.Length(); i++) {
+    if (needsComma) {
+      stream << ",";
+    }
+    entry->mValues[i]->SendToStream(stream);
+    needsComma = true;
+  }
+  stream << "]";
+  return stream;
+}
+
+PLDHashOperator HashTableFree(const nsACString& aKey, PropertyValue* aValue, void* stream)
+{
+  delete aValue;
+  return PLDHashOperator::PL_DHASH_NEXT;
+}
+
+JSCustomObject::~JSCustomObject()
+{
+  mProperties.EnumerateRead(HashTableFree, nullptr);
+}
+
+JSAObjectBuilder::~JSAObjectBuilder()
+{
+}
+
+JSCustomObjectBuilder::JSCustomObjectBuilder()
+{}
+
+void
+JSCustomObjectBuilder::DeleteObject(JSCustomObject* aObject)
+{
+  delete aObject;
+}
+
+void
+JSCustomObjectBuilder::Serialize(JSCustomObject* aObject, std::ostream& stream)
+{
+  stream << aObject;
+}
+
+void
+JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue)
+{
+  aObject->AddProperty(name, aValue);
+}
+
+void
+JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue)
+{
+  aObject->AddProperty(name, aValue);
+}
+
+void
+JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, int aValue)
+{
+  aObject->AddProperty(name, aValue);
+}
+
+void
+JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, double aValue)
+{
+  aObject->AddProperty(name, aValue);
+}
+
+void
+JSCustomObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, const char *aValue)
+{
+  // aValue copy will be freed by the property desctructor (template specialization)
+  aObject->AddProperty(name, strdup(aValue));
+}
+
+void
+JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, int aValue)
+{
+  aArray->AppendElement(aValue);
+}
+
+void
+JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, const char *aValue)
+{
+  // aValue copy will be freed by the property desctructor (template specialization)
+  aArray->AppendElement(strdup(aValue));
+}
+
+void
+JSCustomObjectBuilder::ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject)
+{
+  aArray->AppendElement(aObject);
+}
+
+JSCustomArray*
+JSCustomObjectBuilder::CreateArray() {
+  return new JSCustomArray();
+}
+
+JSCustomObject*
+JSCustomObjectBuilder::CreateObject() {
+  return new JSCustomObject();
+}
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/JSCustomObjectBuilder.h
@@ -0,0 +1,57 @@
+/* -*- 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/. */
+
+#ifndef JSCUSTOMOBJECTBUILDER_H
+#define JSCUSTOMOBJECTBUILDER_H
+
+#include <ostream>
+#include "JSAObjectBuilder.h"
+
+class JSCustomObject;
+class JSCustomArray;
+class JSCustomObjectBuilder;
+
+class JSCustomObjectBuilder : public JSAObjectBuilder
+{
+public:
+
+  // We need to ensure that this object lives on the stack so that GC sees it properly
+  JSCustomObjectBuilder();
+
+  void Serialize(JSCustomObject* aObject, std::ostream& stream);
+
+  void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue);
+  void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue);
+  void DefineProperty(JSCustomObject *aObject, const char *name, int value);
+  void DefineProperty(JSCustomObject *aObject, const char *name, double value);
+  void DefineProperty(JSCustomObject *aObject, const char *name, const char *value, size_t valueLength);
+  void DefineProperty(JSCustomObject *aObject, const char *name, const char *value);
+  void ArrayPush(JSCustomArray *aArray, int value);
+  void ArrayPush(JSCustomArray *aArray, const char *value);
+  void ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject);
+  JSCustomArray  *CreateArray();
+  JSCustomObject *CreateObject();
+
+  // Delete this object and all of its descendant
+  void DeleteObject(JSCustomObject* aObject);
+
+private:
+  // This class can't be copied
+  JSCustomObjectBuilder(const JSCustomObjectBuilder&);
+  JSCustomObjectBuilder& operator=(const JSCustomObjectBuilder&);
+
+  void* operator new(size_t);
+  void* operator new[](size_t);
+  void operator delete(void*) {
+    // Since JSCustomObjectBuilder has a virtual destructor the compiler
+    // has to provide a destructor in the object file that will call
+    // operate delete in case there is a derived class since its
+    // destructor wont know how to free this instance.
+    abort();
+  }
+  void operator delete[](void*);
+};
+
+#endif
--- a/tools/profiler/JSObjectBuilder.cpp
+++ b/tools/profiler/JSObjectBuilder.cpp
@@ -6,144 +6,148 @@
 #include "jsapi.h"
 #include "nsStringGlue.h"
 #include "JSObjectBuilder.h"
 
 JSObjectBuilder::JSObjectBuilder(JSContext *aCx) : mCx(aCx), mOk(JS_TRUE)
 {}
 
 void
-JSObjectBuilder::DefineProperty(JSObject *aObject, const char *name, JSObject *aValue)
+JSObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue)
 {
-  if (!mOk)
-    return;
-
-  mOk = JS_DefineProperty(mCx, aObject, name, OBJECT_TO_JSVAL(aValue), NULL, NULL, JSPROP_ENUMERATE);
+  DefineProperty(aObject, name, (JSCustomObject*)aValue);
 }
 
 void
-JSObjectBuilder::DefineProperty(JSObject *aObject, const char *name, int value)
+JSObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue)
 {
   if (!mOk)
     return;
 
-  mOk = JS_DefineProperty(mCx, aObject, name, INT_TO_JSVAL(value), NULL, NULL, JSPROP_ENUMERATE);
+  mOk = JS_DefineProperty(mCx, (JSObject*)aObject, name, OBJECT_TO_JSVAL((JSObject*)aValue), nullptr, nullptr, JSPROP_ENUMERATE);
 }
 
 void
-JSObjectBuilder::DefineProperty(JSObject *aObject, const char *name, double value)
+JSObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, int value)
 {
   if (!mOk)
     return;
 
-  mOk = JS_DefineProperty(mCx, aObject, name, DOUBLE_TO_JSVAL(value), NULL, NULL, JSPROP_ENUMERATE);
+  mOk = JS_DefineProperty(mCx, (JSObject*)aObject, name, INT_TO_JSVAL(value), nullptr, nullptr, JSPROP_ENUMERATE);
 }
 
 void
-JSObjectBuilder::DefineProperty(JSObject *aObject, const char *name, nsAString &value)
+JSObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, double value)
+{
+  if (!mOk)
+    return;
+
+  mOk = JS_DefineProperty(mCx, (JSObject*)aObject, name, DOUBLE_TO_JSVAL(value), nullptr, nullptr, JSPROP_ENUMERATE);
+}
+
+void
+JSObjectBuilder::DefineProperty(JSCustomObject *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);
+  mOk = JS_DefineProperty(mCx, (JSObject*)aObject, name, STRING_TO_JSVAL(string), nullptr, nullptr, JSPROP_ENUMERATE);
 }
 
 void
-JSObjectBuilder::DefineProperty(JSObject *aObject, const char *name, const char *value, size_t valueLength)
+JSObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, const char *value, size_t valueLength)
 {
   if (!mOk)
     return;
 
   JSString *string = JS_InternStringN(mCx, value, valueLength);
   if (!string) {
     mOk = JS_FALSE;
     return;
   }
 
-  mOk = JS_DefineProperty(mCx, aObject, name, STRING_TO_JSVAL(string), NULL, NULL, JSPROP_ENUMERATE);
-}
+  mOk = JS_DefineProperty(mCx, (JSObject*)aObject, name, STRING_TO_JSVAL(string), nullptr, nullptr, JSPROP_ENUMERATE); }
 
 void
-JSObjectBuilder::DefineProperty(JSObject *aObject, const char *name, const char *value)
+JSObjectBuilder::DefineProperty(JSCustomObject *aObject, const char *name, const char *value)
 {
   DefineProperty(aObject, name, value, strlen(value));
 }
 
 void
-JSObjectBuilder::ArrayPush(JSObject *aArray, int value)
+JSObjectBuilder::ArrayPush(JSCustomArray *aArray, int value)
 {
   if (!mOk)
     return;
 
   jsval objval = INT_TO_JSVAL(value);
   uint32_t length;
-  mOk = JS_GetArrayLength(mCx, aArray, &length);
+  mOk = JS_GetArrayLength(mCx, (JSObject*)aArray, &length);
 
   if (!mOk)
     return;
 
-  mOk = JS_SetElement(mCx, aArray, length, &objval);
+  mOk = JS_SetElement(mCx, (JSObject*)aArray, length, &objval);
 }
 
 void
-JSObjectBuilder::ArrayPush(JSObject *aArray, const char *value)
+JSObjectBuilder::ArrayPush(JSCustomArray *aArray, const char *value)
 {
   if (!mOk)
     return;
 
   JSString *string = JS_NewStringCopyN(mCx, value, strlen(value));
   if (!string) {
     mOk = JS_FALSE;
     return;
   }
 
   jsval objval = STRING_TO_JSVAL(string);
   uint32_t length;
-  mOk = JS_GetArrayLength(mCx, aArray, &length);
+  mOk = JS_GetArrayLength(mCx, (JSObject*)aArray, &length);
 
   if (!mOk)
     return;
 
-  mOk = JS_SetElement(mCx, aArray, length, &objval);
+  mOk = JS_SetElement(mCx, (JSObject*)aArray, length, &objval);
 }
 
 void
-JSObjectBuilder::ArrayPush(JSObject *aArray, JSObject *aObject)
+JSObjectBuilder::ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject)
 {
   if (!mOk)
     return;
 
-  jsval objval = OBJECT_TO_JSVAL(aObject);
-  uint32_t length;
-  mOk = JS_GetArrayLength(mCx, aArray, &length);
+  jsval objval = OBJECT_TO_JSVAL((JSObject*)aObject); uint32_t length;
+  mOk = JS_GetArrayLength(mCx, (JSObject*)aArray, &length);
 
   if (!mOk)
     return;
 
-  mOk = JS_SetElement(mCx, aArray, length, &objval);
+  mOk = JS_SetElement(mCx, (JSObject*)aArray, length, &objval);
 }
 
-JSObject*
+JSCustomArray*
 JSObjectBuilder::CreateArray() {
-  JSObject *array = JS_NewArrayObject(mCx, 0, NULL);
+  JSCustomArray *array = (JSCustomArray*)JS_NewArrayObject(mCx, 0, nullptr);
   if (!array)
     mOk = JS_FALSE;
 
   return array;
 }
 
-JSObject*
+JSCustomObject*
 JSObjectBuilder::CreateObject() {
-  JSObject *obj = JS_NewObject(mCx, NULL, NULL, NULL);
+  JSCustomObject *obj = (JSCustomObject*)JS_NewObject(mCx, nullptr, nullptr, nullptr);
   if (!obj)
     mOk = JS_FALSE;
 
   return obj;
 }
 
--- a/tools/profiler/JSObjectBuilder.h
+++ b/tools/profiler/JSObjectBuilder.h
@@ -1,39 +1,62 @@
 /* -*- 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/. */
 
-class JSObject;
-class JSObjectBuilder;
+#ifndef JSOBJECTBUILDER_H
+#define JSOBJECTBUILDER_H
+
+#include "JSAObjectBuilder.h"
+
+class JSCustomObject;
+class JSCustomObjectBuilder;
+class JSContext;
 class nsAString;
 
 /* 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
+class JSObjectBuilder : public JSAObjectBuilder
 {
 public:
-
   // We need to ensure that this object lives on the stack so that GC sees it properly
-  JSObjectBuilder(JSContext *aCx);
+  explicit JSObjectBuilder(JSContext *aCx);
+  ~JSObjectBuilder() {}
 
-  void DefineProperty(JSObject *aObject, const char *name, JSObject *aValue);
-  void DefineProperty(JSObject *aObject, const char *name, int value);
-  void DefineProperty(JSObject *aObject, const char *name, double value);
-  void DefineProperty(JSObject *aObject, const char *name, nsAString &value);
-  void DefineProperty(JSObject *aObject, const char *name, const char *value, size_t valueLength);
-  void DefineProperty(JSObject *aObject, const char *name, const char *value);
-  void ArrayPush(JSObject *aArray, int value);
-  void ArrayPush(JSObject *aArray, const char *value);
-  void ArrayPush(JSObject *aArray, JSObject *aObject);
-  JSObject *CreateArray();
-  JSObject *CreateObject();
+  void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomObject *aValue);
+  void DefineProperty(JSCustomObject *aObject, const char *name, JSCustomArray *aValue);
+  void DefineProperty(JSCustomObject *aObject, const char *name, int value);
+  void DefineProperty(JSCustomObject *aObject, const char *name, double value);
+  void DefineProperty(JSCustomObject *aObject, const char *name, nsAString &value);
+  void DefineProperty(JSCustomObject *aObject, const char *name, const char *value, size_t valueLength);
+  void DefineProperty(JSCustomObject *aObject, const char *name, const char *value);
+  void ArrayPush(JSCustomArray *aArray, int value);
+  void ArrayPush(JSCustomArray *aArray, const char *value);
+  void ArrayPush(JSCustomArray *aArray, JSCustomArray *aObject);
+  void ArrayPush(JSCustomArray *aArray, JSCustomObject *aObject);
+  JSCustomArray *CreateArray();
+  JSCustomObject *CreateObject();
+
+  JSObject* GetJSObject(JSCustomObject* aObject) { return (JSObject*)aObject; }
 
 private:
-  JSObjectBuilder(JSObjectBuilder&);
+  JSObjectBuilder(const JSObjectBuilder&);
+  JSObjectBuilder& operator=(const JSObjectBuilder&);
+
+  void* operator new(size_t);
+  void* operator new[](size_t);
+  void operator delete(void*) {
+    // Since JSObjectBuilder has a virtual destructor the compiler
+    // has to provide a destructor in the object file that will call
+    // operate delete in case there is a derived class since its
+    // destructor wont know how to free this instance.
+    abort();
+  }
+  void operator delete[](void*);
 
   JSContext *mCx;
   JSObject *mObj;
   int mOk;
 };
 
+#endif
 
--- a/tools/profiler/Makefile.in
+++ b/tools/profiler/Makefile.in
@@ -59,16 +59,17 @@ EXPORT_LIBRARY  = 1
 LIBXUL_LIBRARY  = 1
 IS_COMPONENT    = 1
 
 CPPSRCS		= \
   nsProfilerFactory.cpp \
   nsProfiler.cpp \
   TableTicker.cpp \
   JSObjectBuilder.cpp \
+  JSCustomObjectBuilder.cpp \
   $(NULL)
 
 XPIDLSRCS = \
   nsIProfiler.idl \
   $(NULL)
 
 EXTRA_JS_MODULES = \
   Profiler.jsm \
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -1,28 +1,28 @@
 /* -*- 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 <string>
 #include <stdio.h>
-#include <iostream>
 #include <fstream>
 #include <sstream>
 #include "sps_sampler.h"
 #include "platform.h"
 #include "nsXULAppAPI.h"
 #include "nsThreadUtils.h"
 #include "prenv.h"
 #include "shared-libraries.h"
 #include "mozilla/StackWalk.h"
 
 // JSON
 #include "JSObjectBuilder.h"
+#include "JSCustomObjectBuilder.h"
 #include "nsIJSRuntimeService.h"
 
 // Meta
 #include "nsXPCOM.h"
 #include "nsXPCOMCID.h"
 #include "nsIHttpProtocolHandler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIXULRuntime.h"
@@ -302,27 +302,41 @@ public:
       }
 
       aCallback(entry, tagStringData);
 
       readPos = (readPos + incBy) % mEntrySize;
     }
   }
 
-  JSObject *ToJSObject(JSContext *aCx)
+  void ToStreamAsJSON(std::ostream& stream)
+  {
+    JSCustomObjectBuilder b;
+    JSCustomObject *profile = b.CreateObject();
+    BuildJSObject(b, profile);
+    b.Serialize(profile, stream);
+    b.DeleteObject(profile);
+  }
+
+  JSCustomObject *ToJSObject(JSContext *aCx)
   {
     JSObjectBuilder b(aCx);
+    JSCustomObject *profile = b.CreateObject();
+    BuildJSObject(b, profile);
 
-    JSObject *profile = b.CreateObject();
-    JSObject *samples = b.CreateArray();
+    return profile;
+  }
+
+  void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile) {
+    JSCustomArray *samples = b.CreateArray();
     b.DefineProperty(profile, "samples", samples);
 
-    JSObject *sample = NULL;
-    JSObject *frames = NULL;
-    JSObject *marker = NULL;
+    JSCustomObject *sample = nullptr;
+    JSCustomArray *frames = nullptr;
+    JSCustomArray *marker = nullptr;
 
     int readPos = mReadPos;
     while (readPos != mLastFlushPos) {
       // Number of tag consumed
       int incBy = 1;
       ProfileEntry entry = mEntries[readPos];
 
       // Read ahead to the next tag, if it's a 'd' tag process it now
@@ -377,17 +391,17 @@ public:
               b.DefineProperty(sample, "time", entry.mTagFloat);
             }
           }
           break;
         case 'c':
         case 'l':
           {
             if (sample) {
-              JSObject *frame = b.CreateObject();
+              JSCustomObject *frame = b.CreateObject();
               if (entry.mTagName == 'l') {
                 // Bug 753041
                 // We need a double cast here to tell GCC that we don't want to sign
                 // extend 32-bit addresses starting with 0xFXXXXXX.
                 unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
                 snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
                 b.DefineProperty(frame, "location", tagBuff);
               } else {
@@ -401,18 +415,16 @@ public:
                 }
               }
               b.ArrayPush(frames, frame);
             }
           }
       }
       readPos = (readPos + incBy) % mEntrySize;
     }
-
-    return profile;
   }
 
   ProfileStack* GetStack()
   {
     return mStack;
   }
 private:
   // Circular buffer 'Keep One Slot Open' implementation
@@ -468,25 +480,27 @@ class TableTicker: public Sampler {
 
   virtual void HandleSaveRequest();
 
   ThreadProfile* GetPrimaryThreadProfile()
   {
     return &mPrimaryThreadProfile;
   }
 
+  void ToStreamAsJSON(std::ostream& stream);
   JSObject *ToJSObject(JSContext *aCx);
-  JSObject *GetMetaJSObject(JSObjectBuilder& b);
+  JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
 
   const bool ProfileJS() { return mProfileJS; }
 
 private:
   // Not implemented on platforms which do not support backtracing
   void doBacktrace(ThreadProfile &aProfile, TickSample* aSample);
 
+  void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile);
 private:
   // This represent the application's main thread (SAMPLER_INIT)
   ThreadProfile mPrimaryThreadProfile;
   TimeStamp mStartTime;
   bool mSaveRequested;
   bool mUseStackWalk;
   bool mJankOnly;
   bool mProfileJS;
@@ -508,17 +522,16 @@ WriteCallback(const jschar *buf, uint32_
  * to be sure that it is not being modified while saving.
  */
 class SaveProfileTask : public nsRunnable {
 public:
   SaveProfileTask() {}
 
   NS_IMETHOD Run() {
     TableTicker *t = tlsTicker.get();
-
     // Pause the profiler during saving.
     // This will prevent us from recording sampling
     // regarding profile saving. This will also
     // prevent bugs caused by the circular buffer not
     // being thread safe. Bug 750989.
     t->SetPaused(true);
 
     // Get file path
@@ -538,17 +551,17 @@ public:
     if (NS_FAILED(rv))
       return rv;
 
     rv = tmpFile->GetNativePath(tmpPath);
     if (NS_FAILED(rv))
       return rv;
 #endif
 
-    // Create a JSContext to run a JSObjectBuilder :(
+    // Create a JSContext to run a JSCustomObjectBuilder :(
     // 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;;
     }
@@ -570,52 +583,49 @@ public:
 
       std::ofstream stream;
       stream.open(tmpPath.get());
       // Pause the profiler during saving.
       // This will prevent us from recording sampling
       // regarding profile saving. This will also
       // prevent bugs caused by the circular buffer not
       // being thread safe. Bug 750989.
-      t->SetPaused(true);
       if (stream.is_open()) {
         JSAutoCompartment autoComp(cx, obj);
         JSObject* profileObj = mozilla_sampler_get_profile_data(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);
 
-    t->SetPaused(false);
-
     return NS_OK;
   }
 };
 
 void TableTicker::HandleSaveRequest()
 {
   if (!mSaveRequested)
     return;
   mSaveRequested = false;
 
   // TODO: Use use the ipc/chromium Tasks here to support processes
   // without XPCOM.
   nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
   NS_DispatchToMainThread(runnable);
 }
 
-JSObject* TableTicker::GetMetaJSObject(JSObjectBuilder& b)
+JSCustomObject* TableTicker::GetMetaJSCustomObject(JSAObjectBuilder& b)
 {
-  JSObject *meta = b.CreateObject();
+  JSCustomObject *meta = b.CreateObject();
 
   b.DefineProperty(meta, "version", 2);
   b.DefineProperty(meta, "interval", interval());
   b.DefineProperty(meta, "stackwalk", mUseStackWalk);
   b.DefineProperty(meta, "jank", mJankOnly);
   b.DefineProperty(meta, "processType", XRE_GetProcessType());
 
   nsresult res;
@@ -656,40 +666,54 @@ JSObject* TableTicker::GetMetaJSObject(J
     res = appInfo->GetName(string);
     if (!NS_FAILED(res))
       b.DefineProperty(meta, "product", string.Data());
   }
 
   return meta;
 }
 
+void TableTicker::ToStreamAsJSON(std::ostream& stream)
+{
+  JSCustomObjectBuilder b;
+  JSCustomObject* profile = b.CreateObject();
+  BuildJSObject(b, profile);
+  b.Serialize(profile, stream);
+  b.DeleteObject(profile);
+}
+
 JSObject* TableTicker::ToJSObject(JSContext *aCx)
 {
   JSObjectBuilder b(aCx);
+  JSCustomObject* profile = b.CreateObject();
+  BuildJSObject(b, profile);
+  JSObject* jsProfile = b.GetJSObject(profile);
 
-  JSObject *profile = b.CreateObject();
+  return jsProfile;
+}
 
+void TableTicker::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile)
+{
   // Put shared library info
   b.DefineProperty(profile, "libs", GetSharedLibraryInfoString().c_str());
 
   // Put meta data
-  JSObject *meta = GetMetaJSObject(b);
+  JSCustomObject *meta = GetMetaJSCustomObject(b);
   b.DefineProperty(profile, "meta", meta);
 
   // Lists the samples for each ThreadProfile
-  JSObject *threads = b.CreateArray();
+  JSCustomArray *threads = b.CreateArray();
   b.DefineProperty(profile, "threads", threads);
 
   // For now we only have one thread
   SetPaused(true);
-  JSObject* threadSamples = GetPrimaryThreadProfile()->ToJSObject(aCx);
+  JSCustomObject* threadSamples = b.CreateObject();
+  GetPrimaryThreadProfile()->BuildJSObject(b, threadSamples);
   b.ArrayPush(threads, threadSamples);
   SetPaused(false);
-
-  return profile;
 }
 
 static
 void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
                      ProfileStack *stack, void *lastpc)
 {
   int lineno = -1;
 
@@ -1057,18 +1081,31 @@ void mozilla_sampler_init()
 #if defined(XP_WIN) || defined(XP_MACOSX)
                          , "stackwalk"
 #endif
                          };
   mozilla_sampler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
                         features, sizeof(features)/sizeof(const char*));
 }
 
-void mozilla_sampler_deinit()
+void mozilla_sampler_shutdown()
 {
+  TableTicker *t = tlsTicker.get();
+  if (t) {
+    const char *val = PR_GetEnv("MOZ_PROFILER_SHUTDOWN");
+    if (val) {
+      std::ofstream stream;
+      stream.open(val);
+      if (stream.is_open()) {
+        t->ToStreamAsJSON(stream);
+        stream.close();
+      }
+    }
+  }
+
   mozilla_sampler_stop();
   // We can't delete the Stack because we can be between a
   // sampler call_enter/call_exit point.
   // TODO Need to find a safe time to delete Stack
 }
 
 void mozilla_sampler_save()
 {
--- a/tools/profiler/sps_sampler.h
+++ b/tools/profiler/sps_sampler.h
@@ -3,51 +3,52 @@
  * 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 <stdlib.h>
 #include <signal.h>
 #include <stdarg.h>
 #include "mozilla/ThreadLocal.h"
 #include "nscore.h"
-#include "jsapi.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Util.h"
 #include "nsAlgorithm.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
 #include "jsfriendapi.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
 struct ProfileStack;
 class TableTicker;
+class JSCustomObject;
 
 extern mozilla::ThreadLocal<ProfileStack *> tlsStack;
 extern mozilla::ThreadLocal<TableTicker *> tlsTicker;
 extern bool stack_key_initialized;
 
 #ifndef SAMPLE_FUNCTION_NAME
 # ifdef __GNUC__
 #  define SAMPLE_FUNCTION_NAME __FUNCTION__
 # elif defined(_MSC_VER)
 #  define SAMPLE_FUNCTION_NAME __FUNCTION__
 # else
 #  define SAMPLE_FUNCTION_NAME __func__  // defined in C99, supported in various C++ compilers. Just raw function name.
 # endif
 #endif
 
 #define SAMPLER_INIT() mozilla_sampler_init()
-#define SAMPLER_DEINIT() mozilla_sampler_deinit()
+#define SAMPLER_SHUTDOWN() mozilla_sampler_shutdown()
 #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_FRAME_NUMBER(frameNumber) mozilla_sampler_frame_number(frameNumber)
 #define SAMPLER_SAVE() mozilla_sampler_save()
 #define SAMPLER_GET_PROFILE() mozilla_sampler_get_profile()
@@ -167,16 +168,17 @@ bool mozilla_sampler_is_active();
 void mozilla_sampler_responsiveness(TimeStamp time);
 void mozilla_sampler_frame_number(int frameNumber);
 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();
+void mozilla_sampler_shutdown();
 
 void mozilla_sampler_print_location();
 
 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.