/src/mozilla-central/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | #include "nsPerformanceStats.h" |
6 | | |
7 | | #include "nsMemory.h" |
8 | | #include "nsLiteralString.h" |
9 | | #include "nsCRTGlue.h" |
10 | | #include "nsServiceManagerUtils.h" |
11 | | |
12 | | #include "nsCOMArray.h" |
13 | | #include "nsContentUtils.h" |
14 | | #include "nsIMutableArray.h" |
15 | | #include "nsReadableUtils.h" |
16 | | |
17 | | #include "jsapi.h" |
18 | | #include "nsJSUtils.h" |
19 | | #include "xpcpublic.h" |
20 | | #include "jspubtd.h" |
21 | | |
22 | | #include "nsIDOMWindow.h" |
23 | | #include "nsGlobalWindow.h" |
24 | | #include "nsRefreshDriver.h" |
25 | | #include "nsThreadUtils.h" |
26 | | |
27 | | #include "mozilla/Unused.h" |
28 | | #include "mozilla/ArrayUtils.h" |
29 | | #include "mozilla/dom/ScriptSettings.h" |
30 | | #include "mozilla/EventStateManager.h" |
31 | | #include "mozilla/Services.h" |
32 | | #include "mozilla/Telemetry.h" |
33 | | |
34 | | #if defined(XP_WIN) |
35 | | #include <processthreadsapi.h> |
36 | | #include <windows.h> |
37 | | #else |
38 | | #include <unistd.h> |
39 | | #endif // defined(XP_WIN) |
40 | | |
41 | | #if defined(XP_MACOSX) |
42 | | #include <mach/mach_init.h> |
43 | | #include <mach/mach_interface.h> |
44 | | #include <mach/mach_port.h> |
45 | | #include <mach/mach_types.h> |
46 | | #include <mach/message.h> |
47 | | #include <mach/thread_info.h> |
48 | | #elif defined(XP_UNIX) |
49 | | #include <sys/time.h> |
50 | | #include <sys/resource.h> |
51 | | #endif // defined(XP_UNIX) |
52 | | /* ------------------------------------------------------ |
53 | | * |
54 | | * Utility functions. |
55 | | * |
56 | | */ |
57 | | |
58 | | namespace { |
59 | | |
60 | | /** |
61 | | * Get the private window for the current compartment. |
62 | | * |
63 | | * @return null if the code is not executed in a window or in |
64 | | * case of error, a nsPIDOMWindow otherwise. |
65 | | */ |
66 | | already_AddRefed<nsPIDOMWindowOuter> |
67 | 0 | GetPrivateWindow(JSContext* cx) { |
68 | 0 | nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx); |
69 | 0 | if (!win) { |
70 | 0 | return nullptr; |
71 | 0 | } |
72 | 0 | |
73 | 0 | nsPIDOMWindowOuter* outer = win->AsInner()->GetOuterWindow(); |
74 | 0 | if (!outer) { |
75 | 0 | return nullptr; |
76 | 0 | } |
77 | 0 | |
78 | 0 | nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop(); |
79 | 0 | if (!top) { |
80 | 0 | return nullptr; |
81 | 0 | } |
82 | 0 | |
83 | 0 | return top.forget(); |
84 | 0 | } |
85 | | |
86 | | bool |
87 | 0 | URLForGlobal(JSContext* cx, JS::Handle<JSObject*> global, nsAString& url) { |
88 | 0 | nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(global); |
89 | 0 | if (!principal) { |
90 | 0 | return false; |
91 | 0 | } |
92 | 0 | |
93 | 0 | nsCOMPtr<nsIURI> uri; |
94 | 0 | nsresult rv = principal->GetURI(getter_AddRefs(uri)); |
95 | 0 | if (NS_FAILED(rv) || !uri) { |
96 | 0 | return false; |
97 | 0 | } |
98 | 0 | |
99 | 0 | nsAutoCString spec; |
100 | 0 | rv = uri->GetSpec(spec); |
101 | 0 | if (NS_FAILED(rv)) { |
102 | 0 | return false; |
103 | 0 | } |
104 | 0 | |
105 | 0 | url.Assign(NS_ConvertUTF8toUTF16(spec)); |
106 | 0 | return true; |
107 | 0 | } |
108 | | |
109 | | /** |
110 | | * Extract a somewhat human-readable name from the current context. |
111 | | */ |
112 | | void |
113 | 0 | RealmName(JSContext* cx, JS::Handle<JSObject*> global, nsAString& name) { |
114 | 0 | // Attempt to use the URL as name. |
115 | 0 | if (URLForGlobal(cx, global, name)) { |
116 | 0 | return; |
117 | 0 | } |
118 | 0 | |
119 | 0 | // Otherwise, fallback to XPConnect's less readable but more |
120 | 0 | // complete naming scheme. |
121 | 0 | nsAutoCString cname; |
122 | 0 | xpc::GetCurrentRealmName(cx, cname); |
123 | 0 | name.Assign(NS_ConvertUTF8toUTF16(cname)); |
124 | 0 | } |
125 | | |
126 | | /** |
127 | | * Generate a unique-to-the-application identifier for a group. |
128 | | */ |
129 | | void |
130 | | GenerateUniqueGroupId(uint64_t uid, uint64_t processId, nsAString& groupId) |
131 | 0 | { |
132 | 0 | uint64_t threadId = reinterpret_cast<uint64_t>(mozilla::GetCurrentPhysicalThread()); |
133 | 0 |
|
134 | 0 | groupId.AssignLiteral("process: "); |
135 | 0 | groupId.AppendInt(processId); |
136 | 0 | groupId.AppendLiteral(", thread: "); |
137 | 0 | groupId.AppendInt(threadId); |
138 | 0 | groupId.AppendLiteral(", group: "); |
139 | 0 | groupId.AppendInt(uid); |
140 | 0 | } |
141 | | |
142 | | static const char* TOPICS[] = { |
143 | | "profile-before-change", |
144 | | "quit-application", |
145 | | "quit-application-granted", |
146 | | "xpcom-will-shutdown" |
147 | | }; |
148 | | |
149 | | } // namespace |
150 | | |
151 | | /* ------------------------------------------------------ |
152 | | * |
153 | | * class nsPerformanceObservationTarget |
154 | | * |
155 | | */ |
156 | | |
157 | | |
158 | | NS_IMPL_ISUPPORTS(nsPerformanceObservationTarget, nsIPerformanceObservable) |
159 | | |
160 | | |
161 | | |
162 | | NS_IMETHODIMP |
163 | 0 | nsPerformanceObservationTarget::GetTarget(nsIPerformanceGroupDetails** _result) { |
164 | 0 | if (mDetails) { |
165 | 0 | NS_IF_ADDREF(*_result = mDetails); |
166 | 0 | } |
167 | 0 | return NS_OK; |
168 | 0 | }; |
169 | | |
170 | | void |
171 | 0 | nsPerformanceObservationTarget::SetTarget(nsPerformanceGroupDetails* details) { |
172 | 0 | MOZ_ASSERT(!mDetails); |
173 | 0 | mDetails = details; |
174 | 0 | }; |
175 | | |
176 | | NS_IMETHODIMP |
177 | 0 | nsPerformanceObservationTarget::AddJankObserver(nsIPerformanceObserver* observer) { |
178 | 0 | if (!mObservers.append(observer)) { |
179 | 0 | MOZ_CRASH(); |
180 | 0 | } |
181 | 0 | return NS_OK; |
182 | 0 | }; |
183 | | |
184 | | NS_IMETHODIMP |
185 | 0 | nsPerformanceObservationTarget::RemoveJankObserver(nsIPerformanceObserver* observer) { |
186 | 0 | for (auto iter = mObservers.begin(), end = mObservers.end(); iter < end; ++iter) { |
187 | 0 | if (*iter == observer) { |
188 | 0 | mObservers.erase(iter); |
189 | 0 | return NS_OK; |
190 | 0 | } |
191 | 0 | } |
192 | 0 | return NS_OK; |
193 | 0 | }; |
194 | | |
195 | | bool |
196 | 0 | nsPerformanceObservationTarget::HasObservers() const { |
197 | 0 | return !mObservers.empty(); |
198 | 0 | } |
199 | | |
200 | | void |
201 | 0 | nsPerformanceObservationTarget::NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity) { |
202 | 0 | // Copy the vector to make sure that it won't change under our feet. |
203 | 0 | mozilla::Vector<nsCOMPtr<nsIPerformanceObserver>> observers; |
204 | 0 | if (!observers.appendAll(mObservers)) { |
205 | 0 | MOZ_CRASH(); |
206 | 0 | } |
207 | 0 |
|
208 | 0 | // Now actually notify. |
209 | 0 | for (auto iter = observers.begin(), end = observers.end(); iter < end; ++iter) { |
210 | 0 | nsCOMPtr<nsIPerformanceObserver> observer = *iter; |
211 | 0 | mozilla::Unused << observer->Observe(source, gravity); |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | /* ------------------------------------------------------ |
216 | | * |
217 | | * class nsGroupHolder |
218 | | * |
219 | | */ |
220 | | |
221 | | nsPerformanceObservationTarget* |
222 | 0 | nsGroupHolder::ObservationTarget() { |
223 | 0 | if (!mPendingObservationTarget) { |
224 | 0 | mPendingObservationTarget = new nsPerformanceObservationTarget(); |
225 | 0 | } |
226 | 0 | return mPendingObservationTarget; |
227 | 0 | } |
228 | | |
229 | | nsPerformanceGroup* |
230 | 0 | nsGroupHolder::GetGroup() { |
231 | 0 | return mGroup; |
232 | 0 | } |
233 | | |
234 | | void |
235 | 0 | nsGroupHolder::SetGroup(nsPerformanceGroup* group) { |
236 | 0 | MOZ_ASSERT(!mGroup); |
237 | 0 | mGroup = group; |
238 | 0 | group->SetObservationTarget(ObservationTarget()); |
239 | 0 | mPendingObservationTarget->SetTarget(group->Details()); |
240 | 0 | } |
241 | | |
242 | | /* ------------------------------------------------------ |
243 | | * |
244 | | * struct PerformanceData |
245 | | * |
246 | | */ |
247 | | |
248 | | PerformanceData::PerformanceData() |
249 | | : mTotalUserTime(0) |
250 | | , mTotalSystemTime(0) |
251 | | , mTotalCPOWTime(0) |
252 | | , mTicks(0) |
253 | 0 | { |
254 | 0 | mozilla::PodArrayZero(mDurations); |
255 | 0 | } |
256 | | |
257 | | /* ------------------------------------------------------ |
258 | | * |
259 | | * class nsPerformanceGroupDetails |
260 | | * |
261 | | */ |
262 | | |
263 | | NS_IMPL_ISUPPORTS(nsPerformanceGroupDetails, nsIPerformanceGroupDetails) |
264 | | |
265 | | const nsAString& |
266 | 0 | nsPerformanceGroupDetails::Name() const { |
267 | 0 | return mName; |
268 | 0 | } |
269 | | |
270 | | const nsAString& |
271 | 0 | nsPerformanceGroupDetails::GroupId() const { |
272 | 0 | return mGroupId; |
273 | 0 | } |
274 | | |
275 | | uint64_t |
276 | 0 | nsPerformanceGroupDetails::WindowId() const { |
277 | 0 | return mWindowId; |
278 | 0 | } |
279 | | |
280 | | uint64_t |
281 | 0 | nsPerformanceGroupDetails::ProcessId() const { |
282 | 0 | return mProcessId; |
283 | 0 | } |
284 | | |
285 | | bool |
286 | 0 | nsPerformanceGroupDetails::IsSystem() const { |
287 | 0 | return mIsSystem; |
288 | 0 | } |
289 | | |
290 | | bool |
291 | 0 | nsPerformanceGroupDetails::IsWindow() const { |
292 | 0 | return mWindowId != 0; |
293 | 0 | } |
294 | | |
295 | | bool |
296 | 0 | nsPerformanceGroupDetails::IsContentProcess() const { |
297 | 0 | return XRE_GetProcessType() == GeckoProcessType_Content; |
298 | 0 | } |
299 | | |
300 | | /* readonly attribute AString name; */ |
301 | | NS_IMETHODIMP |
302 | 0 | nsPerformanceGroupDetails::GetName(nsAString& aName) { |
303 | 0 | aName.Assign(Name()); |
304 | 0 | return NS_OK; |
305 | 0 | }; |
306 | | |
307 | | /* readonly attribute AString groupId; */ |
308 | | NS_IMETHODIMP |
309 | 0 | nsPerformanceGroupDetails::GetGroupId(nsAString& aGroupId) { |
310 | 0 | aGroupId.Assign(GroupId()); |
311 | 0 | return NS_OK; |
312 | 0 | }; |
313 | | |
314 | | /* readonly attribute uint64_t windowId; */ |
315 | | NS_IMETHODIMP |
316 | 0 | nsPerformanceGroupDetails::GetWindowId(uint64_t *aWindowId) { |
317 | 0 | *aWindowId = WindowId(); |
318 | 0 | return NS_OK; |
319 | 0 | } |
320 | | |
321 | | /* readonly attribute bool isSystem; */ |
322 | | NS_IMETHODIMP |
323 | 0 | nsPerformanceGroupDetails::GetIsSystem(bool *_retval) { |
324 | 0 | *_retval = IsSystem(); |
325 | 0 | return NS_OK; |
326 | 0 | } |
327 | | |
328 | | /* |
329 | | readonly attribute unsigned long long processId; |
330 | | */ |
331 | | NS_IMETHODIMP |
332 | 0 | nsPerformanceGroupDetails::GetProcessId(uint64_t* processId) { |
333 | 0 | *processId = ProcessId(); |
334 | 0 | return NS_OK; |
335 | 0 | } |
336 | | |
337 | | /* readonly attribute bool IsContentProcess; */ |
338 | | NS_IMETHODIMP |
339 | 0 | nsPerformanceGroupDetails::GetIsContentProcess(bool *_retval) { |
340 | 0 | *_retval = IsContentProcess(); |
341 | 0 | return NS_OK; |
342 | 0 | } |
343 | | |
344 | | |
345 | | /* ------------------------------------------------------ |
346 | | * |
347 | | * class nsPerformanceStats |
348 | | * |
349 | | */ |
350 | | |
351 | | class nsPerformanceStats final: public nsIPerformanceStats |
352 | | { |
353 | | public: |
354 | | NS_DECL_ISUPPORTS |
355 | | NS_DECL_NSIPERFORMANCESTATS |
356 | | NS_FORWARD_NSIPERFORMANCEGROUPDETAILS(mDetails->) |
357 | | |
358 | | nsPerformanceStats(nsPerformanceGroupDetails* item, |
359 | | const PerformanceData& aPerformanceData) |
360 | | : mDetails(item) |
361 | | , mPerformanceData(aPerformanceData) |
362 | 0 | { |
363 | 0 | } |
364 | | |
365 | | |
366 | | private: |
367 | | RefPtr<nsPerformanceGroupDetails> mDetails; |
368 | | PerformanceData mPerformanceData; |
369 | | |
370 | 0 | ~nsPerformanceStats() {} |
371 | | }; |
372 | | |
373 | | NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats, nsIPerformanceGroupDetails) |
374 | | |
375 | | /* readonly attribute unsigned long long totalUserTime; */ |
376 | | NS_IMETHODIMP |
377 | 0 | nsPerformanceStats::GetTotalUserTime(uint64_t *aTotalUserTime) { |
378 | 0 | *aTotalUserTime = mPerformanceData.mTotalUserTime; |
379 | 0 | return NS_OK; |
380 | 0 | }; |
381 | | |
382 | | /* readonly attribute unsigned long long totalSystemTime; */ |
383 | | NS_IMETHODIMP |
384 | 0 | nsPerformanceStats::GetTotalSystemTime(uint64_t *aTotalSystemTime) { |
385 | 0 | *aTotalSystemTime = mPerformanceData.mTotalSystemTime; |
386 | 0 | return NS_OK; |
387 | 0 | }; |
388 | | |
389 | | /* readonly attribute unsigned long long totalCPOWTime; */ |
390 | | NS_IMETHODIMP |
391 | 0 | nsPerformanceStats::GetTotalCPOWTime(uint64_t *aCpowTime) { |
392 | 0 | *aCpowTime = mPerformanceData.mTotalCPOWTime; |
393 | 0 | return NS_OK; |
394 | 0 | }; |
395 | | |
396 | | /* readonly attribute unsigned long long ticks; */ |
397 | | NS_IMETHODIMP |
398 | 0 | nsPerformanceStats::GetTicks(uint64_t *aTicks) { |
399 | 0 | *aTicks = mPerformanceData.mTicks; |
400 | 0 | return NS_OK; |
401 | 0 | }; |
402 | | |
403 | | /* void getDurations (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */ |
404 | | NS_IMETHODIMP |
405 | 0 | nsPerformanceStats::GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) { |
406 | 0 | const size_t length = mozilla::ArrayLength(mPerformanceData.mDurations); |
407 | 0 | if (aCount) { |
408 | 0 | *aCount = length; |
409 | 0 | } |
410 | 0 | *aNumberOfOccurrences = new uint64_t[length]; |
411 | 0 | for (size_t i = 0; i < length; ++i) { |
412 | 0 | (*aNumberOfOccurrences)[i] = mPerformanceData.mDurations[i]; |
413 | 0 | } |
414 | 0 | return NS_OK; |
415 | 0 | }; |
416 | | |
417 | | |
418 | | /* ------------------------------------------------------ |
419 | | * |
420 | | * struct nsPerformanceSnapshot |
421 | | * |
422 | | */ |
423 | | |
424 | | class nsPerformanceSnapshot final : public nsIPerformanceSnapshot |
425 | | { |
426 | | public: |
427 | | NS_DECL_ISUPPORTS |
428 | | NS_DECL_NSIPERFORMANCESNAPSHOT |
429 | | |
430 | 0 | nsPerformanceSnapshot() {} |
431 | | |
432 | | /** |
433 | | * Append statistics to the list of components data. |
434 | | */ |
435 | | void AppendComponentsStats(nsIPerformanceStats* stats); |
436 | | |
437 | | /** |
438 | | * Set the statistics attached to process data. |
439 | | */ |
440 | | void SetProcessStats(nsIPerformanceStats* group); |
441 | | |
442 | | private: |
443 | 0 | ~nsPerformanceSnapshot() {} |
444 | | |
445 | | private: |
446 | | /** |
447 | | * The data for all components. |
448 | | */ |
449 | | nsCOMArray<nsIPerformanceStats> mComponentsData; |
450 | | |
451 | | /** |
452 | | * The data for the process. |
453 | | */ |
454 | | nsCOMPtr<nsIPerformanceStats> mProcessData; |
455 | | }; |
456 | | |
457 | | NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot) |
458 | | |
459 | | |
460 | | /* nsIArray getComponentsData (); */ |
461 | | NS_IMETHODIMP |
462 | | nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents) |
463 | 0 | { |
464 | 0 | const size_t length = mComponentsData.Length(); |
465 | 0 | nsCOMPtr<nsIMutableArray> components = do_CreateInstance(NS_ARRAY_CONTRACTID); |
466 | 0 | for (size_t i = 0; i < length; ++i) { |
467 | 0 | nsCOMPtr<nsIPerformanceStats> stats = mComponentsData[i]; |
468 | 0 | mozilla::DebugOnly<nsresult> rv = components->AppendElement(stats); |
469 | 0 | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
470 | 0 | } |
471 | 0 | components.forget(aComponents); |
472 | 0 | return NS_OK; |
473 | 0 | } |
474 | | |
475 | | /* nsIPerformanceStats getProcessData (); */ |
476 | | NS_IMETHODIMP |
477 | | nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess) |
478 | 0 | { |
479 | 0 | NS_IF_ADDREF(*aProcess = mProcessData); |
480 | 0 | return NS_OK; |
481 | 0 | } |
482 | | |
483 | | void |
484 | | nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats) |
485 | 0 | { |
486 | 0 | mComponentsData.AppendElement(stats); |
487 | 0 | } |
488 | | |
489 | | void |
490 | | nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats) |
491 | 0 | { |
492 | 0 | mProcessData = stats; |
493 | 0 | } |
494 | | |
495 | | |
496 | | |
497 | | /* ------------------------------------------------------ |
498 | | * |
499 | | * class PerformanceAlert |
500 | | * |
501 | | */ |
502 | | class PerformanceAlert final: public nsIPerformanceAlert { |
503 | | public: |
504 | | NS_DECL_ISUPPORTS |
505 | | NS_DECL_NSIPERFORMANCEALERT |
506 | | |
507 | | PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source); |
508 | | private: |
509 | 0 | ~PerformanceAlert() {} |
510 | | |
511 | | const uint32_t mReason; |
512 | | |
513 | | // The highest values reached by this group since the latest alert, |
514 | | // in microseconds. |
515 | | const uint64_t mHighestJank; |
516 | | const uint64_t mHighestCPOW; |
517 | | }; |
518 | | |
519 | | NS_IMPL_ISUPPORTS(PerformanceAlert, nsIPerformanceAlert); |
520 | | |
521 | | PerformanceAlert::PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source) |
522 | | : mReason(reason) |
523 | | , mHighestJank(source->HighestRecentJank()) |
524 | | , mHighestCPOW(source->HighestRecentCPOW()) |
525 | 0 | { } |
526 | | |
527 | | NS_IMETHODIMP |
528 | 0 | PerformanceAlert::GetHighestJank(uint64_t* result) { |
529 | 0 | *result = mHighestJank; |
530 | 0 | return NS_OK; |
531 | 0 | } |
532 | | |
533 | | NS_IMETHODIMP |
534 | 0 | PerformanceAlert::GetHighestCPOW(uint64_t* result) { |
535 | 0 | *result = mHighestCPOW; |
536 | 0 | return NS_OK; |
537 | 0 | } |
538 | | |
539 | | NS_IMETHODIMP |
540 | 0 | PerformanceAlert::GetReason(uint32_t* result) { |
541 | 0 | *result = mReason; |
542 | 0 | return NS_OK; |
543 | 0 | } |
544 | | /* ------------------------------------------------------ |
545 | | * |
546 | | * class PendingAlertsCollector |
547 | | * |
548 | | */ |
549 | | |
550 | | /** |
551 | | * A timer callback in charge of collecting the groups in |
552 | | * `mPendingAlerts` and triggering dispatch of performance alerts. |
553 | | */ |
554 | | class PendingAlertsCollector final : |
555 | | public nsITimerCallback, |
556 | | public nsINamed |
557 | | { |
558 | | public: |
559 | | NS_DECL_ISUPPORTS |
560 | | NS_DECL_NSITIMERCALLBACK |
561 | | NS_DECL_NSINAMED |
562 | | |
563 | | explicit PendingAlertsCollector(nsPerformanceStatsService* service) |
564 | | : mService(service) |
565 | | , mPending(false) |
566 | 0 | { } |
567 | | |
568 | | nsresult Start(uint32_t timerDelayMS); |
569 | | nsresult Dispose(); |
570 | | |
571 | | private: |
572 | 0 | ~PendingAlertsCollector() {} |
573 | | |
574 | | RefPtr<nsPerformanceStatsService> mService; |
575 | | bool mPending; |
576 | | |
577 | | nsCOMPtr<nsITimer> mTimer; |
578 | | |
579 | | mozilla::Vector<uint64_t> mJankLevels; |
580 | | }; |
581 | | |
582 | | NS_IMPL_ISUPPORTS(PendingAlertsCollector, nsITimerCallback, nsINamed); |
583 | | |
584 | | NS_IMETHODIMP |
585 | 0 | PendingAlertsCollector::Notify(nsITimer*) { |
586 | 0 | mPending = false; |
587 | 0 | mService->NotifyJankObservers(mJankLevels); |
588 | 0 | return NS_OK; |
589 | 0 | } |
590 | | |
591 | | NS_IMETHODIMP |
592 | | PendingAlertsCollector::GetName(nsACString& aName) |
593 | 0 | { |
594 | 0 | aName.AssignLiteral("PendingAlertsCollector_timer"); |
595 | 0 | return NS_OK; |
596 | 0 | } |
597 | | |
598 | | nsresult |
599 | 0 | PendingAlertsCollector::Start(uint32_t timerDelayMS) { |
600 | 0 | if (mPending) { |
601 | 0 | // Collector is already started. |
602 | 0 | return NS_OK; |
603 | 0 | } |
604 | 0 | |
605 | 0 | if (!mTimer) { |
606 | 0 | mTimer = NS_NewTimer(); |
607 | 0 | } |
608 | 0 |
|
609 | 0 | nsresult rv = mTimer->InitWithCallback(this, timerDelayMS, nsITimer::TYPE_ONE_SHOT); |
610 | 0 | if (NS_FAILED(rv)) { |
611 | 0 | return rv; |
612 | 0 | } |
613 | 0 | |
614 | 0 | mPending = true; |
615 | 0 | { |
616 | 0 | mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(mJankLevels); |
617 | 0 | MOZ_ASSERT(result); |
618 | 0 | } |
619 | 0 |
|
620 | 0 | return NS_OK; |
621 | 0 | } |
622 | | |
623 | | nsresult |
624 | 0 | PendingAlertsCollector::Dispose() { |
625 | 0 | if (mTimer) { |
626 | 0 | mozilla::Unused << mTimer->Cancel(); |
627 | 0 | mTimer = nullptr; |
628 | 0 | } |
629 | 0 | mService = nullptr; |
630 | 0 | return NS_OK; |
631 | 0 | } |
632 | | |
633 | | |
634 | | |
635 | | /* ------------------------------------------------------ |
636 | | * |
637 | | * class nsPerformanceStatsService |
638 | | * |
639 | | */ |
640 | | |
641 | | NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver) |
642 | | |
643 | | nsPerformanceStatsService::nsPerformanceStatsService() |
644 | | : mIsAvailable(false) |
645 | | , mDisposed(false) |
646 | | #if defined(XP_WIN) |
647 | | , mProcessId(GetCurrentProcessId()) |
648 | | #else |
649 | | , mProcessId(getpid()) |
650 | | #endif |
651 | | , mUIdCounter(0) |
652 | | , mTopGroup(nsPerformanceGroup::Make(this, |
653 | | NS_LITERAL_STRING("<process>"), // name |
654 | | 0, // windowId |
655 | | mProcessId, |
656 | | true, // isSystem |
657 | | nsPerformanceGroup::GroupScope::RUNTIME // scope |
658 | | )) |
659 | | , mIsHandlingUserInput(false) |
660 | | , mProcessStayed(0) |
661 | | , mProcessMoved(0) |
662 | | , mProcessUpdateCounter(0) |
663 | | , mIsMonitoringPerCompartment(false) |
664 | | , mJankAlertThreshold(mozilla::MaxValue<uint64_t>::value) // By default, no alerts |
665 | | , mJankAlertBufferingDelay(1000 /* ms */) |
666 | | , mJankLevelVisibilityThreshold(/* 2 ^ */ 8 /* ms */) |
667 | | , mMaxExpectedDurationOfInteractionUS(150 * 1000) |
668 | 0 | { |
669 | 0 | mPendingAlertsCollector = new PendingAlertsCollector(this); |
670 | 0 |
|
671 | 0 | nsString groupIdForWindows; |
672 | 0 | GenerateUniqueGroupId(GetNextId(), mProcessId, groupIdForWindows); |
673 | 0 | mUniversalTargets.mWindows-> |
674 | 0 | SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal window listener>"), |
675 | 0 | groupIdForWindows, |
676 | 0 | 0, // window id |
677 | 0 | mProcessId, |
678 | 0 | false)); |
679 | 0 | } |
680 | | |
681 | | nsPerformanceStatsService::~nsPerformanceStatsService() |
682 | 0 | { } |
683 | | |
684 | | /** |
685 | | * Clean up the service. |
686 | | * |
687 | | * Called during shutdown. Idempotent. |
688 | | */ |
689 | | void |
690 | | nsPerformanceStatsService::Dispose() |
691 | 0 | { |
692 | 0 | // Make sure that we do not accidentally destroy `this` while we are |
693 | 0 | // cleaning up back references. |
694 | 0 | RefPtr<nsPerformanceStatsService> kungFuDeathGrip(this); |
695 | 0 | mIsAvailable = false; |
696 | 0 |
|
697 | 0 | if (mDisposed) { |
698 | 0 | // Make sure that we don't double-dispose. |
699 | 0 | return; |
700 | 0 | } |
701 | 0 | mDisposed = true; |
702 | 0 |
|
703 | 0 | // Disconnect from nsIObserverService. |
704 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
705 | 0 | if (obs) { |
706 | 0 | for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) { |
707 | 0 | mozilla::Unused << obs->RemoveObserver(this, TOPICS[i]); |
708 | 0 | } |
709 | 0 | } |
710 | 0 |
|
711 | 0 | // Clear up and disconnect from JSAPI. |
712 | 0 | mozilla::dom::AutoJSAPI jsapi; |
713 | 0 | jsapi.Init(); |
714 | 0 | JSContext* cx = jsapi.cx(); |
715 | 0 | js::DisposePerformanceMonitoring(cx); |
716 | 0 |
|
717 | 0 | mozilla::Unused << js::SetStopwatchIsMonitoringCPOW(cx, false); |
718 | 0 | mozilla::Unused << js::SetStopwatchIsMonitoringJank(cx, false); |
719 | 0 |
|
720 | 0 | mozilla::Unused << js::SetStopwatchStartCallback(cx, nullptr, nullptr); |
721 | 0 | mozilla::Unused << js::SetStopwatchCommitCallback(cx, nullptr, nullptr); |
722 | 0 | mozilla::Unused << js::SetGetPerformanceGroupsCallback(cx, nullptr, nullptr); |
723 | 0 |
|
724 | 0 | // Clear up and disconnect the alerts collector. |
725 | 0 | if (mPendingAlertsCollector) { |
726 | 0 | mPendingAlertsCollector->Dispose(); |
727 | 0 | mPendingAlertsCollector = nullptr; |
728 | 0 | } |
729 | 0 | mPendingAlerts.clear(); |
730 | 0 |
|
731 | 0 | // Disconnect universal observers. Per-group observers will be |
732 | 0 | // disconnected below as part of `group->Dispose()`. |
733 | 0 | mUniversalTargets.mWindows = nullptr; |
734 | 0 |
|
735 | 0 | // At this stage, the JS VM may still be holding references to |
736 | 0 | // instances of PerformanceGroup on the stack. To let the service be |
737 | 0 | // collected, we need to break the references from these groups to |
738 | 0 | // `this`. |
739 | 0 | mTopGroup->Dispose(); |
740 | 0 | mTopGroup = nullptr; |
741 | 0 |
|
742 | 0 | // Copy references to the groups to a vector to ensure that we do |
743 | 0 | // not modify the hashtable while iterating it. |
744 | 0 | GroupVector groups; |
745 | 0 | for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) { |
746 | 0 | if (!groups.append(iter.Get()->GetKey())) { |
747 | 0 | MOZ_CRASH(); |
748 | 0 | } |
749 | 0 | } |
750 | 0 | for (auto iter = groups.begin(), end = groups.end(); iter < end; ++iter) { |
751 | 0 | RefPtr<nsPerformanceGroup> group = *iter; |
752 | 0 | group->Dispose(); |
753 | 0 | } |
754 | 0 |
|
755 | 0 | // Any remaining references to PerformanceGroup will be released as |
756 | 0 | // the VM unrolls the stack. If there are any nested event loops, |
757 | 0 | // this may take time. |
758 | 0 | } |
759 | | |
760 | | nsresult |
761 | | nsPerformanceStatsService::Init() |
762 | 0 | { |
763 | 0 | nsresult rv = InitInternal(); |
764 | 0 | if (NS_FAILED(rv)) { |
765 | 0 | // Attempt to clean up. |
766 | 0 | Dispose(); |
767 | 0 | } |
768 | 0 | return rv; |
769 | 0 | } |
770 | | |
771 | | nsresult |
772 | | nsPerformanceStatsService::InitInternal() |
773 | 0 | { |
774 | 0 | // Make sure that we release everything during shutdown. |
775 | 0 | // We are a bit defensive here, as we know that some strange behavior can break the |
776 | 0 | // regular shutdown order. |
777 | 0 | nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); |
778 | 0 | if (obs) { |
779 | 0 | for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) { |
780 | 0 | mozilla::Unused << obs->AddObserver(this, TOPICS[i], false); |
781 | 0 | } |
782 | 0 | } |
783 | 0 |
|
784 | 0 | // Connect to JSAPI. |
785 | 0 | mozilla::dom::AutoJSAPI jsapi; |
786 | 0 | jsapi.Init(); |
787 | 0 | JSContext* cx = jsapi.cx(); |
788 | 0 | if (!js::SetStopwatchStartCallback(cx, StopwatchStartCallback, this)) { |
789 | 0 | return NS_ERROR_UNEXPECTED; |
790 | 0 | } |
791 | 0 | if (!js::SetStopwatchCommitCallback(cx, StopwatchCommitCallback, this)) { |
792 | 0 | return NS_ERROR_UNEXPECTED; |
793 | 0 | } |
794 | 0 | if (!js::SetGetPerformanceGroupsCallback(cx, GetPerformanceGroupsCallback, this)) { |
795 | 0 | return NS_ERROR_UNEXPECTED; |
796 | 0 | } |
797 | 0 | |
798 | 0 | mTopGroup->setIsActive(true); |
799 | 0 | mIsAvailable = true; |
800 | 0 |
|
801 | 0 | return NS_OK; |
802 | 0 | } |
803 | | |
804 | | // Observe shutdown events. |
805 | | NS_IMETHODIMP |
806 | | nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic, |
807 | | const char16_t *aData) |
808 | 0 | { |
809 | 0 | MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0 |
810 | 0 | || strcmp(aTopic, "quit-application") == 0 |
811 | 0 | || strcmp(aTopic, "quit-application-granted") == 0 |
812 | 0 | || strcmp(aTopic, "xpcom-will-shutdown") == 0); |
813 | 0 |
|
814 | 0 | Dispose(); |
815 | 0 | return NS_OK; |
816 | 0 | } |
817 | | |
818 | | /*static*/ bool |
819 | 0 | nsPerformanceStatsService::IsHandlingUserInput() { |
820 | 0 | if (mozilla::EventStateManager::LatestUserInputStart().IsNull()) { |
821 | 0 | return false; |
822 | 0 | } |
823 | 0 | bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMicroseconds(mMaxExpectedDurationOfInteractionUS); |
824 | 0 | return result; |
825 | 0 | } |
826 | | |
827 | | /* [implicit_jscontext] attribute bool isMonitoringCPOW; */ |
828 | | NS_IMETHODIMP |
829 | | nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive) |
830 | 0 | { |
831 | 0 | if (!mIsAvailable) { |
832 | 0 | return NS_ERROR_NOT_AVAILABLE; |
833 | 0 | } |
834 | 0 | |
835 | 0 | *aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(cx); |
836 | 0 | return NS_OK; |
837 | 0 | } |
838 | | NS_IMETHODIMP |
839 | | nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive) |
840 | 0 | { |
841 | 0 | if (!mIsAvailable) { |
842 | 0 | return NS_ERROR_NOT_AVAILABLE; |
843 | 0 | } |
844 | 0 | |
845 | 0 | if (!js::SetStopwatchIsMonitoringCPOW(cx, aIsStopwatchActive)) { |
846 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
847 | 0 | } |
848 | 0 | return NS_OK; |
849 | 0 | } |
850 | | |
851 | | /* [implicit_jscontext] attribute bool isMonitoringJank; */ |
852 | | NS_IMETHODIMP |
853 | | nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive) |
854 | 0 | { |
855 | 0 | if (!mIsAvailable) { |
856 | 0 | return NS_ERROR_NOT_AVAILABLE; |
857 | 0 | } |
858 | 0 | |
859 | 0 | *aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(cx); |
860 | 0 | return NS_OK; |
861 | 0 | } |
862 | | NS_IMETHODIMP |
863 | | nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive) |
864 | 0 | { |
865 | 0 | if (!mIsAvailable) { |
866 | 0 | return NS_ERROR_NOT_AVAILABLE; |
867 | 0 | } |
868 | 0 | |
869 | 0 | if (!js::SetStopwatchIsMonitoringJank(cx, aIsStopwatchActive)) { |
870 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
871 | 0 | } |
872 | 0 | return NS_OK; |
873 | 0 | } |
874 | | |
875 | | /* [implicit_jscontext] attribute bool isMonitoringPerCompartment; */ |
876 | | NS_IMETHODIMP |
877 | | nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment) |
878 | 0 | { |
879 | 0 | if (!mIsAvailable) { |
880 | 0 | return NS_ERROR_NOT_AVAILABLE; |
881 | 0 | } |
882 | 0 | |
883 | 0 | *aIsMonitoringPerCompartment = mIsMonitoringPerCompartment; |
884 | 0 | return NS_OK; |
885 | 0 | } |
886 | | NS_IMETHODIMP |
887 | | nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment) |
888 | 0 | { |
889 | 0 | if (!mIsAvailable) { |
890 | 0 | return NS_ERROR_NOT_AVAILABLE; |
891 | 0 | } |
892 | 0 | |
893 | 0 | if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) { |
894 | 0 | return NS_OK; |
895 | 0 | } |
896 | 0 | |
897 | 0 | // Relatively slow update: walk the entire lost of performance groups, |
898 | 0 | // update the active flag of those that have changed. |
899 | 0 | // |
900 | 0 | // Alternative strategies could be envisioned to make the update |
901 | 0 | // much faster, at the expense of the speed of calling `isActive()`, |
902 | 0 | // (e.g. deferring `isActive()` to the nsPerformanceStatsService), |
903 | 0 | // but we expect that `isActive()` can be called thousands of times |
904 | 0 | // per second, while `SetIsMonitoringPerCompartment` is not called |
905 | 0 | // at all during most Firefox runs. |
906 | 0 | |
907 | 0 | for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) { |
908 | 0 | RefPtr<nsPerformanceGroup> group = iter.Get()->GetKey(); |
909 | 0 | if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) { |
910 | 0 | group->setIsActive(aIsMonitoringPerCompartment); |
911 | 0 | } |
912 | 0 | } |
913 | 0 | mIsMonitoringPerCompartment = aIsMonitoringPerCompartment; |
914 | 0 | return NS_OK; |
915 | 0 | } |
916 | | |
917 | | NS_IMETHODIMP |
918 | 0 | nsPerformanceStatsService::GetJankAlertThreshold(uint64_t* result) { |
919 | 0 | *result = mJankAlertThreshold; |
920 | 0 | return NS_OK; |
921 | 0 | } |
922 | | |
923 | | NS_IMETHODIMP |
924 | 0 | nsPerformanceStatsService::SetJankAlertThreshold(uint64_t value) { |
925 | 0 | mJankAlertThreshold = value; |
926 | 0 | return NS_OK; |
927 | 0 | } |
928 | | |
929 | | NS_IMETHODIMP |
930 | 0 | nsPerformanceStatsService::GetJankAlertBufferingDelay(uint32_t* result) { |
931 | 0 | *result = mJankAlertBufferingDelay; |
932 | 0 | return NS_OK; |
933 | 0 | } |
934 | | |
935 | | NS_IMETHODIMP |
936 | 0 | nsPerformanceStatsService::SetJankAlertBufferingDelay(uint32_t value) { |
937 | 0 | mJankAlertBufferingDelay = value; |
938 | 0 | return NS_OK; |
939 | 0 | } |
940 | | |
941 | | nsresult |
942 | | nsPerformanceStatsService::UpdateTelemetry() |
943 | 0 | { |
944 | 0 | // Promote everything to floating-point explicitly before dividing. |
945 | 0 | const double processStayed = mProcessStayed; |
946 | 0 | const double processMoved = mProcessMoved; |
947 | 0 |
|
948 | 0 | if (processStayed <= 0 || processMoved <= 0 || processStayed + processMoved <= 0) { |
949 | 0 | // Overflow/underflow/nothing to report |
950 | 0 | return NS_OK; |
951 | 0 | } |
952 | 0 | |
953 | 0 | const double proportion = (100 * processStayed) / (processStayed + processMoved); |
954 | 0 | if (proportion < 0 || proportion > 100) { |
955 | 0 | // Overflow/underflow |
956 | 0 | return NS_OK; |
957 | 0 | } |
958 | 0 | |
959 | 0 | mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED, (uint32_t)proportion); |
960 | 0 | return NS_OK; |
961 | 0 | } |
962 | | |
963 | | |
964 | | /* static */ nsIPerformanceStats* |
965 | | nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group) |
966 | 0 | { |
967 | 0 | return GetStatsForGroup(nsPerformanceGroup::Get(group)); |
968 | 0 | } |
969 | | |
970 | | /* static */ nsIPerformanceStats* |
971 | | nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group) |
972 | 0 | { |
973 | 0 | return new nsPerformanceStats(group->Details(), group->data); |
974 | 0 | } |
975 | | |
976 | | /* [implicit_jscontext] nsIPerformanceSnapshot getSnapshot (); */ |
977 | | NS_IMETHODIMP |
978 | | nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot) |
979 | 0 | { |
980 | 0 | if (!mIsAvailable) { |
981 | 0 | return NS_ERROR_NOT_AVAILABLE; |
982 | 0 | } |
983 | 0 | |
984 | 0 | RefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot(); |
985 | 0 | snapshot->SetProcessStats(GetStatsForGroup(mTopGroup)); |
986 | 0 |
|
987 | 0 | for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) { |
988 | 0 | auto* entry = iter.Get(); |
989 | 0 | nsPerformanceGroup* group = entry->GetKey(); |
990 | 0 | if (group->isActive()) { |
991 | 0 | snapshot->AppendComponentsStats(GetStatsForGroup(group)); |
992 | 0 | } |
993 | 0 | } |
994 | 0 |
|
995 | 0 | js::GetPerfMonitoringTestCpuRescheduling(cx, &mProcessStayed, &mProcessMoved); |
996 | 0 |
|
997 | 0 | if (++mProcessUpdateCounter % 10 == 0) { |
998 | 0 | mozilla::Unused << UpdateTelemetry(); |
999 | 0 | } |
1000 | 0 |
|
1001 | 0 | snapshot.forget(aSnapshot); |
1002 | 0 |
|
1003 | 0 | return NS_OK; |
1004 | 0 | } |
1005 | | |
1006 | | uint64_t |
1007 | 0 | nsPerformanceStatsService::GetNextId() { |
1008 | 0 | return ++mUIdCounter; |
1009 | 0 | } |
1010 | | |
1011 | | /* static*/ bool |
1012 | | nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx, |
1013 | | js::PerformanceGroupVector& out, |
1014 | | void* closure) |
1015 | 0 | { |
1016 | 0 | RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure); |
1017 | 0 | return self->GetPerformanceGroups(cx, out); |
1018 | 0 | } |
1019 | | |
1020 | | bool |
1021 | | nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx, |
1022 | | js::PerformanceGroupVector& out) |
1023 | 0 | { |
1024 | 0 | JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); |
1025 | 0 | if (!global) { |
1026 | 0 | // While it is possible for a compartment to have no global |
1027 | 0 | // (e.g. atoms), this compartment is not very interesting for us. |
1028 | 0 | return true; |
1029 | 0 | } |
1030 | 0 | |
1031 | 0 | // All compartments belong to the top group. |
1032 | 0 | if (!out.append(mTopGroup)) { |
1033 | 0 | JS_ReportOutOfMemory(cx); |
1034 | 0 | return false; |
1035 | 0 | } |
1036 | 0 | |
1037 | 0 | nsAutoString name; |
1038 | 0 | RealmName(cx, global, name); |
1039 | 0 | bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global)); |
1040 | 0 |
|
1041 | 0 | // Find out if the compartment is executed by a window. If so, its |
1042 | 0 | // duration should count towards the total duration of the window. |
1043 | 0 | uint64_t windowId = 0; |
1044 | 0 | if (nsCOMPtr<nsPIDOMWindowOuter> ptop = GetPrivateWindow(cx)) { |
1045 | 0 | windowId = ptop->WindowID(); |
1046 | 0 | auto entry = mWindowIdToGroup.PutEntry(windowId); |
1047 | 0 | if (!entry->GetGroup()) { |
1048 | 0 | nsString windowName = name; |
1049 | 0 | windowName.AppendLiteral(" (as window "); |
1050 | 0 | windowName.AppendInt(windowId); |
1051 | 0 | windowName.AppendLiteral(")"); |
1052 | 0 | entry-> |
1053 | 0 | SetGroup(nsPerformanceGroup::Make(this, |
1054 | 0 | windowName, windowId, |
1055 | 0 | mProcessId, isSystem, |
1056 | 0 | nsPerformanceGroup::GroupScope::WINDOW) |
1057 | 0 | ); |
1058 | 0 | } |
1059 | 0 | if (!out.append(entry->GetGroup())) { |
1060 | 0 | JS_ReportOutOfMemory(cx); |
1061 | 0 | return false; |
1062 | 0 | } |
1063 | 0 | } |
1064 | 0 | |
1065 | 0 | // All compartments have their own group. |
1066 | 0 | auto group = |
1067 | 0 | nsPerformanceGroup::Make(this, |
1068 | 0 | name, windowId, |
1069 | 0 | mProcessId, isSystem, |
1070 | 0 | nsPerformanceGroup::GroupScope::COMPARTMENT); |
1071 | 0 | if (!out.append(group)) { |
1072 | 0 | JS_ReportOutOfMemory(cx); |
1073 | 0 | return false; |
1074 | 0 | } |
1075 | 0 | |
1076 | 0 | // Returning a vector that is too large would cause allocations all over the |
1077 | 0 | // place in the JS engine. We want to be sure that all data is stored inline. |
1078 | 0 | MOZ_ASSERT(out.length() <= out.sMaxInlineStorage); |
1079 | 0 | return true; |
1080 | 0 | } |
1081 | | |
1082 | | /*static*/ bool |
1083 | 0 | nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) { |
1084 | 0 | RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure); |
1085 | 0 | return self->StopwatchStart(iteration); |
1086 | 0 | } |
1087 | | |
1088 | | bool |
1089 | 0 | nsPerformanceStatsService::StopwatchStart(uint64_t iteration) { |
1090 | 0 | mIteration = iteration; |
1091 | 0 |
|
1092 | 0 | mIsHandlingUserInput = IsHandlingUserInput(); |
1093 | 0 | mUserInputCount = mozilla::EventStateManager::UserInputCount(); |
1094 | 0 |
|
1095 | 0 | nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart); |
1096 | 0 | if (NS_FAILED(rv)) { |
1097 | 0 | return false; |
1098 | 0 | } |
1099 | 0 | |
1100 | 0 | return true; |
1101 | 0 | } |
1102 | | |
1103 | | /*static*/ bool |
1104 | | nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration, |
1105 | | js::PerformanceGroupVector& recentGroups, |
1106 | | void* closure) |
1107 | 0 | { |
1108 | 0 | RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure); |
1109 | 0 | return self->StopwatchCommit(iteration, recentGroups); |
1110 | 0 | } |
1111 | | |
1112 | | bool |
1113 | | nsPerformanceStatsService::StopwatchCommit(uint64_t iteration, |
1114 | | js::PerformanceGroupVector& recentGroups) |
1115 | 0 | { |
1116 | 0 | MOZ_ASSERT(iteration == mIteration); |
1117 | 0 | MOZ_ASSERT(!recentGroups.empty()); |
1118 | 0 |
|
1119 | 0 | uint64_t userTimeStop, systemTimeStop; |
1120 | 0 | nsresult rv = GetResources(&userTimeStop, &systemTimeStop); |
1121 | 0 | if (NS_FAILED(rv)) { |
1122 | 0 | return false; |
1123 | 0 | } |
1124 | 0 | |
1125 | 0 | // `GetResources` is not guaranteed to be monotonic, so round up |
1126 | 0 | // any negative result to 0 milliseconds. |
1127 | 0 | uint64_t userTimeDelta = 0; |
1128 | 0 | if (userTimeStop > mUserTimeStart) |
1129 | 0 | userTimeDelta = userTimeStop - mUserTimeStart; |
1130 | 0 |
|
1131 | 0 | uint64_t systemTimeDelta = 0; |
1132 | 0 | if (systemTimeStop > mSystemTimeStart) |
1133 | 0 | systemTimeDelta = systemTimeStop - mSystemTimeStart; |
1134 | 0 |
|
1135 | 0 | MOZ_ASSERT(mTopGroup->isUsedInThisIteration()); |
1136 | 0 | const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration); |
1137 | 0 |
|
1138 | 0 | const bool isHandlingUserInput = mIsHandlingUserInput || mozilla::EventStateManager::UserInputCount() > mUserInputCount; |
1139 | 0 |
|
1140 | 0 | // We should only reach this stage if `group` has had some activity. |
1141 | 0 | MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0); |
1142 | 0 | for (auto iter = recentGroups.begin(), end = recentGroups.end(); iter != end; ++iter) { |
1143 | 0 | RefPtr<nsPerformanceGroup> group = nsPerformanceGroup::Get(*iter); |
1144 | 0 | CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, isHandlingUserInput, group); |
1145 | 0 | } |
1146 | 0 |
|
1147 | 0 | // Make sure that `group` was treated along with the other items of `recentGroups`. |
1148 | 0 | MOZ_ASSERT(!mTopGroup->isUsedInThisIteration()); |
1149 | 0 | MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0); |
1150 | 0 |
|
1151 | 0 | if (!mPendingAlerts.empty()) { |
1152 | 0 | mPendingAlertsCollector->Start(mJankAlertBufferingDelay); |
1153 | 0 | } |
1154 | 0 |
|
1155 | 0 | return true; |
1156 | 0 | } |
1157 | | |
1158 | | void |
1159 | | nsPerformanceStatsService::CommitGroup(uint64_t iteration, |
1160 | | uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta, |
1161 | | uint64_t totalCyclesDelta, |
1162 | | bool isHandlingUserInput, |
1163 | 0 | nsPerformanceGroup* group) { |
1164 | 0 |
|
1165 | 0 | MOZ_ASSERT(group->isUsedInThisIteration()); |
1166 | 0 |
|
1167 | 0 | const uint64_t ticksDelta = group->recentTicks(iteration); |
1168 | 0 | const uint64_t cpowTimeDelta = group->recentCPOW(iteration); |
1169 | 0 | const uint64_t cyclesDelta = group->recentCycles(iteration); |
1170 | 0 | group->resetRecentData(); |
1171 | 0 |
|
1172 | 0 | // We have now performed all cleanup and may `return` at any time without fear of leaks. |
1173 | 0 |
|
1174 | 0 | if (group->iteration() != iteration) { |
1175 | 0 | // Stale data, don't commit. |
1176 | 0 | return; |
1177 | 0 | } |
1178 | 0 | |
1179 | 0 | // When we add a group as changed, we immediately set its |
1180 | 0 | // `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at |
1181 | 0 | // this stage, we have already called `resetRecentData` but we |
1182 | 0 | // haven't removed it from the list. |
1183 | 0 | MOZ_ASSERT(ticksDelta != 0); |
1184 | 0 | MOZ_ASSERT(cyclesDelta <= totalCyclesDelta); |
1185 | 0 | if (cyclesDelta == 0 || totalCyclesDelta == 0) { |
1186 | 0 | // Nothing useful, don't commit. |
1187 | 0 | return; |
1188 | 0 | } |
1189 | 0 | |
1190 | 0 | double proportion = (double)cyclesDelta / (double)totalCyclesDelta; |
1191 | 0 | MOZ_ASSERT(proportion <= 1); |
1192 | 0 |
|
1193 | 0 | const uint64_t userTimeDelta = proportion * totalUserTimeDelta; |
1194 | 0 | const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta; |
1195 | 0 |
|
1196 | 0 | group->data.mTotalUserTime += userTimeDelta; |
1197 | 0 | group->data.mTotalSystemTime += systemTimeDelta; |
1198 | 0 | group->data.mTotalCPOWTime += cpowTimeDelta; |
1199 | 0 | group->data.mTicks += ticksDelta; |
1200 | 0 |
|
1201 | 0 | const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta + cpowTimeDelta; |
1202 | 0 | uint64_t duration = 1000; // 1ms in µs |
1203 | 0 | for (size_t i = 0; |
1204 | 0 | i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta; |
1205 | 0 | ++i, duration *= 2) { |
1206 | 0 | group->data.mDurations[i]++; |
1207 | 0 | } |
1208 | 0 |
|
1209 | 0 | group->RecordJank(totalTimeDelta); |
1210 | 0 | group->RecordCPOW(cpowTimeDelta); |
1211 | 0 | if (isHandlingUserInput) { |
1212 | 0 | group->RecordUserInput(); |
1213 | 0 | } |
1214 | 0 |
|
1215 | 0 | if (totalTimeDelta >= mJankAlertThreshold) { |
1216 | 0 | if (!group->HasPendingAlert()) { |
1217 | 0 | if (mPendingAlerts.append(group)) { |
1218 | 0 | group->SetHasPendingAlert(true); |
1219 | 0 | } |
1220 | 0 | return; |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 | } |
1224 | | |
1225 | | nsresult |
1226 | | nsPerformanceStatsService::GetResources(uint64_t* userTime, |
1227 | 0 | uint64_t* systemTime) const { |
1228 | 0 | MOZ_ASSERT(userTime); |
1229 | 0 | MOZ_ASSERT(systemTime); |
1230 | 0 |
|
1231 | | #if defined(XP_MACOSX) |
1232 | | // On MacOS X, to get we per-thread data, we need to |
1233 | | // reach into the kernel. |
1234 | | |
1235 | | mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; |
1236 | | thread_basic_info_data_t info; |
1237 | | mach_port_t port = mach_thread_self(); |
1238 | | kern_return_t err = |
1239 | | thread_info(/* [in] targeted thread*/ port, |
1240 | | /* [in] nature of information*/ THREAD_BASIC_INFO, |
1241 | | /* [out] thread information */ (thread_info_t)&info, |
1242 | | /* [inout] number of items */ &count); |
1243 | | |
1244 | | // We do not need ability to communicate with the thread, so |
1245 | | // let's release the port. |
1246 | | mach_port_deallocate(mach_task_self(), port); |
1247 | | |
1248 | | if (err != KERN_SUCCESS) |
1249 | | return NS_ERROR_FAILURE; |
1250 | | |
1251 | | *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000; |
1252 | | *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000; |
1253 | | |
1254 | | #elif defined(XP_UNIX) |
1255 | | struct rusage rusage; |
1256 | 0 | #if defined(RUSAGE_THREAD) |
1257 | 0 | // Under Linux, we can obtain per-thread statistics |
1258 | 0 | int err = getrusage(RUSAGE_THREAD, &rusage); |
1259 | | #else |
1260 | | // Under other Unices, we need to do with more noisy |
1261 | | // per-process statistics. |
1262 | | int err = getrusage(RUSAGE_SELF, &rusage); |
1263 | | #endif // defined(RUSAGE_THREAD) |
1264 | |
|
1265 | 0 | if (err) |
1266 | 0 | return NS_ERROR_FAILURE; |
1267 | 0 | |
1268 | 0 | *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000; |
1269 | 0 | *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000; |
1270 | 0 |
|
1271 | | #elif defined(XP_WIN) |
1272 | | // Under Windows, we can obtain per-thread statistics. Experience |
1273 | | // seems to suggest that they are not very accurate under Windows |
1274 | | // XP, though. |
1275 | | FILETIME creationFileTime; // Ignored |
1276 | | FILETIME exitFileTime; // Ignored |
1277 | | FILETIME kernelFileTime; |
1278 | | FILETIME userFileTime; |
1279 | | BOOL success = GetThreadTimes(GetCurrentThread(), |
1280 | | &creationFileTime, &exitFileTime, |
1281 | | &kernelFileTime, &userFileTime); |
1282 | | |
1283 | | if (!success) |
1284 | | return NS_ERROR_FAILURE; |
1285 | | |
1286 | | ULARGE_INTEGER kernelTimeInt; |
1287 | | kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime; |
1288 | | kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime; |
1289 | | // Convert 100 ns to 1 us. |
1290 | | *systemTime = kernelTimeInt.QuadPart / 10; |
1291 | | |
1292 | | ULARGE_INTEGER userTimeInt; |
1293 | | userTimeInt.LowPart = userFileTime.dwLowDateTime; |
1294 | | userTimeInt.HighPart = userFileTime.dwHighDateTime; |
1295 | | // Convert 100 ns to 1 us. |
1296 | | *userTime = userTimeInt.QuadPart / 10; |
1297 | | |
1298 | | #endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN) |
1299 | |
|
1300 | 0 | return NS_OK; |
1301 | 0 | } |
1302 | | |
1303 | | void |
1304 | 0 | nsPerformanceStatsService::NotifyJankObservers(const mozilla::Vector<uint64_t>& aPreviousJankLevels) { |
1305 | 0 |
|
1306 | 0 | // The move operation is generally constant time, unless |
1307 | 0 | // `mPendingAlerts.length()` is very small, in which case it's fast anyway. |
1308 | 0 | GroupVector alerts(std::move(mPendingAlerts)); |
1309 | 0 | mPendingAlerts = GroupVector(); // Reconstruct after `Move`. |
1310 | 0 |
|
1311 | 0 | if (!mPendingAlertsCollector) { |
1312 | 0 | // We are shutting down. |
1313 | 0 | return; |
1314 | 0 | } |
1315 | 0 | |
1316 | 0 | // Find out if we have noticed any user-noticeable delay in an |
1317 | 0 | // animation recently (i.e. since the start of the execution of JS |
1318 | 0 | // code that caused this collector to start). If so, we'll mark any |
1319 | 0 | // alert as part of a user-noticeable jank. Note that this doesn't |
1320 | 0 | // mean with any certainty that the alert is the only cause of jank, |
1321 | 0 | // or even the main cause of jank. |
1322 | 0 | mozilla::Vector<uint64_t> latestJankLevels; |
1323 | 0 | { |
1324 | 0 | mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(latestJankLevels); |
1325 | 0 | MOZ_ASSERT(result); |
1326 | 0 | } |
1327 | 0 | MOZ_ASSERT(latestJankLevels.length() == aPreviousJankLevels.length()); |
1328 | 0 |
|
1329 | 0 | bool isJankInAnimation = false; |
1330 | 0 | for (size_t i = mJankLevelVisibilityThreshold; i < latestJankLevels.length(); ++i) { |
1331 | 0 | if (latestJankLevels[i] > aPreviousJankLevels[i]) { |
1332 | 0 | isJankInAnimation = true; |
1333 | 0 | break; |
1334 | 0 | } |
1335 | 0 | } |
1336 | 0 |
|
1337 | 0 | MOZ_ASSERT(!alerts.empty()); |
1338 | 0 | const bool hasUniversalWindowObservers = mUniversalTargets.mWindows->HasObservers(); |
1339 | 0 | for (auto iter = alerts.begin(); iter < alerts.end(); ++iter) { |
1340 | 0 | MOZ_ASSERT(iter); |
1341 | 0 | RefPtr<nsPerformanceGroup> group = *iter; |
1342 | 0 | group->SetHasPendingAlert(false); |
1343 | 0 |
|
1344 | 0 | RefPtr<nsPerformanceGroupDetails> details = group->Details(); |
1345 | 0 | nsPerformanceObservationTarget* targets[3] = { |
1346 | 0 | hasUniversalWindowObservers && details->IsWindow() ? mUniversalTargets.mWindows.get() : nullptr, |
1347 | 0 | group->ObservationTarget() |
1348 | 0 | }; |
1349 | 0 |
|
1350 | 0 | bool isJankInInput = group->HasRecentUserInput(); |
1351 | 0 |
|
1352 | 0 | RefPtr<PerformanceAlert> alert; |
1353 | 0 | for (nsPerformanceObservationTarget* target : targets) { |
1354 | 0 | if (!target) { |
1355 | 0 | continue; |
1356 | 0 | } |
1357 | 0 | if (!alert) { |
1358 | 0 | const uint32_t reason = nsIPerformanceAlert::REASON_SLOWDOWN |
1359 | 0 | | (isJankInAnimation ? nsIPerformanceAlert::REASON_JANK_IN_ANIMATION : 0) |
1360 | 0 | | (isJankInInput ? nsIPerformanceAlert::REASON_JANK_IN_INPUT : 0); |
1361 | 0 | // Wait until we are sure we need to allocate before we allocate. |
1362 | 0 | alert = new PerformanceAlert(reason, group); |
1363 | 0 | } |
1364 | 0 | target->NotifyJankObservers(details, alert); |
1365 | 0 | } |
1366 | 0 |
|
1367 | 0 | group->ResetRecent(); |
1368 | 0 | } |
1369 | 0 |
|
1370 | 0 | } |
1371 | | |
1372 | | NS_IMETHODIMP |
1373 | | nsPerformanceStatsService::GetObservableWindow(uint64_t windowId, |
1374 | 0 | nsIPerformanceObservable** result) { |
1375 | 0 | if (windowId == 0) { |
1376 | 0 | NS_IF_ADDREF(*result = mUniversalTargets.mWindows); |
1377 | 0 | } else { |
1378 | 0 | auto entry = mWindowIdToGroup.PutEntry(windowId); |
1379 | 0 | NS_IF_ADDREF(*result = entry->ObservationTarget()); |
1380 | 0 | } |
1381 | 0 | return NS_OK; |
1382 | 0 | } |
1383 | | |
1384 | | NS_IMETHODIMP |
1385 | 0 | nsPerformanceStatsService::GetAnimationJankLevelThreshold(short* result) { |
1386 | 0 | *result = mJankLevelVisibilityThreshold; |
1387 | 0 | return NS_OK; |
1388 | 0 | } |
1389 | | |
1390 | | NS_IMETHODIMP |
1391 | 0 | nsPerformanceStatsService::SetAnimationJankLevelThreshold(short value) { |
1392 | 0 | mJankLevelVisibilityThreshold = value; |
1393 | 0 | return NS_OK; |
1394 | 0 | } |
1395 | | |
1396 | | NS_IMETHODIMP |
1397 | 0 | nsPerformanceStatsService::GetUserInputDelayThreshold(uint64_t* result) { |
1398 | 0 | *result = mMaxExpectedDurationOfInteractionUS; |
1399 | 0 | return NS_OK; |
1400 | 0 | } |
1401 | | |
1402 | | NS_IMETHODIMP |
1403 | 0 | nsPerformanceStatsService::SetUserInputDelayThreshold(uint64_t value) { |
1404 | 0 | mMaxExpectedDurationOfInteractionUS = value; |
1405 | 0 | return NS_OK; |
1406 | 0 | } |
1407 | | |
1408 | | |
1409 | | |
1410 | | nsPerformanceStatsService::UniversalTargets::UniversalTargets() |
1411 | | : mWindows(new nsPerformanceObservationTarget()) |
1412 | 0 | { } |
1413 | | |
1414 | | /* ------------------------------------------------------ |
1415 | | * |
1416 | | * Class nsPerformanceGroup |
1417 | | * |
1418 | | */ |
1419 | | |
1420 | | /*static*/ nsPerformanceGroup* |
1421 | | nsPerformanceGroup::Make(nsPerformanceStatsService* service, |
1422 | | const nsAString& name, |
1423 | | uint64_t windowId, |
1424 | | uint64_t processId, |
1425 | | bool isSystem, |
1426 | | GroupScope scope) |
1427 | 0 | { |
1428 | 0 | nsString groupId; |
1429 | 0 | ::GenerateUniqueGroupId(service->GetNextId(), processId, groupId); |
1430 | 0 | return new nsPerformanceGroup(service, name, groupId, windowId, processId, isSystem, scope); |
1431 | 0 | } |
1432 | | |
1433 | | nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service, |
1434 | | const nsAString& name, |
1435 | | const nsAString& groupId, |
1436 | | uint64_t windowId, |
1437 | | uint64_t processId, |
1438 | | bool isSystem, |
1439 | | GroupScope scope) |
1440 | | : mDetails(new nsPerformanceGroupDetails(name, groupId, windowId, processId, isSystem)) |
1441 | | , mService(service) |
1442 | | , mScope(scope) |
1443 | | , mHighestJank(0) |
1444 | | , mHighestCPOW(0) |
1445 | | , mHasRecentUserInput(false) |
1446 | | , mHasPendingAlert(false) |
1447 | 0 | { |
1448 | 0 | mozilla::Unused << mService->mGroups.PutEntry(this); |
1449 | 0 |
|
1450 | | #if defined(DEBUG) |
1451 | | if (scope == GroupScope::WINDOW) { |
1452 | | MOZ_ASSERT(mDetails->IsWindow()); |
1453 | | } else if (scope == GroupScope::RUNTIME) { |
1454 | | MOZ_ASSERT(!mDetails->IsWindow()); |
1455 | | } |
1456 | | #endif // defined(DEBUG) |
1457 | 0 | setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment); |
1458 | 0 | } |
1459 | | |
1460 | | void |
1461 | 0 | nsPerformanceGroup::Dispose() { |
1462 | 0 | if (!mService) { |
1463 | 0 | // We have already called `Dispose()`. |
1464 | 0 | return; |
1465 | 0 | } |
1466 | 0 | if (mObservationTarget) { |
1467 | 0 | mObservationTarget = nullptr; |
1468 | 0 | } |
1469 | 0 |
|
1470 | 0 | // Remove any reference to the service. |
1471 | 0 | RefPtr<nsPerformanceStatsService> service; |
1472 | 0 | service.swap(mService); |
1473 | 0 |
|
1474 | 0 | // Remove any dangling pointer to `this`. |
1475 | 0 | service->mGroups.RemoveEntry(this); |
1476 | 0 |
|
1477 | 0 | if (mScope == GroupScope::WINDOW) { |
1478 | 0 | MOZ_ASSERT(mDetails->IsWindow()); |
1479 | 0 | service->mWindowIdToGroup.RemoveEntry(mDetails->WindowId()); |
1480 | 0 | } |
1481 | 0 | } |
1482 | | |
1483 | 0 | nsPerformanceGroup::~nsPerformanceGroup() { |
1484 | 0 | Dispose(); |
1485 | 0 | } |
1486 | | |
1487 | | nsPerformanceGroup::GroupScope |
1488 | 0 | nsPerformanceGroup::Scope() const { |
1489 | 0 | return mScope; |
1490 | 0 | } |
1491 | | |
1492 | | nsPerformanceGroupDetails* |
1493 | 0 | nsPerformanceGroup::Details() const { |
1494 | 0 | return mDetails; |
1495 | 0 | } |
1496 | | |
1497 | | void |
1498 | 0 | nsPerformanceGroup::SetObservationTarget(nsPerformanceObservationTarget* target) { |
1499 | 0 | MOZ_ASSERT(!mObservationTarget); |
1500 | 0 | mObservationTarget = target; |
1501 | 0 | } |
1502 | | |
1503 | | nsPerformanceObservationTarget* |
1504 | 0 | nsPerformanceGroup::ObservationTarget() const { |
1505 | 0 | return mObservationTarget; |
1506 | 0 | } |
1507 | | |
1508 | | bool |
1509 | 0 | nsPerformanceGroup::HasPendingAlert() const { |
1510 | 0 | return mHasPendingAlert; |
1511 | 0 | } |
1512 | | |
1513 | | void |
1514 | 0 | nsPerformanceGroup::SetHasPendingAlert(bool value) { |
1515 | 0 | mHasPendingAlert = value; |
1516 | 0 | } |
1517 | | |
1518 | | |
1519 | | void |
1520 | 0 | nsPerformanceGroup::RecordJank(uint64_t jank) { |
1521 | 0 | if (jank > mHighestJank) { |
1522 | 0 | mHighestJank = jank; |
1523 | 0 | } |
1524 | 0 | } |
1525 | | |
1526 | | void |
1527 | 0 | nsPerformanceGroup::RecordCPOW(uint64_t cpow) { |
1528 | 0 | if (cpow > mHighestCPOW) { |
1529 | 0 | mHighestCPOW = cpow; |
1530 | 0 | } |
1531 | 0 | } |
1532 | | |
1533 | | uint64_t |
1534 | 0 | nsPerformanceGroup::HighestRecentJank() { |
1535 | 0 | return mHighestJank; |
1536 | 0 | } |
1537 | | |
1538 | | uint64_t |
1539 | 0 | nsPerformanceGroup::HighestRecentCPOW() { |
1540 | 0 | return mHighestCPOW; |
1541 | 0 | } |
1542 | | |
1543 | | bool |
1544 | 0 | nsPerformanceGroup::HasRecentUserInput() { |
1545 | 0 | return mHasRecentUserInput; |
1546 | 0 | } |
1547 | | |
1548 | | void |
1549 | 0 | nsPerformanceGroup::RecordUserInput() { |
1550 | 0 | mHasRecentUserInput = true; |
1551 | 0 | } |
1552 | | |
1553 | | void |
1554 | 0 | nsPerformanceGroup::ResetRecent() { |
1555 | 0 | mHighestJank = 0; |
1556 | 0 | mHighestCPOW = 0; |
1557 | 0 | mHasRecentUserInput = false; |
1558 | 0 | } |