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/. */
6
7 #include "mozilla/CPUUsageWatcher.h"
8
9 #include "prsystem.h"
10
11 #ifdef XP_MACOSX
12 # include <sys/resource.h>
13 # include <mach/clock.h>
14 # include <mach/mach_host.h>
15 #endif
16
17 #ifdef CPU_USAGE_WATCHER_ACTIVE
18 # include "mozilla/BackgroundHangMonitor.h"
19 #endif
20
21 namespace mozilla {
22
23 #ifdef CPU_USAGE_WATCHER_ACTIVE
24
25 // Even if the machine only has one processor, tolerate up to 50%
26 // external CPU usage.
27 static const float kTolerableExternalCPUUsageFloor = 0.5f;
28
29 struct CPUStats {
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.
33 uint64_t usageTime;
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
36 uint64_t updateTime;
37 };
38
39 # ifdef XP_MACOSX
40
41 static const uint64_t kMicrosecondsPerSecond = 1000000LL;
42 static const uint64_t kNanosecondsPerMicrosecond = 1000LL;
43
GetMicroseconds(timeval time)44 static uint64_t GetMicroseconds(timeval time) {
45 return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond +
46 (uint64_t)time.tv_usec;
47 }
48
GetMicroseconds(mach_timespec_t time)49 static uint64_t GetMicroseconds(mach_timespec_t time) {
50 return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond +
51 ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond;
52 }
53
GetProcessCPUStats(int32_t numCPUs)54 static Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(
55 int32_t numCPUs) {
56 CPUStats result = {};
57 rusage usage;
58 int32_t rusageResult = getrusage(RUSAGE_SELF, &usage);
59 if (rusageResult == -1) {
60 return Err(GetProcessTimesError);
61 }
62 result.usageTime =
63 GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime);
64
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);
70 }
71 mach_timespec_t time;
72 errorResult = clock_get_time(realtimeClock, &time);
73 if (errorResult != KERN_SUCCESS) {
74 return Err(GetProcessTimesError);
75 }
76 result.updateTime = GetMicroseconds(time);
77
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;
81 return result;
82 }
83
GetGlobalCPUStats()84 static Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() {
85 CPUStats result = {};
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);
93 }
94
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];
99 return result;
100 }
101
102 # endif // XP_MACOSX
103
104 # ifdef XP_WIN
105
106 // A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC
FiletimeToInteger(FILETIME filetime)107 uint64_t FiletimeToInteger(FILETIME filetime) {
108 return ((uint64_t)filetime.dwLowDateTime) | (uint64_t)filetime.dwHighDateTime
109 << 32;
110 }
111
GetProcessCPUStats(int32_t numCPUs)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);
120 if (!success) {
121 return Err(GetProcessTimesError);
122 }
123
124 result.usageTime =
125 FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime);
126
127 FILETIME nowFiletime;
128 GetSystemTimeAsFileTime(&nowFiletime);
129 result.updateTime = FiletimeToInteger(nowFiletime);
130
131 result.usageTime /= numCPUs;
132
133 return result;
134 }
135
GetGlobalCPUStats()136 Result<CPUStats, CPUUsageWatcherError> GetGlobalCPUStats() {
137 CPUStats result = {};
138 FILETIME idleFiletime;
139 FILETIME kernelFiletime;
140 FILETIME userFiletime;
141 bool success = GetSystemTimes(&idleFiletime, &kernelFiletime, &userFiletime);
142
143 if (!success) {
144 return Err(GetSystemTimesError);
145 }
146
147 result.usageTime =
148 FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime);
149 result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime);
150
151 return result;
152 }
153
154 # endif // XP_WIN
155
Init()156 Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() {
157 mNumCPUs = PR_GetNumberOfProcessors();
158 if (mNumCPUs <= 0) {
159 mExternalUsageThreshold = 1.0f;
160 return Err(GetNumberOfProcessorsError);
161 }
162 mExternalUsageThreshold =
163 std::max(1.0f - 1.0f / (float)mNumCPUs, kTolerableExternalCPUUsageFloor);
164
165 CPUStats processTimes;
166 MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
167 mProcessUpdateTime = processTimes.updateTime;
168 mProcessUsageTime = processTimes.usageTime;
169
170 CPUStats globalTimes;
171 MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
172 mGlobalUpdateTime = globalTimes.updateTime;
173 mGlobalUsageTime = globalTimes.usageTime;
174
175 mInitialized = true;
176
177 CPUUsageWatcher* self = this;
178 NS_DispatchToMainThread(NS_NewRunnableFunction(
179 "CPUUsageWatcher::Init",
180 [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); }));
181
182 return Ok();
183 }
184
Uninit()185 void CPUUsageWatcher::Uninit() {
186 if (mInitialized) {
187 BackgroundHangMonitor::UnregisterAnnotator(*this);
188 }
189 mInitialized = false;
190 }
191
CollectCPUUsage()192 Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() {
193 if (!mInitialized) {
194 return Ok();
195 }
196
197 mExternalUsageRatio = 0.0f;
198
199 CPUStats processTimes;
200 MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
201 CPUStats globalTimes;
202 MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
203
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
209 : 0.0f;
210
211 uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime;
212 uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime;
213 float globalUsageNormalized =
214 globalUsageDelta > 0 ? (float)globalUsageDelta / (float)globalUpdateDelta
215 : 0.0f;
216
217 mProcessUsageTime = processTimes.usageTime;
218 mProcessUpdateTime = processTimes.updateTime;
219 mGlobalUsageTime = globalTimes.usageTime;
220 mGlobalUpdateTime = globalTimes.updateTime;
221
222 mExternalUsageRatio =
223 std::max(0.0f, globalUsageNormalized - processUsageNormalized);
224
225 return Ok();
226 }
227
AnnotateHang(BackgroundHangAnnotations & aAnnotations)228 void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {
229 if (!mInitialized) {
230 return;
231 }
232
233 if (mExternalUsageRatio > mExternalUsageThreshold) {
234 aAnnotations.AddAnnotation(u"ExternalCPUHigh"_ns, true);
235 }
236 }
237
238 #else // !CPU_USAGE_WATCHER_ACTIVE
239
240 Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::Init() { return Ok(); }
241
242 void CPUUsageWatcher::Uninit() {}
243
244 Result<Ok, CPUUsageWatcherError> CPUUsageWatcher::CollectCPUUsage() {
245 return Ok();
246 }
247
248 void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {}
249
250 #endif // CPU_USAGE_WATCHER_ACTIVE
251
252 } // namespace mozilla
253