# 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