Coverage Report

Created: 2018-09-25 14:53

/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
}