Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "nsProfiler.h"
#include <fstream>
#include <limits>
#include <sstream>
#include <string>
#include <utility>
#include "GeckoProfiler.h"
#include "ProfilerControl.h"
#include "ProfilerParent.h"
#include "js/Array.h" // JS::NewArrayObject
#include "js/JSON.h"
#include "js/PropertyAndElement.h" // JS_SetElement
#include "js/Value.h"
#include "json/json.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/JSONStringWriteFuncs.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Services.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/Preferences.h"
#include "nsComponentManagerUtils.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILoadContext.h"
#include "nsIWebNavigation.h"
#include "nsProfilerStartParams.h"
#include "nsProxyRelease.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "platform.h"
#include "shared-libraries.h"
#include "zlib.h"
#ifndef ANDROID
# include <cstdio>
#else
# include <android/log.h>
#endif
using namespace mozilla;
using dom::AutoJSAPI;
using dom::Promise;
using std::string;
static constexpr size_t scLengthMax = size_t(JS::MaxStringLength);
// Used when trying to add more JSON data, to account for the extra space needed
// for the log and to close the profile.
static constexpr size_t scLengthAccumulationThreshold = scLengthMax - 16 * 1024;
NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler)
nsProfiler::nsProfiler() : mGathering(false) {}
nsProfiler::~nsProfiler() {
if (mSymbolTableThread) {
mSymbolTableThread->Shutdown();
}
ResetGathering(NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
}
nsresult nsProfiler::Init() { return NS_OK; }
template <typename JsonLogObjectUpdater>
void nsProfiler::Log(JsonLogObjectUpdater&& aJsonLogObjectUpdater) {
if (mGatheringLog) {
MOZ_ASSERT(mGatheringLog->isObject());
std::forward<JsonLogObjectUpdater>(aJsonLogObjectUpdater)(*mGatheringLog);
MOZ_ASSERT(mGatheringLog->isObject());
}
}
template <typename JsonArrayAppender>
void nsProfiler::LogEvent(JsonArrayAppender&& aJsonArrayAppender) {
Log([&](Json::Value& aRoot) {
Json::Value& events = aRoot[Json::StaticString{"events"}];
if (!events.isArray()) {
events = Json::Value{Json::arrayValue};
}
Json::Value newEvent{Json::arrayValue};
newEvent.append(ProfilingLog::Timestamp());
std::forward<JsonArrayAppender>(aJsonArrayAppender)(newEvent);
MOZ_ASSERT(newEvent.isArray());
events.append(std::move(newEvent));
});
}
void nsProfiler::LogEventLiteralString(const char* aEventString) {
LogEvent([&](Json::Value& aEvent) {
aEvent.append(Json::StaticString{aEventString});
});
}
static nsresult FillVectorFromStringArray(Vector<const char*>& aVector,
const nsTArray<nsCString>& aArray) {
if (NS_WARN_IF(!aVector.reserve(aArray.Length()))) {
return NS_ERROR_OUT_OF_MEMORY;
}
for (auto& entry : aArray) {
aVector.infallibleAppend(entry.get());
}
return NS_OK;
}
// Given a PromiseReturningFunction: () -> GenericPromise,
// run the function, and return a JS Promise (through aPromise) that will be
// resolved when the function's GenericPromise gets resolved.
template <typename PromiseReturningFunction>
static nsresult RunFunctionAndConvertPromise(
JSContext* aCx, Promise** aPromise,
PromiseReturningFunction&& aPromiseReturningFunction) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
std::forward<PromiseReturningFunction>(aPromiseReturningFunction)()->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](GenericPromise::ResolveOrRejectValue&&) {
promise->MaybeResolveWithUndefined();
});
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::StartProfiler(uint32_t aEntries, double aInterval,
const nsTArray<nsCString>& aFeatures,
const nsTArray<nsCString>& aFilters,
uint64_t aActiveTabID, double aDuration,
JSContext* aCx, Promise** aPromise) {
ResetGathering(NS_ERROR_DOM_ABORT_ERR);
Vector<const char*> featureStringVector;
nsresult rv = FillVectorFromStringArray(featureStringVector, aFeatures);
if (NS_FAILED(rv)) {
return rv;
}
uint32_t features = ParseFeaturesFromStringArray(
featureStringVector.begin(), featureStringVector.length());
Maybe<double> duration = aDuration > 0.0 ? Some(aDuration) : Nothing();
Vector<const char*> filterStringVector;
rv = FillVectorFromStringArray(filterStringVector, aFilters);
if (NS_FAILED(rv)) {
return rv;
}
return RunFunctionAndConvertPromise(aCx, aPromise, [&]() {
return profiler_start(PowerOfTwo32(aEntries), aInterval, features,
filterStringVector.begin(),
filterStringVector.length(), aActiveTabID, duration);
});
}
NS_IMETHODIMP
nsProfiler::StopProfiler(JSContext* aCx, Promise** aPromise) {
ResetGathering(NS_ERROR_DOM_ABORT_ERR);
return RunFunctionAndConvertPromise(aCx, aPromise,
[]() { return profiler_stop(); });
}
NS_IMETHODIMP
nsProfiler::IsPaused(bool* aIsPaused) {
*aIsPaused = profiler_is_paused();
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::Pause(JSContext* aCx, Promise** aPromise) {
return RunFunctionAndConvertPromise(aCx, aPromise,
[]() { return profiler_pause(); });
}
NS_IMETHODIMP
nsProfiler::Resume(JSContext* aCx, Promise** aPromise) {
return RunFunctionAndConvertPromise(aCx, aPromise,
[]() { return profiler_resume(); });
}
NS_IMETHODIMP
nsProfiler::IsSamplingPaused(bool* aIsSamplingPaused) {
*aIsSamplingPaused = profiler_is_sampling_paused();
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::PauseSampling(JSContext* aCx, Promise** aPromise) {
return RunFunctionAndConvertPromise(
aCx, aPromise, []() { return profiler_pause_sampling(); });
}
NS_IMETHODIMP
nsProfiler::ResumeSampling(JSContext* aCx, Promise** aPromise) {
return RunFunctionAndConvertPromise(
aCx, aPromise, []() { return profiler_resume_sampling(); });
}
NS_IMETHODIMP
nsProfiler::ClearAllPages() {
profiler_clear_all_pages();
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::WaitOnePeriodicSampling(JSContext* aCx, Promise** aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
// The callback cannot officially own the promise RefPtr directly, because
// `Promise` doesn't support multi-threading, and the callback could destroy
// the promise in the sampler thread.
// `nsMainThreadPtrHandle` ensures that the promise can only be destroyed on
// the main thread. And the invocation from the Sampler thread immediately
// dispatches a task back to the main thread, to resolve/reject the promise.
// The lambda needs to be `mutable`, to allow moving-from
// `promiseHandleInSampler`.
if (!profiler_callback_after_sampling(
[promiseHandleInSampler = nsMainThreadPtrHandle<Promise>(
new nsMainThreadPtrHolder<Promise>(
"WaitOnePeriodicSampling promise for Sampler", promise))](
SamplingState aSamplingState) mutable {
SchedulerGroup::Dispatch(NS_NewRunnableFunction(
"nsProfiler::WaitOnePeriodicSampling result on main thread",
[promiseHandleInMT = std::move(promiseHandleInSampler),
aSamplingState]() mutable {
switch (aSamplingState) {
case SamplingState::JustStopped:
case SamplingState::SamplingPaused:
promiseHandleInMT->MaybeReject(NS_ERROR_FAILURE);
break;
case SamplingState::NoStackSamplingCompleted:
case SamplingState::SamplingCompleted:
// The parent process has succesfully done a sampling,
// check the child processes (if any).
ProfilerParent::WaitOnePeriodicSampling()->Then(
GetMainThreadSerialEventTarget(), __func__,
[promiseHandleInMT = std::move(promiseHandleInMT)](
GenericPromise::ResolveOrRejectValue&&) {
promiseHandleInMT->MaybeResolveWithUndefined();
});
break;
default:
MOZ_ASSERT(false, "Unexpected SamplingState value");
promiseHandleInMT->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
break;
}
}));
})) {
// Callback was not added (e.g., profiler is not running) and will never be
// invoked, so we need to resolve the promise here.
promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
}
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetProfile(double aSinceTime, char** aProfile) {
mozilla::UniquePtr<char[]> profile = profiler_get_profile(aSinceTime);
*aProfile = profile.release();
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetSharedLibraries(JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
JS::Rooted<JS::Value> val(aCx);
{
JSONStringWriteFunc<nsCString> buffer;
JSONWriter w(buffer, JSONWriter::SingleLineStyle);
w.StartArrayElement();
SharedLibraryInfo sharedLibraryInfo = SharedLibraryInfo::GetInfoForSelf();
sharedLibraryInfo.SortByAddress();
AppendSharedLibraries(w, sharedLibraryInfo);
w.EndArray();
NS_ConvertUTF8toUTF16 buffer16(buffer.StringCRef());
MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx,
static_cast<const char16_t*>(buffer16.get()),
buffer16.Length(), &val));
}
JS::Rooted<JSObject*> obj(aCx, &val.toObject());
if (!obj) {
return NS_ERROR_FAILURE;
}
aResult.setObject(*obj);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetActiveConfiguration(JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
JS::Rooted<JS::Value> jsValue(aCx);
{
JSONStringWriteFunc<nsCString> buffer;
JSONWriter writer(buffer, JSONWriter::SingleLineStyle);
profiler_write_active_configuration(writer);
NS_ConvertUTF8toUTF16 buffer16(buffer.StringCRef());
MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx,
static_cast<const char16_t*>(buffer16.get()),
buffer16.Length(), &jsValue));
}
if (jsValue.isNull()) {
aResult.setNull();
} else {
JS::Rooted<JSObject*> obj(aCx, &jsValue.toObject());
if (!obj) {
return NS_ERROR_FAILURE;
}
aResult.setObject(*obj);
}
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::DumpProfileToFile(const char* aFilename) {
profiler_save_profile_to_file(aFilename);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetProfileData(double aSinceTime, JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
mozilla::UniquePtr<char[]> profile = profiler_get_profile(aSinceTime);
if (!profile) {
return NS_ERROR_FAILURE;
}
NS_ConvertUTF8toUTF16 js_string(nsDependentCString(profile.get()));
auto profile16 = static_cast<const char16_t*>(js_string.get());
JS::Rooted<JS::Value> val(aCx);
MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, profile16, js_string.Length(), &val));
aResult.set(val);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetProfileDataAsync(double aSinceTime, JSContext* aCx,
Promise** aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (!profiler_is_active()) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
StartGathering(aSinceTime)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](const mozilla::ProfileAndAdditionalInformation& aResult) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
// We're really hosed if we can't get a JS context for some
// reason.
promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
JSContext* cx = jsapi.cx();
// Now parse the JSON so that we resolve with a JS Object.
JS::Rooted<JS::Value> val(cx);
{
NS_ConvertUTF8toUTF16 js_string(aResult.mProfile);
if (!JS_ParseJSON(cx,
static_cast<const char16_t*>(js_string.get()),
js_string.Length(), &val)) {
if (!jsapi.HasException()) {
promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
} else {
JS::Rooted<JS::Value> exn(cx);
DebugOnly<bool> gotException = jsapi.StealException(&exn);
MOZ_ASSERT(gotException);
jsapi.ClearException();
promise->MaybeReject(exn);
}
} else {
promise->MaybeResolve(val);
}
}
},
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetProfileDataAsArrayBuffer(double aSinceTime, JSContext* aCx,
Promise** aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (!profiler_is_active()) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
StartGathering(aSinceTime)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](const mozilla::ProfileAndAdditionalInformation& aResult) {
promise->MaybeResolve(
dom::TypedArrayCreator<dom::ArrayBuffer>(aResult.mProfile));
},
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
promise.forget(aPromise);
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,
Promise** aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (!profiler_is_active()) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
StartGathering(aSinceTime)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](const mozilla::ProfileAndAdditionalInformation& aResult) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
// We're really hosed if we can't get a JS context for some
// reason.
promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
FallibleTArray<uint8_t> outBuff;
nsresult result = CompressString(aResult.mProfile, outBuff);
if (result != NS_OK) {
promise->MaybeReject(result);
return;
}
JSContext* cx = jsapi.cx();
// Get the profile typedArray.
JS::Rooted<JS::Value> typedArrayValue(cx);
if (!ToJSValue(cx,
dom::TypedArrayCreator<dom::ArrayBuffer>(outBuff),
&typedArrayValue)) {
promise->MaybeRejectWithExceptionFromContext(cx);
return;
}
// Get the additional information object.
JS::Rooted<JS::Value> additionalInfoVal(cx);
if (aResult.mAdditionalInformation.isSome()) {
aResult.mAdditionalInformation->ToJSValue(cx, &additionalInfoVal);
} else {
additionalInfoVal.setUndefined();
}
// Create the return object.
JS::Rooted<JSObject*> resultObj(cx, JS_NewPlainObject(cx));
JS_SetProperty(cx, resultObj, "profile", typedArrayValue);
JS_SetProperty(cx, resultObj, "additionalInformation",
additionalInfoVal);
promise->MaybeResolve(resultObj);
},
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::DumpProfileToFileAsync(const nsACString& aFilename,
double aSinceTime, JSContext* aCx,
Promise** aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (!profiler_is_active()) {
return NS_ERROR_FAILURE;
}
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
nsCString filename(aFilename);
StartGathering(aSinceTime)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[filename,
promise](const mozilla::ProfileAndAdditionalInformation& aResult) {
if (aResult.mProfile.Length() >=
size_t(std::numeric_limits<std::streamsize>::max())) {
promise->MaybeReject(NS_ERROR_FILE_TOO_BIG);
return;
}
std::ofstream stream;
stream.open(filename.get());
if (!stream.is_open()) {
promise->MaybeReject(NS_ERROR_FILE_UNRECOGNIZED_PATH);
return;
}
stream.write(aResult.mProfile.get(),
std::streamsize(aResult.mProfile.Length()));
stream.close();
promise->MaybeResolveWithUndefined();
},
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
promise.forget(aPromise);
return NS_OK;
}
RefPtr<nsProfiler::GatheringPromiseFileDump>
nsProfiler::DumpProfileToFileAsyncNoJs(const nsACString& aFilename,
double aSinceTime) {
if (!profiler_is_active()) {
return GatheringPromiseFileDump::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
nsCString filename(aFilename);
return StartGathering(aSinceTime)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[filename](const mozilla::ProfileAndAdditionalInformation& aResult) {
if (aResult.mProfile.Length() >=
size_t(std::numeric_limits<std::streamsize>::max())) {
return GatheringPromiseFileDump::CreateAndReject(
NS_ERROR_FILE_TOO_BIG, __func__);
}
std::ofstream stream;
stream.open(filename.get());
if (!stream.is_open()) {
return GatheringPromiseFileDump::CreateAndReject(
NS_ERROR_FILE_UNRECOGNIZED_PATH, __func__);
}
stream.write(aResult.mProfile.get(),
std::streamsize(aResult.mProfile.Length()));
stream.close();
return GatheringPromiseFileDump::CreateAndResolve(void_t(),
__func__);
},
[](nsresult aRv) {
return GatheringPromiseFileDump::CreateAndReject(aRv, __func__);
});
}
NS_IMETHODIMP
nsProfiler::GetSymbolTable(const nsACString& aDebugPath,
const nsACString& aBreakpadID, JSContext* aCx,
Promise** aPromise) {
MOZ_ASSERT(NS_IsMainThread());
if (NS_WARN_IF(!aCx)) {
return NS_ERROR_FAILURE;
}
nsIGlobalObject* globalObject =
xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
if (NS_WARN_IF(!globalObject)) {
return NS_ERROR_FAILURE;
}
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(globalObject, result);
if (NS_WARN_IF(result.Failed())) {
return result.StealNSResult();
}
GetSymbolTableMozPromise(aDebugPath, aBreakpadID)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise](const SymbolTable& aSymbolTable) {
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(promise->GetGlobalObject()))) {
// We're really hosed if we can't get a JS context for some
// reason.
promise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> addrsArray(cx);
if (!ToJSValue(cx,
dom::TypedArrayCreator<dom::Uint32Array>(
aSymbolTable.mAddrs),
&addrsArray)) {
promise->MaybeRejectWithExceptionFromContext(cx);
return;
}
JS::Rooted<JS::Value> indexArray(cx);
if (!ToJSValue(cx,
dom::TypedArrayCreator<dom::Uint32Array>(
aSymbolTable.mIndex),
&indexArray)) {
promise->MaybeRejectWithExceptionFromContext(cx);
return;
}
JS::Rooted<JS::Value> bufferArray(cx);
if (!ToJSValue(cx,
dom::TypedArrayCreator<dom::Uint8Array>(
aSymbolTable.mBuffer),
&bufferArray)) {
promise->MaybeRejectWithExceptionFromContext(cx);
return;
}
JS::Rooted<JSObject*> tuple(cx, JS::NewArrayObject(cx, 3));
JS_SetElement(cx, tuple, 0, addrsArray);
JS_SetElement(cx, tuple, 1, indexArray);
JS_SetElement(cx, tuple, 2, bufferArray);
promise->MaybeResolve(tuple);
},
[promise](nsresult aRv) { promise->MaybeReject(aRv); });
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetElapsedTime(double* aElapsedTime) {
*aElapsedTime = profiler_time();
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::IsActive(bool* aIsActive) {
*aIsActive = profiler_is_active();
return NS_OK;
}
static void GetArrayOfStringsForFeatures(uint32_t aFeatures,
nsTArray<nsCString>& aFeatureList) {
#define COUNT_IF_SET(n_, str_, Name_, desc_) \
if (ProfilerFeature::Has##Name_(aFeatures)) { \
len++; \
}
// Count the number of features in use.
uint32_t len = 0;
PROFILER_FOR_EACH_FEATURE(COUNT_IF_SET)
#undef COUNT_IF_SET
aFeatureList.SetCapacity(len);
#define DUP_IF_SET(n_, str_, Name_, desc_) \
if (ProfilerFeature::Has##Name_(aFeatures)) { \
aFeatureList.AppendElement(str_); \
}
// Insert the strings for the features in use.
PROFILER_FOR_EACH_FEATURE(DUP_IF_SET)
#undef DUP_IF_SET
}
NS_IMETHODIMP
nsProfiler::GetFeatures(nsTArray<nsCString>& aFeatureList) {
uint32_t features = profiler_get_available_features();
GetArrayOfStringsForFeatures(features, aFeatureList);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetAllFeatures(nsTArray<nsCString>& aFeatureList) {
GetArrayOfStringsForFeatures((uint32_t)-1, aFeatureList);
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetBufferInfo(uint32_t* aCurrentPosition, uint32_t* aTotalSize,
uint32_t* aGeneration) {
MOZ_ASSERT(aCurrentPosition);
MOZ_ASSERT(aTotalSize);
MOZ_ASSERT(aGeneration);
Maybe<ProfilerBufferInfo> info = profiler_get_buffer_info();
if (info) {
*aCurrentPosition = info->mRangeEnd % info->mEntryCount;
*aTotalSize = info->mEntryCount;
*aGeneration = info->mRangeEnd / info->mEntryCount;
} else {
*aCurrentPosition = 0;
*aTotalSize = 0;
*aGeneration = 0;
}
return NS_OK;
}
bool nsProfiler::SendProgressRequest(PendingProfile& aPendingProfile) {
RefPtr<ProfilerParent::SingleProcessProgressPromise> progressPromise =
ProfilerParent::RequestGatherProfileProgress(aPendingProfile.childPid);
if (!progressPromise) {
LOG("RequestGatherProfileProgress(%u) -> null!",
unsigned(aPendingProfile.childPid));
LogEvent([&](Json::Value& aEvent) {
aEvent.append(
Json::StaticString{"Failed to send progress request to pid:"});
aEvent.append(Json::Value::UInt64(aPendingProfile.childPid));
});
// Failed to send request.
return false;
}
DEBUG_LOG("RequestGatherProfileProgress(%u) sent...",
unsigned(aPendingProfile.childPid));
LogEvent([&](Json::Value& aEvent) {
aEvent.append(Json::StaticString{"Requested progress from pid:"});
aEvent.append(Json::Value::UInt64(aPendingProfile.childPid));
});
aPendingProfile.lastProgressRequest = TimeStamp::Now();
progressPromise->Then(
GetMainThreadSerialEventTarget(), __func__,
[self = RefPtr<nsProfiler>(this),
childPid = aPendingProfile.childPid](GatherProfileProgress&& aResult) {
if (!self->mGathering) {
return;
}
PendingProfile* pendingProfile = self->GetPendingProfile(childPid);
DEBUG_LOG(
"RequestGatherProfileProgress(%u) response: %.2f '%s' "
"(%u were pending, %s %u)",
unsigned(childPid),
ProportionValue::FromUnderlyingType(
aResult.progressProportionValueUnderlyingType())
.ToDouble() *
100.0,
aResult.progressLocation().Data(),
unsigned(self->mPendingProfiles.length()),
pendingProfile ? "including" : "excluding", unsigned(childPid));
self->LogEvent([&](Json::Value& aEvent) {
aEvent.append(
Json::StaticString{"Got response from pid, with progress:"});
aEvent.append(Json::Value::UInt64(childPid));
aEvent.append(
Json::Value{ProportionValue::FromUnderlyingType(
aResult.progressProportionValueUnderlyingType())
.ToDouble() *
100.0});
});
if (pendingProfile) {
// We have a progress report for a still-pending profile.
pendingProfile->lastProgressResponse = TimeStamp::Now();
// Has it actually made progress?
if (aResult.progressProportionValueUnderlyingType() !=
pendingProfile->progressProportion.ToUnderlyingType()) {
pendingProfile->lastProgressChange =
pendingProfile->lastProgressResponse;
pendingProfile->progressProportion =
ProportionValue::FromUnderlyingType(
aResult.progressProportionValueUnderlyingType());
pendingProfile->progressLocation = aResult.progressLocation();
self->RestartGatheringTimer();
}
}
},
[self = RefPtr<nsProfiler>(this), childPid = aPendingProfile.childPid](
ipc::ResponseRejectReason&& aReason) {
if (!self->mGathering) {
return;
}
PendingProfile* pendingProfile = self->GetPendingProfile(childPid);
LOG("RequestGatherProfileProgress(%u) rejection: %d "
"(%u were pending, %s %u)",
unsigned(childPid), (int)aReason,
unsigned(self->mPendingProfiles.length()),
pendingProfile ? "including" : "excluding", unsigned(childPid));
self->LogEvent([&](Json::Value& aEvent) {
aEvent.append(Json::StaticString{
"Got progress request rejection from pid, with reason:"});
aEvent.append(Json::Value::UInt64(childPid));
aEvent.append(Json::Value::UInt{static_cast<unsigned>(aReason)});
});
if (pendingProfile) {
// Failure response, assume the child process is gone.
MOZ_ASSERT(self->mPendingProfiles.begin() <= pendingProfile &&
pendingProfile < self->mPendingProfiles.end());
self->mPendingProfiles.erase(pendingProfile);
if (self->mPendingProfiles.empty()) {
// We've got all of the async profiles now. Let's finish off the
// profile and resolve the Promise.
self->FinishGathering();
}
}
});
return true;
}
/* static */ void nsProfiler::GatheringTimerCallback(nsITimer* aTimer,
void* aClosure) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIProfiler> profiler(
do_GetService("@mozilla.org/tools/profiler;1"));
if (!profiler) {
// No (more) profiler service.
return;
}
nsProfiler* self = static_cast<nsProfiler*>(profiler.get());
if (self != aClosure) {
// Different service object!?
return;
}
if (aTimer != self->mGatheringTimer) {
// This timer was cancelled after this callback was queued.
return;
}
bool progressWasMade = false;
// Going backwards, it's easier and cheaper to erase elements if needed.
for (auto iPlus1 = self->mPendingProfiles.length(); iPlus1 != 0; --iPlus1) {
PendingProfile& pendingProfile = self->mPendingProfiles[iPlus1 - 1];
bool needToSendProgressRequest = false;
if (pendingProfile.lastProgressRequest.IsNull()) {
DEBUG_LOG("GatheringTimerCallback() - child %u: No data yet",
unsigned(pendingProfile.childPid));
// First time going through the list, send an initial progress request.
needToSendProgressRequest = true;
// We pretend that progress was made, so we don't give up yet.
progressWasMade = true;
} else if (pendingProfile.lastProgressResponse.IsNull()) {
LOG("GatheringTimerCallback() - child %u: Waiting for first response",
unsigned(pendingProfile.childPid));
// Still waiting for the first response, no progress made here, don't send
// another request.
} else if (pendingProfile.lastProgressResponse <=
pendingProfile.lastProgressRequest) {
LOG("GatheringTimerCallback() - child %u: Waiting for response",
unsigned(pendingProfile.childPid));
// Still waiting for a response to the last request, no progress made
// here, don't send another request.
} else if (pendingProfile.lastProgressChange.IsNull()) {
LOG("GatheringTimerCallback() - child %u: Still waiting for first change",
unsigned(pendingProfile.childPid));
// Still waiting for the first change, no progress made here, but send a
// new request.
needToSendProgressRequest = true;
} else if (pendingProfile.lastProgressRequest <
pendingProfile.lastProgressChange) {
DEBUG_LOG("GatheringTimerCallback() - child %u: Recent change",
unsigned(pendingProfile.childPid));
// We have a recent change, progress was made.
needToSendProgressRequest = true;
progressWasMade = true;
} else {
LOG("GatheringTimerCallback() - child %u: No recent change",
unsigned(pendingProfile.childPid));
needToSendProgressRequest = true;
}
// And send a new progress request.
if (needToSendProgressRequest) {
if (!self->SendProgressRequest(pendingProfile)) {
// Failed to even send the request, consider this process gone.
self->mPendingProfiles.erase(&pendingProfile);
LOG("... Failed to send progress request");
} else {
DEBUG_LOG("... Sent progress request");
}
} else {