/* -*- 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/JSONWriter.h" using namespace mozilla::dom; using namespace mozilla::gfx; namespace mozilla { static const char* const sMetricNames[] = {"DisplayList Building", "Rasterizing", "LayerBuilding", "Layer Transactions", "Compositing", "Reflowing", "Styling"}; 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(); } 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