1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "PerfStats.h"
8 #include "nsAppRunner.h"
9 #include "mozilla/dom/BrowserParent.h"
10 #include "mozilla/dom/CanonicalBrowsingContext.h"
11 #include "mozilla/dom/ContentParent.h"
12 #include "mozilla/dom/ContentProcessManager.h"
13 #include "mozilla/dom/WindowGlobalParent.h"
14 #include "mozilla/gfx/GPUChild.h"
15 #include "mozilla/gfx/GPUProcessManager.h"
16 #include "mozilla/JSONWriter.h"
17 
18 using namespace mozilla::dom;
19 using namespace mozilla::gfx;
20 
21 namespace mozilla {
22 
23 static const char* const sMetricNames[] = {"DisplayList Building",
24                                            "Rasterizing",
25                                            "LayerBuilding",
26                                            "Layer Transactions",
27                                            "Compositing",
28                                            "Reflowing",
29                                            "Styling"};
30 
31 PerfStats::MetricMask PerfStats::sCollectionMask = 0;
32 StaticMutex PerfStats::sMutex;
33 StaticAutoPtr<PerfStats> PerfStats::sSingleton;
34 
SetCollectionMask(MetricMask aMask)35 void PerfStats::SetCollectionMask(MetricMask aMask) {
36   sCollectionMask = aMask;
37   for (uint64_t i = 0; i < static_cast<uint64_t>(Metric::Max); i++) {
38     if (!(sCollectionMask & 1 << i)) {
39       continue;
40     }
41 
42     GetSingleton()->mRecordedTimes[i] = 0;
43   }
44 
45   if (!XRE_IsParentProcess()) {
46     return;
47   }
48 
49   GPUProcessManager* gpuManager = GPUProcessManager::Get();
50   GPUChild* gpuChild = nullptr;
51 
52   if (gpuManager) {
53     gpuChild = gpuManager->GetGPUChild();
54     if (gpuChild) {
55       gpuChild->SendUpdatePerfStatsCollectionMask(aMask);
56     }
57   }
58 
59   nsTArray<ContentParent*> contentParents;
60   ContentParent::GetAll(contentParents);
61 
62   for (ContentParent* parent : contentParents) {
63     Unused << parent->SendUpdatePerfStatsCollectionMask(aMask);
64   }
65 }
66 
GetSingleton()67 PerfStats* PerfStats::GetSingleton() {
68   if (!sSingleton) {
69     sSingleton = new PerfStats;
70   }
71 
72   return sSingleton.get();
73 }
74 
RecordMeasurementStartInternal(Metric aMetric)75 void PerfStats::RecordMeasurementStartInternal(Metric aMetric) {
76   StaticMutexAutoLock lock(sMutex);
77 
78   GetSingleton()->mRecordedStarts[static_cast<size_t>(aMetric)] =
79       TimeStamp::Now();
80 }
81 
RecordMeasurementEndInternal(Metric aMetric)82 void PerfStats::RecordMeasurementEndInternal(Metric aMetric) {
83   StaticMutexAutoLock lock(sMutex);
84 
85   MOZ_ASSERT(sSingleton);
86 
87   sSingleton->mRecordedTimes[static_cast<size_t>(aMetric)] +=
88       (TimeStamp::Now() -
89        sSingleton->mRecordedStarts[static_cast<size_t>(aMetric)])
90           .ToMilliseconds();
91 }
92 
93 struct StringWriteFunc : public JSONWriteFunc {
94   nsCString& mString;
95 
StringWriteFuncmozilla::StringWriteFunc96   explicit StringWriteFunc(nsCString& aString) : mString(aString) {}
Writemozilla::StringWriteFunc97   virtual void Write(const char* aStr) override { mString.Append(aStr); }
Writemozilla::StringWriteFunc98   virtual void Write(const char* aStr, size_t aLen) override {
99     mString.Append(aStr, aLen);
100   }
101 };
102 
AppendJSONStringAsProperty(nsCString & aDest,const char * aPropertyName,const nsCString & aJSON)103 void AppendJSONStringAsProperty(nsCString& aDest, const char* aPropertyName,
104                                 const nsCString& aJSON) {
105   // We need to manually append into the string here, since JSONWriter has no
106   // way to allow us to write an existing JSON object into a property.
107   aDest.Append(",\n\"");
108   aDest.Append(aPropertyName);
109   aDest.Append("\": ");
110   aDest.Append(aJSON);
111 }
112 
113 struct PerfStatsCollector {
PerfStatsCollectormozilla::PerfStatsCollector114   PerfStatsCollector() : writer(MakeUnique<StringWriteFunc>(string)) {}
115 
AppendPerfStatsmozilla::PerfStatsCollector116   void AppendPerfStats(const nsCString& aString, ContentParent* aParent) {
117     writer.StartObjectElement();
118     writer.StringProperty("type", "content");
119     writer.IntProperty("id", aParent->ChildID());
120     const ManagedContainer<PBrowserParent>& browsers =
121         aParent->ManagedPBrowserParent();
122 
123     writer.StartArrayProperty("urls");
124     for (auto iter = browsers.ConstIter(); !iter.Done(); iter.Next()) {
125       RefPtr<BrowserParent> parent =
126           BrowserParent::GetFrom(iter.Get()->GetKey());
127 
128       CanonicalBrowsingContext* ctx = parent->GetBrowsingContext();
129       if (!ctx) {
130         continue;
131       }
132 
133       WindowGlobalParent* windowGlobal = ctx->GetCurrentWindowGlobal();
134       if (!windowGlobal) {
135         continue;
136       }
137 
138       RefPtr<nsIURI> uri = windowGlobal->GetDocumentURI();
139       if (!uri) {
140         continue;
141       }
142 
143       nsAutoCString url;
144       uri->GetSpec(url);
145 
146       writer.StringElement(url.BeginReading());
147     }
148     writer.EndArray();
149     AppendJSONStringAsProperty(string, "perfstats", aString);
150     writer.EndObject();
151   }
152 
AppendPerfStatsmozilla::PerfStatsCollector153   void AppendPerfStats(const nsCString& aString, GPUChild* aChild) {
154     writer.StartObjectElement();
155     writer.StringProperty("type", "gpu");
156     writer.IntProperty("id", aChild->Id());
157     AppendJSONStringAsProperty(string, "perfstats", aString);
158     writer.EndObject();
159   }
160 
~PerfStatsCollectormozilla::PerfStatsCollector161   ~PerfStatsCollector() {
162     writer.EndArray();
163     writer.End();
164     promise.Resolve(string, __func__);
165   }
166   nsCString string;
167   JSONWriter writer;
168   MozPromiseHolder<PerfStats::PerfStatsPromise> promise;
169 };
170 
CollectPerfStatsJSONInternal()171 auto PerfStats::CollectPerfStatsJSONInternal() -> RefPtr<PerfStatsPromise> {
172   if (!PerfStats::sCollectionMask) {
173     return PerfStatsPromise::CreateAndReject(false, __func__);
174   }
175 
176   if (!XRE_IsParentProcess()) {
177     return PerfStatsPromise::CreateAndResolve(
178         CollectLocalPerfStatsJSONInternal(), __func__);
179   }
180 
181   std::shared_ptr<PerfStatsCollector> collector =
182       std::make_shared<PerfStatsCollector>();
183 
184   JSONWriter& w = collector->writer;
185 
186   w.Start();
187   {
188     w.StartArrayProperty("processes");
189     {
190       w.StartObjectElement();
191       {
192         w.StringProperty("type", "parent");
193         AppendJSONStringAsProperty(collector->string, "perfstats",
194                                    CollectLocalPerfStatsJSONInternal());
195       }
196       w.EndObject();
197 
198       GPUProcessManager* gpuManager = GPUProcessManager::Get();
199       GPUChild* gpuChild = nullptr;
200 
201       if (gpuManager) {
202         gpuChild = gpuManager->GetGPUChild();
203       }
204       nsTArray<ContentParent*> contentParents;
205       ContentParent::GetAll(contentParents);
206 
207       if (gpuChild) {
208         gpuChild->SendCollectPerfStatsJSON(
209             [collector, gpuChild](const nsCString& aString) {
210               collector->AppendPerfStats(aString, gpuChild);
211             },
212             // The only feasible errors here are if something goes wrong in the
213             // the bridge, we choose to ignore those.
214             [](mozilla::ipc::ResponseRejectReason) {});
215       }
216       for (ContentParent* parent : contentParents) {
217         RefPtr<ContentParent> parentRef = parent;
218         parent->SendCollectPerfStatsJSON(
219             [collector, parentRef](const nsCString& aString) {
220               collector->AppendPerfStats(aString, parentRef.get());
221             },
222             // The only feasible errors here are if something goes wrong in the
223             // the bridge, we choose to ignore those.
224             [](mozilla::ipc::ResponseRejectReason) {});
225       }
226     }
227   }
228 
229   return collector->promise.Ensure(__func__);
230 }
231 
CollectLocalPerfStatsJSONInternal()232 nsCString PerfStats::CollectLocalPerfStatsJSONInternal() {
233   StaticMutexAutoLock lock(PerfStats::sMutex);
234 
235   nsCString jsonString;
236 
237   JSONWriter w(MakeUnique<StringWriteFunc>(jsonString));
238   w.Start();
239   {
240     w.StartArrayProperty("metrics");
241     {
242       for (uint64_t i = 0; i < static_cast<uint64_t>(Metric::Max); i++) {
243         if (!(sCollectionMask & (1 << i))) {
244           continue;
245         }
246 
247         w.StartObjectElement();
248         {
249           w.IntProperty("id", i);
250           w.StringProperty("metric", sMetricNames[i]);
251           w.DoubleProperty("time", mRecordedTimes[i]);
252         }
253         w.EndObject();
254       }
255     }
256     w.EndArray();
257   }
258   w.End();
259 
260   return jsonString;
261 }
262 
263 }  // namespace mozilla
264