/src/mozilla-central/xpcom/threads/CPUUsageWatcher.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | namespace mozilla { |
18 | | |
19 | | #ifdef CPU_USAGE_WATCHER_ACTIVE |
20 | | |
21 | | // Even if the machine only has one processor, tolerate up to 50% |
22 | | // external CPU usage. |
23 | | static const float kTolerableExternalCPUUsageFloor = 0.5f; |
24 | | |
25 | | struct CPUStats { |
26 | | // The average CPU usage time, which can be summed across all cores in the |
27 | | // system, or averaged between them. Whichever it is, it needs to be in the |
28 | | // same units as updateTime. |
29 | | uint64_t usageTime; |
30 | | // A monotonically increasing value in the same units as usageTime, which can |
31 | | // be used to determine the percentage of active vs idle time |
32 | | uint64_t updateTime; |
33 | | }; |
34 | | |
35 | | #ifdef XP_MACOSX |
36 | | |
37 | | static const uint64_t kMicrosecondsPerSecond = 1000000LL; |
38 | | static const uint64_t kNanosecondsPerMicrosecond = 1000LL; |
39 | | static const uint64_t kCPUCheckInterval = kMicrosecondsPerSecond / 2LL; |
40 | | |
41 | | uint64_t GetMicroseconds(timeval time) { |
42 | | return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + |
43 | | (uint64_t)time.tv_usec; |
44 | | } |
45 | | |
46 | | uint64_t GetMicroseconds(mach_timespec_t time) { |
47 | | return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + |
48 | | ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond; |
49 | | } |
50 | | |
51 | | Result<CPUStats, CPUUsageWatcherError> |
52 | | GetProcessCPUStats(int32_t numCPUs) { |
53 | | CPUStats result = {}; |
54 | | rusage usage; |
55 | | int32_t rusageResult = getrusage(RUSAGE_SELF, &usage); |
56 | | if (rusageResult == -1) { |
57 | | return Err(GetProcessTimesError); |
58 | | } |
59 | | result.usageTime = GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime); |
60 | | |
61 | | clock_serv_t realtimeClock; |
62 | | kern_return_t errorResult = |
63 | | host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &realtimeClock); |
64 | | if (errorResult != KERN_SUCCESS) { |
65 | | return Err(GetProcessTimesError); |
66 | | } |
67 | | mach_timespec_t time; |
68 | | errorResult = clock_get_time(realtimeClock, &time); |
69 | | if (errorResult != KERN_SUCCESS) { |
70 | | return Err(GetProcessTimesError); |
71 | | } |
72 | | result.updateTime = GetMicroseconds(time); |
73 | | |
74 | | // getrusage will give us the sum of the values across all |
75 | | // of our cores. Divide by the number of CPUs to get an average. |
76 | | result.usageTime /= numCPUs; |
77 | | return result; |
78 | | } |
79 | | |
80 | | Result<CPUStats, CPUUsageWatcherError> |
81 | | GetGlobalCPUStats() { |
82 | | CPUStats result = {}; |
83 | | host_cpu_load_info_data_t loadInfo; |
84 | | mach_msg_type_number_t loadInfoCount = HOST_CPU_LOAD_INFO_COUNT; |
85 | | kern_return_t statsResult = host_statistics(mach_host_self(), |
86 | | HOST_CPU_LOAD_INFO, |
87 | | (host_info_t)&loadInfo, |
88 | | &loadInfoCount); |
89 | | if (statsResult != KERN_SUCCESS) { |
90 | | return Err(HostStatisticsError); |
91 | | } |
92 | | |
93 | | result.usageTime = loadInfo.cpu_ticks[CPU_STATE_USER] + |
94 | | loadInfo.cpu_ticks[CPU_STATE_NICE] + |
95 | | loadInfo.cpu_ticks[CPU_STATE_SYSTEM]; |
96 | | result.updateTime = result.usageTime + loadInfo.cpu_ticks[CPU_STATE_IDLE]; |
97 | | return result; |
98 | | } |
99 | | |
100 | | #endif // XP_MACOSX |
101 | | |
102 | | #ifdef XP_WIN |
103 | | |
104 | | // A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC |
105 | | static const uint64_t kFILETIMETicksPerSecond = 10000000; |
106 | | static const uint64_t kCPUCheckInterval = kFILETIMETicksPerSecond / 2; |
107 | | |
108 | | uint64_t |
109 | | FiletimeToInteger(FILETIME filetime) { |
110 | | return ((uint64_t)filetime.dwLowDateTime) | |
111 | | (uint64_t)filetime.dwHighDateTime << 32; |
112 | | } |
113 | | |
114 | | Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(int32_t numCPUs) { |
115 | | CPUStats result = {}; |
116 | | FILETIME creationFiletime; |
117 | | FILETIME exitFiletime; |
118 | | FILETIME kernelFiletime; |
119 | | FILETIME userFiletime; |
120 | | bool success = GetProcessTimes(GetCurrentProcess(), |
121 | | &creationFiletime, |
122 | | &exitFiletime, |
123 | | &kernelFiletime, |
124 | | &userFiletime); |
125 | | if (!success) { |
126 | | return Err(GetProcessTimesError); |
127 | | } |
128 | | |
129 | | result.usageTime = FiletimeToInteger(kernelFiletime) + |
130 | | FiletimeToInteger(userFiletime); |
131 | | |
132 | | FILETIME nowFiletime; |
133 | | GetSystemTimeAsFileTime(&nowFiletime); |
134 | | result.updateTime = FiletimeToInteger(nowFiletime); |
135 | | |
136 | | result.usageTime /= numCPUs; |
137 | | |
138 | | return result; |
139 | | } |
140 | | |
141 | | Result<CPUStats, CPUUsageWatcherError> |
142 | | GetGlobalCPUStats() { |
143 | | CPUStats result = {}; |
144 | | FILETIME idleFiletime; |
145 | | FILETIME kernelFiletime; |
146 | | FILETIME userFiletime; |
147 | | bool success = GetSystemTimes(&idleFiletime, |
148 | | &kernelFiletime, |
149 | | &userFiletime); |
150 | | |
151 | | if (!success) { |
152 | | return Err(GetSystemTimesError); |
153 | | } |
154 | | |
155 | | result.usageTime = FiletimeToInteger(kernelFiletime) + |
156 | | FiletimeToInteger(userFiletime); |
157 | | result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime); |
158 | | |
159 | | return result; |
160 | | } |
161 | | |
162 | | #endif // XP_WIN |
163 | | |
164 | | Result<Ok, CPUUsageWatcherError> |
165 | | CPUUsageWatcher::Init() |
166 | | { |
167 | | mNumCPUs = PR_GetNumberOfProcessors(); |
168 | | if (mNumCPUs <= 0) { |
169 | | mExternalUsageThreshold = 1.0f; |
170 | | return Err(GetNumberOfProcessorsError); |
171 | | } |
172 | | mExternalUsageThreshold = std::max(1.0f - 1.0f / (float)mNumCPUs, |
173 | | kTolerableExternalCPUUsageFloor); |
174 | | |
175 | | CPUStats processTimes; |
176 | | MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); |
177 | | mProcessUpdateTime = processTimes.updateTime; |
178 | | mProcessUsageTime = processTimes.usageTime; |
179 | | |
180 | | CPUStats globalTimes; |
181 | | MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); |
182 | | mGlobalUpdateTime = globalTimes.updateTime; |
183 | | mGlobalUsageTime = globalTimes.usageTime; |
184 | | |
185 | | mInitialized = true; |
186 | | |
187 | | CPUUsageWatcher* self = this; |
188 | | NS_DispatchToMainThread( |
189 | | NS_NewRunnableFunction("CPUUsageWatcher::Init", |
190 | | [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); })); |
191 | | |
192 | | return Ok(); |
193 | | } |
194 | | |
195 | | void |
196 | | CPUUsageWatcher::Uninit() |
197 | | { |
198 | | if (mInitialized) { |
199 | | BackgroundHangMonitor::UnregisterAnnotator(*this); |
200 | | } |
201 | | mInitialized = false; |
202 | | } |
203 | | |
204 | | Result<Ok, CPUUsageWatcherError> |
205 | | CPUUsageWatcher::CollectCPUUsage() |
206 | | { |
207 | | if (!mInitialized) { |
208 | | return Ok(); |
209 | | } |
210 | | |
211 | | mExternalUsageRatio = 0.0f; |
212 | | |
213 | | CPUStats processTimes; |
214 | | MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); |
215 | | CPUStats globalTimes; |
216 | | MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); |
217 | | |
218 | | uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime; |
219 | | uint64_t processUpdateDelta = processTimes.updateTime - mProcessUpdateTime; |
220 | | float processUsageNormalized = processUsageDelta > 0 ? |
221 | | (float)processUsageDelta / (float)processUpdateDelta : |
222 | | 0.0f; |
223 | | |
224 | | uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime; |
225 | | uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime; |
226 | | float globalUsageNormalized = globalUsageDelta > 0 ? |
227 | | (float)globalUsageDelta / (float)globalUpdateDelta : |
228 | | 0.0f; |
229 | | |
230 | | mProcessUsageTime = processTimes.usageTime; |
231 | | mProcessUpdateTime = processTimes.updateTime; |
232 | | mGlobalUsageTime = globalTimes.usageTime; |
233 | | mGlobalUpdateTime = globalTimes.updateTime; |
234 | | |
235 | | mExternalUsageRatio = std::max(0.0f, |
236 | | globalUsageNormalized - processUsageNormalized); |
237 | | |
238 | | return Ok(); |
239 | | } |
240 | | |
241 | | void |
242 | | CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { |
243 | | if (!mInitialized) { |
244 | | return; |
245 | | } |
246 | | |
247 | | if (mExternalUsageRatio > mExternalUsageThreshold) { |
248 | | aAnnotations.AddAnnotation(NS_LITERAL_STRING("ExternalCPUHigh"), true); |
249 | | } |
250 | | } |
251 | | |
252 | | #else // !CPU_USAGE_WATCHER_ACTIVE |
253 | | |
254 | | Result<Ok, CPUUsageWatcherError> |
255 | | CPUUsageWatcher::Init() |
256 | 3 | { |
257 | 3 | return Ok(); |
258 | 3 | } |
259 | | |
260 | 0 | void CPUUsageWatcher::Uninit() {} |
261 | | |
262 | | Result<Ok, CPUUsageWatcherError> |
263 | | CPUUsageWatcher::CollectCPUUsage() |
264 | 0 | { |
265 | 0 | return Ok(); |
266 | 0 | } |
267 | | |
268 | 0 | void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {} |
269 | | |
270 | | #endif // CPU_USAGE_WATCHER_ACTIVE |
271 | | |
272 | | } // namespace mozilla |