|
1 /* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "LoadMonitor.h" |
|
7 #include "LoadManager.h" |
|
8 #include "nsString.h" |
|
9 #include "prlog.h" |
|
10 #include "prtime.h" |
|
11 #include "prinrval.h" |
|
12 #include "prsystem.h" |
|
13 #include "prprf.h" |
|
14 |
|
15 #include "nsString.h" |
|
16 #include "nsThreadUtils.h" |
|
17 #include "nsReadableUtils.h" |
|
18 #include "nsNetUtil.h" |
|
19 #include "nsILineInputStream.h" |
|
20 #include "nsIObserverService.h" |
|
21 #include "nsIServiceManager.h" |
|
22 |
|
23 #include "mozilla/TimeStamp.h" |
|
24 #include "mozilla/Services.h" |
|
25 |
|
26 #if defined(ANDROID) || defined(LINUX) |
|
27 #include <sys/time.h> |
|
28 #include <sys/resource.h> |
|
29 #include <unistd.h> |
|
30 #endif |
|
31 |
|
32 // NSPR_LOG_MODULES=LoadManager:5 |
|
33 #undef LOG |
|
34 #undef LOG_ENABLED |
|
35 #if defined(PR_LOGGING) |
|
36 #define LOG(args) PR_LOG(gLoadManagerLog, PR_LOG_DEBUG, args) |
|
37 #define LOG_ENABLED() PR_LOG_TEST(gLoadManagerLog, 4) |
|
38 #define LOG_MANY_ENABLED() PR_LOG_TEST(gLoadManagerLog, 5) |
|
39 #else |
|
40 #define LOG(args) |
|
41 #define LOG_ENABLED() (false) |
|
42 #define LOG_MANY_ENABLED() (false) |
|
43 #endif |
|
44 |
|
45 namespace mozilla { |
|
46 |
|
47 NS_IMPL_ISUPPORTS(LoadMonitor, nsIObserver) |
|
48 |
|
49 LoadMonitor::LoadMonitor(int aLoadUpdateInterval) |
|
50 : mLoadUpdateInterval(aLoadUpdateInterval), |
|
51 mLock("LoadMonitor.mLock"), |
|
52 mCondVar(mLock, "LoadMonitor.mCondVar"), |
|
53 mShutdownPending(false), |
|
54 mLoadInfoThread(nullptr), |
|
55 mSystemLoad(0.0f), |
|
56 mProcessLoad(0.0f), |
|
57 mLoadNotificationCallback(nullptr) |
|
58 { |
|
59 } |
|
60 |
|
61 LoadMonitor::~LoadMonitor() |
|
62 { |
|
63 Shutdown(); |
|
64 } |
|
65 |
|
66 NS_IMETHODIMP |
|
67 LoadMonitor::Observe(nsISupports* /* aSubject */, |
|
68 const char* aTopic, |
|
69 const char16_t* /* aData */) |
|
70 { |
|
71 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); |
|
72 MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); |
|
73 Shutdown(); |
|
74 return NS_OK; |
|
75 } |
|
76 |
|
77 class LoadMonitorAddObserver : public nsRunnable |
|
78 { |
|
79 public: |
|
80 LoadMonitorAddObserver(nsRefPtr<LoadMonitor> loadMonitor) |
|
81 { |
|
82 mLoadMonitor = loadMonitor; |
|
83 } |
|
84 |
|
85 NS_IMETHOD Run() |
|
86 { |
|
87 nsCOMPtr<nsIObserverService> observerService = |
|
88 mozilla::services::GetObserverService(); |
|
89 if (!observerService) |
|
90 return NS_ERROR_FAILURE; |
|
91 |
|
92 nsresult rv = observerService->AddObserver(mLoadMonitor, "xpcom-shutdown-threads", false); |
|
93 NS_ENSURE_SUCCESS(rv, rv); |
|
94 |
|
95 return NS_OK; |
|
96 } |
|
97 |
|
98 private: |
|
99 nsRefPtr<LoadMonitor> mLoadMonitor; |
|
100 }; |
|
101 |
|
102 class LoadMonitorRemoveObserver : public nsRunnable |
|
103 { |
|
104 public: |
|
105 LoadMonitorRemoveObserver(nsRefPtr<LoadMonitor> loadMonitor) |
|
106 { |
|
107 mLoadMonitor = loadMonitor; |
|
108 } |
|
109 |
|
110 NS_IMETHOD Run() |
|
111 { |
|
112 // remove xpcom shutdown observer |
|
113 nsCOMPtr<nsIObserverService> observerService = |
|
114 mozilla::services::GetObserverService(); |
|
115 |
|
116 if (observerService) |
|
117 observerService->RemoveObserver(mLoadMonitor, "xpcom-shutdown-threads"); |
|
118 |
|
119 return NS_OK; |
|
120 } |
|
121 |
|
122 private: |
|
123 nsRefPtr<LoadMonitor> mLoadMonitor; |
|
124 }; |
|
125 |
|
126 void LoadMonitor::Shutdown() |
|
127 { |
|
128 MutexAutoLock lock(mLock); |
|
129 if (mLoadInfoThread) { |
|
130 mShutdownPending = true; |
|
131 mCondVar.Notify(); |
|
132 |
|
133 mLoadInfoThread = nullptr; |
|
134 |
|
135 nsRefPtr<LoadMonitorRemoveObserver> remObsRunner = new LoadMonitorRemoveObserver(this); |
|
136 if (!NS_IsMainThread()) { |
|
137 NS_DispatchToMainThread(remObsRunner, NS_DISPATCH_NORMAL); |
|
138 } else { |
|
139 remObsRunner->Run(); |
|
140 } |
|
141 } |
|
142 } |
|
143 |
|
144 class LoadStats |
|
145 { |
|
146 public: |
|
147 LoadStats() : |
|
148 mPrevTotalTimes(0), |
|
149 mPrevCpuTimes(0), |
|
150 mPrevLoad(0) {}; |
|
151 |
|
152 double GetLoad() { return (double)mPrevLoad; }; |
|
153 |
|
154 uint64_t mPrevTotalTimes; |
|
155 uint64_t mPrevCpuTimes; |
|
156 float mPrevLoad; // Previous load value. |
|
157 }; |
|
158 |
|
159 class LoadInfo : public mozilla::RefCounted<LoadInfo> |
|
160 { |
|
161 public: |
|
162 MOZ_DECLARE_REFCOUNTED_TYPENAME(LoadInfo) |
|
163 LoadInfo(int aLoadUpdateInterval); |
|
164 double GetSystemLoad() { return mSystemLoad.GetLoad(); }; |
|
165 double GetProcessLoad() { return mProcessLoad.GetLoad(); }; |
|
166 nsresult UpdateSystemLoad(); |
|
167 nsresult UpdateProcessLoad(); |
|
168 |
|
169 private: |
|
170 void UpdateCpuLoad(uint64_t ticks_per_interval, |
|
171 uint64_t current_total_times, |
|
172 uint64_t current_cpu_times, |
|
173 LoadStats* loadStat); |
|
174 LoadStats mSystemLoad; |
|
175 LoadStats mProcessLoad; |
|
176 uint64_t mTicksPerInterval; |
|
177 int mLoadUpdateInterval; |
|
178 }; |
|
179 |
|
180 LoadInfo::LoadInfo(int aLoadUpdateInterval) |
|
181 : mLoadUpdateInterval(aLoadUpdateInterval) |
|
182 { |
|
183 #if defined(ANDROID) || defined(LINUX) |
|
184 mTicksPerInterval = (sysconf(_SC_CLK_TCK) * mLoadUpdateInterval) / 1000; |
|
185 #endif |
|
186 } |
|
187 |
|
188 void LoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval, |
|
189 uint64_t current_total_times, |
|
190 uint64_t current_cpu_times, |
|
191 LoadStats *loadStat) { |
|
192 |
|
193 // Check if we get an inconsistent number of ticks. |
|
194 if (((current_total_times - loadStat->mPrevTotalTimes) |
|
195 > (ticks_per_interval * 10)) |
|
196 || current_total_times < loadStat->mPrevTotalTimes |
|
197 || current_cpu_times < loadStat->mPrevCpuTimes) { |
|
198 // Bug at least on the Nexus 4 and Galaxy S4 |
|
199 // https://code.google.com/p/android/issues/detail?id=41630 |
|
200 // We do need to update our previous times, or we can get stuck |
|
201 // when there is a blip upwards and then we get a bunch of consecutive |
|
202 // lower times. Just skip the load calculation. |
|
203 LOG(("Inconsistent time values are passed. ignored")); |
|
204 // Try to recover next tick |
|
205 loadStat->mPrevTotalTimes = current_total_times; |
|
206 loadStat->mPrevCpuTimes = current_cpu_times; |
|
207 return; |
|
208 } |
|
209 |
|
210 const uint64_t cpu_diff = current_cpu_times - loadStat->mPrevCpuTimes; |
|
211 const uint64_t total_diff = current_total_times - loadStat->mPrevTotalTimes; |
|
212 if (total_diff > 0) { |
|
213 float result = (float)cpu_diff / (float)total_diff; |
|
214 loadStat->mPrevLoad = result; |
|
215 } |
|
216 loadStat->mPrevTotalTimes = current_total_times; |
|
217 loadStat->mPrevCpuTimes = current_cpu_times; |
|
218 } |
|
219 |
|
220 nsresult LoadInfo::UpdateSystemLoad() |
|
221 { |
|
222 #if defined(LINUX) || defined(ANDROID) |
|
223 nsCOMPtr<nsIFile> procStatFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); |
|
224 procStatFile->InitWithPath(NS_LITERAL_STRING("/proc/stat")); |
|
225 |
|
226 nsCOMPtr<nsIInputStream> fileInputStream; |
|
227 nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), |
|
228 procStatFile); |
|
229 NS_ENSURE_SUCCESS(rv, rv); |
|
230 |
|
231 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv); |
|
232 NS_ENSURE_SUCCESS(rv, rv); |
|
233 |
|
234 nsAutoCString buffer; |
|
235 bool isMore = true; |
|
236 lineInputStream->ReadLine(buffer, &isMore); |
|
237 |
|
238 uint64_t user; |
|
239 uint64_t nice; |
|
240 uint64_t system; |
|
241 uint64_t idle; |
|
242 if (PR_sscanf(buffer.get(), "cpu %llu %llu %llu %llu", |
|
243 &user, &nice, |
|
244 &system, &idle) != 4) { |
|
245 LOG(("Error parsing /proc/stat")); |
|
246 return NS_ERROR_FAILURE; |
|
247 } |
|
248 |
|
249 const uint64_t cpu_times = nice + system + user; |
|
250 const uint64_t total_times = cpu_times + idle; |
|
251 |
|
252 UpdateCpuLoad(mTicksPerInterval, |
|
253 total_times, |
|
254 cpu_times, |
|
255 &mSystemLoad); |
|
256 return NS_OK; |
|
257 #else |
|
258 // Not implemented |
|
259 return NS_OK; |
|
260 #endif |
|
261 } |
|
262 |
|
263 nsresult LoadInfo::UpdateProcessLoad() { |
|
264 #if defined(LINUX) || defined(ANDROID) |
|
265 struct timeval tv; |
|
266 gettimeofday(&tv, nullptr); |
|
267 const uint64_t total_times = tv.tv_sec * PR_USEC_PER_SEC + tv.tv_usec; |
|
268 |
|
269 rusage usage; |
|
270 if (getrusage(RUSAGE_SELF, &usage) < 0) { |
|
271 LOG(("getrusage failed")); |
|
272 return NS_ERROR_FAILURE; |
|
273 } |
|
274 |
|
275 const uint64_t cpu_times = |
|
276 (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * PR_USEC_PER_SEC + |
|
277 usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; |
|
278 |
|
279 UpdateCpuLoad(PR_USEC_PER_MSEC * mLoadUpdateInterval, |
|
280 total_times, |
|
281 cpu_times, |
|
282 &mProcessLoad); |
|
283 #endif // defined(LINUX) || defined(ANDROID) |
|
284 return NS_OK; |
|
285 } |
|
286 |
|
287 class LoadInfoCollectRunner : public nsRunnable |
|
288 { |
|
289 public: |
|
290 LoadInfoCollectRunner(nsRefPtr<LoadMonitor> loadMonitor, |
|
291 int aLoadUpdateInterval) |
|
292 : mLoadUpdateInterval(aLoadUpdateInterval), |
|
293 mLoadNoiseCounter(0) |
|
294 { |
|
295 mLoadMonitor = loadMonitor; |
|
296 mLoadInfo = new LoadInfo(mLoadUpdateInterval); |
|
297 } |
|
298 |
|
299 NS_IMETHOD Run() |
|
300 { |
|
301 MutexAutoLock lock(mLoadMonitor->mLock); |
|
302 while (!mLoadMonitor->mShutdownPending) { |
|
303 mLoadInfo->UpdateSystemLoad(); |
|
304 mLoadInfo->UpdateProcessLoad(); |
|
305 float sysLoad = mLoadInfo->GetSystemLoad(); |
|
306 float procLoad = mLoadInfo->GetProcessLoad(); |
|
307 if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) { |
|
308 LOG(("System Load: %f Process Load: %f", sysLoad, procLoad)); |
|
309 mLoadNoiseCounter = 0; |
|
310 } |
|
311 mLoadMonitor->SetSystemLoad(sysLoad); |
|
312 mLoadMonitor->SetProcessLoad(procLoad); |
|
313 mLoadMonitor->FireCallbacks(); |
|
314 |
|
315 mLoadMonitor->mCondVar.Wait(PR_MillisecondsToInterval(mLoadUpdateInterval)); |
|
316 } |
|
317 return NS_OK; |
|
318 } |
|
319 |
|
320 private: |
|
321 RefPtr<LoadInfo> mLoadInfo; |
|
322 nsRefPtr<LoadMonitor> mLoadMonitor; |
|
323 int mLoadUpdateInterval; |
|
324 int mLoadNoiseCounter; |
|
325 }; |
|
326 |
|
327 void |
|
328 LoadMonitor::SetProcessLoad(float load) { |
|
329 mLock.AssertCurrentThreadOwns(); |
|
330 mProcessLoad = load; |
|
331 } |
|
332 |
|
333 void |
|
334 LoadMonitor::SetSystemLoad(float load) { |
|
335 mLock.AssertCurrentThreadOwns(); |
|
336 mSystemLoad = load; |
|
337 } |
|
338 |
|
339 float |
|
340 LoadMonitor::GetProcessLoad() { |
|
341 MutexAutoLock lock(mLock); |
|
342 float load = mProcessLoad; |
|
343 return load; |
|
344 } |
|
345 |
|
346 void |
|
347 LoadMonitor::FireCallbacks() { |
|
348 if (mLoadNotificationCallback) { |
|
349 mLoadNotificationCallback->LoadChanged(mSystemLoad, mProcessLoad); |
|
350 } |
|
351 } |
|
352 |
|
353 float |
|
354 LoadMonitor::GetSystemLoad() { |
|
355 MutexAutoLock lock(mLock); |
|
356 float load = mSystemLoad; |
|
357 return load; |
|
358 } |
|
359 |
|
360 nsresult |
|
361 LoadMonitor::Init(nsRefPtr<LoadMonitor> &self) |
|
362 { |
|
363 LOG(("Initializing LoadMonitor")); |
|
364 |
|
365 #if defined(ANDROID) || defined(LINUX) |
|
366 nsRefPtr<LoadMonitorAddObserver> addObsRunner = new LoadMonitorAddObserver(self); |
|
367 NS_DispatchToMainThread(addObsRunner, NS_DISPATCH_NORMAL); |
|
368 |
|
369 NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread)); |
|
370 |
|
371 nsRefPtr<LoadInfoCollectRunner> runner = |
|
372 new LoadInfoCollectRunner(self, mLoadUpdateInterval); |
|
373 mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL); |
|
374 #endif |
|
375 |
|
376 return NS_OK; |
|
377 } |
|
378 |
|
379 void |
|
380 LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback) |
|
381 { |
|
382 mLoadNotificationCallback = aCallback; |
|
383 } |
|
384 |
|
385 } |