summaryrefslogtreecommitdiffstats
path: root/tools/performance
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/performance/PerfStats.cpp317
-rw-r--r--tools/performance/PerfStats.h161
-rw-r--r--tools/performance/moz.build17
3 files changed, 495 insertions, 0 deletions
diff --git a/tools/performance/PerfStats.cpp b/tools/performance/PerfStats.cpp
new file mode 100644
index 0000000000..d15977ddbf
--- /dev/null
+++ b/tools/performance/PerfStats.cpp
@@ -0,0 +1,317 @@
+/* -*- 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 "PerfStats.h"
+#include "nsAppRunner.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/gfx/GPUChild.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+namespace mozilla {
+
+#define METRIC_NAME(metric) #metric,
+static const char* const sMetricNames[] = {
+ FOR_EACH_PERFSTATS_METRIC(METRIC_NAME)
+#undef METRIC_NAME
+ "Invalid"};
+
+PerfStats::MetricMask PerfStats::sCollectionMask = 0;
+StaticMutex PerfStats::sMutex;
+StaticAutoPtr<PerfStats> PerfStats::sSingleton;
+
+void PerfStats::SetCollectionMask(MetricMask aMask) {
+ sCollectionMask = aMask;
+ GetSingleton()->ResetCollection();
+
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ GPUProcessManager* gpuManager = GPUProcessManager::Get();
+ GPUChild* gpuChild = nullptr;
+
+ if (gpuManager) {
+ gpuChild = gpuManager->GetGPUChild();
+ if (gpuChild) {
+ gpuChild->SendUpdatePerfStatsCollectionMask(aMask);
+ }
+ }
+
+ nsTArray<ContentParent*> contentParents;
+ ContentParent::GetAll(contentParents);
+
+ for (ContentParent* parent : contentParents) {
+ Unused << parent->SendUpdatePerfStatsCollectionMask(aMask);
+ }
+}
+
+PerfStats::MetricMask PerfStats::GetCollectionMask() { return sCollectionMask; }
+
+PerfStats* PerfStats::GetSingleton() {
+ if (!sSingleton) {
+ sSingleton = new PerfStats;
+ }
+
+ return sSingleton.get();
+}
+
+void PerfStats::RecordMeasurementStartInternal(Metric aMetric) {
+ StaticMutexAutoLock lock(sMutex);
+
+ GetSingleton()->mRecordedStarts[static_cast<size_t>(aMetric)] =
+ TimeStamp::Now();
+}
+
+void PerfStats::RecordMeasurementEndInternal(Metric aMetric) {
+ StaticMutexAutoLock lock(sMutex);
+
+ MOZ_ASSERT(sSingleton);
+
+ sSingleton->mRecordedTimes[static_cast<size_t>(aMetric)] +=
+ (TimeStamp::Now() -
+ sSingleton->mRecordedStarts[static_cast<size_t>(aMetric)])
+ .ToMilliseconds();
+ sSingleton->mRecordedCounts[static_cast<size_t>(aMetric)]++;
+}
+
+void PerfStats::RecordMeasurementInternal(Metric aMetric,
+ TimeDuration aDuration) {
+ StaticMutexAutoLock lock(sMutex);
+
+ MOZ_ASSERT(sSingleton);
+
+ sSingleton->mRecordedTimes[static_cast<size_t>(aMetric)] +=
+ aDuration.ToMilliseconds();
+ sSingleton->mRecordedCounts[static_cast<size_t>(aMetric)]++;
+}
+
+void PerfStats::RecordMeasurementCounterInternal(Metric aMetric,
+ uint64_t aIncrementAmount) {
+ StaticMutexAutoLock lock(sMutex);
+
+ MOZ_ASSERT(sSingleton);
+
+ sSingleton->mRecordedTimes[static_cast<size_t>(aMetric)] +=
+ double(aIncrementAmount);
+ sSingleton->mRecordedCounts[static_cast<size_t>(aMetric)]++;
+}
+
+void AppendJSONStringAsProperty(nsCString& aDest, const char* aPropertyName,
+ const nsACString& 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);
+}
+
+static void WriteContentParent(nsCString& aRawString, JSONWriter& aWriter,
+ const nsACString& aString,
+ ContentParent* aParent) {
+ aWriter.StringProperty("type", "content");
+ aWriter.IntProperty("id", aParent->ChildID());
+ const ManagedContainer<PBrowserParent>& browsers =
+ aParent->ManagedPBrowserParent();
+
+ aWriter.StartArrayProperty("urls");
+ for (const auto& key : browsers) {
+ // This only reports -current- URLs, not ones that may have been here in
+ // the past, this is unfortunate especially for processes which are dying
+ // and that have no more active URLs.
+ RefPtr<BrowserParent> parent = BrowserParent::GetFrom(key);
+
+ CanonicalBrowsingContext* ctx = parent->GetBrowsingContext();
+ if (!ctx) {
+ continue;
+ }
+
+ WindowGlobalParent* windowGlobal = ctx->GetCurrentWindowGlobal();
+ if (!windowGlobal) {
+ continue;
+ }
+
+ RefPtr<nsIURI> uri = windowGlobal->GetDocumentURI();
+ if (!uri) {
+ continue;
+ }
+
+ nsAutoCString url;
+ uri->GetSpec(url);
+
+ aWriter.StringElement(url);
+ }
+ aWriter.EndArray();
+ AppendJSONStringAsProperty(aRawString, "perfstats", aString);
+}
+
+struct PerfStatsCollector {
+ PerfStatsCollector() : writer(MakeUnique<JSONStringRefWriteFunc>(string)) {}
+
+ void AppendPerfStats(const nsCString& aString, ContentParent* aParent) {
+ writer.StartObjectElement();
+ WriteContentParent(string, writer, aString, aParent);
+ 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<PerfStats::PerfStatsPromise> promise;
+};
+
+void PerfStats::ResetCollection() {
+ for (uint64_t i = 0; i < static_cast<uint64_t>(Metric::Max); i++) {
+ if (!(sCollectionMask & 1 << i)) {
+ continue;
+ }
+
+ mRecordedTimes[i] = 0;
+ mRecordedCounts[i] = 0;
+ }
+
+ mStoredPerfStats.Clear();
+}
+
+void PerfStats::StorePerfStatsInternal(dom::ContentParent* aParent,
+ const nsACString& aPerfStats) {
+ nsCString jsonString;
+ JSONStringRefWriteFunc jw(jsonString);
+ JSONWriter w(jw);
+
+ // To generate correct JSON here we don't call start and end. That causes
+ // this to use Single Line mode, sadly.
+ WriteContentParent(jsonString, w, aPerfStats, aParent);
+
+ mStoredPerfStats.AppendElement(jsonString);
+}
+
+auto PerfStats::CollectPerfStatsJSONInternal() -> RefPtr<PerfStatsPromise> {
+ if (!PerfStats::sCollectionMask) {
+ return PerfStatsPromise::CreateAndReject(false, __func__);
+ }
+
+ if (!XRE_IsParentProcess()) {
+ return PerfStatsPromise::CreateAndResolve(
+ CollectLocalPerfStatsJSONInternal(), __func__);
+ }
+
+ std::shared_ptr<PerfStatsCollector> collector =
+ std::make_shared<PerfStatsCollector>();
+
+ JSONWriter& w = collector->writer;
+
+ w.Start();
+ {
+ w.StartArrayProperty("processes");
+ {
+ w.StartObjectElement();
+ {
+ w.StringProperty("type", "parent");
+ AppendJSONStringAsProperty(collector->string, "perfstats",
+ CollectLocalPerfStatsJSONInternal());
+ }
+ w.EndObject();
+
+ // Append any processes that closed earlier.
+ for (nsCString& string : mStoredPerfStats) {
+ w.StartObjectElement();
+ // This trick makes indentation even more messed up than it already
+ // was. However it produces technically correct JSON.
+ collector->string.Append(string);
+ w.EndObject();
+ }
+ // We do not clear this, we only clear stored perfstats when the mask is
+ // reset.
+
+ GPUProcessManager* gpuManager = GPUProcessManager::Get();
+ GPUChild* gpuChild = nullptr;
+
+ if (gpuManager) {
+ gpuChild = gpuManager->GetGPUChild();
+ }
+ nsTArray<ContentParent*> contentParents;
+ ContentParent::GetAll(contentParents);
+
+ if (gpuChild) {
+ gpuChild->SendCollectPerfStatsJSON(
+ [collector, gpuChild = RefPtr{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<ContentParent> 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;
+
+ JSONStringRefWriteFunc jw(jsonString);
+ JSONWriter w(jw);
+ w.Start();
+ {
+ w.StartArrayProperty("metrics");
+ {
+ for (uint64_t i = 0; i < static_cast<uint64_t>(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.IntProperty("count", mRecordedCounts[i]);
+ }
+ w.EndObject();
+ }
+ }
+ w.EndArray();
+ }
+ w.End();
+
+ return jsonString;
+}
+
+} // namespace mozilla
diff --git a/tools/performance/PerfStats.h b/tools/performance/PerfStats.h
new file mode 100644
index 0000000000..68165e8ca3
--- /dev/null
+++ b/tools/performance/PerfStats.h
@@ -0,0 +1,161 @@
+/* -*- 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/. */
+
+#ifndef PerfStats_h
+#define PerfStats_h
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/MozPromise.h"
+#include <memory>
+#include <string>
+#include <limits>
+
+// PerfStats
+//
+// Framework for low overhead selective collection of internal performance
+// metrics through ChromeUtils.
+//
+// Gathering: in C++, wrap execution in an RAII class
+// PerfStats::AutoMetricRecording<PerfStats::Metric::MyMetric> or call
+// PerfStats::RecordMeasurement{Start,End} manually. Use
+// RecordMeasurementCount() for incrementing counters.
+//
+// Controlling: Use ChromeUtils.SetPerfStatsCollectionMask(mask), where mask=0
+// disables all metrics and mask=0xFFFFFFFF enables all of them.
+//
+// Reporting: Results can be accessed with ChromeUtils.CollectPerfStats().
+// Browsertime will sum results across processes and report them.
+
+// Define a new metric by adding it to this list. It will be created as a class
+// enum value mozilla::PerfStats::Metric::MyMetricName.
+#define FOR_EACH_PERFSTATS_METRIC(MACRO) \
+ MACRO(DisplayListBuilding) \
+ MACRO(Rasterizing) \
+ MACRO(WrDisplayListBuilding) \
+ MACRO(LayerTransactions) \
+ MACRO(Compositing) \
+ MACRO(Reflowing) \
+ MACRO(Styling) \
+ MACRO(HttpChannelCompletion) \
+ MACRO(HttpChannelCompletion_Network) \
+ MACRO(HttpChannelCompletion_Cache) \
+ MACRO(HttpChannelAsyncOpenToTransactionPending) \
+ MACRO(HttpChannelResponseStartParentToContent) \
+ MACRO(HttpChannelResponseEndParentToContent) \
+ MACRO(JSBC_Compression) \
+ MACRO(JSBC_Decompression) \
+ MACRO(JSBC_IO_Read) \
+ MACRO(JSBC_IO_Write) \
+ MACRO(MinorGC) \
+ MACRO(MajorGC) \
+ MACRO(NonIdleMajorGC)
+
+namespace mozilla {
+
+namespace dom {
+// Forward declaration.
+class ContentParent;
+} // namespace dom
+
+class PerfStats {
+ public:
+ typedef MozPromise<nsCString, bool, true> PerfStatsPromise;
+
+ enum class Metric : uint32_t {
+#define DECLARE_ENUM(metric) metric,
+ FOR_EACH_PERFSTATS_METRIC(DECLARE_ENUM)
+#undef DECLARE_ENUM
+ Max
+ };
+
+ // MetricMask is a bitmask based on 'Metric', i.e. Metric::LayerBuilding (2)
+ // is synonymous to 1 << 2 in MetricMask.
+ using MetricMask = uint64_t;
+
+ static void RecordMeasurementStart(Metric aMetric) {
+ if (!(sCollectionMask & (1 << static_cast<uint64_t>(aMetric)))) {
+ return;
+ }
+ RecordMeasurementStartInternal(aMetric);
+ }
+
+ static void RecordMeasurementEnd(Metric aMetric) {
+ if (!(sCollectionMask & (1 << static_cast<uint64_t>(aMetric)))) {
+ return;
+ }
+ RecordMeasurementEndInternal(aMetric);
+ }
+
+ static void RecordMeasurement(Metric aMetric, TimeDuration aDuration) {
+ if (!(sCollectionMask & (1 << static_cast<uint64_t>(aMetric)))) {
+ return;
+ }
+ RecordMeasurementInternal(aMetric, aDuration);
+ }
+
+ static void RecordMeasurementCounter(Metric aMetric,
+ uint64_t aIncrementAmount) {
+ if (!(sCollectionMask & (1 << static_cast<uint64_t>(aMetric)))) {
+ return;
+ }
+ RecordMeasurementCounterInternal(aMetric, aIncrementAmount);
+ }
+
+ template <Metric N>
+ class AutoMetricRecording {
+ public:
+ AutoMetricRecording() { PerfStats::RecordMeasurementStart(N); }
+ ~AutoMetricRecording() { PerfStats::RecordMeasurementEnd(N); }
+ };
+
+ static void SetCollectionMask(MetricMask aMask);
+ static MetricMask GetCollectionMask();
+
+ static RefPtr<PerfStatsPromise> CollectPerfStatsJSON() {
+ return GetSingleton()->CollectPerfStatsJSONInternal();
+ }
+
+ static nsCString CollectLocalPerfStatsJSON() {
+ return GetSingleton()->CollectLocalPerfStatsJSONInternal();
+ }
+
+ static void StorePerfStats(dom::ContentParent* aParent,
+ const nsACString& aPerfStats) {
+ GetSingleton()->StorePerfStatsInternal(aParent, aPerfStats);
+ }
+
+ private:
+ static PerfStats* GetSingleton();
+ static void RecordMeasurementStartInternal(Metric aMetric);
+ static void RecordMeasurementEndInternal(Metric aMetric);
+ static void RecordMeasurementInternal(Metric aMetric, TimeDuration aDuration);
+ static void RecordMeasurementCounterInternal(Metric aMetric,
+ uint64_t aIncrementAmount);
+
+ void ResetCollection();
+ void StorePerfStatsInternal(dom::ContentParent* aParent,
+ const nsACString& aPerfStats);
+ RefPtr<PerfStatsPromise> CollectPerfStatsJSONInternal();
+ nsCString CollectLocalPerfStatsJSONInternal();
+
+ static MetricMask sCollectionMask;
+ static StaticMutex sMutex MOZ_UNANNOTATED;
+ static StaticAutoPtr<PerfStats> sSingleton;
+ TimeStamp mRecordedStarts[static_cast<size_t>(Metric::Max)];
+ double mRecordedTimes[static_cast<size_t>(Metric::Max)];
+ uint32_t mRecordedCounts[static_cast<size_t>(Metric::Max)];
+ nsTArray<nsCString> mStoredPerfStats;
+};
+
+static_assert(1 << (static_cast<uint64_t>(PerfStats::Metric::Max) - 1) <=
+ std::numeric_limits<PerfStats::MetricMask>::max(),
+ "More metrics than can fit into sCollectionMask bitmask");
+
+} // namespace mozilla
+
+#endif // PerfStats_h
diff --git a/tools/performance/moz.build b/tools/performance/moz.build
new file mode 100644
index 0000000000..ea82c83126
--- /dev/null
+++ b/tools/performance/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+UNIFIED_SOURCES += [
+ "PerfStats.cpp",
+]
+
+EXPORTS.mozilla += [
+ "PerfStats.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"