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