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