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 34 kB image not shown  

Quelle  ProfilerParent.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; 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 "ProfilerParent.h"

#ifdef MOZ_GECKO_PROFILER
#  include "nsProfiler.h"
#  include "platform.h"
#endif

#include "GeckoProfiler.h"
#include "ProfilerControl.h"
#include "mozilla/BaseAndGeckoProfilerDetail.h"
#include "mozilla/BaseProfilerDetail.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/DataMutex.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/ProfileBufferControlledChunkManager.h"
#include "mozilla/ProfilerBufferSize.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Unused.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

#include <utility>

namespace mozilla {

using namespace ipc;

/* static */
Endpoint<PProfilerChild> ProfilerParent::CreateForProcess(
    base::ProcessId aOtherPid) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  Endpoint<PProfilerChild> child;
#ifdef MOZ_GECKO_PROFILER
  Endpoint<PProfilerParent> parent;
  nsresult rv = PProfiler::CreateEndpoints(&parent, &child);

  if (NS_FAILED(rv)) {
    MOZ_CRASH("Failed to create top level actor for PProfiler!");
  }

  RefPtr<ProfilerParent> actor = new ProfilerParent(aOtherPid);
  if (!parent.Bind(actor)) {
    MOZ_CRASH("Failed to bind parent actor for PProfiler!");
  }

  actor->Init();
#endif

  return child;
}

#ifdef MOZ_GECKO_PROFILER

class ProfilerParentTracker;

// This class is responsible for gathering updates from chunk managers in
// different process, and request for the oldest chunks to be destroyed whenever
// the given memory limit is reached.
class ProfileBufferGlobalController final {
 public:
  explicit ProfileBufferGlobalController(size_t aMaximumBytes);

  ~ProfileBufferGlobalController();

  void HandleChildChunkManagerUpdate(
      base::ProcessId aProcessId,
      ProfileBufferControlledChunkManager::Update&& aUpdate);

  static bool IsLockedOnCurrentThread();

 private:
  // Calls aF(Json::Value&).
  template <typename F>
  void Log(F&& aF);

  static void LogUpdateChunks(Json::Value& updates, base::ProcessId aProcessId,
                              const TimeStamp& aTimeStamp, int aChunkDiff);
  void LogUpdate(base::ProcessId aProcessId,
                 const ProfileBufferControlledChunkManager::Update& aUpdate);
  void LogDeletion(base::ProcessId aProcessId, const TimeStamp& aTimeStamp);

  void HandleChunkManagerNonFinalUpdate(
      base::ProcessId aProcessId,
      ProfileBufferControlledChunkManager::Update&& aUpdate,
      ProfileBufferControlledChunkManager& aParentChunkManager);

  const size_t mMaximumBytes;

  const base::ProcessId mParentProcessId = base::GetCurrentProcId();

  struct ParentChunkManagerAndPendingUpdate {
    ProfileBufferControlledChunkManager* mChunkManager = nullptr;
    ProfileBufferControlledChunkManager::Update mPendingUpdate;
  };

  static DataMutexBase<ParentChunkManagerAndPendingUpdate,
                       baseprofiler::detail::BaseProfilerMutex>
      sParentChunkManagerAndPendingUpdate;

  size_t mUnreleasedTotalBytes = 0;

  struct PidAndBytes {
    base::ProcessId mProcessId;
    size_t mBytes;

    // For searching and sorting.
    bool operator==(base::ProcessId aSearchedProcessId) const {
      return mProcessId == aSearchedProcessId;
    }
    bool operator==(const PidAndBytes& aOther) const {
      return mProcessId == aOther.mProcessId;
    }
    bool operator<(base::ProcessId aSearchedProcessId) const {
      return mProcessId < aSearchedProcessId;
    }
    bool operator<(const PidAndBytes& aOther) const {
      return mProcessId < aOther.mProcessId;
    }
  };
  using PidAndBytesArray = nsTArray<PidAndBytes>;
  PidAndBytesArray mUnreleasedBytesByPid;

  size_t mReleasedTotalBytes = 0;

  struct TimeStampAndBytesAndPid {
    TimeStamp mTimeStamp;
    size_t mBytes;
    base::ProcessId mProcessId;

    // For searching and sorting.
    bool operator==(const TimeStampAndBytesAndPid& aOther) const {
      // Sort first by timestamps, and then by pid in rare cases with the same
      // timestamps.
      return mTimeStamp == aOther.mTimeStamp && mProcessId == aOther.mProcessId;
    }
    bool operator<(const TimeStampAndBytesAndPid& aOther) const {
      // Sort first by timestamps, and then by pid in rare cases with the same
      // timestamps.
      return mTimeStamp < aOther.mTimeStamp ||
             (MOZ_UNLIKELY(mTimeStamp == aOther.mTimeStamp) &&
              mProcessId < aOther.mProcessId);
    }
  };
  using TimeStampAndBytesAndPidArray = nsTArray<TimeStampAndBytesAndPid>;
  TimeStampAndBytesAndPidArray mReleasedChunksByTime;
};

/* static */
MOZ_RUNINIT DataMutexBase<
    ProfileBufferGlobalController::ParentChunkManagerAndPendingUpdate,
    baseprofiler::detail::BaseProfilerMutex>
    ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate{
        "ProfileBufferGlobalController::sParentChunkManagerAndPendingUpdate"};

// This singleton class tracks live ProfilerParent's (meaning there's a current
// connection with a child process).
// It also knows when the local profiler is running.
// And when both the profiler is running and at least one child is present, it
// creates a ProfileBufferGlobalController and forwards chunk updates to it.
class ProfilerParentTracker final {
 public:
  static void StartTracking(ProfilerParent* aParent);
  static void StopTracking(ProfilerParent* aParent);

  static void ProfilerStarted(uint32_t aEntries);
  static void ProfilerWillStopIfStarted();

  // Number of non-destroyed tracked ProfilerParents.
  static size_t ProfilerParentCount();

  template <typename FuncType>
  static void Enumerate(FuncType&& aIterFunc);

  template <typename FuncType>
  static void ForChild(base::ProcessId aChildPid, FuncType&& aIterFunc);

  static void ForwardChildChunkManagerUpdate(
      base::ProcessId aProcessId,
      ProfileBufferControlledChunkManager::Update&& aUpdate);

  ProfilerParentTracker();
  ~ProfilerParentTracker();

 private:
  // Get the singleton instance; Create one on the first request, unless we are
  // past XPCOMShutdownThreads, which is when it should get destroyed.
  static ProfilerParentTracker* GetInstance();

  // List of parents for currently-connected child processes.
  nsTArray<ProfilerParent*> mProfilerParents;

  // If non-0, the parent profiler is running, with this limit (in number of
  // entries.) This is needed here, because the parent profiler may start
  // running before child processes are known (e.g., startup profiling).
  uint32_t mEntries = 0;

  // When the profiler is running and there is at least one parent-child
  // connection, this is the controller that should receive chunk updates.
  Maybe<ProfileBufferGlobalController> mMaybeController;
};

template <typename F>
void ProfileBufferGlobalController::Log(F&& aF) {
  ProfilingLog::Access([&](Json::Value& aLog) {
    Json::Value& root = aLog[Json::StaticString{"bufferGlobalController"}];
    if (!root.isObject()) {
      root = Json::Value(Json::objectValue);
      root[Json::StaticString{"logBegin" TIMESTAMP_JSON_SUFFIX}] =
          ProfilingLog::Timestamp();
    }
    std::forward<F>(aF)(root);
  });
}

/* static */
void ProfileBufferGlobalController::LogUpdateChunks(Json::Value& updates,
                                                    base::ProcessId aProcessId,
                                                    const TimeStamp& aTimeStamp,
                                                    int aChunkDiff) {
  MOZ_ASSERT(updates.isArray());
  Json::Value row{Json::arrayValue};
  row.append(Json::Value{Json::UInt64(aProcessId)});
  row.append(ProfilingLog::Timestamp(aTimeStamp));
  row.append(Json::Value{Json::Int(aChunkDiff)});
  updates.append(std::move(row));
}

void ProfileBufferGlobalController::LogUpdate(
    base::ProcessId aProcessId,
    const ProfileBufferControlledChunkManager::Update& aUpdate) {
  Log([&](Json::Value& aRoot) {
    Json::Value& updates = aRoot[Json::StaticString{"updates"}];
    if (!updates.isArray()) {
      aRoot[Json::StaticString{"updatesSchema"}] =
          Json::StaticString{"0: pid, 1: chunkRelease_TSms, 3: chunkDiff"};
      updates = Json::Value{Json::arrayValue};
    }
    if (aUpdate.IsFinal()) {
      LogUpdateChunks(updates, aProcessId, TimeStamp{}, 0);
    } else if (!aUpdate.IsNotUpdate()) {
      for (const auto& chunk : aUpdate.NewlyReleasedChunksRef()) {
        LogUpdateChunks(updates, aProcessId, chunk.mDoneTimeStamp, 1);
      }
    }
  });
}

void ProfileBufferGlobalController::LogDeletion(base::ProcessId aProcessId,
                                                const TimeStamp& aTimeStamp) {
  Log([&](Json::Value& aRoot) {
    Json::Value& updates = aRoot[Json::StaticString{"updates"}];
    if (!updates.isArray()) {
      updates = Json::Value{Json::arrayValue};
    }
    LogUpdateChunks(updates, aProcessId, aTimeStamp, -1);
  });
}

ProfileBufferGlobalController::ProfileBufferGlobalController(
    size_t aMaximumBytes)
    : mMaximumBytes(aMaximumBytes) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  Log([](Json::Value& aRoot) {
    aRoot[Json::StaticString{"controllerCreationTime" TIMESTAMP_JSON_SUFFIX}] =
        ProfilingLog::Timestamp();
  });

  // This is the local chunk manager for this parent process, so updates can be
  // handled here.
  ProfileBufferControlledChunkManager* parentChunkManager =
      profiler_get_controlled_chunk_manager();

  if (NS_WARN_IF(!parentChunkManager)) {
    Log([](Json::Value& aRoot) {
      aRoot[Json::StaticString{"controllerCreationFailureReason"}] =
          "No parent chunk manager";
    });
    return;
  }

  {
    auto lockedParentChunkManagerAndPendingUpdate =
        sParentChunkManagerAndPendingUpdate.Lock();
    lockedParentChunkManagerAndPendingUpdate->mChunkManager =
        parentChunkManager;
  }

  parentChunkManager->SetUpdateCallback(
      [this](ProfileBufferControlledChunkManager::Update&& aUpdate) {
        MOZ_ASSERT(!aUpdate.IsNotUpdate(),
                   "Update callback should never be given a non-update");
        auto lockedParentChunkManagerAndPendingUpdate =
            sParentChunkManagerAndPendingUpdate.Lock();
        if (aUpdate.IsFinal()) {
          // Final update of the parent.
          // We cannot keep the chunk manager, and there's no point handling
          // updates anymore. Do some cleanup now, to free resources before
          // we're destroyed.
          lockedParentChunkManagerAndPendingUpdate->mChunkManager = nullptr;
          lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
          mUnreleasedTotalBytes = 0;
          mUnreleasedBytesByPid.Clear();
          mReleasedTotalBytes = 0;
          mReleasedChunksByTime.Clear();
          return;
        }
        if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
          // No chunk manager, ignore updates.
          return;
        }
        // Special handling of parent non-final updates:
        // These updates are coming from *this* process, and may originate from
        // scopes in any thread where any lock is held, so using other locks (to
        // e.g., dispatch tasks or send IPCs) could trigger a deadlock. Instead,
        // parent updates are stored locally and handled when the next
        // non-parent update needs handling, see HandleChildChunkManagerUpdate.
        lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Fold(
            std::move(aUpdate));
      });
}

ProfileBufferGlobalController ::~ProfileBufferGlobalController() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  // Extract the parent chunk manager (if still set).
  // This means any update after this will be ignored.
  ProfileBufferControlledChunkManager* parentChunkManager = []() {
    auto lockedParentChunkManagerAndPendingUpdate =
        sParentChunkManagerAndPendingUpdate.Lock();
    lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
    return std::exchange(
        lockedParentChunkManagerAndPendingUpdate->mChunkManager, nullptr);
  }();
  if (parentChunkManager) {
    // We had not received a final update yet, so the chunk manager is still
    // valid. Reset the callback in the chunk manager, this will immediately
    // invoke the callback with the final empty update; see handling above.
    parentChunkManager->SetUpdateCallback({});
  }
}

void ProfileBufferGlobalController::HandleChildChunkManagerUpdate(
    base::ProcessId aProcessId,
    ProfileBufferControlledChunkManager::Update&& aUpdate) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  MOZ_ASSERT(aProcessId != mParentProcessId);

  MOZ_ASSERT(!aUpdate.IsNotUpdate(),
             "HandleChildChunkManagerUpdate should not be given a non-update");

  auto lockedParentChunkManagerAndPendingUpdate =
      sParentChunkManagerAndPendingUpdate.Lock();
  if (!lockedParentChunkManagerAndPendingUpdate->mChunkManager) {
    // No chunk manager, ignore updates.
    return;
  }

  if (aUpdate.IsFinal()) {
    // Final update in a child process, remove all traces of that process.
    LogUpdate(aProcessId, aUpdate);
    size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId);
    if (index != PidAndBytesArray::NoIndex) {
      // We already have a value for this pid.
      PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index];
      mUnreleasedTotalBytes -= pidAndBytes.mBytes;
      mUnreleasedBytesByPid.RemoveElementAt(index);
    }

    size_t released = 0;
    mReleasedChunksByTime.RemoveElementsBy(
        [&released, aProcessId](const auto& chunk) {
          const bool match = chunk.mProcessId == aProcessId;
          if (match) {
            released += chunk.mBytes;
          }
          return match;
        });
    if (released != 0) {
      mReleasedTotalBytes -= released;
    }

    // Total can only have gone down, so there's no need to check the limit.
    return;
  }

  // Non-final update in child process.

  // Before handling the child update, we may have pending updates from the
  // parent, which can be processed now since we're in an IPC callback outside
  // of any profiler-related scope.
  if (!lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsNotUpdate()) {
    MOZ_ASSERT(
        !lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.IsFinal());
    HandleChunkManagerNonFinalUpdate(
        mParentProcessId,
        std::move(lockedParentChunkManagerAndPendingUpdate->mPendingUpdate),
        *lockedParentChunkManagerAndPendingUpdate->mChunkManager);
    lockedParentChunkManagerAndPendingUpdate->mPendingUpdate.Clear();
  }

  HandleChunkManagerNonFinalUpdate(
      aProcessId, std::move(aUpdate),
      *lockedParentChunkManagerAndPendingUpdate->mChunkManager);
}

/* static */
bool ProfileBufferGlobalController::IsLockedOnCurrentThread() {
  return sParentChunkManagerAndPendingUpdate.Mutex().IsLockedOnCurrentThread();
}

void ProfileBufferGlobalController::HandleChunkManagerNonFinalUpdate(
    base::ProcessId aProcessId,
    ProfileBufferControlledChunkManager::Update&& aUpdate,
    ProfileBufferControlledChunkManager& aParentChunkManager) {
  MOZ_ASSERT(!aUpdate.IsFinal());
  LogUpdate(aProcessId, aUpdate);

  size_t index = mUnreleasedBytesByPid.BinaryIndexOf(aProcessId);
  if (index != PidAndBytesArray::NoIndex) {
    // We already have a value for this pid.
    PidAndBytes& pidAndBytes = mUnreleasedBytesByPid[index];
    mUnreleasedTotalBytes =
        mUnreleasedTotalBytes - pidAndBytes.mBytes + aUpdate.UnreleasedBytes();
    pidAndBytes.mBytes = aUpdate.UnreleasedBytes();
  } else {
    // New pid.
    mUnreleasedBytesByPid.InsertElementSorted(
        PidAndBytes{aProcessId, aUpdate.UnreleasedBytes()});
    mUnreleasedTotalBytes += aUpdate.UnreleasedBytes();
  }

  size_t destroyedReleased = 0;
  if (!aUpdate.OldestDoneTimeStamp().IsNull()) {
    size_t i = 0;
    for (; i < mReleasedChunksByTime.Length(); ++i) {
      if (mReleasedChunksByTime[i].mTimeStamp >=
          aUpdate.OldestDoneTimeStamp()) {
        break;
      }
    }
    // Here, i is the index of the first item that's at or after
    // aUpdate.mOldestDoneTimeStamp, so chunks from aProcessId before that have
    // been destroyed.
    while (i != 0) {
      --i;
      const TimeStampAndBytesAndPid& item = mReleasedChunksByTime[i];
      if (item.mProcessId == aProcessId) {
        destroyedReleased += item.mBytes;
        mReleasedChunksByTime.RemoveElementAt(i);
      }
    }
  }

  size_t newlyReleased = 0;
  for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk :
       aUpdate.NewlyReleasedChunksRef()) {
    newlyReleased += chunk.mBufferBytes;
    mReleasedChunksByTime.InsertElementSorted(TimeStampAndBytesAndPid{
        chunk.mDoneTimeStamp, chunk.mBufferBytes, aProcessId});
  }

  mReleasedTotalBytes = mReleasedTotalBytes - destroyedReleased + newlyReleased;

#  ifdef DEBUG
  size_t totalReleased = 0;
  for (const TimeStampAndBytesAndPid& item : mReleasedChunksByTime) {
    totalReleased += item.mBytes;
  }
  MOZ_ASSERT(mReleasedTotalBytes == totalReleased);
#  endif  // DEBUG

  std::vector<ProfileBufferControlledChunkManager::ChunkMetadata> toDestroy;
  while (mUnreleasedTotalBytes + mReleasedTotalBytes > mMaximumBytes &&
         !mReleasedChunksByTime.IsEmpty()) {
    // We have reached the global memory limit, and there *are* released chunks
    // that can be destroyed. Start with the first one, which is the oldest.
    const TimeStampAndBytesAndPid& oldest = mReleasedChunksByTime[0];
    LogDeletion(oldest.mProcessId, oldest.mTimeStamp);
    mReleasedTotalBytes -= oldest.mBytes;
    if (oldest.mProcessId == mParentProcessId) {
      aParentChunkManager.DestroyChunksAtOrBefore(oldest.mTimeStamp);
    } else {
      ProfilerParentTracker::ForChild(
          oldest.mProcessId,
          [timestamp = oldest.mTimeStamp](ProfilerParent* profilerParent) {
            Unused << profilerParent->SendDestroyReleasedChunksAtOrBefore(
                timestamp);
          });
    }
    mReleasedChunksByTime.RemoveElementAt(0);
  }
}

/* static */
ProfilerParentTracker* ProfilerParentTracker::GetInstance() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  // The main instance pointer, it will be initialized at most once, before
  // XPCOMShutdownThreads.
  static StaticAutoPtr<ProfilerParentTracker> instance;
  if (MOZ_UNLIKELY(!instance)) {
    if (PastShutdownPhase(ShutdownPhase::XPCOMShutdownThreads)) {
      return nullptr;
    }

    instance = new ProfilerParentTracker();

    // The tracker should get destroyed before threads are shutdown, because its
    // destruction closes extant channels, which could trigger promise
    // rejections that need to be dispatched to other threads.
    ClearOnShutdown(&instance, ShutdownPhase::XPCOMShutdownThreads);
  }

  return instance.get();
}

/* static */
void ProfilerParentTracker::StartTracking(ProfilerParent* aProfilerParent) {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker) {
    return;
  }

  if (tracker->mMaybeController.isNothing() && tracker->mEntries != 0) {
    // There is no controller yet, but the profiler has started.
    // Since we're adding a ProfilerParent, it's a good time to start
    // controlling the global memory usage of the profiler.
    // (And this helps delay the Controller startup, because the parent profiler
    // can start *very* early in the process, when some resources like threads
    // are not ready yet.)
    tracker->mMaybeController.emplace(size_t(tracker->mEntries) *
                                      scBytesPerEntry);
  }

  tracker->mProfilerParents.AppendElement(aProfilerParent);
}

/* static */
void ProfilerParentTracker::StopTracking(ProfilerParent* aParent) {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker) {
    return;
  }

  tracker->mProfilerParents.RemoveElement(aParent);
}

/* static */
void ProfilerParentTracker::ProfilerStarted(uint32_t aEntries) {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker) {
    return;
  }

  tracker->mEntries = ClampToAllowedEntries(aEntries);

  if (tracker->mMaybeController.isNothing() &&
      !tracker->mProfilerParents.IsEmpty()) {
    // We are already tracking child processes, so it's a good time to start
    // controlling the global memory usage of the profiler.
    tracker->mMaybeController.emplace(size_t(tracker->mEntries) *
                                      scBytesPerEntry);
  }
}

/* static */
void ProfilerParentTracker::ProfilerWillStopIfStarted() {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker) {
    return;
  }

  tracker->mEntries = 0;
  tracker->mMaybeController = Nothing{};
}

/* static */
size_t ProfilerParentTracker::ProfilerParentCount() {
  size_t count = 0;
  ProfilerParentTracker* tracker = GetInstance();
  if (tracker) {
    for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
      if (!profilerParent->mDestroyed) {
        ++count;
      }
    }
  }
  return count;
}

template <typename FuncType>
/* static */
void ProfilerParentTracker::Enumerate(FuncType&& aIterFunc) {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker) {
    return;
  }

  for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
    if (!profilerParent->mDestroyed) {
      aIterFunc(profilerParent);
    }
  }
}

template <typename FuncType>
/* static */
void ProfilerParentTracker::ForChild(base::ProcessId aChildPid,
                                     FuncType&& aIterFunc) {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker) {
    return;
  }

  for (ProfilerParent* profilerParent : tracker->mProfilerParents) {
    if (profilerParent->mChildPid == aChildPid) {
      if (!profilerParent->mDestroyed) {
        std::forward<FuncType>(aIterFunc)(profilerParent);
      }
      return;
    }
  }
}

/* static */
void ProfilerParentTracker::ForwardChildChunkManagerUpdate(
    base::ProcessId aProcessId,
    ProfileBufferControlledChunkManager::Update&& aUpdate) {
  ProfilerParentTracker* tracker = GetInstance();
  if (!tracker || tracker->mMaybeController.isNothing()) {
    return;
  }

  MOZ_ASSERT(!aUpdate.IsNotUpdate(),
             "No process should ever send a non-update");
  tracker->mMaybeController->HandleChildChunkManagerUpdate(aProcessId,
                                                           std::move(aUpdate));
}

ProfilerParentTracker::ProfilerParentTracker() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  MOZ_COUNT_CTOR(ProfilerParentTracker);
}

ProfilerParentTracker::~ProfilerParentTracker() {
  // This destructor should only be called on the main thread.
  MOZ_RELEASE_ASSERT(NS_IsMainThread() ||
                     // OR we're not on the main thread (including if we are
                     // past the end of `main()`), which is fine *if* there are
                     // no ProfilerParent's still registered, in which case
                     // nothing else will happen in this destructor anyway.
                     // See bug 1713971 for more information.
                     mProfilerParents.IsEmpty());
  MOZ_COUNT_DTOR(ProfilerParentTracker);

  // Close the channels of any profiler parents that haven't been destroyed.
  for (ProfilerParent* profilerParent : mProfilerParents.Clone()) {
    if (!profilerParent->mDestroyed) {
      // Keep the object alive until the call to Close() has completed.
      // Close() will trigger a call to DeallocPProfilerParent.
      RefPtr<ProfilerParent> actor = profilerParent;
      actor->Close();
    }
  }
}

ProfilerParent::ProfilerParent(base::ProcessId aChildPid)
    : mChildPid(aChildPid), mDestroyed(false) {
  MOZ_COUNT_CTOR(ProfilerParent);

  MOZ_RELEASE_ASSERT(NS_IsMainThread());
}

void ProfilerParent::Init() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());

  ProfilerParentTracker::StartTracking(this);

  // We propagated the profiler state from the parent process to the child
  // process through MOZ_PROFILER_STARTUP* environment variables.
  // However, the profiler state might have changed in this process since then,
  // and now that an active communication channel has been established with the
  // child process, it's a good time to sync up the two profilers again.

  int entries = 0;
  Maybe<double> duration = Nothing();
  double interval = 0;
  mozilla::Vector<const char*> filters;
  uint32_t features;
  uint64_t activeTabID;
  profiler_get_start_params(&entries, &duration, &interval, &features, &filters,
                            &activeTabID);

  if (entries != 0) {
    ProfilerInitParams ipcParams;
    ipcParams.enabled() = true;
    ipcParams.entries() = entries;
    ipcParams.duration() = duration;
    ipcParams.interval() = interval;
    ipcParams.features() = features;
    ipcParams.activeTabID() = activeTabID;

    // If the filters exclude our pid, make sure it's stopped, otherwise
    // continue with starting it.
    if (!profiler::detail::FiltersExcludePid(
            filters, ProfilerProcessId::FromNumber(mChildPid))) {
      ipcParams.filters().SetCapacity(filters.length());
      for (const char* filter : filters) {
        ipcParams.filters().AppendElement(filter);
      }

      Unused << SendEnsureStarted(ipcParams);
      RequestChunkManagerUpdate();
      return;
    }
  }

  Unused << SendStop();
}
#endif  // MOZ_GECKO_PROFILER

ProfilerParent::~ProfilerParent() {
  MOZ_COUNT_DTOR(ProfilerParent);

  MOZ_RELEASE_ASSERT(NS_IsMainThread());
#ifdef MOZ_GECKO_PROFILER
  ProfilerParentTracker::StopTracking(this);
#endif
}

#ifdef MOZ_GECKO_PROFILER
/* static */
nsTArray<ProfilerParent::SingleProcessProfilePromiseAndChildPid>
ProfilerParent::GatherProfiles() {
  nsTArray<SingleProcessProfilePromiseAndChildPid> results;
  if (!NS_IsMainThread()) {
    return results;
  }

  results.SetCapacity(ProfilerParentTracker::ProfilerParentCount());
  ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) {
    results.AppendElement(SingleProcessProfilePromiseAndChildPid{
        profilerParent->SendGatherProfile(), profilerParent->mChildPid});
  });
  return results;
}

/* static */
RefPtr<ProfilerParent::SingleProcessProgressPromise>
ProfilerParent::RequestGatherProfileProgress(base::ProcessId aChildPid) {
  RefPtr<SingleProcessProgressPromise> promise;
  ProfilerParentTracker::ForChild(
      aChildPid, [&promise](ProfilerParent* profilerParent) {
        promise = profilerParent->SendGetGatherProfileProgress();
      });
  return promise;
}

// Magic value for ProfileBufferChunkManagerUpdate::unreleasedBytes meaning
// that this is a final update from a child.
constexpr static uint64_t scUpdateUnreleasedBytesFINAL = uint64_t(-1);

/* static */
ProfileBufferChunkManagerUpdate ProfilerParent::MakeFinalUpdate() {
  return ProfileBufferChunkManagerUpdate{
      uint64_t(scUpdateUnreleasedBytesFINAL), 0, TimeStamp{},
      nsTArray<ProfileBufferChunkMetadata>{}};
}

/* static */
bool ProfilerParent::IsLockedOnCurrentThread() {
  return ProfileBufferGlobalController::IsLockedOnCurrentThread();
}

void ProfilerParent::RequestChunkManagerUpdate() {
  if (mDestroyed) {
    return;
  }

  RefPtr<AwaitNextChunkManagerUpdatePromise> updatePromise =
      SendAwaitNextChunkManagerUpdate();
  updatePromise->Then(
      GetMainThreadSerialEventTarget(), __func__,
      [self = RefPtr<ProfilerParent>(this)](
          const ProfileBufferChunkManagerUpdate& aUpdate) {
        if (aUpdate.unreleasedBytes() == scUpdateUnreleasedBytesFINAL) {
          // Special value meaning it's the final update from that child.
          ProfilerParentTracker::ForwardChildChunkManagerUpdate(
              self->mChildPid,
              ProfileBufferControlledChunkManager::Update(nullptr));
        } else {
          // Not the final update, translate it.
          std::vector<ProfileBufferControlledChunkManager::ChunkMetadata>
              chunks;
          if (!aUpdate.newlyReleasedChunks().IsEmpty()) {
            chunks.reserve(aUpdate.newlyReleasedChunks().Length());
            for (const ProfileBufferChunkMetadata& chunk :
                 aUpdate.newlyReleasedChunks()) {
              chunks.emplace_back(chunk.doneTimeStamp(), chunk.bufferBytes());
            }
          }
          // Let the tracker handle it.
          ProfilerParentTracker::ForwardChildChunkManagerUpdate(
              self->mChildPid,
              ProfileBufferControlledChunkManager::Update(
                  aUpdate.unreleasedBytes(), aUpdate.releasedBytes(),
                  aUpdate.oldestDoneTimeStamp(), std::move(chunks)));
          // This was not a final update, so start a new request.
          self->RequestChunkManagerUpdate();
        }
      },
      [self = RefPtr<ProfilerParent>(this)](
          mozilla::ipc::ResponseRejectReason aReason) {
        // Rejection could be for a number of reasons, assume the child will
        // not respond anymore, so we pretend we received a final update.
        ProfilerParentTracker::ForwardChildChunkManagerUpdate(
            self->mChildPid,
            ProfileBufferControlledChunkManager::Update(nullptr));
      });
}

// Ref-counted class that resolves a promise on destruction.
// Usage:
// RefPtr<GenericPromise> f() {
//   return PromiseResolverOnDestruction::RunTask(
//     [](RefPtr<PromiseResolverOnDestruction> aPromiseResolver){
//       // Give *copies* of aPromiseResolver to asynchronous sub-tasks, the
//       // last remaining RefPtr destruction will resolve the promise.
//     });
// }
class PromiseResolverOnDestruction {
 public:
  NS_INLINE_DECL_REFCOUNTING(PromiseResolverOnDestruction)

  template <typename TaskFunction>
  static RefPtr<GenericPromise> RunTask(TaskFunction&& aTaskFunction) {
    RefPtr<PromiseResolverOnDestruction> promiseResolver =
        new PromiseResolverOnDestruction();
    RefPtr<GenericPromise> promise =
        promiseResolver->mPromiseHolder.Ensure(__func__);
    std::forward<TaskFunction>(aTaskFunction)(std::move(promiseResolver));
    return promise;
  }

 private:
  PromiseResolverOnDestruction() = default;

  ~PromiseResolverOnDestruction() {
    mPromiseHolder.ResolveIfExists(/* unused */ true, __func__);
  }

  MozPromiseHolder<GenericPromise> mPromiseHolder;
};

// Given a ProfilerParentSendFunction: (ProfilerParent*) -> some MozPromise,
// run the function on all live ProfilerParents and return a GenericPromise, and
// when their promise gets resolve, resolve our Generic promise.
template <typename ProfilerParentSendFunction>
static RefPtr<GenericPromise> SendAndConvertPromise(
    ProfilerParentSendFunction&& aProfilerParentSendFunction) {
  if (!NS_IsMainThread()) {
    return GenericPromise::CreateAndResolve(/* unused */ true, __func__);
  }

  return PromiseResolverOnDestruction::RunTask(
      [&](RefPtr<PromiseResolverOnDestruction> aPromiseResolver) {
        ProfilerParentTracker::Enumerate([&](ProfilerParent* profilerParent) {
          std::forward<ProfilerParentSendFunction>(aProfilerParentSendFunction)(
              profilerParent)
              ->Then(GetMainThreadSerialEventTarget(), __func__,
                     [aPromiseResolver](
                         typename std::remove_reference_t<
                             decltype(*std::forward<ProfilerParentSendFunction>(
                                 aProfilerParentSendFunction)(
                                 profilerParent))>::ResolveOrRejectValue&&) {
                       // Whatever the resolution/rejection is, do nothing.
                       // The lambda aPromiseResolver ref-count will decrease.
                     });
        });
      });
}

/* static */
RefPtr<GenericPromise> ProfilerParent::ProfilerStarted(
    nsIProfilerStartParams* aParams) {
  if (!NS_IsMainThread()) {
    return GenericPromise::CreateAndResolve(/* unused */ true, __func__);
  }

  ProfilerInitParams ipcParams;
  double duration;
  ipcParams.enabled() = true;
  aParams->GetEntries(&ipcParams.entries());
  aParams->GetDuration(&duration);
  if (duration > 0.0) {
    ipcParams.duration() = Some(duration);
  } else {
    ipcParams.duration() = Nothing();
  }
  aParams->GetInterval(&ipcParams.interval());
  aParams->GetFeatures(&ipcParams.features());
  ipcParams.filters() = aParams->GetFilters().Clone();
  // We need filters as a Span<const char*> to test pids in the lambda below.
  auto filtersCStrings = nsTArray<const char*>{aParams->GetFilters().Length()};
  for (const auto& filter : aParams->GetFilters()) {
    filtersCStrings.AppendElement(filter.Data());
  }
  aParams->GetActiveTabID(&ipcParams.activeTabID());

  ProfilerParentTracker::ProfilerStarted(ipcParams.entries());

  return SendAndConvertPromise([&](ProfilerParent* profilerParent) {
    if (profiler::detail::FiltersExcludePid(
            filtersCStrings,
            ProfilerProcessId::FromNumber(profilerParent->mChildPid))) {
      // This pid is excluded, don't start the profiler at all.
      return PProfilerParent::StartPromise::CreateAndResolve(/* unused */ true,
                                                             __func__);
    }
    auto promise = profilerParent->SendStart(ipcParams);
    profilerParent->RequestChunkManagerUpdate();
    return promise;
  });
}

/* static */
void ProfilerParent::ProfilerWillStopIfStarted() {
  if (!NS_IsMainThread()) {
    return;
  }

  ProfilerParentTracker::ProfilerWillStopIfStarted();
}

/* static */
RefPtr<GenericPromise> ProfilerParent::ProfilerStopped() {
  return SendAndConvertPromise([](ProfilerParent* profilerParent) {
    return profilerParent->SendStop();
  });
}

/* static */
RefPtr<GenericPromise> ProfilerParent::ProfilerPaused() {
  return SendAndConvertPromise([](ProfilerParent* profilerParent) {
    return profilerParent->SendPause();
  });
}

/* static */
RefPtr<GenericPromise> ProfilerParent::ProfilerResumed() {
  return SendAndConvertPromise([](ProfilerParent* profilerParent) {
    return profilerParent->SendResume();
  });
}

/* static */
RefPtr<GenericPromise> ProfilerParent::ProfilerPausedSampling() {
  return SendAndConvertPromise([](ProfilerParent* profilerParent) {
    return profilerParent->SendPauseSampling();
  });
}

/* static */
RefPtr<GenericPromise> ProfilerParent::ProfilerResumedSampling() {
  return SendAndConvertPromise([](ProfilerParent* profilerParent) {
    return profilerParent->SendResumeSampling();
  });
}

/* static */
void ProfilerParent::ClearAllPages() {
  if (!NS_IsMainThread()) {
    return;
  }

  ProfilerParentTracker::Enumerate([](ProfilerParent* profilerParent) {
    Unused << profilerParent->SendClearAllPages();
  });
}

/* static */
RefPtr<GenericPromise> ProfilerParent::WaitOnePeriodicSampling() {
  return SendAndConvertPromise([](ProfilerParent* profilerParent) {
    return profilerParent->SendWaitOnePeriodicSampling();
  });
}

void ProfilerParent::ActorDestroy(ActorDestroyReason aActorDestroyReason) {
  MOZ_RELEASE_ASSERT(NS_IsMainThread());
  mDestroyed = true;
}

#endif

}  // namespace mozilla

Messung V0.5
C=88 H=95 G=91

¤ Dauer der Verarbeitung: 0.16 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.