Fix for bug 748669 (Make JS_{Start,Stop}Profiling work with Instruments on Lion). r=sfink/ted.
authorPeter Van der Beken <peterv@propagandism.org>
Sat, 21 Jan 2012 21:40:37 +0100
changeset 125437 126563fd3ba10dc52db44b92d051334d53b9cf2b
parent 125436 453ccf5b5d29103f3f1cec62daf88664c0b60e8a
child 125443 8156df33b757083912751c01a13f460ee282b536
child 125509 99425ced91f27a8443d0b858b5a281a18dee57a9
push id24455
push userpvanderbeken@mozilla.com
push dateTue, 19 Mar 2013 20:44:32 +0000
treeherdermozilla-central@126563fd3ba1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink, ted
bugs748669
milestone22.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
Fix for bug 748669 (Make JS_{Start,Stop}Profiling work with Instruments on Lion). r=sfink/ted.
browser/config/mozconfigs/macosx-universal/nightly
configure.in
js/src/Makefile.in
js/src/builtin/Profilers.cpp
js/src/configure.in
js/src/devtools/Instruments.cpp
js/src/devtools/Instruments.h
js/src/shell/Makefile.in
js/xpconnect/shell/Makefile.in
--- a/browser/config/mozconfigs/macosx-universal/nightly
+++ b/browser/config/mozconfigs/macosx-universal/nightly
@@ -4,16 +4,18 @@
 ac_add_options --enable-application=browser
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-codesighs
 ac_add_options --disable-install-strip
 ac_add_options --enable-signmar
 ac_add_options --enable-profiling
+ac_add_options --enable-instruments
+ac_add_options --enable-dtrace
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
--- a/configure.in
+++ b/configure.in
@@ -1721,16 +1721,28 @@ MOZ_ARG_ENABLE_BOOL(shark,
     MOZ_SHARK=1,
     MOZ_SHARK= )
 if test -n "$MOZ_SHARK"; then
     MOZ_PROFILING=1
     AC_DEFINE(MOZ_SHARK)
 fi
 
 dnl ========================================================
+dnl instruments
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(instruments,
+[  --enable-instruments    Enable instruments remote profiling. Implies --enable-profiling.],
+    MOZ_INSTRUMENTS=1,
+    MOZ_INSTRUMENTS= )
+if test -n "$MOZ_INSTRUMENTS"; then
+    MOZ_PROFILING=1
+    AC_DEFINE(MOZ_INSTRUMENTS)
+fi
+
+dnl ========================================================
 dnl callgrind
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(callgrind,
 [  --enable-callgrind      Enable callgrind profiling. Implies --enable-profiling.],
     MOZ_CALLGRIND=1,
     MOZ_CALLGRIND= )
 if test -n "$MOZ_CALLGRIND"; then
     MOZ_PROFILING=1
@@ -8500,16 +8512,17 @@ AC_SUBST(MOZ_DEBUG_DISABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_FLAGS)
 AC_SUBST(MOZ_DEBUG_LDFLAGS)
 AC_SUBST(WARNINGS_AS_ERRORS)
 AC_SUBST(MOZ_EXTENSIONS)
 AC_SUBST(MOZ_JSDEBUGGER)
 AC_SUBST(MOZ_ENABLE_PROFILER_SPS)
 AC_SUBST(MOZ_JPROF)
 AC_SUBST(MOZ_SHARK)
+AC_SUBST(MOZ_INSTRUMENTS)
 AC_SUBST(MOZ_CALLGRIND)
 AC_SUBST(MOZ_VTUNE)
 AC_SUBST(MOZ_ETW)
 AC_SUBST(MOZ_PROFILING)
 AC_SUBST(LIBICONV)
 AC_SUBST(MOZ_PLACES)
 AC_SUBST(MOZ_TOOLKIT_SEARCH)
 AC_SUBST(MOZ_FEEDS)
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -139,16 +139,17 @@ CPPSRCS		= \
 		StoreBuffer.cpp \
 		Iteration.cpp \
 		Zone.cpp \
 		Verifier.cpp \
 		StringBuffer.cpp \
 		Unicode.cpp \
 		Xdr.cpp \
 		Module.cpp \
+		Instruments.cpp \
 		$(NULL)
 
 # Changes to internal header files, used externally, massively slow down
 # browser builds.  Don't add new files here unless you know what you're
 # doing!
 INSTALLED_HEADERS = \
 		js-config.h \
 		jscpucfg.h \
--- a/js/src/builtin/Profilers.cpp
+++ b/js/src/builtin/Profilers.cpp
@@ -16,16 +16,17 @@
 #include "vm/Stack-inl.h"
 
 #ifdef MOZ_CALLGRIND
 #include <valgrind/callgrind.h>
 #endif
 
 #ifdef __APPLE__
 #include "devtools/sharkctl.h"
+#include "devtools/Instruments.h"
 #endif
 
 using namespace js;
 
 using mozilla::ArrayLength;
 
 /* Thread-unsafe error management */
 
@@ -46,40 +47,67 @@ UnsafeError(const char *format, ...)
 }
 
 JS_PUBLIC_API(const char *)
 JS_UnsafeGetLastProfilingError()
 {
     return gLastError;
 }
 
+#ifdef __APPLE__
+static bool
+StartOSXProfiling(const char *profileName = NULL)
+{
+    bool ok = true;
+    const char* profiler = NULL;
+#ifdef MOZ_SHARK
+    ok = Shark::Start();
+    profiler = "Shark";
+#endif
+#ifdef MOZ_INSTRUMENTS
+    ok = Instruments::Start();
+    profiler = "Instruments";
+#endif
+    if (!ok) {
+        if (profileName)
+            UnsafeError("Failed to start %s for %s", profiler, profileName);
+        else
+            UnsafeError("Failed to start %s", profiler);
+        return false;
+    }
+    return true;
+}
+#endif
+
 JS_PUBLIC_API(JSBool)
 JS_StartProfiling(const char *profileName)
 {
     JSBool ok = JS_TRUE;
-#if defined(MOZ_SHARK) && defined(__APPLE__)
-    if (!Shark::Start()) {
-        UnsafeError("Failed to start Shark for %s", profileName);
-        ok = JS_FALSE;
-    }
+#ifdef __APPLE__
+    ok = StartOSXProfiling(profileName);
 #endif
 #ifdef __linux__
     if (!js_StartPerf())
         ok = JS_FALSE;
 #endif
     return ok;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_StopProfiling(const char *profileName)
 {
     JSBool ok = JS_TRUE;
-#if defined(MOZ_SHARK) && defined(__APPLE__)
+#ifdef __APPLE__
+#ifdef MOZ_SHARK
     Shark::Stop();
 #endif
+#ifdef MOZ_INSTRUMENTS
+    Instruments::Stop(profileName);
+#endif
+#endif
 #ifdef __linux__
     if (!js_StopPerf())
         ok = JS_FALSE;
 #endif
     return ok;
 }
 
 /*
@@ -87,32 +115,47 @@ JS_StopProfiling(const char *profileName
  * backends are available.
  */
 static JSBool
 ControlProfilers(bool toState)
 {
     JSBool ok = JS_TRUE;
 
     if (! Probes::ProfilingActive && toState) {
-#if defined(MOZ_SHARK) && defined(__APPLE__)
-        if (!Shark::Start()) {
-            UnsafeError("Failed to start Shark");
-            ok = JS_FALSE;
+#ifdef __APPLE__
+#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS)
+        const char* profiler;
+#ifdef MOZ_SHARK
+        ok = Shark::Start();
+        profiler = "Shark";
+#endif
+#ifdef MOZ_INSTRUMENTS
+        ok = Instruments::Resume();
+        profiler = "Instruments";
+#endif
+        if (!ok) {
+            UnsafeError("Failed to start %s", profiler);
         }
 #endif
+#endif
 #ifdef MOZ_CALLGRIND
         if (! js_StartCallgrind()) {
             UnsafeError("Failed to start Callgrind");
             ok = JS_FALSE;
         }
 #endif
     } else if (Probes::ProfilingActive && ! toState) {
-#if defined(MOZ_SHARK) && defined(__APPLE__)
+#ifdef __APPLE__
+#ifdef MOZ_SHARK
         Shark::Stop();
 #endif
+#ifdef MOZ_INSTRUMENTS
+        Instruments::Pause();
+#endif
+#endif
 #ifdef MOZ_CALLGRIND
         if (! js_StopCallgrind()) {
             UnsafeError("failed to stop Callgrind");
             ok = JS_FALSE;
         }
 #endif
     }
 
@@ -258,17 +301,17 @@ DumpProfile(JSContext *cx, unsigned argc
             ret = JS_DumpProfile(filename.mBytes, profileName.mBytes);
         }
     }
 
     JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(ret));
     return true;
 }
 
-#ifdef MOZ_SHARK
+#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS)
 
 static JSBool
 IgnoreAndReturnTrue(JSContext *cx, unsigned argc, jsval *vp)
 {
     JS_SET_RVAL(cx, vp, JSVAL_TRUE);
     return true;
 }
 
@@ -307,17 +350,17 @@ DumpCallgrind(JSContext *cx, unsigned ar
 #endif
 
 static JSFunctionSpec profiling_functions[] = {
     JS_FN("startProfiling",  StartProfiling,      1,0),
     JS_FN("stopProfiling",   StopProfiling,       1,0),
     JS_FN("pauseProfilers",  PauseProfilers,      1,0),
     JS_FN("resumeProfilers", ResumeProfilers,     1,0),
     JS_FN("dumpProfile",     DumpProfile,         2,0),
-#ifdef MOZ_SHARK
+#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS)
     /* Keep users of the old shark API happy. */
     JS_FN("connectShark",    IgnoreAndReturnTrue, 0,0),
     JS_FN("disconnectShark", IgnoreAndReturnTrue, 0,0),
     JS_FN("startShark",      StartProfiling,      0,0),
     JS_FN("stopShark",       StopProfiling,       0,0),
 #endif
 #ifdef MOZ_CALLGRIND
     JS_FN("startCallgrind", StartCallgrind,       0,0),
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -3641,16 +3641,29 @@ MOZ_ARG_ENABLE_BOOL(shark,
     MOZ_SHARK=1,
     MOZ_SHARK= )
 if test -n "$MOZ_SHARK"; then
     MOZ_PROFILING=1
     AC_DEFINE(MOZ_SHARK)
 fi
 
 dnl ========================================================
+dnl instruments
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(instruments,
+[  --enable-instruments    Enable instruments remote profiling. Implies --enable-profiling.],
+    MOZ_INSTRUMENTS=1,
+    MOZ_INSTRUMENTS= )
+if test -n "$MOZ_INSTRUMENTS"; then
+    MOZ_PROFILING=1
+    AC_DEFINE(MOZ_INSTRUMENTS)
+    LIBS="$LIBS -framework CoreFoundation"
+fi
+
+dnl ========================================================
 dnl callgrind
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(callgrind,
 [  --enable-callgrind      Enable callgrind profiling. Implies --enable-profiling.],
     MOZ_CALLGRIND=1,
     MOZ_CALLGRIND= )
 if test -n "$MOZ_CALLGRIND"; then
     MOZ_PROFILING=1
@@ -4118,16 +4131,17 @@ AC_SUBST(MOZ_DEBUG)
 AC_SUBST(MOZ_DEBUG_SYMBOLS)
 AC_SUBST(MOZ_DEBUG_ENABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_DISABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_FLAGS)
 AC_SUBST(MOZ_DEBUG_LDFLAGS)
 AC_SUBST(WARNINGS_AS_ERRORS)
 AC_SUBST(MOZ_JPROF)
 AC_SUBST(MOZ_SHARK)
+AC_SUBST(MOZ_INSTRUMENTS)
 AC_SUBST(MOZ_CALLGRIND)
 AC_SUBST(MOZ_VTUNE)
 AC_SUBST(MOZ_ETW)
 AC_SUBST(MOZ_PROFILING)
 AC_SUBST(LIBICONV)
 
 AC_SUBST(ENABLE_TESTS)
 
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/Instruments.cpp
@@ -0,0 +1,233 @@
+/* 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 "Instruments.h"
+
+#ifdef __APPLE__
+
+#include "jsapi.h"
+#include <dlfcn.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+// There are now 2 paths to the DTPerformanceSession framework. We try to load
+// the one contained in /Applications/Xcode.app first, falling back to the one
+// contained in /Library/Developer/4.0/Instruments.
+#define DTPerformanceLibraryPath "/Applications/Xcode.app/Contents/Developer/Library/Frameworks/DTPerformanceSession.framework/Versions/Current/DTPerformanceSession"
+#define OldDTPerformanceLibraryPath "/Library/Developer/4.0/Instruments/Frameworks/DTPerformanceSession.framework/Versions/Current/DTPerformanceSession"
+
+extern "C" {
+
+typedef CFTypeRef DTPerformanceSessionRef;
+
+#define DTPerformanceSession_TimeProfiler               "com.apple.instruments.dtps.timeprofiler"
+// DTPerformanceSession_Option_SamplingInterval is measured in microseconds
+#define DTPerformanceSession_Option_SamplingInterval    "com.apple.instruments.dtps.option.samplinginterval"
+
+typedef void (*dtps_errorcallback_t)(CFStringRef, CFErrorRef);
+typedef DTPerformanceSessionRef (*DTPerformanceSessionCreateFunction)(CFStringRef, CFStringRef, CFDictionaryRef, CFErrorRef*);
+typedef bool (*DTPerformanceSessionAddInstrumentFunction)(DTPerformanceSessionRef, CFStringRef, CFDictionaryRef, dtps_errorcallback_t, CFErrorRef*);
+typedef bool (*DTPerformanceSessionIsRecordingFunction)(DTPerformanceSessionRef);
+typedef bool (*DTPerformanceSessionStartFunction)(DTPerformanceSessionRef, CFArrayRef, CFErrorRef*);
+typedef bool (*DTPerformanceSessionStopFunction)(DTPerformanceSessionRef, CFArrayRef, CFErrorRef*);
+typedef bool (*DTPerformanceSessionSaveFunction)(DTPerformanceSessionRef, CFStringRef, CFErrorRef*);
+
+} // extern "C"
+
+namespace Instruments {
+
+static const int kSamplingInterval = 20; // microseconds
+
+template<typename T>
+class AutoReleased
+{
+public:
+  AutoReleased(T aTypeRef) : mTypeRef(aTypeRef)
+  {
+  }
+  ~AutoReleased()
+  {
+    if (mTypeRef) {
+      CFRelease(mTypeRef);
+    }
+  }
+
+  operator T()
+  {
+    return mTypeRef;
+  }
+
+private:
+  T mTypeRef;
+};
+
+#define DTPERFORMANCE_SYMBOLS \
+  SYMBOL(DTPerformanceSessionCreate) \
+  SYMBOL(DTPerformanceSessionAddInstrument) \
+  SYMBOL(DTPerformanceSessionIsRecording) \
+  SYMBOL(DTPerformanceSessionStart) \
+  SYMBOL(DTPerformanceSessionStop) \
+  SYMBOL(DTPerformanceSessionSave)
+
+#define SYMBOL(_sym) \
+  _sym##Function _sym = NULL;
+
+DTPERFORMANCE_SYMBOLS
+
+#undef SYMBOL
+
+void*
+LoadDTPerformanceLibraries(bool dontLoad)
+{
+  int flags = RTLD_LAZY | RTLD_LOCAL | RTLD_NODELETE;
+  if (dontLoad) {
+    flags |= RTLD_NOLOAD;
+  }
+
+  void *DTPerformanceLibrary = dlopen(DTPerformanceLibraryPath, flags);
+  if (!DTPerformanceLibrary) {
+    DTPerformanceLibrary = dlopen(OldDTPerformanceLibraryPath, flags);
+  }
+  return DTPerformanceLibrary;
+}
+
+bool
+LoadDTPerformanceLibrary()
+{
+  void *DTPerformanceLibrary = LoadDTPerformanceLibraries(true);
+  if (!DTPerformanceLibrary) {
+    DTPerformanceLibrary = LoadDTPerformanceLibraries(false);
+    if (!DTPerformanceLibrary) {
+      return false;
+    }
+  }
+
+#define SYMBOL(_sym) \
+  _sym = reinterpret_cast<_sym##Function>(dlsym(DTPerformanceLibrary, #_sym)); \
+  if (!_sym) { \
+    dlclose(DTPerformanceLibrary); \
+    DTPerformanceLibrary = NULL; \
+    return false; \
+  }
+
+  DTPERFORMANCE_SYMBOLS
+
+#undef SYMBOL
+
+  dlclose(DTPerformanceLibrary);
+
+  return true;
+}
+
+static DTPerformanceSessionRef gSession;
+
+bool
+Error(CFErrorRef error)
+{
+  if (gSession) {
+    CFErrorRef unused = NULL;
+    DTPerformanceSessionStop(gSession, NULL, &unused);
+    CFRelease(gSession);
+    gSession = NULL;
+  }
+#ifdef DEBUG
+  AutoReleased<CFDataRef> data =
+    CFStringCreateExternalRepresentation(NULL, CFErrorCopyDescription(error),
+                                         kCFStringEncodingUTF8, '?');
+  if (data != NULL) {
+    printf("%.*s\n\n", (int)CFDataGetLength(data), CFDataGetBytePtr(data));
+  }
+#endif
+  return false;
+}
+
+bool
+Start()
+{
+  if (gSession) {
+    return false;
+  }
+
+  if (!LoadDTPerformanceLibrary()) {
+    return false;
+  }
+
+  AutoReleased<CFStringRef> process =
+    CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), getpid());
+  if (!process) {
+    return false;
+  }
+  CFErrorRef error = NULL;
+  gSession = DTPerformanceSessionCreate(NULL, process, NULL, &error);
+  if (!gSession) {
+    return Error(error);
+  }
+
+  AutoReleased<CFNumberRef> interval =
+    CFNumberCreate(0, kCFNumberIntType, &kSamplingInterval);
+  if (!interval) {
+    return false;
+  }
+  CFStringRef keys[1] = { CFSTR(DTPerformanceSession_Option_SamplingInterval) };
+  CFNumberRef values[1] = { interval };
+  AutoReleased<CFDictionaryRef> options =
+    CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys,
+                       (const void**)values, 1, &kCFTypeDictionaryKeyCallBacks,
+                       &kCFTypeDictionaryValueCallBacks);
+  if (!options) {
+    return false;
+  }
+
+  if (!DTPerformanceSessionAddInstrument(gSession,
+                                         CFSTR(DTPerformanceSession_TimeProfiler),
+                                         options, NULL, &error)) {
+    return Error(error);
+  }
+
+  return Resume();
+}
+
+void
+Pause()
+{
+  if (gSession && DTPerformanceSessionIsRecording(gSession)) {
+    CFErrorRef error = NULL;
+    if (!DTPerformanceSessionStop(gSession, NULL, &error)) {
+      Error(error);
+    }
+  }
+}
+
+bool
+Resume()
+{
+  if (!gSession) {
+    return false;
+  }
+
+  CFErrorRef error = NULL;
+  return DTPerformanceSessionStart(gSession, NULL, &error) ||
+         Error(error);
+}
+
+void
+Stop(const char* profileName)
+{
+  Pause();
+
+  CFErrorRef error = NULL;
+  AutoReleased<CFStringRef> name =
+    CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s%s"), "/tmp/",
+                             profileName ? profileName : "mozilla");
+  if (!DTPerformanceSessionSave(gSession, name, &error)) {
+    Error(error);
+    return;
+  }
+
+  CFRelease(gSession);
+  gSession = NULL;
+}
+
+} // namespace Instruments
+
+#endif /* __APPLE__ */
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/Instruments.h
@@ -0,0 +1,21 @@
+/* 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 Instruments_h__
+#define Instruments_h__
+
+#ifdef __APPLE__
+
+namespace Instruments {
+
+bool Start();
+void Pause();
+bool Resume();
+void Stop(const char* profileName);
+
+}
+
+#endif /* __APPLE__ */
+
+#endif /* Instruments_h__ */
--- a/js/src/shell/Makefile.in
+++ b/js/src/shell/Makefile.in
@@ -54,22 +54,16 @@ SHELL_AUTOLOAD := js-gdb.py.in
 SHELL_AUTOLOAD_FLAGS := -Dtopsrcdir=$(abspath $(topsrcdir))
 
 INSTALL_TARGETS += SHELL_INSTALL_AUTOLOAD
 SHELL_INSTALL_AUTOLOAD_FILES := $(CURDIR)/js-gdb.py
 SHELL_INSTALL_AUTOLOAD_DEST := $(DIST)/bin
 
 include $(topsrcdir)/config/rules.mk
 
-ifdef MOZ_SHARK
-CFLAGS += -F/System/Library/PrivateFrameworks
-CXXFLAGS += -F/System/Library/PrivateFrameworks
-LDFLAGS += -F/System/Library/PrivateFrameworks -framework CHUD
-endif
-
 # People expect the js shell to wind up in the top-level JS dir.
 libs::
 	$(INSTALL) $(IFLAGS2) $(PROGRAM) $(DEPTH)
 
 GARBAGE += $(DEPTH)/$(PROGRAM)
 
 install:: $(PROGRAM)
 	$(SYSINSTALL) $^ $(DESTDIR)$(bindir)
--- a/js/xpconnect/shell/Makefile.in
+++ b/js/xpconnect/shell/Makefile.in
@@ -65,18 +65,16 @@ endif
 
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
 DEFINES		+= -DJS_THREADSAFE
 
 ifdef MOZ_SHARK
 DEFINES += -DMOZ_SHARK
-CFLAGS += -F/System/Library/PrivateFrameworks
-LDFLAGS += -F/System/Library/PrivateFrameworks -framework CHUD
 endif
 ifdef MOZ_CALLGRIND
 DEFINES += -DMOZ_CALLGRIND
 endif
 ifdef MOZ_VTUNE
 DEFINES += -DMOZ_VTUNE
 CXXFLAGS += -IC:/Program\ Files/Intel/VTune/Analyzer/Include
 LIBS += C:/Program\ Files/Intel/VTune/Analyzer/Lib/VtuneApi.lib