diff options
Diffstat (limited to '')
-rw-r--r-- | tools/performance/PerfStats.cpp | 317 | ||||
-rw-r--r-- | tools/performance/PerfStats.h | 161 | ||||
-rw-r--r-- | tools/performance/moz.build | 17 |
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" |