Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/tools/profiler/gecko/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 53 kB image not shown  

Quelle  nsProfiler.cpp   Sprache: C

 
/* -*- 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 "SharedLibraries.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 {
      DEBUG_LOG("... No progress request");
    }
  }

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

  // Not finished yet.

  if (progressWasMade) {
    // We made some progress, just restart the timer.
    DEBUG_LOG("GatheringTimerCallback() - Progress made, restart timer");
    self->RestartGatheringTimer();
    return;
  }

  DEBUG_LOG("GatheringTimerCallback() - Timeout!");
  self->mGatheringTimer = nullptr;
  if (!profiler_is_active() || !self->mGathering) {
    // Not gathering anymore.
    return;
  }
  self->LogEvent([&](Json::Value& aEvent) {
    aEvent.append(Json::StaticString{
        "No progress made recently, giving up; pending pids:"});
    for (const PendingProfile& pendingProfile : self->mPendingProfiles) {
      aEvent.append(Json::Value::UInt64(pendingProfile.childPid));
    }
  });
  NS_WARNING("Profiler failed to gather profiles from all sub-processes");
  // We have really reached a timeout while gathering, finish now.
  // TODO: Add information about missing processes.
  self->FinishGathering();
}

void nsProfiler::RestartGatheringTimer() {
  if (mGatheringTimer) {
    uint32_t delayMs = 0;
    const nsresult r = mGatheringTimer->GetDelay(&delayMs);
    mGatheringTimer->Cancel();
    if (NS_FAILED(r) || delayMs == 0 ||
        NS_FAILED(mGatheringTimer->InitWithNamedFuncCallback(
            GatheringTimerCallback, this, delayMs,
            nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
            "nsProfilerGatheringTimer"))) {
      // Can't restart the timer, so we can't wait any longer.
      FinishGathering();
    }
  }
}

nsProfiler::PendingProfile* nsProfiler::GetPendingProfile(
    base::ProcessId aChildPid) {
  for (PendingProfile& pendingProfile : mPendingProfiles) {
    if (pendingProfile.childPid == aChildPid) {
      return &pendingProfile;
    }
  }
  return nullptr;
}

void nsProfiler::GatheredOOPProfile(
    base::ProcessId aChildPid, const nsACString& aProfile,
    mozilla::Maybe<ProfileGenerationAdditionalInformation>&&
        aAdditionalInformation) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  if (!profiler_is_active()) {
    return;
  }

  if (!mGathering) {
    // If we're not actively gathering, then we don't actually care that we
    // gathered a profile here. This can happen for processes that exit while
    // profiling.
    return;
  }

  MOZ_RELEASE_ASSERT(mWriter.isSome(),
                     "Should always have a writer if mGathering is true");

  // Combine all the additional information into a single struct.
  if (aAdditionalInformation.isSome()) {
    mProfileGenerationAdditionalInformation->Append(
        std::move(*aAdditionalInformation));
  }

  if (!aProfile.IsEmpty()) {
    if (mWriter->ChunkedWriteFunc().Length() + aProfile.Length() <
        scLengthAccumulationThreshold) {
      // TODO: Remove PromiseFlatCString, see bug 1657033.
      mWriter->Splice(PromiseFlatCString(aProfile));
    } else {
      LogEvent([&](Json::Value& aEvent) {
        aEvent.append(
            Json::StaticString{"Discarded child profile that would make the "
                               "full profile too big, pid and size:"});
        aEvent.append(Json::Value::UInt64(aChildPid));
        aEvent.append(Json::Value::UInt64{aProfile.Length()});
      });
    }
  }

  if (PendingProfile* pendingProfile = GetPendingProfile(aChildPid);
      pendingProfile) {
    mPendingProfiles.erase(pendingProfile);

    if (mPendingProfiles.empty()) {
      // We've got all of the async profiles now. Let's finish off the profile
      // and resolve the Promise.
      FinishGathering();
    }
  }

  // Not finished yet, restart the timer to let any remaining child enough time
  // to do their profile-streaming.
  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 mozilla::ProfileAndAdditionalInformation& aResult) {
            FallibleTArray<uint8_t> outBuff;
            nsresult result = CompressString(aResult.mProfile, 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());

  if (mGathering) {
    // If we're already gathering, return a rejected promise - this isn't
    // going to end well.
    return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
  }

  mGathering = true;
  mGatheringLog = mozilla::MakeUnique<Json::Value>(Json::objectValue);
  (*mGatheringLog)[Json::StaticString{
      "profileGatheringLogBegin" TIMESTAMP_JSON_SUFFIX}] =
      ProfilingLog::Timestamp();

  if (mGatheringTimer) {
    mGatheringTimer->Cancel();
    mGatheringTimer = nullptr;
  }

  // Start building shared library info starting from the current process.
  mProfileGenerationAdditionalInformation.emplace(
      SharedLibraryInfo::GetInfoForSelf());

  // Request profiles from the other processes. This will trigger asynchronous
  // calls to ProfileGatherer::GatheredOOPProfile as the profiles arrive.
  //
  // Do this before the call to profiler_stream_json_for_this_process() because
  // that call is slow and we want to let the other processes grab their
  // profiles as soon as possible.
  nsTArray<ProfilerParent::SingleProcessProfilePromiseAndChildPid> profiles =
      ProfilerParent::GatherProfiles();

  MOZ_ASSERT(mPendingProfiles.empty());
  if (!mPendingProfiles.reserve(profiles.Length())) {
    ResetGathering(NS_ERROR_OUT_OF_MEMORY);
    return GatheringPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
  }

  mFailureLatchSource.emplace();
  mWriter.emplace(*mFailureLatchSource);

  UniquePtr<ProfilerCodeAddressService> service =
      profiler_code_address_service_for_presymbolication();

  // Start building up the JSON result and grab the profile from this process.
  mWriter->Start();
  auto rv = profiler_stream_json_for_this_process(*mWriter, aSinceTime,
                                                  /* aIsShuttingDown */ false,
                                                  service.get());
  if (rv.isErr()) {
    // The profiler is inactive. This either means that it was inactive even
    // at the time that ProfileGatherer::Start() was called, or that it was
    // stopped on a different thread since that call. Either way, we need to
    // reject the promise and stop gathering.
    ResetGathering(NS_ERROR_NOT_AVAILABLE);
    return GatheringPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
  }

  LogEvent([&](Json::Value& aEvent) {
    aEvent.append(
        Json::StaticString{"Generated parent process profile, size:"});
    aEvent.append(Json::Value::UInt64{mWriter->ChunkedWriteFunc().Length()});
  });

  mWriter->StartArrayProperty("processes");

  // If we have any process exit profiles, add them immediately.
  if (Vector<nsCString> exitProfiles = profiler_move_exit_profiles();
      !exitProfiles.empty()) {
    for (auto& exitProfile : exitProfiles) {
      if (!exitProfile.IsEmpty()) {
        if (exitProfile[0] == '*') {
          LogEvent([&](Json::Value& aEvent) {
            aEvent.append(
                Json::StaticString{"Exit non-profile with error message:"});
            aEvent.append(exitProfile.Data() + 1);
          });
        } else if (mWriter->ChunkedWriteFunc().Length() + exitProfile.Length() <
                   scLengthAccumulationThreshold) {
          mWriter->Splice(exitProfile);
          LogEvent([&](Json::Value& aEvent) {
            aEvent.append(Json::StaticString{"Added exit profile with size:"});
            aEvent.append(Json::Value::UInt64{exitProfile.Length()});
          });
        } else {
          LogEvent([&](Json::Value& aEvent) {
            aEvent.append(
                Json::StaticString{"Discarded an exit profile that would make "
                                   "the full profile too big, size:"});
            aEvent.append(Json::Value::UInt64{exitProfile.Length()});
          });
        }
      }
    }

    LogEvent([&](Json::Value& aEvent) {
      aEvent.append(Json::StaticString{
          "Processed all exit profiles, total size so far:"});
      aEvent.append(Json::Value::UInt64{mWriter->ChunkedWriteFunc().Length()});
    });
  } else {
    // There are no pending profiles, we're already done.
    LogEventLiteralString("No exit profiles.");
  }

  mPromiseHolder.emplace();
  RefPtr<GatheringPromise> promise = mPromiseHolder->Ensure(__func__);

  // Keep the array property "processes" and the root object in mWriter open
  // until FinishGathering() is called. As profiles from the other processes
  // come in, they will be inserted and end up in the right spot.
  // FinishGathering() will close the array and the root object.

  if (!profiles.IsEmpty()) {
    // There *are* pending profiles, let's add handlers for their promises.

    // This timeout value is used to monitor progress while gathering child
    // profiles. The timer will be restarted after we receive a response with
    // any progress.
    constexpr uint32_t cMinChildTimeoutS = 1u;  // 1 second minimum and default.
    constexpr uint32_t cMaxChildTimeoutS = 60u;  // 1 minute max.
    uint32_t childTimeoutS = Preferences::GetUint(
        "devtools.performance.recording.child.timeout_s", cMinChildTimeoutS);
    if (childTimeoutS < cMinChildTimeoutS) {
      childTimeoutS = cMinChildTimeoutS;
    } else if (childTimeoutS > cMaxChildTimeoutS) {
      childTimeoutS = cMaxChildTimeoutS;
    }
    const uint32_t childTimeoutMs = childTimeoutS * PR_MSEC_PER_SEC;
    Unused << NS_NewTimerWithFuncCallback(
        getter_AddRefs(mGatheringTimer), GatheringTimerCallback, this,
        childTimeoutMs, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
        "nsProfilerGatheringTimer", GetMainThreadSerialEventTarget());

    MOZ_ASSERT(mPendingProfiles.capacity() >= profiles.Length());
    for (const auto& profile : profiles) {
      mPendingProfiles.infallibleAppend(PendingProfile{profile.childPid});
      LogEvent([&](Json::Value& aEvent) {
        aEvent.append(Json::StaticString{"Waiting for pending profile, pid:"});
        aEvent.append(Json::Value::UInt64(profile.childPid));
      });
      profile.profilePromise->Then(
          GetMainThreadSerialEventTarget(), __func__,
          [self = RefPtr<nsProfiler>(this), childPid = profile.childPid](
              IPCProfileAndAdditionalInformation&& aResult) {
            PendingProfile* pendingProfile = self->GetPendingProfile(childPid);
            mozilla::ipc::Shmem profileShmem = aResult.profileShmem();
            LOG("GatherProfile(%u) response: %u bytes (%u were pending, %s %u)",
                unsigned(childPid), unsigned(profileShmem.Size<char>()),
                unsigned(self->mPendingProfiles.length()),
                pendingProfile ? "including" : "excluding"unsigned(childPid));
            if (profileShmem.IsReadable()) {
              self->LogEvent([&](Json::Value& aEvent) {
                aEvent.append(
                    Json::StaticString{"Got profile from pid, with size:"});
                aEvent.append(Json::Value::UInt64(childPid));
                aEvent.append(Json::Value::UInt64{profileShmem.Size<char>()});
              });
              const nsDependentCSubstring profileString(
                  profileShmem.get<char>(), profileShmem.Size<char>() - 1);
              if (profileString.IsEmpty() || profileString[0] != '*') {
                self->GatheredOOPProfile(
                    childPid, profileString,
                    std::move(aResult.additionalInformation()));
              } else {
                self->LogEvent([&](Json::Value& aEvent) {
                  aEvent.append(Json::StaticString{
                      "Child non-profile from pid, with error message:"});
                  aEvent.append(Json::Value::UInt64(childPid));
                  aEvent.append(profileString.Data() + 1);
                });
                self->GatheredOOPProfile(childPid, ""_ns, Nothing());
              }
            } else {
              // This can happen if the child failed to allocate
              // the Shmem (or maliciously sent an invalid Shmem).
              self->LogEvent([&](Json::Value& aEvent) {
                aEvent.append(Json::StaticString{"Got failure from pid:"});
                aEvent.append(Json::Value::UInt64(childPid));
              });
              self->GatheredOOPProfile(childPid, ""_ns, Nothing());
            }
          },
          [self = RefPtr<nsProfiler>(this),
           childPid = profile.childPid](ipc::ResponseRejectReason&& aReason) {
            PendingProfile* pendingProfile = self->GetPendingProfile(childPid);
            LOG("GatherProfile(%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 rejection from pid, with reason:"});
              aEvent.append(Json::Value::UInt64(childPid));
              aEvent.append(Json::Value::UInt{static_cast<unsigned>(aReason)});
            });
            self->GatheredOOPProfile(childPid, ""_ns, Nothing());
          });
    }
  } else {
    // There are no pending profiles, we're already done.
    LogEventLiteralString("No pending child profiles.");
    FinishGathering();
  }

  return promise;
}

RefPtr<nsProfiler::SymbolTablePromise> nsProfiler::GetSymbolTableMozPromise(
    const nsACString& aDebugPath, const nsACString& aBreakpadID) {
  MozPromiseHolder<SymbolTablePromise> promiseHolder;
  RefPtr<SymbolTablePromise> promise = promiseHolder.Ensure(__func__);

  if (!mSymbolTableThread) {
    nsresult rv = NS_NewNamedThread("ProfSymbolTable",
                                    getter_AddRefs(mSymbolTableThread));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      promiseHolder.Reject(NS_ERROR_FAILURE, __func__);
      return promise;
    }
  }

  nsresult rv = mSymbolTableThread->Dispatch(NS_NewRunnableFunction(
      "nsProfiler::GetSymbolTableMozPromise runnable on ProfSymbolTable thread",
      [promiseHolder = std::move(promiseHolder),
       debugPath = nsCString(aDebugPath),
       breakpadID = nsCString(aBreakpadID)]() mutable {
        AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("profiler_get_symbol_table",
                                              OTHER, debugPath);
        SymbolTable symbolTable;
        bool succeeded = profiler_get_symbol_table(
            debugPath.get(), breakpadID.get(), &symbolTable);
        if (succeeded) {
          promiseHolder.Resolve(std::move(symbolTable), __func__);
        } else {
          promiseHolder.Reject(NS_ERROR_FAILURE, __func__);
        }
      }));

  if (NS_WARN_IF(NS_FAILED(rv))) {
    // Get-symbol task was not dispatched and therefore won't fulfill the
    // promise, we must reject the promise now.
    promiseHolder.Reject(NS_ERROR_FAILURE, __func__);
  }

  return promise;
}

void nsProfiler::FinishGathering() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  MOZ_RELEASE_ASSERT(mWriter.isSome());
  MOZ_RELEASE_ASSERT(mPromiseHolder.isSome());
  MOZ_RELEASE_ASSERT(mProfileGenerationAdditionalInformation.isSome());

  // Close the "processes" array property.
  mWriter->EndArray();

  if (mGatheringLog) {
    LogEvent([&](Json::Value& aEvent) {
      aEvent.append(Json::StaticString{"Finished gathering, total size:"});
      aEvent.append(Json::Value::UInt64{mWriter->ChunkedWriteFunc().Length()});
    });
    (*mGatheringLog)[Json::StaticString{
        "profileGatheringLogEnd" TIMESTAMP_JSON_SUFFIX}] =
        ProfilingLog::Timestamp();
    mWriter->StartObjectProperty("profileGatheringLog");
    {
      nsAutoCString pid;
      pid.AppendInt(int64_t(profiler_current_process_id().ToNumber()));
      Json::String logString = ToCompactString(*mGatheringLog);
      mGatheringLog = nullptr;
      mWriter->SplicedJSONProperty(pid, logString);
    }
    mWriter->EndObject();
  }

  // Close the root object of the generated JSON.
  mWriter->End();

  if (const char* failure = mWriter->GetFailure(); failure) {
#ifndef ANDROID
    fprintf(stderr, "JSON generation failure: %s", failure);
#else
    __android_log_print(ANDROID_LOG_INFO, "GeckoProfiler",
                        "JSON generation failure: %s", failure);
#endif
    NS_WARNING("Error during JSON generation, probably OOM.");
    ResetGathering(NS_ERROR_OUT_OF_MEMORY);
    return;
  }

  // And try to resolve the promise with the profile JSON.
  const size_t len = mWriter->ChunkedWriteFunc().Length();
  if (len >= scLengthMax) {
    NS_WARNING("Profile JSON is too big to fit in a string.");
    ResetGathering(NS_ERROR_FILE_TOO_BIG);
    return;
  }

  nsCString result;
  if (!result.SetLength(len, fallible)) {
    NS_WARNING("Cannot allocate a string for the Profile JSON.");
    ResetGathering(NS_ERROR_OUT_OF_MEMORY);
    return;
  }
  MOZ_ASSERT(*(result.Data() + len) == '\0',
             "We expected a null at the end of the string buffer, to be "
             "rewritten by CopyDataIntoLazilyAllocatedBuffer");

  charconst resultBeginWriting = result.BeginWriting();
  if (!resultBeginWriting) {
    NS_WARNING("Cannot access the string to write the Profile JSON.");
    ResetGathering(NS_ERROR_CACHE_WRITE_ACCESS_DENIED);
    return;
  }

  // Here, we have enough space reserved in `result`, starting at
  // `resultBeginWriting`, copy the JSON profile there.
  if (!mWriter->ChunkedWriteFunc().CopyDataIntoLazilyAllocatedBuffer(
          [&](size_t aBufferLen) -> char* {
            MOZ_RELEASE_ASSERT(aBufferLen == len + 1);
            return resultBeginWriting;
          })) {
    NS_WARNING("Could not copy profile JSON, probably OOM.");
    ResetGathering(NS_ERROR_FILE_TOO_BIG);
    return;
  }
  MOZ_ASSERT(*(result.Data() + len) == '\0',
             "We still expected a null at the end of the string buffer");

  mProfileGenerationAdditionalInformation->FinishGathering();
  mPromiseHolder->Resolve(
      ProfileAndAdditionalInformation{
          std::move(result),
          std::move(*mProfileGenerationAdditionalInformation)},
      __func__);

  ResetGathering(NS_ERROR_UNEXPECTED);
}

void nsProfiler::ResetGathering(nsresult aPromiseRejectionIfPending) {
  // If we have an unfulfilled Promise in flight, we should reject it before
  // destroying the promise holder.
  if (mPromiseHolder.isSome()) {
    mPromiseHolder->RejectIfExists(aPromiseRejectionIfPending, __func__);
    mPromiseHolder.reset();
  }
  mPendingProfiles.clearAndFree();
  mGathering = false;
  mGatheringLog = nullptr;
  if (mGatheringTimer) {
    mGatheringTimer->Cancel();
    mGatheringTimer = nullptr;
  }
  mWriter.reset();
  mFailureLatchSource.reset();
  mProfileGenerationAdditionalInformation.reset();
}

Messung V0.5
C=93 H=97 G=94

¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.