Bug 788022 - Add support for dalvik profiling. r=snorp,kats
authorBenoit Girard <b56girard@gmail.com>
Tue, 23 Apr 2013 13:10:29 -0400
changeset 140601 42f859a219d647f6bc5b297add90ca31ae59a737
parent 140600 57af1961439a359356a05b391cf8d29a8783e34a
child 140602 f8077e8edd44d68177aa90d969733562d916de4c
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, kats
bugs788022
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 788022 - Add support for dalvik profiling. r=snorp,kats
mobile/android/base/GeckoJavaSampler.java
mobile/android/base/Makefile.in
mobile/android/base/jni-generator.py
mozglue/android/jni-stubs.inc
tools/profiler/BreakpadSampler.cpp
tools/profiler/GeckoProfiler.h
tools/profiler/GeckoProfilerFunc.h
tools/profiler/GeckoProfilerImpl.h
tools/profiler/TableTicker.cpp
tools/profiler/TableTicker.h
tools/profiler/platform.cpp
tools/profiler/platform.h
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJNI.cpp
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoJavaSampler.java
@@ -0,0 +1,184 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+package org.mozilla.gecko;
+
+import android.util.Log;
+import java.lang.Thread;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GeckoJavaSampler {
+    private static final String LOGTAG = "JavaSampler";
+    private static Thread sSamplingThread = null;
+    private static SamplingThread sSamplingRunnable = null;
+    private static Thread sMainThread = null;
+
+    // Use the same timer primitive as the profiler
+    // to get a perfect sample syncing.
+    private static native double getProfilerTime();
+
+    private static class Sample {
+        public Frame[] mFrames;
+        public double mTime;
+        public Sample(StackTraceElement[] aStack) {
+            mFrames = new Frame[aStack.length];
+            mTime = getProfilerTime();
+            for (int i = 0; i < aStack.length; i++) {
+                mFrames[aStack.length - 1 - i] = new Frame();
+                mFrames[aStack.length - 1 - i].fileName = aStack[i].getFileName();
+                mFrames[aStack.length - 1 - i].lineNo = aStack[i].getLineNumber();
+                mFrames[aStack.length - 1 - i].methodName = aStack[i].getMethodName();
+                mFrames[aStack.length - 1 - i].className = aStack[i].getClassName();
+            }
+        }
+    }
+    private static class Frame {
+        public String fileName;
+        public int lineNo;
+        public String methodName;
+        public String className;
+    }
+
+    private static class SamplingThread implements Runnable {
+        private final int mInterval;
+        private final int mSampleCount;
+
+        private boolean mPauseSampler = false;
+        private boolean mStopSampler = false;
+
+        private Map<Integer,Sample[]> mSamples = new HashMap<Integer,Sample[]>();
+        private int mSamplePos;
+
+        public SamplingThread(final int aInterval, final int aSampleCount) {
+            // If we sample faster then 10ms we get to many missed samples
+            mInterval = Math.max(10, aInterval);
+            mSampleCount = aSampleCount;
+        }
+
+        public void run() {
+            synchronized (GeckoJavaSampler.class) {
+                mSamples.put(0, new Sample[mSampleCount]);
+                mSamplePos = 0;
+
+                // Find the main thread
+                Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
+                for (Thread t : threadSet) {
+                    if (t.getName().compareToIgnoreCase("main") == 0) {
+                        sMainThread = t;
+                        break;
+                    }
+                }
+
+                if (sMainThread == null) {
+                    Log.e(LOGTAG, "Main thread not found");
+                    return;
+                }
+            }
+
+            while (true) {
+                try {
+                    Thread.sleep(mInterval);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                synchronized (GeckoJavaSampler.class) {
+                    if (!mPauseSampler) {
+                        StackTraceElement[] bt = sMainThread.getStackTrace();
+                        mSamples.get(0)[mSamplePos] = new Sample(bt);
+                        mSamplePos = (mSamplePos+1) % mSamples.get(0).length;
+                    }
+                    if (mStopSampler) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        private Sample getSample(int aThreadId, int aSampleId) {
+            if (aThreadId < mSamples.size() && aSampleId < mSamples.get(aThreadId).length &&
+                mSamples.get(aThreadId)[aSampleId] != null) {
+                int startPos = 0;
+                if (mSamples.get(aThreadId)[mSamplePos] != null) {
+                    startPos = mSamplePos;
+                }
+                int readPos = (startPos + aSampleId) % mSamples.get(aThreadId).length;
+                return mSamples.get(aThreadId)[readPos];
+            }
+            return null;
+        }
+    }
+
+    public synchronized static String getThreadName(int aThreadId) {
+        if (aThreadId == 0 && sMainThread != null) {
+            return sMainThread.getName();
+        }
+        return null;
+    }
+
+    private synchronized static Sample getSample(int aThreadId, int aSampleId) {
+        return sSamplingRunnable.getSample(aThreadId, aSampleId);
+    }
+    public synchronized static double getSampleTime(int aThreadId, int aSampleId) {
+        Sample sample = getSample(aThreadId, aSampleId);
+        if (sample != null) {
+            System.out.println("Sample: " + sample.mTime);
+            return sample.mTime;
+        }
+        return 0;
+    }
+    public synchronized static String getFrameName(int aThreadId, int aSampleId, int aFrameId) {
+        Sample sample = getSample(aThreadId, aSampleId);
+        if (sample != null && aFrameId < sample.mFrames.length) {
+            Frame frame = sample.mFrames[aFrameId];
+            if (frame == null) {
+                return null;
+            }
+            return frame.className + "." + frame.methodName + "()";
+        }
+        return null;
+    }
+
+    public static void start(int aInterval, int aSamples) {
+        synchronized (GeckoJavaSampler.class) {
+            sSamplingRunnable = new SamplingThread(aInterval, aSamples);
+            sSamplingThread = new Thread(sSamplingRunnable, "Java Sampler");
+            sSamplingThread.start();
+        }
+    }
+
+    public static void pause() {
+        synchronized (GeckoJavaSampler.class) {
+            sSamplingRunnable.mPauseSampler = true;
+        }
+    }
+
+    public static void unpause() {
+        synchronized (GeckoJavaSampler.class) {
+            sSamplingRunnable.mPauseSampler = false;
+        }
+    }
+
+    public static void stop() {
+        synchronized (GeckoJavaSampler.class) {
+            if (sSamplingThread == null) {
+                return;
+            }
+
+            sSamplingRunnable.mStopSampler = true;
+            try {
+                sSamplingThread.join();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            sSamplingThread = null;
+            sSamplingRunnable = null;
+        }
+    }
+}
+
+
+
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -102,16 +102,17 @@ FENNEC_JAVA_FILES = \
   GeckoMessageReceiver.java \
   GeckoSubMenu.java \
   GeckoPreferences.java \
   GeckoPreferenceFragment.java \
   GeckoProfile.java \
   GeckoPopupMenu.java \
   GeckoSmsManager.java \
   GeckoThread.java \
+  GeckoJavaSampler.java \
   GlobalHistory.java \
   GeckoViewsFactory.java \
   HeightChangeAnimation.java \
   InputMethods.java \
   JavaAddonManager.java \
   LightweightTheme.java \
   LightweightThemeDrawable.java \
   LinkPreference.java \
@@ -1147,16 +1148,17 @@ jars/webrtc.jar: $(addprefix $(srcdir)/,
 endif
 
 jars:
 	@echo "MKDIR jars"
 	$(NSINSTALL) -D jars
 
 CLASSES_WITH_JNI= \
     org.mozilla.gecko.GeckoAppShell \
+		org.mozilla.gecko.GeckoJavaSampler \
     $(NULL)
 
 ifdef MOZ_WEBSMS_BACKEND
 # Note: if you are building with MOZ_WEBSMS_BACKEND turned on, then
 # you will get a build error because the generated jni-stubs.inc will
 # be different than the one checked in (i.e. it will have the sms-related
 # JNI stubs as well). Just copy the generated file to mozglue/android/
 # like the error message says and rebuild. All should be well after that.
--- a/mobile/android/base/jni-generator.py
+++ b/mobile/android/base/jni-generator.py
@@ -64,17 +64,17 @@ class Generator:
             match = re.match(paramsRegex, line)
             if match:
                 paramTypes = re.split('\s*,\s*', match.group(1))
                 paramNames = ['arg%d' % i for i in range(0, len(paramTypes))]
                 if returnType == 'void':
                     returnValue = ''
                 elif returnType == 'jobject':
                     returnValue = 'NULL'
-                elif returnType in ('jint', 'jfloat'):
+                elif returnType in ('jint', 'jfloat', 'jdouble'):
                     returnValue = '0'
                 else:
                     raise Exception(('Unsupported JNI return type %s found; '
                                      + 'please update mobile/android/base/'
                                      + 'jni-generator.py to handle this case!')
                                     % returnType)
 
                 self.write('JNI_STUBS', STUB_TEMPLATE % {
--- a/mozglue/android/jni-stubs.inc
+++ b/mozglue/android/jni-stubs.inc
@@ -374,8 +374,27 @@ Java_org_mozilla_gecko_GeckoAppShell_not
      f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(arg0, arg1, arg2, arg3);
 }
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult", &f_Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult);
 #endif
 
+#ifdef JNI_STUBS
+
+typedef jdouble (*Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime_t)(JNIEnv *, jclass);
+static Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime_t f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime;
+extern "C" NS_EXPORT jdouble JNICALL
+Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime(JNIEnv * arg0, jclass arg1) {
+    if (!f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime) {
+        arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
+                       "JNI Function called before it was loaded");
+        return 0;
+    }
+    return f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime(arg0, arg1);
+}
+#endif
+
+#ifdef JNI_BINDINGS
+  xul_dlsym("Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime", &f_Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime);
+#endif
+
--- a/tools/profiler/BreakpadSampler.cpp
+++ b/tools/profiler/BreakpadSampler.cpp
@@ -240,17 +240,17 @@ void TableTicker::UnwinderTick(TickSampl
 
   // Add any extras
   if (!sLastTracerEvent.IsNull() && sample) {
     TimeDuration delta = sample->timestamp - sLastTracerEvent;
     utb__addEntry( utb, ProfileEntry('r', delta.ToMilliseconds()) );
   }
 
   if (sample) {
-    TimeDuration delta = sample->timestamp - mStartTime;
+    TimeDuration delta = sample->timestamp - sStartTime;
     utb__addEntry( utb, ProfileEntry('t', delta.ToMilliseconds()) );
   }
 
   if (sLastFrameNumber != sFrameNumber) {
     utb__addEntry( utb, ProfileEntry('f', sFrameNumber) );
     sLastFrameNumber = sFrameNumber;
   }
 
--- a/tools/profiler/GeckoProfiler.h
+++ b/tools/profiler/GeckoProfiler.h
@@ -137,16 +137,18 @@ static inline void profiler_unlock() {}
 
 static inline void profiler_register_thread(const char* name) {}
 static inline void profiler_unregister_thread() {}
 
 // Call by the JSRuntime's operation callback. This is used to enable
 // profiling on auxilerary threads.
 static inline void profiler_js_operation_callback() {}
 
+static inline double profiler_time() { return 0; }
+
 #else
 
 #include "GeckoProfilerImpl.h"
 
 #endif
 
 class GeckoProfilerInitRAII {
 public:
--- a/tools/profiler/GeckoProfilerFunc.h
+++ b/tools/profiler/GeckoProfilerFunc.h
@@ -56,13 +56,15 @@ void mozilla_sampler_lock();
 
 // Unlock the profiler, leaving it stopped and fires profiler-unlocked.
 void mozilla_sampler_unlock();
 
 // Register/unregister threads with the profiler
 bool mozilla_sampler_register_thread(const char* name);
 void mozilla_sampler_unregister_thread();
 
+double mozilla_sampler_time();
+
 /* Returns true if env var SPS_NEW is set to anything, else false. */
 extern bool sps_version2();
 
 #endif
 
--- a/tools/profiler/GeckoProfilerImpl.h
+++ b/tools/profiler/GeckoProfilerImpl.h
@@ -158,16 +158,22 @@ void profiler_js_operation_callback()
   PseudoStack *stack = tlsPseudoStack.get();
   if (!stack) {
     return;
   }
 
   stack->jsOperationCallback();
 }
 
+static inline
+double profiler_time()
+{
+  return mozilla_sampler_time();
+}
+
 // 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 SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
 #define SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, line) SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line)
 #define SAMPLER_APPEND_LINE_NUMBER(id) SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
 
 #define PROFILER_LABEL(name_space, info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__)
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -29,16 +29,20 @@
 #include "nsIXULRuntime.h"
 #include "nsIXULAppInfo.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "PlatformMacros.h"
 
+#ifdef ANDROID
+  #include "AndroidBridge.h"
+#endif
+
 // JS
 #include "jsdbgapi.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
@@ -155,16 +159,64 @@ JSObject* TableTicker::ToJSObject(JSCont
   JSObjectBuilder b(aCx);
   JSCustomObject* profile = b.CreateObject();
   BuildJSObject(b, profile);
   JSObject* jsProfile = b.GetJSObject(profile);
 
   return jsProfile;
 }
 
+#ifdef ANDROID
+static
+JSCustomObject* BuildJavaThreadJSObject(JSAObjectBuilder& b)
+{
+  JSCustomObject* javaThread = b.CreateObject();
+  b.DefineProperty(javaThread, "name", "Java Main Thread");
+
+  JSCustomArray *samples = b.CreateArray();
+  b.DefineProperty(javaThread, "samples", samples);
+
+  int sampleId = 0;
+  while (true) {
+    int frameId = 0;
+    JSCustomObject *sample = nullptr;
+    JSCustomArray *frames = nullptr;
+    while (true) {
+      nsCString result;
+      bool hasFrame = AndroidBridge::Bridge()->GetFrameNameJavaProfiling(0, sampleId, frameId, result);
+      if (!hasFrame) {
+        if (frames) {
+          b.DefineProperty(sample, "frames", frames);
+        }
+        break;
+      }
+      if (!sample) {
+        sample = b.CreateObject();
+        frames = b.CreateArray();
+        b.DefineProperty(sample, "frames", frames);
+        b.ArrayPush(samples, sample);
+
+        double sampleTime = AndroidBridge::Bridge()->GetSampleTimeJavaProfiling(0, sampleId);
+        b.DefineProperty(sample, "time", sampleTime);
+      }
+      JSCustomObject *frame = b.CreateObject();
+      b.DefineProperty(frame, "location", result.BeginReading());
+      b.ArrayPush(frames, frame);
+      frameId++;
+    }
+    if (frameId == 0) {
+      break;
+    }
+    sampleId++;
+  }
+
+  return javaThread;
+}
+#endif
+
 void TableTicker::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile)
 {
   // Put shared library info
   b.DefineProperty(profile, "libs", GetSharedLibraryInfoString().c_str());
 
   // Put meta data
   JSCustomObject *meta = GetMetaJSCustomObject(b);
   b.DefineProperty(profile, "meta", meta);
@@ -186,18 +238,29 @@ void TableTicker::BuildJSObject(JSAObjec
       MutexAutoLock lock(*sRegisteredThreads->at(i)->Profile()->GetMutex());
 
       JSCustomObject* threadSamples = b.CreateObject();
       sRegisteredThreads->at(i)->Profile()->BuildJSObject(b, threadSamples);
       b.ArrayPush(threads, threadSamples);
     }
   }
 
+#ifdef ANDROID
+  if (ProfileJava()) {
+    AndroidBridge::Bridge()->PauseJavaProfiling();
+
+    JSCustomObject* javaThread = BuildJavaThreadJSObject(b);
+    b.ArrayPush(threads, javaThread);
+
+    AndroidBridge::Bridge()->UnpauseJavaProfiling();
+  }
+#endif
+
   SetPaused(false);
-} 
+}
 
 // END SaveProfileTask et al
 ////////////////////////////////////////////////////////////////////////
 
 static
 void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr)
 {
   aProfile.addTag(ProfileEntry(aTagName, ""));
@@ -447,17 +510,17 @@ void TableTicker::InplaceTick(TickSample
     currThreadProfile.flush();
 
   if (!sLastTracerEvent.IsNull() && sample && currThreadProfile.IsMainThread()) {
     TimeDuration delta = sample->timestamp - sLastTracerEvent;
     currThreadProfile.addTag(ProfileEntry('r', delta.ToMilliseconds()));
   }
 
   if (sample) {
-    TimeDuration delta = sample->timestamp - mStartTime;
+    TimeDuration delta = sample->timestamp - sStartTime;
     currThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds()));
   }
 
   if (sLastFrameNumber != sFrameNumber) {
     currThreadProfile.addTag(ProfileEntry('f', sFrameNumber));
     sLastFrameNumber = sFrameNumber;
   }
 }
--- a/tools/profiler/TableTicker.h
+++ b/tools/profiler/TableTicker.h
@@ -25,29 +25,31 @@ extern unsigned int sLastSampledEventGen
 class BreakpadSampler;
 
 class TableTicker: public Sampler {
  public:
   TableTicker(int aInterval, int aEntrySize,
               const char** aFeatures, uint32_t aFeatureCount)
     : Sampler(aInterval, true, aEntrySize)
     , mPrimaryThreadProfile(nullptr)
-    , mStartTime(TimeStamp::Now())
     , mSaveRequested(false)
     , mUnwinderThread(false)
   {
     mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
 
     //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
     mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
     mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
-    mProfileThreads = true || hasFeature(aFeatures, aFeatureCount, "threads");
+    mProfileJava = hasFeature(aFeatures, aFeatureCount, "java");
+    mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads");
     mUnwinderThread = hasFeature(aFeatures, aFeatureCount, "unwinder") || sps_version2();
     mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
 
+    sStartTime = TimeStamp::Now();
+
     {
       mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex);
 
       // Create ThreadProfile for each registered thread
       for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
         ThreadInfo* info = sRegisteredThreads->at(i);
 
         if (!info->IsMainThread() && !mProfileThreads)
@@ -118,34 +120,35 @@ class TableTicker: public Sampler {
   }
 
   void ToStreamAsJSON(std::ostream& stream);
   virtual JSObject *ToJSObject(JSContext *aCx);
   JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
 
   bool HasUnwinderThread() const { return mUnwinderThread; }
   bool ProfileJS() const { return mProfileJS; }
+  bool ProfileJava() const { return mProfileJava; }
   bool ProfileThreads() const { return mProfileThreads; }
 
 protected:
   // Called within a signal. This function must be reentrant
   virtual void UnwinderTick(TickSample* sample);
 
   // Called within a signal. This function must be reentrant
   virtual void InplaceTick(TickSample* sample);
 
   // Not implemented on platforms which do not support backtracing
   void doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample);
 
   void BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile);
 
   // This represent the application's main thread (SAMPLER_INIT)
   ThreadProfile* mPrimaryThreadProfile;
-  TimeStamp mStartTime;
   bool mSaveRequested;
   bool mAddLeafAddresses;
   bool mUseStackWalk;
   bool mJankOnly;
   bool mProfileJS;
   bool mProfileThreads;
   bool mUnwinderThread;
+  bool mProfileJava;
 };
 
--- a/tools/profiler/platform.cpp
+++ b/tools/profiler/platform.cpp
@@ -15,25 +15,30 @@
 #include "TableTicker.h"
 #include "UnwinderThread2.h"
 #include "nsIObserverService.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "mozilla/Services.h"
 #include "nsThreadUtils.h"
 
+#ifdef ANDROID
+  #include "AndroidBridge.h"
+#endif
+
 mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
 mozilla::ThreadLocal<TableTicker *> tlsTicker;
 // We need to track whether we've been initialized otherwise
 // we end up using tlsStack without initializing it.
 // Because tlsStack is totally opaque to us we can't reuse
 // it as the flag itself.
 bool stack_key_initialized;
 
 TimeStamp   sLastTracerEvent; // is raced on
+TimeStamp   sStartTime;
 int         sFrameNumber = 0;
 int         sLastFrameNumber = 0;
 int         sInitCount = 0; // Each init must have a matched shutdown.
 static bool sIsProfiling = false; // is raced on
 
 /* used to keep track of the last event that we sampled during */
 unsigned int sLastSampledEventGeneration = 0;
 
@@ -378,16 +383,17 @@ const char** mozilla_sampler_get_feature
     // Include the C++ leaf node if not stackwalking. DevTools
     // profiler doesn't want the native addresses.
     "leaf",
 #endif
 #if !defined(SPS_OS_windows)
     // Use a seperate thread of walking the stack.
     "unwinder",
 #endif
+    "java",
     // Only record samples during periods of bad responsiveness
     "jank",
     // Tell the JS engine to emmit pseudostack entries in the
     // pro/epilogue.
     "js",
     // Profile the registered secondary threads.
     "threads",
     NULL
@@ -440,16 +446,27 @@ void mozilla_sampler_start(int aProfileE
         ThreadProfile* thread_profile = info->Profile();
         if (!thread_profile) {
           continue;
         }
         thread_profile->GetPseudoStack()->enableJSSampling();
       }
   }
 
+#ifdef ANDROID
+  if (t->ProfileJava()) {
+    int javaInterval = aInterval;
+    // Java sampling doesn't accuratly keep up with 1ms sampling
+    if (javaInterval < 10) {
+      aInterval = 10;
+    }
+    mozilla::AndroidBridge::Bridge()->StartJavaProfiling(javaInterval, 1000);
+  }
+#endif
+
   sIsProfiling = true;
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-started", nullptr);
 }
 
 void mozilla_sampler_stop()
@@ -555,12 +572,18 @@ bool mozilla_sampler_register_thread(con
   return Sampler::RegisterCurrentThread(aName, stack, false);
 }
 
 void mozilla_sampler_unregister_thread()
 {
   Sampler::UnregisterCurrentThread();
 }
 
+double mozilla_sampler_time()
+{
+  TimeDuration delta = TimeStamp::Now() - sStartTime;
+  return delta.ToMilliseconds();
+}
+
 // END externally visible functions
 ////////////////////////////////////////////////////////////////////////
 
 
--- a/tools/profiler/platform.h
+++ b/tools/profiler/platform.h
@@ -75,16 +75,18 @@
     } while (0)
 
 #endif
 
 #if defined(XP_MACOSX) || defined(XP_WIN)
 #define ENABLE_SPS_LEAF_DATA
 #endif
 
+extern mozilla::TimeStamp sStartTime;
+
 typedef uint8_t* Address;
 
 // ----------------------------------------------------------------------------
 // Mutex
 //
 // Mutexes are used for serializing access to non-reentrant sections of code.
 // The implementations of mutex should allow for nested/recursive locking.
 
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -171,16 +171,25 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jDisableNetworkNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableNetworkNotifications", "()V");
 
     jGetScreenOrientation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getScreenOrientation", "()S");
     jEnableScreenOrientationNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableScreenOrientationNotifications", "()V");
     jDisableScreenOrientationNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableScreenOrientationNotifications", "()V");
     jLockScreenOrientation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "lockScreenOrientation", "(I)V");
     jUnlockScreenOrientation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "unlockScreenOrientation", "()V");
 
+    jGeckoJavaSamplerClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("org/mozilla/gecko/GeckoJavaSampler"));
+    jStart = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "start", "(II)V");
+    jStop = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "stop", "()V");
+    jPause = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "pause", "()V");
+    jUnpause = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "unpause", "()V");
+    jGetThreadName = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "getThreadName", "(I)Ljava/lang/String;");
+    jGetFrameName = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "getFrameName", "(III)Ljava/lang/String;");
+    jGetSampleTime = jEnv->GetStaticMethodID(jGeckoJavaSamplerClass, "getSampleTime", "(II)D");
+
     jThumbnailHelperClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("org/mozilla/gecko/ThumbnailHelper"));
     jNotifyThumbnail = jEnv->GetStaticMethodID(jThumbnailHelperClass, "notifyThumbnail", "(Ljava/nio/ByteBuffer;IZ)V");
 
     jStringClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("java/lang/String"));
 
     jSurfaceClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("android/view/Surface"));
 
     if (!GetStaticIntField("android/os/Build$VERSION", "SDK_INT", &mAPIVersion, jEnv))
@@ -2408,16 +2417,130 @@ AndroidBridge::RemovePluginView(jobject 
     env->CallStaticVoidMethod(mGeckoAppShellClass, jRemovePluginView, view, isFullScreen);
 }
 
 extern "C"
 __attribute__ ((visibility("default")))
 jobject JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv *env, jclass, jlong size);
 
+void
+AndroidBridge::StartJavaProfiling(int aInterval, int aSamples)
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    env->CallStaticVoidMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                              AndroidBridge::Bridge()->jStart,
+                              aInterval, aSamples);
+}
+
+void
+AndroidBridge::StopJavaProfiling()
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    env->CallStaticVoidMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                              AndroidBridge::Bridge()->jStop);
+}
+
+void
+AndroidBridge::PauseJavaProfiling()
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    env->CallStaticVoidMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                              AndroidBridge::Bridge()->jPause);
+}
+
+void
+AndroidBridge::UnpauseJavaProfiling()
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    env->CallStaticVoidMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                              AndroidBridge::Bridge()->jUnpause);
+}
+
+bool
+AndroidBridge::GetThreadNameJavaProfiling(uint32_t aThreadId, nsCString & aResult)
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return false;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    jstring jstrThreadName = static_cast<jstring>(
+        env->CallStaticObjectMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                                    AndroidBridge::Bridge()->jGetThreadName,
+                                    aThreadId));
+
+    if (!jstrThreadName)
+        return false;
+
+    nsJNIString jniStr(jstrThreadName, env);
+    CopyUTF16toUTF8(jniStr.get(), aResult);
+    return true;
+}
+
+bool
+AndroidBridge::GetFrameNameJavaProfiling(uint32_t aThreadId, uint32_t aSampleId,
+                                          uint32_t aFrameId, nsCString & aResult)
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return false;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    jstring jstrSampleName = static_cast<jstring>(
+        env->CallStaticObjectMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                                    AndroidBridge::Bridge()->jGetFrameName,
+                                    aThreadId, aSampleId, aFrameId));
+
+    if (!jstrSampleName)
+        return false;
+
+    nsJNIString jniStr(jstrSampleName, env);
+    CopyUTF16toUTF8(jniStr.get(), aResult);
+    return true;
+}
+
+double
+AndroidBridge::GetSampleTimeJavaProfiling(uint32_t aThreadId, uint32_t aSampleId)
+{
+    JNIEnv* env = GetJNIForThread();
+    if (!env)
+        return 0;
+
+    AutoLocalJNIFrame jniFrame(env);
+
+    jdouble jSampleTime =
+        env->CallStaticDoubleMethod(AndroidBridge::Bridge()->jGeckoJavaSamplerClass,
+                                    AndroidBridge::Bridge()->jGetSampleTime,
+                                    aThreadId, aSampleId);
+
+    return jSampleTime;
+}
 
 void
 AndroidBridge::SendThumbnail(jobject buffer, int32_t tabId, bool success) {
     // Regardless of whether we successfully captured a thumbnail, we need to
     // send a response to process the remaining entries in the queue. If we
     // don't get an env here, we'll stall the thumbnail loop, but there isn't
     // much we can do about it.
     JNIEnv* env = GetJNIEnv();
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -147,16 +147,24 @@ public:
     /* These are all implemented in Java */
     static void NotifyIME(int aType);
 
     static void NotifyIMEContext(int aState, const nsAString& aTypeHint,
                                  const nsAString& aModeHint, const nsAString& aActionHint);
 
     static void NotifyIMEChange(const PRUnichar *aText, uint32_t aTextLen, int aStart, int aEnd, int aNewEnd);
 
+    void StartJavaProfiling(int aInterval, int aSamples);
+    void StopJavaProfiling();
+    void PauseJavaProfiling();
+    void UnpauseJavaProfiling();
+    bool GetThreadNameJavaProfiling(uint32_t aThreadId, nsCString & aResult);
+    bool GetFrameNameJavaProfiling(uint32_t aThreadId, uint32_t aSampleId, uint32_t aFrameId, nsCString & aResult);
+    double GetSampleTimeJavaProfiling(uint32_t aThreadId, uint32_t aSampleId);
+
     nsresult CaptureThumbnail(nsIDOMWindow *window, int32_t bufW, int32_t bufH, int32_t tabId, jobject buffer);
     void SendThumbnail(jobject buffer, int32_t tabId, bool success);
     nsresult GetDisplayPort(bool aPageSizeUpdate, bool aIsBrowserContentDisplayed, int32_t tabId, nsIAndroidViewport* metrics, nsIAndroidDisplayport** displayPort);
 
     bool ProgressiveUpdateCallback(bool aHasPendingNewThebesContent, const gfx::Rect& aDisplayPort, float aDisplayResolution, bool aDrawingCritical, gfx::Rect& aViewport, float& aScaleX, float& aScaleY);
 
     void AcknowledgeEvent();
 
@@ -355,16 +363,25 @@ public:
 
     int GetAPIVersion() { return mAPIVersion; }
     bool IsHoneycomb() { return mAPIVersion >= 11 && mAPIVersion <= 13; }
 
     void ScheduleComposite();
     void RegisterSurfaceTextureFrameListener(jobject surfaceTexture, int id);
     void UnregisterSurfaceTextureFrameListener(jobject surfaceTexture);
 
+    jclass jGeckoJavaSamplerClass;
+    jmethodID jStart;
+    jmethodID jStop;
+    jmethodID jPause;
+    jmethodID jUnpause;
+    jmethodID jGetThreadName;
+    jmethodID jGetFrameName;
+    jmethodID jGetSampleTime;
+
     void GetGfxInfoData(nsACString& aRet);
     nsresult GetProxyForURI(const nsACString & aSpec,
                             const nsACString & aScheme,
                             const nsACString & aHost,
                             const int32_t      aPort,
                             nsACString & aResult);
 protected:
     static AndroidBridge *sBridge;
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -33,16 +33,17 @@
 #include "mozilla/dom/SmsMessage.h"
 #include "mozilla/dom/mobilemessage/Constants.h"
 #include "mozilla/dom/mobilemessage/Types.h"
 #include "mozilla/dom/mobilemessage/PSms.h"
 #include "mozilla/dom/mobilemessage/SmsParent.h"
 #include "nsIMobileMessageDatabaseService.h"
 #include "nsPluginInstanceOwner.h"
 #include "nsSurfaceTexture.h"
+#include "GeckoProfiler.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::mobilemessage;
 
 /* Forward declare all the JNI methods as extern "C" */
 
 extern "C" {
@@ -871,9 +872,15 @@ Java_org_mozilla_gecko_GeckoAppShell_onS
   if (!st) {
     __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "Failed to find nsSurfaceTexture with id %d", id);
     return;
   }
 
   st->NotifyFrameAvailable();
 }
 
+NS_EXPORT jdouble JNICALL
+Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime(JNIEnv *jenv, jclass jc)
+{
+  return profiler_time();
 }
+
+}