Global Metrics

path: .metrics.mi.mi_sei
old: 22.890477867599735
new: -20.106512789302787

path: .metrics.mi.mi_original
old: 36.4251273767445
new: 25.011851586450845

path: .metrics.mi.mi_visual_studio
old: 21.301244079967542
new: 14.626813793246107

path: .metrics.halstead.volume
old: 3231.3909820522326
new: 7795.941977063929

path: .metrics.halstead.length
old: 499.0
new: 1033.0

path: .metrics.halstead.time
old: 8130.596664518521
new: 16614.347511597916

path: .metrics.halstead.n1
old: 27.0
new: 29.0

path: .metrics.halstead.vocabulary
old: 89.0
new: 187.0

path: .metrics.halstead.effort
old: 146350.73996133337
new: 299058.2552087625

path: .metrics.halstead.N1
old: 291.0
new: 615.0

path: .metrics.halstead.purity_ratio
old: 0.9970784244537072
new: 1.25351288196581

path: .metrics.halstead.N2
old: 208.0
new: 418.0

path: .metrics.halstead.difficulty
old: 45.29032258064516
new: 38.36075949367088

path: .metrics.halstead.bugs
old: 0.9257108635252772
new: 1.4906737668451473

path: .metrics.halstead.estimated_program_length
old: 497.5421338023999
new: 1294.878807070682

path: .metrics.halstead.level
old: 0.02207977207977208
new: 0.02606830556013859

path: .metrics.halstead.n2
old: 62.0
new: 158.0

path: .metrics.nom.functions
old: 6.0
new: 14.0

path: .metrics.nom.total
old: 6.0
new: 18.0

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

path: .metrics.cyclomatic.average
old: 2.857142857142857
new: 2.0

path: .metrics.cyclomatic.sum
old: 20.0
new: 36.0

path: .metrics.loc.ploc
old: 106.0
new: 215.0

path: .metrics.loc.sloc
old: 228.0
new: 277.0

path: .metrics.loc.cloc
old: 110.0
new: 12.0

path: .metrics.loc.blank
old: 12.0
new: 50.0

path: .metrics.loc.lloc
old: 31.0
new: 82.0

path: .metrics.nargs.sum
old: 33.0
new: 14.0

path: .metrics.nargs.average
old: 5.5
new: 0.7777777777777778

path: .metrics.cognitive.average
old: 3.1666666666666665
new: 1.2222222222222223

path: .metrics.cognitive.sum
old: 19.0
new: 22.0

path: .metrics.nexits.average
old: 1.3333333333333333
new: 0.3333333333333333

path: .metrics.nexits.sum
old: 8.0
new: 6.0

Spaces Data

Minimal test - lines (21, 277)

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

path: .spaces[0].metrics.nom.functions
old: 1.0
new: 14.0

path: .spaces[0].metrics.nom.total
old: 1.0
new: 18.0

path: .spaces[0].metrics.mi.mi_original
old: 109.32690234865424
new: 26.603424725710894

path: .spaces[0].metrics.mi.mi_visual_studio
old: 63.933861022604816
new: 15.557558319129177

path: .spaces[0].metrics.mi.mi_sei
old: 108.26538255658764
new: -22.031296693380508

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

path: .spaces[0].metrics.nargs.average
old: 0.0
new: 0.7777777777777778

path: .spaces[0].metrics.cyclomatic.sum
old: 2.0
new: 35.0

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

path: .spaces[0].metrics.loc.cloc
old: 1.0
new: 6.0

path: .spaces[0].metrics.loc.lloc
old: 3.0
new: 82.0

path: .spaces[0].metrics.loc.sloc
old: 8.0
new: 257.0

path: .spaces[0].metrics.loc.ploc
old: 8.0
new: 203.0

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

path: .spaces[0].metrics.cognitive.average
old: 1.0
new: 1.2222222222222223

path: .spaces[0].metrics.cognitive.sum
old: 1.0
new: 22.0

path: .spaces[0].metrics.nexits.average
old: 1.0
new: 0.3333333333333333

path: .spaces[0].metrics.nexits.sum
old: 1.0
new: 6.0

path: .spaces[0].metrics.halstead.n2
old: 10.0
new: 146.0

path: .spaces[0].metrics.halstead.time
old: 129.37387194323057
new: 16975.26239429908

path: .spaces[0].metrics.halstead.volume
old: 199.03672606650855
new: 7577.881700733478

path: .spaces[0].metrics.halstead.estimated_program_length
old: 81.32499728470782
new: 1190.595834455182

path: .spaces[0].metrics.halstead.n1
old: 13.0
new: 29.0

path: .spaces[0].metrics.halstead.N1
old: 26.0
new: 611.0

path: .spaces[0].metrics.halstead.purity_ratio
old: 1.8482953928342687
new: 1.1706940358458031

path: .spaces[0].metrics.halstead.length
old: 44.0
new: 1017.0

path: .spaces[0].metrics.halstead.vocabulary
old: 23.0
new: 175.0

path: .spaces[0].metrics.halstead.bugs
old: 0.058563200005583176
new: 1.5121843742215806

path: .spaces[0].metrics.halstead.difficulty
old: 11.7
new: 40.321917808219176

path: .spaces[0].metrics.halstead.level
old: 0.08547008547008547
new: 0.02480040767793443

path: .spaces[0].metrics.halstead.N2
old: 18.0
new: 406.0

path: .spaces[0].metrics.halstead.effort
old: 2328.7296949781503
new: 305554.72309738345

Code

namespace mozilla {

static const char* const sMetricNames[] = {"DisplayList Building",
                                           "Rasterizing",
                                           "LayerBuilding",
                                           "Layer Transactions",
                                           "Compositing",
                                           "Reflowing",
                                           "Styling",
                                           "HttpChannelCompletion_Network",
                                           "HttpChannelCompletion_Cache"};

static_assert(sizeof(sMetricNames) / sizeof(sMetricNames[0]) ==
              static_cast(PerfStats::Metric::Max));

PerfStats::MetricMask PerfStats::sCollectionMask = 0;
StaticMutex PerfStats::sMutex;
StaticAutoPtr PerfStats::sSingleton;

void PerfStats::SetCollectionMask(MetricMask aMask) {
  sCollectionMask = aMask;
  for (uint64_t i = 0; i < static_cast(Metric::Max); i++) {
    if (!(sCollectionMask & 1 << i)) {
      continue;
    }

    GetSingleton()->mRecordedTimes[i] = 0;
  }

  if (!XRE_IsParentProcess()) {
    return;
  }

  GPUProcessManager* gpuManager = GPUProcessManager::Get();
  GPUChild* gpuChild = nullptr;

  if (gpuManager) {
    gpuChild = gpuManager->GetGPUChild();
    if (gpuChild) {
      gpuChild->SendUpdatePerfStatsCollectionMask(aMask);
    }
  }

  nsTArray contentParents;
  ContentParent::GetAll(contentParents);

  for (ContentParent* parent : contentParents) {
    Unused << parent->SendUpdatePerfStatsCollectionMask(aMask);
  }
}

PerfStats* PerfStats::GetSingleton() {
  if (!sSingleton) {
    sSingleton = new PerfStats;
  }

  return sSingleton.get();
}

void PerfStats::RecordMeasurementStartInternal(Metric aMetric) {
  StaticMutexAutoLock lock(sMutex);

  GetSingleton()->mRecordedStarts[static_cast(aMetric)] =
      TimeStamp::Now();
}

void PerfStats::RecordMeasurementEndInternal(Metric aMetric) {
  StaticMutexAutoLock lock(sMutex);

  MOZ_ASSERT(sSingleton);

  sSingleton->mRecordedTimes[static_cast(aMetric)] +=
      (TimeStamp::Now() -
       sSingleton->mRecordedStarts[static_cast(aMetric)])
          .ToMilliseconds();
}

void PerfStats::RecordMeasurementInternal(Metric aMetric,
                                          TimeDuration aDuration) {
  StaticMutexAutoLock lock(sMutex);

  MOZ_ASSERT(sSingleton);

  sSingleton->mRecordedTimes[static_cast(aMetric)] +=
      aDuration.ToMilliseconds();
}

struct StringWriteFunc : public JSONWriteFunc {
  nsCString& mString;

  explicit StringWriteFunc(nsCString& aString) : mString(aString) {}
  virtual void Write(const Span& aStr) override {
    mString.Append(aStr);
  }
};

void AppendJSONStringAsProperty(nsCString& aDest, const char* aPropertyName,
                                const nsCString& aJSON) {
  // We need to manually append into the string here, since JSONWriter has no
  // way to allow us to write an existing JSON object into a property.
  aDest.Append(",\n\"");
  aDest.Append(aPropertyName);
  aDest.Append("\": ");
  aDest.Append(aJSON);
}

struct PerfStatsCollector {
  PerfStatsCollector() : writer(MakeUnique(string)) {}

  void AppendPerfStats(const nsCString& aString, ContentParent* aParent) {
    writer.StartObjectElement();
    writer.StringProperty("type", "content");
    writer.IntProperty("id", aParent->ChildID());
    const ManagedContainer& browsers =
        aParent->ManagedPBrowserParent();

    writer.StartArrayProperty("urls");
    for (auto iter = browsers.ConstIter(); !iter.Done(); iter.Next()) {
      RefPtr parent =
          BrowserParent::GetFrom(iter.Get()->GetKey());

      CanonicalBrowsingContext* ctx = parent->GetBrowsingContext();
      if (!ctx) {
        continue;
      }

      WindowGlobalParent* windowGlobal = ctx->GetCurrentWindowGlobal();
      if (!windowGlobal) {
        continue;
      }

      RefPtr uri = windowGlobal->GetDocumentURI();
      if (!uri) {
        continue;
      }

      nsAutoCString url;
      uri->GetSpec(url);

      writer.StringElement(url);
    }
    writer.EndArray();
    AppendJSONStringAsProperty(string, "perfstats", aString);
    writer.EndObject();
  }

  void AppendPerfStats(const nsCString& aString, GPUChild* aChild) {
    writer.StartObjectElement();
    writer.StringProperty("type", "gpu");
    writer.IntProperty("id", aChild->Id());
    AppendJSONStringAsProperty(string, "perfstats", aString);
    writer.EndObject();
  }

  ~PerfStatsCollector() {
    writer.EndArray();
    writer.End();
    promise.Resolve(string, __func__);
  }
  nsCString string;
  JSONWriter writer;
  MozPromiseHolder promise;
};

auto PerfStats::CollectPerfStatsJSONInternal() -> RefPtr {
  if (!PerfStats::sCollectionMask) {
    return PerfStatsPromise::CreateAndReject(false, __func__);
  }

  if (!XRE_IsParentProcess()) {
    return PerfStatsPromise::CreateAndResolve(
        CollectLocalPerfStatsJSONInternal(), __func__);
  }

  std::shared_ptr collector =
      std::make_shared();

  JSONWriter& w = collector->writer;

  w.Start();
  {
    w.StartArrayProperty("processes");
    {
      w.StartObjectElement();
      {
        w.StringProperty("type", "parent");
        AppendJSONStringAsProperty(collector->string, "perfstats",
                                   CollectLocalPerfStatsJSONInternal());
      }
      w.EndObject();

      GPUProcessManager* gpuManager = GPUProcessManager::Get();
      GPUChild* gpuChild = nullptr;

      if (gpuManager) {
        gpuChild = gpuManager->GetGPUChild();
      }
      nsTArray contentParents;
      ContentParent::GetAll(contentParents);

      if (gpuChild) {
        gpuChild->SendCollectPerfStatsJSON(
            [collector, gpuChild](const nsCString& aString) {
              collector->AppendPerfStats(aString, gpuChild);
            },
            // The only feasible errors here are if something goes wrong in the
            // the bridge, we choose to ignore those.
            [](mozilla::ipc::ResponseRejectReason) {});
      }
      for (ContentParent* parent : contentParents) {
        RefPtr parentRef = parent;
        parent->SendCollectPerfStatsJSON(
            [collector, parentRef](const nsCString& aString) {
              collector->AppendPerfStats(aString, parentRef.get());
            },
            // The only feasible errors here are if something goes wrong in the
            // the bridge, we choose to ignore those.
            [](mozilla::ipc::ResponseRejectReason) {});
      }
    }
  }

  return collector->promise.Ensure(__func__);
}

nsCString PerfStats::CollectLocalPerfStatsJSONInternal() {
  StaticMutexAutoLock lock(PerfStats::sMutex);

  nsCString jsonString;

  JSONWriter w(MakeUnique(jsonString));
  w.Start();
  {
    w.StartArrayProperty("metrics");
    {
      for (uint64_t i = 0; i < static_cast(Metric::Max); i++) {
        if (!(sCollectionMask & (1 << i))) {
          continue;
        }

        w.StartObjectElement();
        {
          w.IntProperty("id", i);
          w.StringProperty("metric", MakeStringSpan(sMetricNames[i]));
          w.DoubleProperty("time", mRecordedTimes[i]);
        }
        w.EndObject();
      }
    }
    w.EndArray();
  }
  w.End();

  return jsonString;
}

}  // namespace mozilla