Bug 788022 - Add support for dalvik profiling. r=snorp,kats

--HG--
extra : rebase_source : 3eb56af40018a546586fd0fb33e343589ddcf207
This commit is contained in:
Benoit Girard 2013-04-23 13:10:29 -04:00
parent f88d7a33ae
commit 755a4f5c71
15 changed files with 460 additions and 7 deletions

View File

@ -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;
}
}
}

View File

@ -107,6 +107,7 @@ FENNEC_JAVA_FILES = \
GeckoPopupMenu.java \
GeckoSmsManager.java \
GeckoThread.java \
GeckoJavaSampler.java \
GlobalHistory.java \
GeckoViewsFactory.java \
HeightChangeAnimation.java \
@ -1152,6 +1153,7 @@ jars:
CLASSES_WITH_JNI= \
org.mozilla.gecko.GeckoAppShell \
org.mozilla.gecko.GeckoJavaSampler \
$(NULL)
ifdef MOZ_WEBSMS_BACKEND

View File

@ -69,7 +69,7 @@ class Generator:
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; '

View File

@ -379,3 +379,22 @@ Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv * arg0, jclas
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

View File

@ -245,7 +245,7 @@ void TableTicker::UnwinderTick(TickSample* sample)
}
if (sample) {
TimeDuration delta = sample->timestamp - mStartTime;
TimeDuration delta = sample->timestamp - sStartTime;
utb__addEntry( utb, ProfileEntry('t', delta.ToMilliseconds()) );
}

View File

@ -142,6 +142,8 @@ static inline void profiler_unregister_thread() {}
// profiling on auxilerary threads.
static inline void profiler_js_operation_callback() {}
static inline double profiler_time() { return 0; }
#else
#include "GeckoProfilerImpl.h"

View File

@ -61,6 +61,8 @@ void mozilla_sampler_unlock();
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();

View File

@ -163,6 +163,12 @@ void profiler_js_operation_callback()
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

View File

@ -34,6 +34,10 @@
#include "mozilla/Services.h"
#include "PlatformMacros.h"
#ifdef ANDROID
#include "AndroidBridge.h"
#endif
// JS
#include "jsdbgapi.h"
@ -160,6 +164,54 @@ JSObject* TableTicker::ToJSObject(JSContext *aCx)
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
@ -191,8 +243,19 @@ void TableTicker::BuildJSObject(JSAObjectBuilder& b, JSCustomObject* profile)
}
}
#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
////////////////////////////////////////////////////////////////////////
@ -452,7 +515,7 @@ void TableTicker::InplaceTick(TickSample* sample)
}
if (sample) {
TimeDuration delta = sample->timestamp - mStartTime;
TimeDuration delta = sample->timestamp - sStartTime;
currThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds()));
}

View File

@ -30,7 +30,6 @@ class TableTicker: public Sampler {
const char** aFeatures, uint32_t aFeatureCount)
: Sampler(aInterval, true, aEntrySize)
, mPrimaryThreadProfile(nullptr)
, mStartTime(TimeStamp::Now())
, mSaveRequested(false)
, mUnwinderThread(false)
{
@ -39,10 +38,13 @@ class TableTicker: public Sampler {
//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);
@ -123,6 +125,7 @@ class TableTicker: public Sampler {
bool HasUnwinderThread() const { return mUnwinderThread; }
bool ProfileJS() const { return mProfileJS; }
bool ProfileJava() const { return mProfileJava; }
bool ProfileThreads() const { return mProfileThreads; }
protected:
@ -139,7 +142,6 @@ protected:
// This represent the application's main thread (SAMPLER_INIT)
ThreadProfile* mPrimaryThreadProfile;
TimeStamp mStartTime;
bool mSaveRequested;
bool mAddLeafAddresses;
bool mUseStackWalk;
@ -147,5 +149,6 @@ protected:
bool mProfileJS;
bool mProfileThreads;
bool mUnwinderThread;
bool mProfileJava;
};

View File

@ -20,6 +20,10 @@
#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
@ -29,6 +33,7 @@ mozilla::ThreadLocal<TableTicker *> tlsTicker;
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.
@ -383,6 +388,7 @@ const char** mozilla_sampler_get_features()
// 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
@ -445,6 +451,17 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval,
}
}
#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();
@ -560,6 +577,12 @@ void mozilla_sampler_unregister_thread()
Sampler::UnregisterCurrentThread();
}
double mozilla_sampler_time()
{
TimeDuration delta = TimeStamp::Now() - sStartTime;
return delta.ToMilliseconds();
}
// END externally visible functions
////////////////////////////////////////////////////////////////////////

View File

@ -80,6 +80,8 @@
#define ENABLE_SPS_LEAF_DATA
#endif
extern mozilla::TimeStamp sStartTime;
typedef uint8_t* Address;
// ----------------------------------------------------------------------------

View File

@ -176,6 +176,15 @@ AndroidBridge::Init(JNIEnv *jEnv,
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");
@ -2413,6 +2422,120 @@ __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) {

View File

@ -152,6 +152,14 @@ public:
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);
@ -360,6 +368,15 @@ public:
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,

View File

@ -38,6 +38,7 @@
#include "nsIMobileMessageDatabaseService.h"
#include "nsPluginInstanceOwner.h"
#include "nsSurfaceTexture.h"
#include "GeckoProfiler.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -876,4 +877,10 @@ Java_org_mozilla_gecko_GeckoAppShell_onSurfaceTextureFrameAvailable(JNIEnv* jenv
st->NotifyFrameAvailable();
}
NS_EXPORT jdouble JNICALL
Java_org_mozilla_gecko_GeckoJavaSampler_getProfilerTime(JNIEnv *jenv, jclass jc)
{
return profiler_time();
}
}