Global Metrics

path: .metrics.cognitive.average
old: 0.0
new: 0.9230769230769232

path: .metrics.cognitive.sum
old: 0.0
new: 24.0

path: .metrics.nom.total
old: 5.0
new: 26.0

path: .metrics.nom.closures
old: 0.0
new: 3.0

path: .metrics.nom.functions
old: 5.0
new: 23.0

path: .metrics.cyclomatic.sum
old: 14.0
new: 49.0

path: .metrics.cyclomatic.average
old: 1.0
new: 1.96

path: .metrics.nexits.average
old: 0.2
new: 0.8461538461538461

path: .metrics.nexits.sum
old: 1.0
new: 22.0

path: .metrics.loc.ploc
old: 100.0
new: 224.0

path: .metrics.loc.cloc
old: 93.0
new: 36.0

path: .metrics.loc.lloc
old: 40.0
new: 91.0

path: .metrics.loc.blank
old: 29.0
new: 35.0

path: .metrics.loc.sloc
old: 222.0
new: 295.0

path: .metrics.mi.mi_visual_studio
old: 23.732962059236353
new: 12.509984374811763

path: .metrics.mi.mi_sei
old: 26.42035696689115
new: -14.091834076760538

path: .metrics.mi.mi_original
old: 40.583365121294165
new: 21.392073280928116

path: .metrics.halstead.vocabulary
old: 61.0
new: 159.0

path: .metrics.halstead.level
old: 0.04448563484708063
new: 0.031077348066298343

path: .metrics.halstead.length
old: 347.0
new: 989.0

path: .metrics.halstead.n2
old: 48.0
new: 135.0

path: .metrics.halstead.purity_ratio
old: 0.9111928425661376
new: 1.0772590552266632

path: .metrics.halstead.difficulty
old: 22.479166666666668
new: 32.17777777777778

path: .metrics.halstead.volume
old: 2057.9658561343217
new: 7232.441242776228

path: .metrics.halstead.bugs
old: 0.42956048778563777
new: 1.2611673533606034

path: .metrics.halstead.n1
old: 13.0
new: 24.0

path: .metrics.halstead.N1
old: 181.0
new: 627.0

path: .metrics.halstead.N2
old: 166.0
new: 362.0

path: .metrics.halstead.estimated_program_length
old: 316.1839163704497
new: 1065.4092056191698

path: .metrics.halstead.time
old: 2570.0754152418212
new: 12929.104838938243

path: .metrics.halstead.effort
old: 46261.35747435278
new: 232723.8871008884

path: .metrics.nargs.sum
old: 0.0
new: 9.0

path: .metrics.nargs.average
old: 0.0
new: 0.34615384615384615

Spaces Data

Minimal test - lines (15, 295)

path: .spaces[0].metrics.mi.mi_sei
old: null
new: -14.155229312196912

path: .spaces[0].metrics.mi.mi_visual_studio
old: null
new: 13.139746181025956

path: .spaces[0].metrics.mi.mi_original
old: null
new: 22.46896596955439

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 2.0

path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 48.0

path: .spaces[0].metrics.halstead.level
old: null
new: 0.030345471521942113

path: .spaces[0].metrics.halstead.time
old: 0.0
new: 13090.948257987846

path: .spaces[0].metrics.halstead.N2
old: 1.0
new: 357.0

path: .spaces[0].metrics.halstead.n1
old: 0.0
new: 24.0

path: .spaces[0].metrics.halstead.N1
old: 0.0
new: 627.0

path: .spaces[0].metrics.halstead.effort
old: 0.0
new: 235637.06864378124

path: .spaces[0].metrics.halstead.n2
old: 1.0
new: 130.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: null
new: 1022.9469157110068

path: .spaces[0].metrics.halstead.difficulty
old: 0.0
new: 32.95384615384615

path: .spaces[0].metrics.halstead.length
old: 1.0
new: 984.0

path: .spaces[0].metrics.halstead.vocabulary
old: 1.0
new: 154.0

path: .spaces[0].metrics.halstead.bugs
old: 0.0
new: 1.2716701764742624

path: .spaces[0].metrics.halstead.volume
old: 0.0
new: 7150.517956043783

path: .spaces[0].metrics.halstead.purity_ratio
old: null
new: 1.0395801988932996

path: .spaces[0].metrics.nom.functions
old: 0.0
new: 23.0

path: .spaces[0].metrics.nom.total
old: 0.0
new: 26.0

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 3.0

path: .spaces[0].metrics.nexits.average
old: null
new: 0.8461538461538461

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 22.0

path: .spaces[0].metrics.cognitive.average
old: null
new: 0.9230769230769232

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 24.0

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 9.0

path: .spaces[0].metrics.nargs.average
old: null
new: 0.34615384615384615

path: .spaces[0].metrics.loc.blank
old: 0.0
new: 32.0

path: .spaces[0].metrics.loc.sloc
old: 1.0
new: 281.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 91.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 30.0

path: .spaces[0].metrics.loc.ploc
old: 1.0
new: 219.0

Code

namespace mozilla {

/* static */ DataMutexBase
    ProfilerChild::sPendingChunkManagerUpdate{
        "ProfilerChild::sPendingChunkManagerUpdate"};

ProfilerChild::ProfilerChild()
    : mThread(NS_GetCurrentThread()), mDestroyed(false) {
  MOZ_COUNT_CTOR(ProfilerChild);
}

ProfilerChild::~ProfilerChild() { MOZ_COUNT_DTOR(ProfilerChild); }

void ProfilerChild::ResolveChunkUpdate(
    PProfilerChild::AwaitNextChunkManagerUpdateResolver& aResolve) {
  MOZ_ASSERT(!!aResolve,
             "ResolveChunkUpdate should only be called when there's a pending "
             "resolver");
  MOZ_ASSERT(
      !mChunkManagerUpdate.IsNotUpdate(),
      "ResolveChunkUpdate should only be called with a real or final update");
  MOZ_ASSERT(
      !mDestroyed,
      "ResolveChunkUpdate should not be called if the actor was destroyed");
  if (mChunkManagerUpdate.IsFinal()) {
    // Final update, send a special "unreleased value", but don't clear the
    // local copy so we know we got the final update.
    std::move(aResolve)(ProfilerParent::MakeFinalUpdate());
  } else {
    // Optimization note: The ProfileBufferChunkManagerUpdate constructor takes
    // the newly-released chunks nsTArray by reference-to-const, therefore
    // constructing and then moving the array here would make a copy. So instead
    // we first give it an empty array, and then we can write the data directly
    // into the update's array.
    ProfileBufferChunkManagerUpdate update{
        mChunkManagerUpdate.UnreleasedBytes(),
        mChunkManagerUpdate.ReleasedBytes(),
        mChunkManagerUpdate.OldestDoneTimeStamp(),
        {}};
    update.newlyReleasedChunks().SetCapacity(
        mChunkManagerUpdate.NewlyReleasedChunksRef().size());
    for (const ProfileBufferControlledChunkManager::ChunkMetadata& chunk :
         mChunkManagerUpdate.NewlyReleasedChunksRef()) {
      update.newlyReleasedChunks().EmplaceBack(chunk.mDoneTimeStamp,
                                               chunk.mBufferBytes);
    }

    std::move(aResolve)(update);

    // Clear the update we just sent, so it's ready for later updates to be
    // folded into it.
    mChunkManagerUpdate.Clear();
  }

  // Discard the resolver, so it's empty next time there's a new request.
  aResolve = nullptr;
}

void ProfilerChild::ProcessChunkManagerUpdate(
    ProfileBufferControlledChunkManager::Update&& aUpdate) {
  if (mDestroyed) {
    return;
  }
  // Always store the data, it could be the final update.
  mChunkManagerUpdate.Fold(std::move(aUpdate));
  if (mAwaitNextChunkManagerUpdateResolver) {
    // There is already a pending resolver, give it the info now.
    ResolveChunkUpdate(mAwaitNextChunkManagerUpdateResolver);
  }
}

/* static */ void ProfilerChild::ProcessPendingUpdate() {
  auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
  if (!lockedUpdate->mProfilerChild || lockedUpdate->mUpdate.IsNotUpdate()) {
    return;
  }
  lockedUpdate->mProfilerChild->mThread->Dispatch(NS_NewRunnableFunction(
      "ProfilerChild::ProcessPendingUpdate", []() mutable {
        auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
        if (!lockedUpdate->mProfilerChild ||
            lockedUpdate->mUpdate.IsNotUpdate()) {
          return;
        }
        lockedUpdate->mProfilerChild->ProcessChunkManagerUpdate(
            std::move(lockedUpdate->mUpdate));
        lockedUpdate->mUpdate.Clear();
      }));
}

/* static */ bool ProfilerChild::IsLockedOnCurrentThread() {
  return sPendingChunkManagerUpdate.Mutex().IsLockedOnCurrentThread();
}

void ProfilerChild::SetupChunkManager() {
  mChunkManager = profiler_get_controlled_chunk_manager();
  if (NS_WARN_IF(!mChunkManager)) {
    return;
  }

  // Make sure there are no updates (from a previous run).
  mChunkManagerUpdate.Clear();
  {
    auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
    lockedUpdate->mProfilerChild = this;
    lockedUpdate->mUpdate.Clear();
  }

  mChunkManager->SetUpdateCallback(
      [](ProfileBufferControlledChunkManager::Update&& aUpdate) {
        // Updates from the chunk manager are stored for later processing.
        // We avoid dispatching a task, as this could deadlock (if the queueing
        // mutex is held elsewhere).
        auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
        if (!lockedUpdate->mProfilerChild) {
          return;
        }
        lockedUpdate->mUpdate.Fold(std::move(aUpdate));
      });
}

void ProfilerChild::ResetChunkManager() {
  if (!mChunkManager) {
    return;
  }

  // We have a chunk manager, reset the callback, which will add a final
  // pending update.
  mChunkManager->SetUpdateCallback({});

  // Clear the pending update.
  auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
  lockedUpdate->mProfilerChild = nullptr;
  lockedUpdate->mUpdate.Clear();
  // And process a final update right now.
  ProcessChunkManagerUpdate(
      ProfileBufferControlledChunkManager::Update(nullptr));

  mChunkManager = nullptr;
  mAwaitNextChunkManagerUpdateResolver = nullptr;
}

mozilla::ipc::IPCResult ProfilerChild::RecvStart(
    const ProfilerInitParams& params) {
  nsTArray filterArray;
  for (size_t i = 0; i < params.filters().Length(); ++i) {
    filterArray.AppendElement(params.filters()[i].get());
  }

  profiler_start(PowerOfTwo32(params.entries()), params.interval(),
                 params.features(), filterArray.Elements(),
                 filterArray.Length(), params.activeBrowsingContextID(),
                 params.duration());

  SetupChunkManager();

  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvEnsureStarted(
    const ProfilerInitParams& params) {
  nsTArray filterArray;
  for (size_t i = 0; i < params.filters().Length(); ++i) {
    filterArray.AppendElement(params.filters()[i].get());
  }

  profiler_ensure_started(PowerOfTwo32(params.entries()), params.interval(),
                          params.features(), filterArray.Elements(),
                          filterArray.Length(),
                          params.activeBrowsingContextID(), params.duration());

  SetupChunkManager();

  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvStop() {
  ResetChunkManager();
  profiler_stop();
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvPause() {
  profiler_pause();
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvResume() {
  profiler_resume();
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvPauseSampling() {
  profiler_pause_sampling();
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvResumeSampling() {
  profiler_resume_sampling();
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvClearAllPages() {
  profiler_clear_all_pages();
  return IPC_OK();
}

static nsCString CollectProfileOrEmptyString(bool aIsShuttingDown) {
  nsCString profileCString;
  UniquePtr profile =
      profiler_get_profile(/* aSinceTime */ 0, aIsShuttingDown);
  if (profile) {
    size_t len = strlen(profile.get());
    profileCString.Adopt(profile.release(), len);
  }
  return profileCString;
}

mozilla::ipc::IPCResult ProfilerChild::RecvAwaitNextChunkManagerUpdate(
    AwaitNextChunkManagerUpdateResolver&& aResolve) {
  MOZ_ASSERT(!mDestroyed,
             "Recv... should not be called if the actor was destroyed");
  // Pick up pending updates if any.
  {
    auto lockedUpdate = sPendingChunkManagerUpdate.Lock();
    if (lockedUpdate->mProfilerChild && !lockedUpdate->mUpdate.IsNotUpdate()) {
      mChunkManagerUpdate.Fold(std::move(lockedUpdate->mUpdate));
      lockedUpdate->mUpdate.Clear();
    }
  }
  if (mChunkManagerUpdate.IsNotUpdate()) {
    // No data yet, store the resolver for later.
    mAwaitNextChunkManagerUpdateResolver = std::move(aResolve);
  } else {
    // We have data, send it now.
    ResolveChunkUpdate(aResolve);
  }
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvDestroyReleasedChunksAtOrBefore(
    const TimeStamp& aTimeStamp) {
  if (mChunkManager) {
    mChunkManager->DestroyChunksAtOrBefore(aTimeStamp);
  }
  return IPC_OK();
}

mozilla::ipc::IPCResult ProfilerChild::RecvGatherProfile(
    GatherProfileResolver&& aResolve) {
  mozilla::ipc::Shmem shmem;
  profiler_get_profile_json_into_lazily_allocated_buffer(
      [&](size_t allocationSize) -> char* {
        if (AllocShmem(allocationSize,
                       mozilla::ipc::Shmem::SharedMemory::TYPE_BASIC, &shmem)) {
          return shmem.get();
        }
        return nullptr;
      },
      /* aSinceTime */ 0,
      /* aIsShuttingDown */ false);
  aResolve(std::move(shmem));
  return IPC_OK();
}

void ProfilerChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) {
  mDestroyed = true;
}

void ProfilerChild::Destroy() {
  ResetChunkManager();
  if (!mDestroyed) {
    Close();
  }
}

nsCString ProfilerChild::GrabShutdownProfile() {
  return CollectProfileOrEmptyString(/* aIsShuttingDown */ true);
}

}  // namespace mozilla