1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/CPUUsageWatcher.h"
12 # include <sys/resource.h>
13 # include <mach/clock.h>
14 # include <mach/mach_host.h>
17 #ifdef CPU_USAGE_WATCHER_ACTIVE
18 # include "mozilla/BackgroundHangMonitor.h"
23 #ifdef CPU_USAGE_WATCHER_ACTIVE
25 // Even if the machine only has one processor, tolerate up to 50%
26 // external CPU usage.
27 static const float kTolerableExternalCPUUsageFloor
= 0.5f
;
30 // The average CPU usage time, which can be summed across all cores in the
31 // system, or averaged between them. Whichever it is, it needs to be in the
32 // same units as updateTime.
34 // A monotonically increasing value in the same units as usageTime, which can
35 // be used to determine the percentage of active vs idle time
41 static const uint64_t kMicrosecondsPerSecond
= 1000000LL;
42 static const uint64_t kNanosecondsPerMicrosecond
= 1000LL;
44 static uint64_t GetMicroseconds(timeval time
) {
45 return ((uint64_t)time
.tv_sec
) * kMicrosecondsPerSecond
+
46 (uint64_t)time
.tv_usec
;
49 static uint64_t GetMicroseconds(mach_timespec_t time
) {
50 return ((uint64_t)time
.tv_sec
) * kMicrosecondsPerSecond
+
51 ((uint64_t)time
.tv_nsec
) / kNanosecondsPerMicrosecond
;
54 static Result
<CPUStats
, CPUUsageWatcherError
> GetProcessCPUStats(
58 int32_t rusageResult
= getrusage(RUSAGE_SELF
, &usage
);
59 if (rusageResult
== -1) {
60 return Err(GetProcessTimesError
);
63 GetMicroseconds(usage
.ru_utime
) + GetMicroseconds(usage
.ru_stime
);
65 clock_serv_t realtimeClock
;
66 kern_return_t errorResult
=
67 host_get_clock_service(mach_host_self(), REALTIME_CLOCK
, &realtimeClock
);
68 if (errorResult
!= KERN_SUCCESS
) {
69 return Err(GetProcessTimesError
);
72 errorResult
= clock_get_time(realtimeClock
, &time
);
73 if (errorResult
!= KERN_SUCCESS
) {
74 return Err(GetProcessTimesError
);
76 result
.updateTime
= GetMicroseconds(time
);
78 // getrusage will give us the sum of the values across all
79 // of our cores. Divide by the number of CPUs to get an average.
80 result
.usageTime
/= numCPUs
;
84 static Result
<CPUStats
, CPUUsageWatcherError
> GetGlobalCPUStats() {
86 host_cpu_load_info_data_t loadInfo
;
87 mach_msg_type_number_t loadInfoCount
= HOST_CPU_LOAD_INFO_COUNT
;
88 kern_return_t statsResult
=
89 host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO
,
90 (host_info_t
)&loadInfo
, &loadInfoCount
);
91 if (statsResult
!= KERN_SUCCESS
) {
92 return Err(HostStatisticsError
);
95 result
.usageTime
= loadInfo
.cpu_ticks
[CPU_STATE_USER
] +
96 loadInfo
.cpu_ticks
[CPU_STATE_NICE
] +
97 loadInfo
.cpu_ticks
[CPU_STATE_SYSTEM
];
98 result
.updateTime
= result
.usageTime
+ loadInfo
.cpu_ticks
[CPU_STATE_IDLE
];
106 // A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC
107 uint64_t FiletimeToInteger(FILETIME filetime
) {
108 return ((uint64_t)filetime
.dwLowDateTime
) | (uint64_t)filetime
.dwHighDateTime
112 Result
<CPUStats
, CPUUsageWatcherError
> GetProcessCPUStats(int32_t numCPUs
) {
113 CPUStats result
= {};
114 FILETIME creationFiletime
;
115 FILETIME exitFiletime
;
116 FILETIME kernelFiletime
;
117 FILETIME userFiletime
;
118 bool success
= GetProcessTimes(GetCurrentProcess(), &creationFiletime
,
119 &exitFiletime
, &kernelFiletime
, &userFiletime
);
121 return Err(GetProcessTimesError
);
125 FiletimeToInteger(kernelFiletime
) + FiletimeToInteger(userFiletime
);
127 FILETIME nowFiletime
;
128 GetSystemTimeAsFileTime(&nowFiletime
);
129 result
.updateTime
= FiletimeToInteger(nowFiletime
);
131 result
.usageTime
/= numCPUs
;
136 Result
<CPUStats
, CPUUsageWatcherError
> GetGlobalCPUStats() {
137 CPUStats result
= {};
138 FILETIME idleFiletime
;
139 FILETIME kernelFiletime
;
140 FILETIME userFiletime
;
141 bool success
= GetSystemTimes(&idleFiletime
, &kernelFiletime
, &userFiletime
);
144 return Err(GetSystemTimesError
);
148 FiletimeToInteger(kernelFiletime
) + FiletimeToInteger(userFiletime
);
149 result
.updateTime
= result
.usageTime
+ FiletimeToInteger(idleFiletime
);
156 Result
<Ok
, CPUUsageWatcherError
> CPUUsageWatcher::Init() {
157 mNumCPUs
= PR_GetNumberOfProcessors();
159 mExternalUsageThreshold
= 1.0f
;
160 return Err(GetNumberOfProcessorsError
);
162 mExternalUsageThreshold
=
163 std::max(1.0f
- 1.0f
/ (float)mNumCPUs
, kTolerableExternalCPUUsageFloor
);
165 CPUStats processTimes
;
166 MOZ_TRY_VAR(processTimes
, GetProcessCPUStats(mNumCPUs
));
167 mProcessUpdateTime
= processTimes
.updateTime
;
168 mProcessUsageTime
= processTimes
.usageTime
;
170 CPUStats globalTimes
;
171 MOZ_TRY_VAR(globalTimes
, GetGlobalCPUStats());
172 mGlobalUpdateTime
= globalTimes
.updateTime
;
173 mGlobalUsageTime
= globalTimes
.usageTime
;
177 CPUUsageWatcher
* self
= this;
178 NS_DispatchToMainThread(NS_NewRunnableFunction(
179 "CPUUsageWatcher::Init",
180 [=]() { BackgroundHangMonitor::RegisterAnnotator(*self
); }));
185 void CPUUsageWatcher::Uninit() {
187 BackgroundHangMonitor::UnregisterAnnotator(*this);
189 mInitialized
= false;
192 Result
<Ok
, CPUUsageWatcherError
> CPUUsageWatcher::CollectCPUUsage() {
197 mExternalUsageRatio
= 0.0f
;
199 CPUStats processTimes
;
200 MOZ_TRY_VAR(processTimes
, GetProcessCPUStats(mNumCPUs
));
201 CPUStats globalTimes
;
202 MOZ_TRY_VAR(globalTimes
, GetGlobalCPUStats());
204 uint64_t processUsageDelta
= processTimes
.usageTime
- mProcessUsageTime
;
205 uint64_t processUpdateDelta
= processTimes
.updateTime
- mProcessUpdateTime
;
206 float processUsageNormalized
=
207 processUsageDelta
> 0
208 ? (float)processUsageDelta
/ (float)processUpdateDelta
211 uint64_t globalUsageDelta
= globalTimes
.usageTime
- mGlobalUsageTime
;
212 uint64_t globalUpdateDelta
= globalTimes
.updateTime
- mGlobalUpdateTime
;
213 float globalUsageNormalized
=
214 globalUsageDelta
> 0 ? (float)globalUsageDelta
/ (float)globalUpdateDelta
217 mProcessUsageTime
= processTimes
.usageTime
;
218 mProcessUpdateTime
= processTimes
.updateTime
;
219 mGlobalUsageTime
= globalTimes
.usageTime
;
220 mGlobalUpdateTime
= globalTimes
.updateTime
;
222 mExternalUsageRatio
=
223 std::max(0.0f
, globalUsageNormalized
- processUsageNormalized
);
228 void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations
& aAnnotations
) {
233 if (mExternalUsageRatio
> mExternalUsageThreshold
) {
234 aAnnotations
.AddAnnotation(u
"ExternalCPUHigh"_ns
, true);
238 #else // !CPU_USAGE_WATCHER_ACTIVE
240 Result
<Ok
, CPUUsageWatcherError
> CPUUsageWatcher::Init() { return Ok(); }
242 void CPUUsageWatcher::Uninit() {}
244 Result
<Ok
, CPUUsageWatcherError
> CPUUsageWatcher::CollectCPUUsage() {
248 void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations
& aAnnotations
) {}
250 #endif // CPU_USAGE_WATCHER_ACTIVE
252 } // namespace mozilla