Line data Source code
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 : nsGlobalWindow* win = xpc::CurrentWindowOrNull(cx);
69 0 : if (!win) {
70 0 : return nullptr;
71 : }
72 :
73 0 : nsPIDOMWindowOuter* outer = win->AsInner()->GetOuterWindow();
74 0 : if (!outer) {
75 0 : return nullptr;
76 : }
77 :
78 0 : nsCOMPtr<nsPIDOMWindowOuter> top = outer->GetTop();
79 0 : if (!top) {
80 0 : return nullptr;
81 : }
82 :
83 0 : return top.forget();
84 : }
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 : }
92 :
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 : }
98 :
99 0 : nsAutoCString spec;
100 0 : rv = uri->GetSpec(spec);
101 0 : if (NS_FAILED(rv)) {
102 0 : return false;
103 : }
104 :
105 0 : url.Assign(NS_ConvertUTF8toUTF16(spec));
106 0 : return true;
107 : }
108 :
109 : /**
110 : * Extract a somewhat human-readable name from the current context.
111 : */
112 : void
113 0 : CompartmentName(JSContext* cx, JS::Handle<JSObject*> global, nsAString& name) {
114 : // Attempt to use the URL as name.
115 0 : if (URLForGlobal(cx, global, name)) {
116 0 : return;
117 : }
118 :
119 : // Otherwise, fallback to XPConnect's less readable but more
120 : // complete naming scheme.
121 0 : nsAutoCString cname;
122 0 : xpc::GetCurrentCompartmentName(cx, cname);
123 0 : name.Assign(NS_ConvertUTF8toUTF16(cname));
124 : }
125 :
126 : /**
127 : * Generate a unique-to-the-application identifier for a group.
128 : */
129 : void
130 0 : GenerateUniqueGroupId(uint64_t uid, uint64_t processId, nsAString& groupId)
131 : {
132 0 : uint64_t threadId = reinterpret_cast<uint64_t>(mozilla::GetCurrentPhysicalThread());
133 :
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 0 : 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 : }
167 0 : return NS_OK;
168 : };
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 : }
181 0 : return NS_OK;
182 : };
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 : }
191 : }
192 0 : return NS_OK;
193 : };
194 :
195 : bool
196 0 : nsPerformanceObservationTarget::HasObservers() const {
197 0 : return !mObservers.empty();
198 : }
199 :
200 : void
201 0 : nsPerformanceObservationTarget::NotifyJankObservers(nsIPerformanceGroupDetails* source, nsIPerformanceAlert* gravity) {
202 : // 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 : }
207 :
208 : // 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 : }
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 : }
226 0 : return mPendingObservationTarget;
227 : }
228 :
229 : nsPerformanceGroup*
230 0 : nsGroupHolder::GetGroup() {
231 0 : return mGroup;
232 : }
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 0 : PerformanceData::PerformanceData()
249 : : mTotalUserTime(0)
250 : , mTotalSystemTime(0)
251 : , mTotalCPOWTime(0)
252 0 : , mTicks(0)
253 : {
254 0 : mozilla::PodArrayZero(mDurations);
255 0 : }
256 :
257 : /* ------------------------------------------------------
258 : *
259 : * class nsPerformanceGroupDetails
260 : *
261 : */
262 :
263 0 : NS_IMPL_ISUPPORTS(nsPerformanceGroupDetails, nsIPerformanceGroupDetails)
264 :
265 : const nsAString&
266 0 : nsPerformanceGroupDetails::Name() const {
267 0 : return mName;
268 : }
269 :
270 : const nsAString&
271 0 : nsPerformanceGroupDetails::GroupId() const {
272 0 : return mGroupId;
273 : }
274 :
275 : uint64_t
276 0 : nsPerformanceGroupDetails::WindowId() const {
277 0 : return mWindowId;
278 : }
279 :
280 : uint64_t
281 0 : nsPerformanceGroupDetails::ProcessId() const {
282 0 : return mProcessId;
283 : }
284 :
285 : bool
286 0 : nsPerformanceGroupDetails::IsSystem() const {
287 0 : return mIsSystem;
288 : }
289 :
290 : bool
291 0 : nsPerformanceGroupDetails::IsWindow() const {
292 0 : return mWindowId != 0;
293 : }
294 :
295 : bool
296 0 : nsPerformanceGroupDetails::IsContentProcess() const {
297 0 : return XRE_GetProcessType() == GeckoProcessType_Content;
298 : }
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 : };
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 : };
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 : }
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 : }
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 : }
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 : }
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 0 : NS_FORWARD_NSIPERFORMANCEGROUPDETAILS(mDetails->)
357 :
358 0 : nsPerformanceStats(nsPerformanceGroupDetails* item,
359 : const PerformanceData& aPerformanceData)
360 0 : : mDetails(item)
361 0 : , mPerformanceData(aPerformanceData)
362 : {
363 0 : }
364 :
365 :
366 : private:
367 : RefPtr<nsPerformanceGroupDetails> mDetails;
368 : PerformanceData mPerformanceData;
369 :
370 0 : ~nsPerformanceStats() {}
371 : };
372 :
373 0 : 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 : };
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 : };
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 : };
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 : };
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 : }
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 : }
414 0 : return NS_OK;
415 : };
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 0 : NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
458 :
459 :
460 : /* nsIArray getComponentsData (); */
461 : NS_IMETHODIMP
462 0 : nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents)
463 : {
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, false);
469 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
470 : }
471 0 : components.forget(aComponents);
472 0 : return NS_OK;
473 : }
474 :
475 : /* nsIPerformanceStats getProcessData (); */
476 : NS_IMETHODIMP
477 0 : nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess)
478 : {
479 0 : NS_IF_ADDREF(*aProcess = mProcessData);
480 0 : return NS_OK;
481 : }
482 :
483 : void
484 0 : nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats)
485 : {
486 0 : mComponentsData.AppendElement(stats);
487 0 : }
488 :
489 : void
490 0 : nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats)
491 : {
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 0 : NS_IMPL_ISUPPORTS(PerformanceAlert, nsIPerformanceAlert);
520 :
521 0 : PerformanceAlert::PerformanceAlert(const uint32_t reason, nsPerformanceGroup* source)
522 : : mReason(reason)
523 0 : , mHighestJank(source->HighestRecentJank())
524 0 : , 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 : }
532 :
533 : NS_IMETHODIMP
534 0 : PerformanceAlert::GetHighestCPOW(uint64_t* result) {
535 0 : *result = mHighestCPOW;
536 0 : return NS_OK;
537 : }
538 :
539 : NS_IMETHODIMP
540 0 : PerformanceAlert::GetReason(uint32_t* result) {
541 0 : *result = mReason;
542 0 : return NS_OK;
543 : }
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 0 : explicit PendingAlertsCollector(nsPerformanceStatsService* service)
564 0 : : mService(service)
565 0 : , 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 0 : 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 : }
590 :
591 : NS_IMETHODIMP
592 0 : PendingAlertsCollector::GetName(nsACString& aName)
593 : {
594 0 : aName.AssignASCII("PendingAlertsCollector_timer");
595 0 : return NS_OK;
596 : }
597 :
598 : NS_IMETHODIMP
599 0 : PendingAlertsCollector::SetName(const char* aName)
600 : {
601 0 : return NS_ERROR_NOT_IMPLEMENTED;
602 : }
603 :
604 : nsresult
605 0 : PendingAlertsCollector::Start(uint32_t timerDelayMS) {
606 0 : if (mPending) {
607 : // Collector is already started.
608 0 : return NS_OK;
609 : }
610 :
611 0 : if (!mTimer) {
612 0 : mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
613 : }
614 :
615 0 : nsresult rv = mTimer->InitWithCallback(this, timerDelayMS, nsITimer::TYPE_ONE_SHOT);
616 0 : if (NS_FAILED(rv)) {
617 0 : return rv;
618 : }
619 :
620 0 : mPending = true;
621 : {
622 0 : mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(mJankLevels);
623 0 : MOZ_ASSERT(result);
624 : }
625 :
626 0 : return NS_OK;
627 : }
628 :
629 : nsresult
630 0 : PendingAlertsCollector::Dispose() {
631 0 : if (mTimer) {
632 0 : mozilla::Unused << mTimer->Cancel();
633 0 : mTimer = nullptr;
634 : }
635 0 : mService = nullptr;
636 0 : return NS_OK;
637 : }
638 :
639 :
640 :
641 : /* ------------------------------------------------------
642 : *
643 : * class nsPerformanceStatsService
644 : *
645 : */
646 :
647 0 : NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver)
648 :
649 0 : nsPerformanceStatsService::nsPerformanceStatsService()
650 : : mIsAvailable(false)
651 : , mDisposed(false)
652 : #if defined(XP_WIN)
653 : , mProcessId(GetCurrentProcessId())
654 : #else
655 0 : , mProcessId(getpid())
656 : #endif
657 : , mUIdCounter(0)
658 : , mTopGroup(nsPerformanceGroup::Make(this,
659 0 : NS_LITERAL_STRING("<process>"), // name
660 : 0, // windowId
661 0 : mProcessId,
662 : true, // isSystem
663 : nsPerformanceGroup::GroupScope::RUNTIME // scope
664 0 : ))
665 : , mIsHandlingUserInput(false)
666 : , mProcessStayed(0)
667 : , mProcessMoved(0)
668 : , mProcessUpdateCounter(0)
669 : , mIsMonitoringPerCompartment(false)
670 : , mJankAlertThreshold(mozilla::MaxValue<uint64_t>::value) // By default, no alerts
671 : , mJankAlertBufferingDelay(1000 /* ms */)
672 : , mJankLevelVisibilityThreshold(/* 2 ^ */ 8 /* ms */)
673 0 : , mMaxExpectedDurationOfInteractionUS(150 * 1000)
674 : {
675 0 : mPendingAlertsCollector = new PendingAlertsCollector(this);
676 :
677 0 : nsString groupIdForWindows;
678 0 : GenerateUniqueGroupId(GetNextId(), mProcessId, groupIdForWindows);
679 : mUniversalTargets.mWindows->
680 0 : SetTarget(new nsPerformanceGroupDetails(NS_LITERAL_STRING("<universal window listener>"),
681 : groupIdForWindows,
682 : 0, // window id
683 0 : mProcessId,
684 0 : false));
685 0 : }
686 :
687 0 : nsPerformanceStatsService::~nsPerformanceStatsService()
688 0 : { }
689 :
690 : /**
691 : * Clean up the service.
692 : *
693 : * Called during shutdown. Idempotent.
694 : */
695 : void
696 0 : nsPerformanceStatsService::Dispose()
697 : {
698 : // Make sure that we do not accidentally destroy `this` while we are
699 : // cleaning up back references.
700 0 : RefPtr<nsPerformanceStatsService> kungFuDeathGrip(this);
701 0 : mIsAvailable = false;
702 :
703 0 : if (mDisposed) {
704 : // Make sure that we don't double-dispose.
705 0 : return;
706 : }
707 0 : mDisposed = true;
708 :
709 : // Disconnect from nsIObserverService.
710 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
711 0 : if (obs) {
712 0 : for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
713 0 : mozilla::Unused << obs->RemoveObserver(this, TOPICS[i]);
714 : }
715 : }
716 :
717 : // Clear up and disconnect from JSAPI.
718 0 : mozilla::dom::AutoJSAPI jsapi;
719 0 : jsapi.Init();
720 0 : JSContext* cx = jsapi.cx();
721 0 : js::DisposePerformanceMonitoring(cx);
722 :
723 0 : mozilla::Unused << js::SetStopwatchIsMonitoringCPOW(cx, false);
724 0 : mozilla::Unused << js::SetStopwatchIsMonitoringJank(cx, false);
725 :
726 0 : mozilla::Unused << js::SetStopwatchStartCallback(cx, nullptr, nullptr);
727 0 : mozilla::Unused << js::SetStopwatchCommitCallback(cx, nullptr, nullptr);
728 0 : mozilla::Unused << js::SetGetPerformanceGroupsCallback(cx, nullptr, nullptr);
729 :
730 : // Clear up and disconnect the alerts collector.
731 0 : if (mPendingAlertsCollector) {
732 0 : mPendingAlertsCollector->Dispose();
733 0 : mPendingAlertsCollector = nullptr;
734 : }
735 0 : mPendingAlerts.clear();
736 :
737 : // Disconnect universal observers. Per-group observers will be
738 : // disconnected below as part of `group->Dispose()`.
739 0 : mUniversalTargets.mWindows = nullptr;
740 :
741 : // At this stage, the JS VM may still be holding references to
742 : // instances of PerformanceGroup on the stack. To let the service be
743 : // collected, we need to break the references from these groups to
744 : // `this`.
745 0 : mTopGroup->Dispose();
746 0 : mTopGroup = nullptr;
747 :
748 : // Copy references to the groups to a vector to ensure that we do
749 : // not modify the hashtable while iterating it.
750 0 : GroupVector groups;
751 0 : for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
752 0 : if (!groups.append(iter.Get()->GetKey())) {
753 0 : MOZ_CRASH();
754 : }
755 : }
756 0 : for (auto iter = groups.begin(), end = groups.end(); iter < end; ++iter) {
757 0 : RefPtr<nsPerformanceGroup> group = *iter;
758 0 : group->Dispose();
759 : }
760 :
761 : // Any remaining references to PerformanceGroup will be released as
762 : // the VM unrolls the stack. If there are any nested event loops,
763 : // this may take time.
764 : }
765 :
766 : nsresult
767 0 : nsPerformanceStatsService::Init()
768 : {
769 0 : nsresult rv = InitInternal();
770 0 : if (NS_FAILED(rv)) {
771 : // Attempt to clean up.
772 0 : Dispose();
773 : }
774 0 : return rv;
775 : }
776 :
777 : nsresult
778 0 : nsPerformanceStatsService::InitInternal()
779 : {
780 : // Make sure that we release everything during shutdown.
781 : // We are a bit defensive here, as we know that some strange behavior can break the
782 : // regular shutdown order.
783 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
784 0 : if (obs) {
785 0 : for (size_t i = 0; i < mozilla::ArrayLength(TOPICS); ++i) {
786 0 : mozilla::Unused << obs->AddObserver(this, TOPICS[i], false);
787 : }
788 : }
789 :
790 : // Connect to JSAPI.
791 0 : mozilla::dom::AutoJSAPI jsapi;
792 0 : jsapi.Init();
793 0 : JSContext* cx = jsapi.cx();
794 0 : if (!js::SetStopwatchStartCallback(cx, StopwatchStartCallback, this)) {
795 0 : return NS_ERROR_UNEXPECTED;
796 : }
797 0 : if (!js::SetStopwatchCommitCallback(cx, StopwatchCommitCallback, this)) {
798 0 : return NS_ERROR_UNEXPECTED;
799 : }
800 0 : if (!js::SetGetPerformanceGroupsCallback(cx, GetPerformanceGroupsCallback, this)) {
801 0 : return NS_ERROR_UNEXPECTED;
802 : }
803 :
804 0 : mTopGroup->setIsActive(true);
805 0 : mIsAvailable = true;
806 :
807 0 : return NS_OK;
808 : }
809 :
810 : // Observe shutdown events.
811 : NS_IMETHODIMP
812 0 : nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic,
813 : const char16_t *aData)
814 : {
815 0 : MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0
816 : || strcmp(aTopic, "quit-application") == 0
817 : || strcmp(aTopic, "quit-application-granted") == 0
818 : || strcmp(aTopic, "xpcom-will-shutdown") == 0);
819 :
820 0 : Dispose();
821 0 : return NS_OK;
822 : }
823 :
824 : /*static*/ bool
825 0 : nsPerformanceStatsService::IsHandlingUserInput() {
826 0 : if (mozilla::EventStateManager::LatestUserInputStart().IsNull()) {
827 0 : return false;
828 : }
829 0 : bool result = mozilla::TimeStamp::Now() - mozilla::EventStateManager::LatestUserInputStart() <= mozilla::TimeDuration::FromMicroseconds(mMaxExpectedDurationOfInteractionUS);
830 0 : return result;
831 : }
832 :
833 : /* [implicit_jscontext] attribute bool isMonitoringCPOW; */
834 : NS_IMETHODIMP
835 0 : nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive)
836 : {
837 0 : if (!mIsAvailable) {
838 0 : return NS_ERROR_NOT_AVAILABLE;
839 : }
840 :
841 0 : *aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(cx);
842 0 : return NS_OK;
843 : }
844 : NS_IMETHODIMP
845 0 : nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive)
846 : {
847 0 : if (!mIsAvailable) {
848 0 : return NS_ERROR_NOT_AVAILABLE;
849 : }
850 :
851 0 : if (!js::SetStopwatchIsMonitoringCPOW(cx, aIsStopwatchActive)) {
852 0 : return NS_ERROR_OUT_OF_MEMORY;
853 : }
854 0 : return NS_OK;
855 : }
856 :
857 : /* [implicit_jscontext] attribute bool isMonitoringJank; */
858 : NS_IMETHODIMP
859 0 : nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive)
860 : {
861 0 : if (!mIsAvailable) {
862 0 : return NS_ERROR_NOT_AVAILABLE;
863 : }
864 :
865 0 : *aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(cx);
866 0 : return NS_OK;
867 : }
868 : NS_IMETHODIMP
869 0 : nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
870 : {
871 0 : if (!mIsAvailable) {
872 0 : return NS_ERROR_NOT_AVAILABLE;
873 : }
874 :
875 0 : if (!js::SetStopwatchIsMonitoringJank(cx, aIsStopwatchActive)) {
876 0 : return NS_ERROR_OUT_OF_MEMORY;
877 : }
878 0 : return NS_OK;
879 : }
880 :
881 : /* [implicit_jscontext] attribute bool isMonitoringPerCompartment; */
882 : NS_IMETHODIMP
883 0 : nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment)
884 : {
885 0 : if (!mIsAvailable) {
886 0 : return NS_ERROR_NOT_AVAILABLE;
887 : }
888 :
889 0 : *aIsMonitoringPerCompartment = mIsMonitoringPerCompartment;
890 0 : return NS_OK;
891 : }
892 : NS_IMETHODIMP
893 0 : nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment)
894 : {
895 0 : if (!mIsAvailable) {
896 0 : return NS_ERROR_NOT_AVAILABLE;
897 : }
898 :
899 0 : if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) {
900 0 : return NS_OK;
901 : }
902 :
903 : // Relatively slow update: walk the entire lost of performance groups,
904 : // update the active flag of those that have changed.
905 : //
906 : // Alternative strategies could be envisioned to make the update
907 : // much faster, at the expense of the speed of calling `isActive()`,
908 : // (e.g. deferring `isActive()` to the nsPerformanceStatsService),
909 : // but we expect that `isActive()` can be called thousands of times
910 : // per second, while `SetIsMonitoringPerCompartment` is not called
911 : // at all during most Firefox runs.
912 :
913 0 : for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
914 0 : RefPtr<nsPerformanceGroup> group = iter.Get()->GetKey();
915 0 : if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) {
916 0 : group->setIsActive(aIsMonitoringPerCompartment);
917 : }
918 : }
919 0 : mIsMonitoringPerCompartment = aIsMonitoringPerCompartment;
920 0 : return NS_OK;
921 : }
922 :
923 : NS_IMETHODIMP
924 0 : nsPerformanceStatsService::GetJankAlertThreshold(uint64_t* result) {
925 0 : *result = mJankAlertThreshold;
926 0 : return NS_OK;
927 : }
928 :
929 : NS_IMETHODIMP
930 0 : nsPerformanceStatsService::SetJankAlertThreshold(uint64_t value) {
931 0 : mJankAlertThreshold = value;
932 0 : return NS_OK;
933 : }
934 :
935 : NS_IMETHODIMP
936 0 : nsPerformanceStatsService::GetJankAlertBufferingDelay(uint32_t* result) {
937 0 : *result = mJankAlertBufferingDelay;
938 0 : return NS_OK;
939 : }
940 :
941 : NS_IMETHODIMP
942 0 : nsPerformanceStatsService::SetJankAlertBufferingDelay(uint32_t value) {
943 0 : mJankAlertBufferingDelay = value;
944 0 : return NS_OK;
945 : }
946 :
947 : nsresult
948 0 : nsPerformanceStatsService::UpdateTelemetry()
949 : {
950 : // Promote everything to floating-point explicitly before dividing.
951 0 : const double processStayed = mProcessStayed;
952 0 : const double processMoved = mProcessMoved;
953 :
954 0 : if (processStayed <= 0 || processMoved <= 0 || processStayed + processMoved <= 0) {
955 : // Overflow/underflow/nothing to report
956 0 : return NS_OK;
957 : }
958 :
959 0 : const double proportion = (100 * processStayed) / (processStayed + processMoved);
960 0 : if (proportion < 0 || proportion > 100) {
961 : // Overflow/underflow
962 0 : return NS_OK;
963 : }
964 :
965 0 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED, (uint32_t)proportion);
966 0 : return NS_OK;
967 : }
968 :
969 :
970 : /* static */ nsIPerformanceStats*
971 0 : nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group)
972 : {
973 0 : return GetStatsForGroup(nsPerformanceGroup::Get(group));
974 : }
975 :
976 : /* static */ nsIPerformanceStats*
977 0 : nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group)
978 : {
979 0 : return new nsPerformanceStats(group->Details(), group->data);
980 : }
981 :
982 : /* [implicit_jscontext] nsIPerformanceSnapshot getSnapshot (); */
983 : NS_IMETHODIMP
984 0 : nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
985 : {
986 0 : if (!mIsAvailable) {
987 0 : return NS_ERROR_NOT_AVAILABLE;
988 : }
989 :
990 0 : RefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
991 0 : snapshot->SetProcessStats(GetStatsForGroup(mTopGroup));
992 :
993 0 : for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) {
994 0 : auto* entry = iter.Get();
995 0 : nsPerformanceGroup* group = entry->GetKey();
996 0 : if (group->isActive()) {
997 0 : snapshot->AppendComponentsStats(GetStatsForGroup(group));
998 : }
999 : }
1000 :
1001 0 : js::GetPerfMonitoringTestCpuRescheduling(cx, &mProcessStayed, &mProcessMoved);
1002 :
1003 0 : if (++mProcessUpdateCounter % 10 == 0) {
1004 0 : mozilla::Unused << UpdateTelemetry();
1005 : }
1006 :
1007 0 : snapshot.forget(aSnapshot);
1008 :
1009 0 : return NS_OK;
1010 : }
1011 :
1012 : uint64_t
1013 0 : nsPerformanceStatsService::GetNextId() {
1014 0 : return ++mUIdCounter;
1015 : }
1016 :
1017 : /* static*/ bool
1018 0 : nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx,
1019 : js::PerformanceGroupVector& out,
1020 : void* closure)
1021 : {
1022 0 : RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
1023 0 : return self->GetPerformanceGroups(cx, out);
1024 : }
1025 :
1026 : bool
1027 0 : nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx,
1028 : js::PerformanceGroupVector& out)
1029 : {
1030 0 : JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
1031 0 : if (!global) {
1032 : // While it is possible for a compartment to have no global
1033 : // (e.g. atoms), this compartment is not very interesting for us.
1034 0 : return true;
1035 : }
1036 :
1037 : // All compartments belong to the top group.
1038 0 : if (!out.append(mTopGroup)) {
1039 0 : JS_ReportOutOfMemory(cx);
1040 0 : return false;
1041 : }
1042 :
1043 0 : nsAutoString name;
1044 0 : CompartmentName(cx, global, name);
1045 0 : bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
1046 :
1047 : // Find out if the compartment is executed by a window. If so, its
1048 : // duration should count towards the total duration of the window.
1049 0 : uint64_t windowId = 0;
1050 0 : if (nsCOMPtr<nsPIDOMWindowOuter> ptop = GetPrivateWindow(cx)) {
1051 0 : windowId = ptop->WindowID();
1052 0 : auto entry = mWindowIdToGroup.PutEntry(windowId);
1053 0 : if (!entry->GetGroup()) {
1054 0 : nsString windowName = name;
1055 0 : windowName.AppendLiteral(" (as window ");
1056 0 : windowName.AppendInt(windowId);
1057 0 : windowName.AppendLiteral(")");
1058 : entry->
1059 0 : SetGroup(nsPerformanceGroup::Make(this,
1060 : windowName, windowId,
1061 0 : mProcessId, isSystem,
1062 : nsPerformanceGroup::GroupScope::WINDOW)
1063 0 : );
1064 : }
1065 0 : if (!out.append(entry->GetGroup())) {
1066 0 : JS_ReportOutOfMemory(cx);
1067 0 : return false;
1068 : }
1069 : }
1070 :
1071 : // All compartments have their own group.
1072 : auto group =
1073 0 : nsPerformanceGroup::Make(this,
1074 : name, windowId,
1075 0 : mProcessId, isSystem,
1076 0 : nsPerformanceGroup::GroupScope::COMPARTMENT);
1077 0 : if (!out.append(group)) {
1078 0 : JS_ReportOutOfMemory(cx);
1079 0 : return false;
1080 : }
1081 :
1082 : // Returning a vector that is too large would cause allocations all over the
1083 : // place in the JS engine. We want to be sure that all data is stored inline.
1084 0 : MOZ_ASSERT(out.length() <= out.sMaxInlineStorage);
1085 0 : return true;
1086 : }
1087 :
1088 : /*static*/ bool
1089 0 : nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) {
1090 0 : RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
1091 0 : return self->StopwatchStart(iteration);
1092 : }
1093 :
1094 : bool
1095 0 : nsPerformanceStatsService::StopwatchStart(uint64_t iteration) {
1096 0 : mIteration = iteration;
1097 :
1098 0 : mIsHandlingUserInput = IsHandlingUserInput();
1099 0 : mUserInputCount = mozilla::EventStateManager::UserInputCount();
1100 :
1101 0 : nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart);
1102 0 : if (NS_FAILED(rv)) {
1103 0 : return false;
1104 : }
1105 :
1106 0 : return true;
1107 : }
1108 :
1109 : /*static*/ bool
1110 0 : nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration,
1111 : js::PerformanceGroupVector& recentGroups,
1112 : void* closure)
1113 : {
1114 0 : RefPtr<nsPerformanceStatsService> self = reinterpret_cast<nsPerformanceStatsService*>(closure);
1115 0 : return self->StopwatchCommit(iteration, recentGroups);
1116 : }
1117 :
1118 : bool
1119 0 : nsPerformanceStatsService::StopwatchCommit(uint64_t iteration,
1120 : js::PerformanceGroupVector& recentGroups)
1121 : {
1122 0 : MOZ_ASSERT(iteration == mIteration);
1123 0 : MOZ_ASSERT(!recentGroups.empty());
1124 :
1125 : uint64_t userTimeStop, systemTimeStop;
1126 0 : nsresult rv = GetResources(&userTimeStop, &systemTimeStop);
1127 0 : if (NS_FAILED(rv)) {
1128 0 : return false;
1129 : }
1130 :
1131 : // `GetResources` is not guaranteed to be monotonic, so round up
1132 : // any negative result to 0 milliseconds.
1133 0 : uint64_t userTimeDelta = 0;
1134 0 : if (userTimeStop > mUserTimeStart)
1135 0 : userTimeDelta = userTimeStop - mUserTimeStart;
1136 :
1137 0 : uint64_t systemTimeDelta = 0;
1138 0 : if (systemTimeStop > mSystemTimeStart)
1139 0 : systemTimeDelta = systemTimeStop - mSystemTimeStart;
1140 :
1141 0 : MOZ_ASSERT(mTopGroup->isUsedInThisIteration());
1142 0 : const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration);
1143 :
1144 0 : const bool isHandlingUserInput = mIsHandlingUserInput || mozilla::EventStateManager::UserInputCount() > mUserInputCount;
1145 :
1146 : // We should only reach this stage if `group` has had some activity.
1147 0 : MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0);
1148 0 : for (auto iter = recentGroups.begin(), end = recentGroups.end(); iter != end; ++iter) {
1149 0 : RefPtr<nsPerformanceGroup> group = nsPerformanceGroup::Get(*iter);
1150 0 : CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, isHandlingUserInput, group);
1151 : }
1152 :
1153 : // Make sure that `group` was treated along with the other items of `recentGroups`.
1154 0 : MOZ_ASSERT(!mTopGroup->isUsedInThisIteration());
1155 0 : MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0);
1156 :
1157 0 : if (!mPendingAlerts.empty()) {
1158 0 : mPendingAlertsCollector->Start(mJankAlertBufferingDelay);
1159 : }
1160 :
1161 0 : return true;
1162 : }
1163 :
1164 : void
1165 0 : nsPerformanceStatsService::CommitGroup(uint64_t iteration,
1166 : uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta,
1167 : uint64_t totalCyclesDelta,
1168 : bool isHandlingUserInput,
1169 : nsPerformanceGroup* group) {
1170 :
1171 0 : MOZ_ASSERT(group->isUsedInThisIteration());
1172 :
1173 0 : const uint64_t ticksDelta = group->recentTicks(iteration);
1174 0 : const uint64_t cpowTimeDelta = group->recentCPOW(iteration);
1175 0 : const uint64_t cyclesDelta = group->recentCycles(iteration);
1176 0 : group->resetRecentData();
1177 :
1178 : // We have now performed all cleanup and may `return` at any time without fear of leaks.
1179 :
1180 0 : if (group->iteration() != iteration) {
1181 : // Stale data, don't commit.
1182 0 : return;
1183 : }
1184 :
1185 : // When we add a group as changed, we immediately set its
1186 : // `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at
1187 : // this stage, we have already called `resetRecentData` but we
1188 : // haven't removed it from the list.
1189 0 : MOZ_ASSERT(ticksDelta != 0);
1190 0 : MOZ_ASSERT(cyclesDelta <= totalCyclesDelta);
1191 0 : if (cyclesDelta == 0 || totalCyclesDelta == 0) {
1192 : // Nothing useful, don't commit.
1193 0 : return;
1194 : }
1195 :
1196 0 : double proportion = (double)cyclesDelta / (double)totalCyclesDelta;
1197 0 : MOZ_ASSERT(proportion <= 1);
1198 :
1199 0 : const uint64_t userTimeDelta = proportion * totalUserTimeDelta;
1200 0 : const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta;
1201 :
1202 0 : group->data.mTotalUserTime += userTimeDelta;
1203 0 : group->data.mTotalSystemTime += systemTimeDelta;
1204 0 : group->data.mTotalCPOWTime += cpowTimeDelta;
1205 0 : group->data.mTicks += ticksDelta;
1206 :
1207 0 : const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta + cpowTimeDelta;
1208 0 : uint64_t duration = 1000; // 1ms in µs
1209 0 : for (size_t i = 0;
1210 0 : i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta;
1211 0 : ++i, duration *= 2) {
1212 0 : group->data.mDurations[i]++;
1213 : }
1214 :
1215 0 : group->RecordJank(totalTimeDelta);
1216 0 : group->RecordCPOW(cpowTimeDelta);
1217 0 : if (isHandlingUserInput) {
1218 0 : group->RecordUserInput();
1219 : }
1220 :
1221 0 : if (totalTimeDelta >= mJankAlertThreshold) {
1222 0 : if (!group->HasPendingAlert()) {
1223 0 : if (mPendingAlerts.append(group)) {
1224 0 : group->SetHasPendingAlert(true);
1225 : }
1226 0 : return;
1227 : }
1228 : }
1229 :
1230 0 : return;
1231 : }
1232 :
1233 : nsresult
1234 0 : nsPerformanceStatsService::GetResources(uint64_t* userTime,
1235 : uint64_t* systemTime) const {
1236 0 : MOZ_ASSERT(userTime);
1237 0 : MOZ_ASSERT(systemTime);
1238 :
1239 : #if defined(XP_MACOSX)
1240 : // On MacOS X, to get we per-thread data, we need to
1241 : // reach into the kernel.
1242 :
1243 : mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
1244 : thread_basic_info_data_t info;
1245 : mach_port_t port = mach_thread_self();
1246 : kern_return_t err =
1247 : thread_info(/* [in] targeted thread*/ port,
1248 : /* [in] nature of information*/ THREAD_BASIC_INFO,
1249 : /* [out] thread information */ (thread_info_t)&info,
1250 : /* [inout] number of items */ &count);
1251 :
1252 : // We do not need ability to communicate with the thread, so
1253 : // let's release the port.
1254 : mach_port_deallocate(mach_task_self(), port);
1255 :
1256 : if (err != KERN_SUCCESS)
1257 : return NS_ERROR_FAILURE;
1258 :
1259 : *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
1260 : *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
1261 :
1262 : #elif defined(XP_UNIX)
1263 : struct rusage rusage;
1264 : #if defined(RUSAGE_THREAD)
1265 : // Under Linux, we can obtain per-thread statistics
1266 0 : int err = getrusage(RUSAGE_THREAD, &rusage);
1267 : #else
1268 : // Under other Unices, we need to do with more noisy
1269 : // per-process statistics.
1270 : int err = getrusage(RUSAGE_SELF, &rusage);
1271 : #endif // defined(RUSAGE_THREAD)
1272 :
1273 0 : if (err)
1274 0 : return NS_ERROR_FAILURE;
1275 :
1276 0 : *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000;
1277 0 : *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000;
1278 :
1279 : #elif defined(XP_WIN)
1280 : // Under Windows, we can obtain per-thread statistics. Experience
1281 : // seems to suggest that they are not very accurate under Windows
1282 : // XP, though.
1283 : FILETIME creationFileTime; // Ignored
1284 : FILETIME exitFileTime; // Ignored
1285 : FILETIME kernelFileTime;
1286 : FILETIME userFileTime;
1287 : BOOL success = GetThreadTimes(GetCurrentThread(),
1288 : &creationFileTime, &exitFileTime,
1289 : &kernelFileTime, &userFileTime);
1290 :
1291 : if (!success)
1292 : return NS_ERROR_FAILURE;
1293 :
1294 : ULARGE_INTEGER kernelTimeInt;
1295 : kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime;
1296 : kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime;
1297 : // Convert 100 ns to 1 us.
1298 : *systemTime = kernelTimeInt.QuadPart / 10;
1299 :
1300 : ULARGE_INTEGER userTimeInt;
1301 : userTimeInt.LowPart = userFileTime.dwLowDateTime;
1302 : userTimeInt.HighPart = userFileTime.dwHighDateTime;
1303 : // Convert 100 ns to 1 us.
1304 : *userTime = userTimeInt.QuadPart / 10;
1305 :
1306 : #endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
1307 :
1308 0 : return NS_OK;
1309 : }
1310 :
1311 : void
1312 0 : nsPerformanceStatsService::NotifyJankObservers(const mozilla::Vector<uint64_t>& aPreviousJankLevels) {
1313 :
1314 : // The move operation is generally constant time, unless
1315 : // `mPendingAlerts.length()` is very small, in which case it's fast anyway.
1316 0 : GroupVector alerts(Move(mPendingAlerts));
1317 0 : mPendingAlerts = GroupVector(); // Reconstruct after `Move`.
1318 :
1319 0 : if (!mPendingAlertsCollector) {
1320 : // We are shutting down.
1321 0 : return;
1322 : }
1323 :
1324 : // Find out if we have noticed any user-noticeable delay in an
1325 : // animation recently (i.e. since the start of the execution of JS
1326 : // code that caused this collector to start). If so, we'll mark any
1327 : // alert as part of a user-noticeable jank. Note that this doesn't
1328 : // mean with any certainty that the alert is the only cause of jank,
1329 : // or even the main cause of jank.
1330 0 : mozilla::Vector<uint64_t> latestJankLevels;
1331 : {
1332 0 : mozilla::DebugOnly<bool> result = nsRefreshDriver::GetJankLevels(latestJankLevels);
1333 0 : MOZ_ASSERT(result);
1334 : }
1335 0 : MOZ_ASSERT(latestJankLevels.length() == aPreviousJankLevels.length());
1336 :
1337 0 : bool isJankInAnimation = false;
1338 0 : for (size_t i = mJankLevelVisibilityThreshold; i < latestJankLevels.length(); ++i) {
1339 0 : if (latestJankLevels[i] > aPreviousJankLevels[i]) {
1340 0 : isJankInAnimation = true;
1341 0 : break;
1342 : }
1343 : }
1344 :
1345 0 : MOZ_ASSERT(!alerts.empty());
1346 0 : const bool hasUniversalWindowObservers = mUniversalTargets.mWindows->HasObservers();
1347 0 : for (auto iter = alerts.begin(); iter < alerts.end(); ++iter) {
1348 0 : MOZ_ASSERT(iter);
1349 0 : RefPtr<nsPerformanceGroup> group = *iter;
1350 0 : group->SetHasPendingAlert(false);
1351 :
1352 0 : RefPtr<nsPerformanceGroupDetails> details = group->Details();
1353 : nsPerformanceObservationTarget* targets[3] = {
1354 0 : hasUniversalWindowObservers && details->IsWindow() ? mUniversalTargets.mWindows.get() : nullptr,
1355 0 : group->ObservationTarget()
1356 0 : };
1357 :
1358 0 : bool isJankInInput = group->HasRecentUserInput();
1359 :
1360 0 : RefPtr<PerformanceAlert> alert;
1361 0 : for (nsPerformanceObservationTarget* target : targets) {
1362 0 : if (!target) {
1363 0 : continue;
1364 : }
1365 0 : if (!alert) {
1366 : const uint32_t reason = nsIPerformanceAlert::REASON_SLOWDOWN
1367 0 : | (isJankInAnimation ? nsIPerformanceAlert::REASON_JANK_IN_ANIMATION : 0)
1368 0 : | (isJankInInput ? nsIPerformanceAlert::REASON_JANK_IN_INPUT : 0);
1369 : // Wait until we are sure we need to allocate before we allocate.
1370 0 : alert = new PerformanceAlert(reason, group);
1371 : }
1372 0 : target->NotifyJankObservers(details, alert);
1373 : }
1374 :
1375 0 : group->ResetRecent();
1376 : }
1377 :
1378 : }
1379 :
1380 : NS_IMETHODIMP
1381 0 : nsPerformanceStatsService::GetObservableWindow(uint64_t windowId,
1382 : nsIPerformanceObservable** result) {
1383 0 : if (windowId == 0) {
1384 0 : NS_IF_ADDREF(*result = mUniversalTargets.mWindows);
1385 : } else {
1386 0 : auto entry = mWindowIdToGroup.PutEntry(windowId);
1387 0 : NS_IF_ADDREF(*result = entry->ObservationTarget());
1388 : }
1389 0 : return NS_OK;
1390 : }
1391 :
1392 : NS_IMETHODIMP
1393 0 : nsPerformanceStatsService::GetAnimationJankLevelThreshold(short* result) {
1394 0 : *result = mJankLevelVisibilityThreshold;
1395 0 : return NS_OK;
1396 : }
1397 :
1398 : NS_IMETHODIMP
1399 0 : nsPerformanceStatsService::SetAnimationJankLevelThreshold(short value) {
1400 0 : mJankLevelVisibilityThreshold = value;
1401 0 : return NS_OK;
1402 : }
1403 :
1404 : NS_IMETHODIMP
1405 0 : nsPerformanceStatsService::GetUserInputDelayThreshold(uint64_t* result) {
1406 0 : *result = mMaxExpectedDurationOfInteractionUS;
1407 0 : return NS_OK;
1408 : }
1409 :
1410 : NS_IMETHODIMP
1411 0 : nsPerformanceStatsService::SetUserInputDelayThreshold(uint64_t value) {
1412 0 : mMaxExpectedDurationOfInteractionUS = value;
1413 0 : return NS_OK;
1414 : }
1415 :
1416 :
1417 :
1418 0 : nsPerformanceStatsService::UniversalTargets::UniversalTargets()
1419 0 : : mWindows(new nsPerformanceObservationTarget())
1420 0 : { }
1421 :
1422 : /* ------------------------------------------------------
1423 : *
1424 : * Class nsPerformanceGroup
1425 : *
1426 : */
1427 :
1428 : /*static*/ nsPerformanceGroup*
1429 0 : nsPerformanceGroup::Make(nsPerformanceStatsService* service,
1430 : const nsAString& name,
1431 : uint64_t windowId,
1432 : uint64_t processId,
1433 : bool isSystem,
1434 : GroupScope scope)
1435 : {
1436 0 : nsString groupId;
1437 0 : ::GenerateUniqueGroupId(service->GetNextId(), processId, groupId);
1438 0 : return new nsPerformanceGroup(service, name, groupId, windowId, processId, isSystem, scope);
1439 : }
1440 :
1441 0 : nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service,
1442 : const nsAString& name,
1443 : const nsAString& groupId,
1444 : uint64_t windowId,
1445 : uint64_t processId,
1446 : bool isSystem,
1447 0 : GroupScope scope)
1448 0 : : mDetails(new nsPerformanceGroupDetails(name, groupId, windowId, processId, isSystem))
1449 : , mService(service)
1450 : , mScope(scope)
1451 : , mHighestJank(0)
1452 : , mHighestCPOW(0)
1453 : , mHasRecentUserInput(false)
1454 0 : , mHasPendingAlert(false)
1455 : {
1456 0 : mozilla::Unused << mService->mGroups.PutEntry(this);
1457 :
1458 : #if defined(DEBUG)
1459 0 : if (scope == GroupScope::WINDOW) {
1460 0 : MOZ_ASSERT(mDetails->IsWindow());
1461 0 : } else if (scope == GroupScope::RUNTIME) {
1462 0 : MOZ_ASSERT(!mDetails->IsWindow());
1463 : }
1464 : #endif // defined(DEBUG)
1465 0 : setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment);
1466 0 : }
1467 :
1468 : void
1469 0 : nsPerformanceGroup::Dispose() {
1470 0 : if (!mService) {
1471 : // We have already called `Dispose()`.
1472 0 : return;
1473 : }
1474 0 : if (mObservationTarget) {
1475 0 : mObservationTarget = nullptr;
1476 : }
1477 :
1478 : // Remove any reference to the service.
1479 0 : RefPtr<nsPerformanceStatsService> service;
1480 0 : service.swap(mService);
1481 :
1482 : // Remove any dangling pointer to `this`.
1483 0 : service->mGroups.RemoveEntry(this);
1484 :
1485 0 : if (mScope == GroupScope::WINDOW) {
1486 0 : MOZ_ASSERT(mDetails->IsWindow());
1487 0 : service->mWindowIdToGroup.RemoveEntry(mDetails->WindowId());
1488 : }
1489 : }
1490 :
1491 0 : nsPerformanceGroup::~nsPerformanceGroup() {
1492 0 : Dispose();
1493 0 : }
1494 :
1495 : nsPerformanceGroup::GroupScope
1496 0 : nsPerformanceGroup::Scope() const {
1497 0 : return mScope;
1498 : }
1499 :
1500 : nsPerformanceGroupDetails*
1501 0 : nsPerformanceGroup::Details() const {
1502 0 : return mDetails;
1503 : }
1504 :
1505 : void
1506 0 : nsPerformanceGroup::SetObservationTarget(nsPerformanceObservationTarget* target) {
1507 0 : MOZ_ASSERT(!mObservationTarget);
1508 0 : mObservationTarget = target;
1509 0 : }
1510 :
1511 : nsPerformanceObservationTarget*
1512 0 : nsPerformanceGroup::ObservationTarget() const {
1513 0 : return mObservationTarget;
1514 : }
1515 :
1516 : bool
1517 0 : nsPerformanceGroup::HasPendingAlert() const {
1518 0 : return mHasPendingAlert;
1519 : }
1520 :
1521 : void
1522 0 : nsPerformanceGroup::SetHasPendingAlert(bool value) {
1523 0 : mHasPendingAlert = value;
1524 0 : }
1525 :
1526 :
1527 : void
1528 0 : nsPerformanceGroup::RecordJank(uint64_t jank) {
1529 0 : if (jank > mHighestJank) {
1530 0 : mHighestJank = jank;
1531 : }
1532 0 : }
1533 :
1534 : void
1535 0 : nsPerformanceGroup::RecordCPOW(uint64_t cpow) {
1536 0 : if (cpow > mHighestCPOW) {
1537 0 : mHighestCPOW = cpow;
1538 : }
1539 0 : }
1540 :
1541 : uint64_t
1542 0 : nsPerformanceGroup::HighestRecentJank() {
1543 0 : return mHighestJank;
1544 : }
1545 :
1546 : uint64_t
1547 0 : nsPerformanceGroup::HighestRecentCPOW() {
1548 0 : return mHighestCPOW;
1549 : }
1550 :
1551 : bool
1552 0 : nsPerformanceGroup::HasRecentUserInput() {
1553 0 : return mHasRecentUserInput;
1554 : }
1555 :
1556 : void
1557 0 : nsPerformanceGroup::RecordUserInput() {
1558 0 : mHasRecentUserInput = true;
1559 0 : }
1560 :
1561 : void
1562 0 : nsPerformanceGroup::ResetRecent() {
1563 0 : mHighestJank = 0;
1564 0 : mHighestCPOW = 0;
1565 0 : mHasRecentUserInput = false;
1566 0 : }
|