зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1666226 - Add GeckoView APIs for starting and stopping the Gecko profiler r=geckoview-reviewers,mstange,agi
*** Differential Revision: https://phabricator.services.mozilla.com/D133404
This commit is contained in:
Родитель
8b2ec126dc
Коммит
cb0a86aae4
|
@ -1772,6 +1772,8 @@ package org.mozilla.geckoview {
|
|||
method public void addMarker(@NonNull String);
|
||||
method @Nullable public Double getProfilerTime();
|
||||
method public boolean isProfilerActive();
|
||||
method public void startProfiler(@NonNull String[], @NonNull String[]);
|
||||
method @NonNull public GeckoResult<byte[]> stopProfiler();
|
||||
}
|
||||
|
||||
public abstract class RuntimeSettings implements Parcelable {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package org.mozilla.geckoview.test
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.geckoview.test.util.UiThreadUtils
|
||||
import java.util.zip.GZIPInputStream
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.json.JSONObject
|
||||
import org.junit.Test
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ProfilerControllerTest : BaseSessionTest() {
|
||||
|
||||
@Test
|
||||
fun startAndStopProfiler(){
|
||||
sessionRule.runtime.profilerController.startProfiler(arrayOf<String>(),arrayOf<String>())
|
||||
val result = sessionRule.runtime.profilerController.stopProfiler()
|
||||
val byteArray = sessionRule.waitForResult(result)
|
||||
val head = (byteArray[0].toInt() and 0xff) or (byteArray[1].toInt() shl 8 and 0xff00)
|
||||
assertThat("Header of byte array should be the same as the GZIP one",
|
||||
head, equalTo(GZIPInputStream.GZIP_MAGIC))
|
||||
|
||||
val profileString = StringBuilder()
|
||||
val gzipInputStream = GZIPInputStream(ByteArrayInputStream(byteArray))
|
||||
val bufferedReader = BufferedReader(InputStreamReader(gzipInputStream))
|
||||
|
||||
var line = bufferedReader.readLine()
|
||||
while(line!=null) {
|
||||
profileString.append(line)
|
||||
line = bufferedReader.readLine()
|
||||
}
|
||||
|
||||
val json = JSONObject(profileString.toString())
|
||||
assertThat("profile JSON object must not be empty",
|
||||
json.length(), greaterThan(0))
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.mozglue.JNIObject;
|
||||
import org.mozilla.geckoview.GeckoResult;
|
||||
|
||||
/**
|
||||
* Takes samples and adds markers for Java threads for the Gecko profiler.
|
||||
|
@ -773,6 +774,22 @@ public class GeckoJavaSampler {
|
|||
}
|
||||
}
|
||||
|
||||
@WrapForJNI(dispatchTo = "gecko", stubName = "StartProfiler")
|
||||
private static native void startProfilerNative(String[] aFilters, String[] aFeaturesArr);
|
||||
|
||||
@WrapForJNI(dispatchTo = "gecko", stubName = "StopProfiler")
|
||||
private static native void stopProfilerNative(GeckoResult<byte[]> aResult);
|
||||
|
||||
public static void startProfiler(final String[] aFilters, final String[] aFeaturesArr) {
|
||||
startProfilerNative(aFilters, aFeaturesArr);
|
||||
}
|
||||
|
||||
public static GeckoResult<byte[]> stopProfiler() {
|
||||
final GeckoResult<byte[]> result = new GeckoResult<byte[]>();
|
||||
stopProfilerNative(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the device brand and model as a string. */
|
||||
@WrapForJNI
|
||||
public static String getDeviceInformation() {
|
||||
|
|
|
@ -154,4 +154,29 @@ public class ProfilerController {
|
|||
public void addMarker(@NonNull final String aMarkerName) {
|
||||
addMarker(aMarkerName, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the Gecko profiler with the given settings. This is used by embedders which want to
|
||||
* control the profiler from the embedding app. This allows them to provide an easier access point
|
||||
* to profiling, as an alternative to the traditional way of using a desktop Firefox instance
|
||||
* connected via USB + adb.
|
||||
*
|
||||
* @param aFilters The list of threads to profile, as an array of string of thread names filters.
|
||||
* Each filter is used as a case-insensitive substring match against the actual thread names.
|
||||
* @param aFeaturesArr The list of profiler features to enable for profiling, as a string array.
|
||||
*/
|
||||
public void startProfiler(
|
||||
@NonNull final String[] aFilters, @NonNull final String[] aFeaturesArr) {
|
||||
GeckoJavaSampler.startProfiler(aFilters, aFeaturesArr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the profiler and capture the recorded profile. This method is asynchronous.
|
||||
*
|
||||
* @return GeckoResult for the captured profile. The profile is returned as a byte[] buffer
|
||||
* containing a gzip-compressed payload (with gzip header) of the profile JSON.
|
||||
*/
|
||||
public @NonNull GeckoResult<byte[]> stopProfiler() {
|
||||
return GeckoJavaSampler.stopProfiler();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1144,4 +1144,4 @@ to allow adding gecko profiler markers.
|
|||
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport(android.content.Context,android.os.Bundle,java.lang.String)
|
||||
[65.25]: {{javadoc_uri}}/GeckoResult.html
|
||||
|
||||
[api-version]: f4393a16a61b77f77a91788ac6e2bcf366a113b6
|
||||
[api-version]: 24b60ce96bd68c81a55110bd1a3c57442dd8c65f
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "ProfilerIOInterposeObserver.h"
|
||||
#include "ProfilerParent.h"
|
||||
#include "ProfilerRustBindings.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "shared-libraries.h"
|
||||
#include "VTuneProfiler.h"
|
||||
|
||||
|
@ -100,6 +101,7 @@
|
|||
#include <type_traits>
|
||||
|
||||
#if defined(GP_OS_android)
|
||||
# include "JavaExceptions.h"
|
||||
# include "mozilla/java/GeckoJavaSamplerNatives.h"
|
||||
# include "mozilla/jni/Refs.h"
|
||||
#endif
|
||||
|
@ -237,6 +239,59 @@ class GeckoJavaSampler
|
|||
}
|
||||
return profiler_time();
|
||||
};
|
||||
|
||||
static void JavaStringArrayToCharArray(jni::ObjectArray::Param& aJavaArray,
|
||||
Vector<const char*>& aCharArray,
|
||||
JNIEnv* aJni) {
|
||||
int arraySize = aJavaArray->Length();
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
jstring javaString =
|
||||
(jstring)(aJni->GetObjectArrayElement(aJavaArray.Get(), i));
|
||||
const char* filterString = aJni->GetStringUTFChars(javaString, 0);
|
||||
// These strings are leaked. FIXME
|
||||
MOZ_RELEASE_ASSERT(aCharArray.append(&filterString, 0));
|
||||
}
|
||||
}
|
||||
|
||||
static void StartProfiler(jni::ObjectArray::Param aFiltersArray,
|
||||
jni::ObjectArray::Param aFeaturesArray) {
|
||||
JNIEnv* jni = jni::GetEnvForThread();
|
||||
Vector<const char*> filtersTemp;
|
||||
Vector<const char*> featureStringArray;
|
||||
|
||||
JavaStringArrayToCharArray(aFiltersArray, filtersTemp, jni);
|
||||
JavaStringArrayToCharArray(aFeaturesArray, featureStringArray, jni);
|
||||
|
||||
uint32_t features = 0;
|
||||
features = ParseFeaturesFromStringArray(featureStringArray.begin(),
|
||||
featureStringArray.length());
|
||||
|
||||
// 128 * 1024 * 1024 is the entries preset that is given in
|
||||
// devtools/client/performance-new/popup/background.jsm.js
|
||||
profiler_start(PowerOfTwo32(128 * 1024 * 1024), 5.0, features,
|
||||
filtersTemp.begin(), filtersTemp.length(), 0, Nothing());
|
||||
}
|
||||
|
||||
static void StopProfiler(jni::Object::Param aGeckoResult) {
|
||||
auto result = java::GeckoResult::LocalRef(aGeckoResult);
|
||||
profiler_pause();
|
||||
nsCOMPtr<nsIProfiler> nsProfiler(
|
||||
do_GetService("@mozilla.org/tools/profiler;1"));
|
||||
nsProfiler->GetProfileDataAsGzippedArrayBufferAndroid(0)->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[result](FallibleTArray<uint8_t> compressedProfile) {
|
||||
result->Complete(jni::ByteArray::New(
|
||||
reinterpret_cast<const int8_t*>(compressedProfile.Elements()),
|
||||
compressedProfile.Length()));
|
||||
},
|
||||
[result](nsresult aRv) {
|
||||
char errorString[9];
|
||||
sprintf(errorString, "%08x", aRv);
|
||||
result->CompleteExceptionally(
|
||||
mozilla::java::sdk::IllegalStateException::New(errorString)
|
||||
.Cast<jni::Throwable>());
|
||||
});
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/Maybe.h"
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
#include "nsStringFwd.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
%}
|
||||
|
||||
[ref] native nsCString(const nsCString);
|
||||
|
@ -190,4 +191,8 @@ interface nsIProfiler : nsISupports
|
|||
* Dump the collected profile to a file.
|
||||
*/
|
||||
void dumpProfileToFile(in string aFilename);
|
||||
|
||||
%{C++
|
||||
virtual RefPtr<mozilla::MozPromise<FallibleTArray<uint8_t>, nsresult, true>> GetProfileDataAsGzippedArrayBufferAndroid(double aSinceTime) = 0;
|
||||
%}
|
||||
};
|
||||
|
|
|
@ -463,6 +463,47 @@ nsProfiler::GetProfileDataAsArrayBuffer(double aSinceTime, JSContext* aCx,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult CompressString(const nsCString& aString,
|
||||
FallibleTArray<uint8_t>& aOutBuff) {
|
||||
// Compress a buffer via zlib (as with `compress()`), but emit a
|
||||
// gzip header as well. Like `compress()`, this is limited to 4GB in
|
||||
// size, but that shouldn't be an issue for our purposes.
|
||||
uLongf outSize = compressBound(aString.Length());
|
||||
if (!aOutBuff.SetLength(outSize, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
int zerr;
|
||||
z_stream stream;
|
||||
stream.zalloc = nullptr;
|
||||
stream.zfree = nullptr;
|
||||
stream.opaque = nullptr;
|
||||
stream.next_out = (Bytef*)aOutBuff.Elements();
|
||||
stream.avail_out = aOutBuff.Length();
|
||||
stream.next_in = (z_const Bytef*)aString.Data();
|
||||
stream.avail_in = aString.Length();
|
||||
|
||||
// A windowBits of 31 is the default (15) plus 16 for emitting a
|
||||
// gzip header; a memLevel of 8 is the default.
|
||||
zerr =
|
||||
deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
|
||||
/* windowBits */ 31, /* memLevel */ 8, Z_DEFAULT_STRATEGY);
|
||||
if (zerr != Z_OK) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
zerr = deflate(&stream, Z_FINISH);
|
||||
outSize = stream.total_out;
|
||||
deflateEnd(&stream);
|
||||
|
||||
if (zerr != Z_STREAM_END) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
aOutBuff.TruncateLength(outSize);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsProfiler::GetProfileDataAsGzippedArrayBuffer(double aSinceTime,
|
||||
JSContext* aCx,
|
||||
|
@ -500,47 +541,14 @@ nsProfiler::GetProfileDataAsGzippedArrayBuffer(double aSinceTime,
|
|||
return;
|
||||
}
|
||||
|
||||
// Compress a buffer via zlib (as with `compress()`), but emit a
|
||||
// gzip header as well. Like `compress()`, this is limited to 4GB in
|
||||
// size, but that shouldn't be an issue for our purposes.
|
||||
uLongf outSize = compressBound(aResult.Length());
|
||||
FallibleTArray<uint8_t> outBuff;
|
||||
if (!outBuff.SetLength(outSize, fallible)) {
|
||||
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
|
||||
nsresult result = CompressString(aResult, outBuff);
|
||||
|
||||
if (result != NS_OK) {
|
||||
promise->MaybeReject(result);
|
||||
return;
|
||||
}
|
||||
|
||||
int zerr;
|
||||
z_stream stream;
|
||||
stream.zalloc = nullptr;
|
||||
stream.zfree = nullptr;
|
||||
stream.opaque = nullptr;
|
||||
stream.next_out = (Bytef*)outBuff.Elements();
|
||||
stream.avail_out = outBuff.Length();
|
||||
stream.next_in = (z_const Bytef*)aResult.Data();
|
||||
stream.avail_in = aResult.Length();
|
||||
|
||||
// A windowBits of 31 is the default (15) plus 16 for emitting a
|
||||
// gzip header; a memLevel of 8 is the default.
|
||||
zerr = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
|
||||
/* windowBits */ 31, /* memLevel */ 8,
|
||||
Z_DEFAULT_STRATEGY);
|
||||
if (zerr != Z_OK) {
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
zerr = deflate(&stream, Z_FINISH);
|
||||
outSize = stream.total_out;
|
||||
deflateEnd(&stream);
|
||||
|
||||
if (zerr != Z_STREAM_END) {
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
outBuff.TruncateLength(outSize);
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
JSObject* typedArray = dom::ArrayBuffer::Create(
|
||||
cx, outBuff.Length(), outBuff.Elements());
|
||||
|
@ -986,6 +994,31 @@ void nsProfiler::GatheredOOPProfile(base::ProcessId aChildPid,
|
|||
RestartGatheringTimer();
|
||||
}
|
||||
|
||||
RefPtr<nsProfiler::GatheringPromiseAndroid>
|
||||
nsProfiler::GetProfileDataAsGzippedArrayBufferAndroid(double aSinceTime) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!profiler_is_active()) {
|
||||
return GatheringPromiseAndroid::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
|
||||
return StartGathering(aSinceTime)
|
||||
->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[](const nsCString& profileResult) {
|
||||
FallibleTArray<uint8_t> outBuff;
|
||||
nsresult result = CompressString(profileResult, outBuff);
|
||||
if (result != NS_OK) {
|
||||
return GatheringPromiseAndroid::CreateAndReject(result, __func__);
|
||||
}
|
||||
return GatheringPromiseAndroid::CreateAndResolve(std::move(outBuff),
|
||||
__func__);
|
||||
},
|
||||
[](nsresult aRv) {
|
||||
return GatheringPromiseAndroid::CreateAndReject(aRv, __func__);
|
||||
});
|
||||
}
|
||||
|
||||
RefPtr<nsProfiler::GatheringPromise> nsProfiler::StartGathering(
|
||||
double aSinceTime) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
|
|
@ -38,6 +38,8 @@ class nsProfiler final : public nsIProfiler {
|
|||
private:
|
||||
~nsProfiler();
|
||||
|
||||
typedef mozilla::MozPromise<FallibleTArray<uint8_t>, nsresult, true>
|
||||
GatheringPromiseAndroid;
|
||||
typedef mozilla::MozPromise<nsCString, nsresult, false> GatheringPromise;
|
||||
typedef mozilla::MozPromise<mozilla::SymbolTable, nsresult, true>
|
||||
SymbolTablePromise;
|
||||
|
@ -53,6 +55,9 @@ class nsProfiler final : public nsIProfiler {
|
|||
RefPtr<SymbolTablePromise> GetSymbolTableMozPromise(
|
||||
const nsACString& aDebugPath, const nsACString& aBreakpadID);
|
||||
|
||||
RefPtr<nsProfiler::GatheringPromiseAndroid>
|
||||
GetProfileDataAsGzippedArrayBufferAndroid(double aSinceTime) override;
|
||||
|
||||
struct ExitProfile {
|
||||
nsCString mJSON;
|
||||
uint64_t mBufferPositionAtGatherTime;
|
||||
|
|
Загрузка…
Ссылка в новой задаче