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 "mozilla/Logging.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 "nsIInputStream.h"
20 #include "nsIFile.h"
21 #include "nsILineInputStream.h"
22 #include "nsIObserverService.h"
23 #include "nsIServiceManager.h"
24
25 #include "mozilla/TimeStamp.h"
26 #include "mozilla/Services.h"
27
28 #ifdef XP_UNIX
29 #include <sys/time.h>
30 #include <sys/resource.h>
31 #include <unistd.h>
32 #endif
33
34 #ifdef XP_MACOSX
35 #include <mach/mach_host.h>
36 #include <mach/mach_init.h>
37 #include <mach/host_info.h>
38 #endif
39
40 #if defined(__DragonFly__) || defined(__FreeBSD__) \
41 || defined(__NetBSD__) || defined(__OpenBSD__)
42 #include <sys/sysctl.h>
43 # if defined(__OpenBSD__)
44 #define KERN_CP_TIME KERN_CPTIME
45 # endif
46 #endif
47
48 #if defined(__NetBSD__) || defined(__OpenBSD__)
49 #include <sys/sched.h>
50 #endif
51
52 #ifdef XP_WIN
53 #include <pdh.h>
54 #include <tchar.h>
55 #pragma comment(lib, "pdh.lib")
56 #endif
57
58 // MOZ_LOG=LoadManager:5
59 #undef LOG
60 #undef LOG_ENABLED
61 #define LOG(args) MOZ_LOG(gLoadManagerLog, mozilla::LogLevel::Debug, args)
62 #define LOG_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Debug)
63 #define LOG_MANY_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Verbose)
64
65 namespace mozilla {
66
NS_IMPL_ISUPPORTS(LoadMonitor,nsIObserver)67 NS_IMPL_ISUPPORTS(LoadMonitor, nsIObserver)
68
69 LoadMonitor::LoadMonitor(int aLoadUpdateInterval)
70 : mLoadUpdateInterval(aLoadUpdateInterval),
71 mLock("LoadMonitor.mLock"),
72 mCondVar(mLock, "LoadMonitor.mCondVar"),
73 mShutdownPending(false),
74 mLoadInfoThread(nullptr),
75 mSystemLoad(0.0f),
76 mProcessLoad(0.0f),
77 mLoadNotificationCallback(nullptr)
78 {
79 }
80
~LoadMonitor()81 LoadMonitor::~LoadMonitor()
82 {
83 Shutdown();
84 }
85
86 NS_IMETHODIMP
Observe(nsISupports *,const char * aTopic,const char16_t *)87 LoadMonitor::Observe(nsISupports* /* aSubject */,
88 const char* aTopic,
89 const char16_t* /* aData */)
90 {
91 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
92 MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!");
93 Shutdown();
94 return NS_OK;
95 }
96
97 class LoadMonitorAddObserver : public Runnable
98 {
99 public:
LoadMonitorAddObserver(RefPtr<LoadMonitor> loadMonitor)100 explicit LoadMonitorAddObserver(RefPtr<LoadMonitor> loadMonitor)
101 {
102 mLoadMonitor = loadMonitor;
103 }
104
Run()105 NS_IMETHOD Run() override
106 {
107 nsCOMPtr<nsIObserverService> observerService =
108 mozilla::services::GetObserverService();
109 if (!observerService)
110 return NS_ERROR_FAILURE;
111
112 nsresult rv = observerService->AddObserver(mLoadMonitor, "xpcom-shutdown-threads", false);
113 NS_ENSURE_SUCCESS(rv, rv);
114
115 return NS_OK;
116 }
117
118 private:
119 RefPtr<LoadMonitor> mLoadMonitor;
120 };
121
122 class LoadMonitorRemoveObserver : public Runnable
123 {
124 public:
LoadMonitorRemoveObserver(RefPtr<LoadMonitor> loadMonitor)125 explicit LoadMonitorRemoveObserver(RefPtr<LoadMonitor> loadMonitor)
126 {
127 mLoadMonitor = loadMonitor;
128 }
129
Run()130 NS_IMETHOD Run() override
131 {
132 // remove xpcom shutdown observer
133 nsCOMPtr<nsIObserverService> observerService =
134 mozilla::services::GetObserverService();
135
136 if (observerService)
137 observerService->RemoveObserver(mLoadMonitor, "xpcom-shutdown-threads");
138
139 return NS_OK;
140 }
141
142 private:
143 RefPtr<LoadMonitor> mLoadMonitor;
144 };
145
Shutdown()146 void LoadMonitor::Shutdown()
147 {
148 if (mLoadInfoThread) {
149 {
150 MutexAutoLock lock(mLock);
151 LOG(("LoadMonitor: shutting down"));
152 mShutdownPending = true;
153 mCondVar.Notify();
154 }
155
156 // Note: can't just call ->Shutdown() from here; that spins the event
157 // loop here, causing re-entrancy issues if we're invoked from cycle
158 // collection. Argh.
159 mLoadInfoThread = nullptr;
160
161 RefPtr<LoadMonitorRemoveObserver> remObsRunner = new LoadMonitorRemoveObserver(this);
162 if (!NS_IsMainThread()) {
163 NS_DispatchToMainThread(remObsRunner);
164 } else {
165 remObsRunner->Run();
166 }
167 }
168 }
169
170 #ifdef XP_WIN
171 static LPCTSTR TotalCounterPath = _T("\\Processor(_Total)\\% Processor Time");
172
173 class WinProcMon
174 {
175 public:
WinProcMon()176 WinProcMon():
177 mQuery(0), mCounter(0) {};
178 ~WinProcMon();
179 nsresult Init();
180 nsresult QuerySystemLoad(float* load_percent);
181 static const uint64_t TicksPerSec = 10000000; //100nsec tick (10MHz)
182 private:
183 PDH_HQUERY mQuery;
184 PDH_HCOUNTER mCounter;
185 };
186
~WinProcMon()187 WinProcMon::~WinProcMon()
188 {
189 if (mQuery != 0) {
190 PdhCloseQuery(mQuery);
191 mQuery = 0;
192 }
193 }
194
195 nsresult
Init()196 WinProcMon::Init()
197 {
198 PDH_HQUERY query;
199 PDH_HCOUNTER counter;
200
201 // Get a query handle to the Performance Data Helper
202 PDH_STATUS status = PdhOpenQuery(
203 NULL, // No log file name: use real-time source
204 0, // zero out user data token: unsued
205 &query);
206
207 if (status != ERROR_SUCCESS) {
208 LOG(("PdhOpenQuery error = %X", status));
209 return NS_ERROR_FAILURE;
210 }
211
212 // Add a pre-defined high performance counter to the query.
213 // This one is for the total CPU usage.
214 status = PdhAddCounter(query, TotalCounterPath, 0, &counter);
215
216 if (status != ERROR_SUCCESS) {
217 PdhCloseQuery(query);
218 LOG(("PdhAddCounter (_Total) error = %X", status));
219 return NS_ERROR_FAILURE;
220 }
221
222 // Need to make an initial query call to set up data capture.
223 status = PdhCollectQueryData(query);
224
225 if (status != ERROR_SUCCESS) {
226 PdhCloseQuery(query);
227 LOG(("PdhCollectQueryData (init) error = %X", status));
228 return NS_ERROR_FAILURE;
229 }
230
231 mQuery = query;
232 mCounter = counter;
233 return NS_OK;
234 }
235
QuerySystemLoad(float * load_percent)236 nsresult WinProcMon::QuerySystemLoad(float* load_percent)
237 {
238 *load_percent = 0;
239
240 if (mQuery == 0) {
241 return NS_ERROR_FAILURE;
242 }
243
244 // Update all counters associated with this query object.
245 PDH_STATUS status = PdhCollectQueryData(mQuery);
246
247 if (status != ERROR_SUCCESS) {
248 LOG(("PdhCollectQueryData error = %X", status));
249 return NS_ERROR_FAILURE;
250 }
251
252 PDH_FMT_COUNTERVALUE counter;
253 // maximum is 100% regardless of CPU core count.
254 status = PdhGetFormattedCounterValue(
255 mCounter,
256 PDH_FMT_DOUBLE,
257 (LPDWORD)NULL,
258 &counter);
259
260 if (ERROR_SUCCESS != status ||
261 // There are multiple success return values.
262 !IsSuccessSeverity(counter.CStatus)) {
263 LOG(("PdhGetFormattedCounterValue error"));
264 return NS_ERROR_FAILURE;
265 }
266
267 // The result is a percent value, reduce to match expected scale.
268 *load_percent = (float)(counter.doubleValue / 100.0f);
269 return NS_OK;
270 }
271 #endif
272
273 // Use a non-generic class name, because otherwise we can get name collisions
274 // with other classes in the codebase. The normal way of dealing with that is
275 // to put the class in an anonymous namespace, but this class is used as a
276 // member of RTCLoadInfo, which can't be in the anonymous namespace, so it also
277 // can't be in an anonymous namespace: gcc warns about that setup and this
278 // directory is fail-on-warnings.
279 class RTCLoadStats
280 {
281 public:
RTCLoadStats()282 RTCLoadStats() :
283 mPrevTotalTimes(0),
284 mPrevCpuTimes(0),
285 mPrevLoad(0) {};
286
GetLoad()287 double GetLoad() { return (double)mPrevLoad; };
288
289 uint64_t mPrevTotalTimes;
290 uint64_t mPrevCpuTimes;
291 float mPrevLoad; // Previous load value.
292 };
293
294 // Use a non-generic class name, because otherwise we can get name collisions
295 // with other classes in the codebase. The normal way of dealing with that is
296 // to put the class in an anonymous namespace, but this class is used as a
297 // member of LoadInfoCollectRunner, which can't be in the anonymous namespace,
298 // so it also can't be in an anonymous namespace: gcc warns about that setup
299 // and this directory is fail-on-warnings.
300 class RTCLoadInfo final
301 {
302 private:
~RTCLoadInfo()303 ~RTCLoadInfo() {}
304
305 public:
306 NS_INLINE_DECL_REFCOUNTING(RTCLoadInfo)
307
RTCLoadInfo()308 RTCLoadInfo(): mLoadUpdateInterval(0) {};
309 nsresult Init(int aLoadUpdateInterval);
GetSystemLoad()310 double GetSystemLoad() { return mSystemLoad.GetLoad(); };
GetProcessLoad()311 double GetProcessLoad() { return mProcessLoad.GetLoad(); };
312 nsresult UpdateSystemLoad();
313 nsresult UpdateProcessLoad();
314
315 private:
316 void UpdateCpuLoad(uint64_t ticks_per_interval,
317 uint64_t current_total_times,
318 uint64_t current_cpu_times,
319 RTCLoadStats* loadStat);
320 #ifdef XP_WIN
321 WinProcMon mSysMon;
322 HANDLE mProcHandle;
323 int mNumProcessors;
324 #endif
325 RTCLoadStats mSystemLoad;
326 RTCLoadStats mProcessLoad;
327 uint64_t mTicksPerInterval;
328 int mLoadUpdateInterval;
329 };
330
Init(int aLoadUpdateInterval)331 nsresult RTCLoadInfo::Init(int aLoadUpdateInterval)
332 {
333 mLoadUpdateInterval = aLoadUpdateInterval;
334 #ifdef XP_WIN
335 mTicksPerInterval = (WinProcMon::TicksPerSec /*Hz*/
336 * mLoadUpdateInterval /*msec*/) / 1000 ;
337 mNumProcessors = PR_GetNumberOfProcessors();
338 mProcHandle = GetCurrentProcess();
339 return mSysMon.Init();
340 #else
341 mTicksPerInterval = (sysconf(_SC_CLK_TCK) * mLoadUpdateInterval) / 1000;
342 return NS_OK;
343 #endif
344 }
345
UpdateCpuLoad(uint64_t ticks_per_interval,uint64_t current_total_times,uint64_t current_cpu_times,RTCLoadStats * loadStat)346 void RTCLoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval,
347 uint64_t current_total_times,
348 uint64_t current_cpu_times,
349 RTCLoadStats *loadStat) {
350 // Check if we get an inconsistent number of ticks.
351 if (((current_total_times - loadStat->mPrevTotalTimes)
352 > (ticks_per_interval * 10))
353 || current_total_times < loadStat->mPrevTotalTimes
354 || current_cpu_times < loadStat->mPrevCpuTimes) {
355 // Bug at least on the Nexus 4 and Galaxy S4
356 // https://code.google.com/p/android/issues/detail?id=41630
357 // We do need to update our previous times, or we can get stuck
358 // when there is a blip upwards and then we get a bunch of consecutive
359 // lower times. Just skip the load calculation.
360 LOG(("Inconsistent time values are passed. ignored"));
361 // Try to recover next tick
362 loadStat->mPrevTotalTimes = current_total_times;
363 loadStat->mPrevCpuTimes = current_cpu_times;
364 return;
365 }
366
367 const uint64_t cpu_diff = current_cpu_times - loadStat->mPrevCpuTimes;
368 const uint64_t total_diff = current_total_times - loadStat->mPrevTotalTimes;
369 if (total_diff > 0) {
370 #ifdef XP_WIN
371 float result = (float)cpu_diff / (float)total_diff/ (float)mNumProcessors;
372 #else
373 float result = (float)cpu_diff / (float)total_diff;
374 #endif
375 loadStat->mPrevLoad = result;
376 }
377 loadStat->mPrevTotalTimes = current_total_times;
378 loadStat->mPrevCpuTimes = current_cpu_times;
379 }
380
UpdateSystemLoad()381 nsresult RTCLoadInfo::UpdateSystemLoad()
382 {
383 #if defined(LINUX) || defined(ANDROID)
384 nsCOMPtr<nsIFile> procStatFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
385 procStatFile->InitWithPath(NS_LITERAL_STRING("/proc/stat"));
386
387 nsCOMPtr<nsIInputStream> fileInputStream;
388 nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
389 procStatFile);
390 NS_ENSURE_SUCCESS(rv, rv);
391
392 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
393 NS_ENSURE_SUCCESS(rv, rv);
394
395 nsAutoCString buffer;
396 bool isMore = true;
397 lineInputStream->ReadLine(buffer, &isMore);
398
399 uint64_t user;
400 uint64_t nice;
401 uint64_t system;
402 uint64_t idle;
403 if (PR_sscanf(buffer.get(), "cpu %llu %llu %llu %llu",
404 &user, &nice,
405 &system, &idle) != 4) {
406 LOG(("Error parsing /proc/stat"));
407 return NS_ERROR_FAILURE;
408 }
409
410 const uint64_t cpu_times = nice + system + user;
411 const uint64_t total_times = cpu_times + idle;
412
413 UpdateCpuLoad(mTicksPerInterval,
414 total_times,
415 cpu_times,
416 &mSystemLoad);
417 return NS_OK;
418 #elif defined(XP_MACOSX)
419 mach_msg_type_number_t info_cnt = HOST_CPU_LOAD_INFO_COUNT;
420 host_cpu_load_info_data_t load_info;
421 kern_return_t rv = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
422 (host_info_t)(&load_info), &info_cnt);
423
424 if (rv != KERN_SUCCESS || info_cnt != HOST_CPU_LOAD_INFO_COUNT) {
425 LOG(("Error from mach/host_statistics call"));
426 return NS_ERROR_FAILURE;
427 }
428
429 const uint64_t cpu_times = load_info.cpu_ticks[CPU_STATE_NICE]
430 + load_info.cpu_ticks[CPU_STATE_SYSTEM]
431 + load_info.cpu_ticks[CPU_STATE_USER];
432 const uint64_t total_times = cpu_times + load_info.cpu_ticks[CPU_STATE_IDLE];
433
434 UpdateCpuLoad(mTicksPerInterval,
435 total_times,
436 cpu_times,
437 &mSystemLoad);
438 return NS_OK;
439 #elif defined(__DragonFly__) || defined(__FreeBSD__) \
440 || defined(__NetBSD__) || defined(__OpenBSD__)
441 #if defined(__NetBSD__)
442 uint64_t cp_time[CPUSTATES];
443 #else
444 long cp_time[CPUSTATES];
445 #endif // __NetBSD__
446 size_t sz = sizeof(cp_time);
447 #ifdef KERN_CP_TIME
448 int mib[] = {
449 CTL_KERN,
450 KERN_CP_TIME,
451 };
452 u_int miblen = sizeof(mib) / sizeof(mib[0]);
453 if (sysctl(mib, miblen, &cp_time, &sz, nullptr, 0)) {
454 #else
455 if (sysctlbyname("kern.cp_time", &cp_time, &sz, nullptr, 0)) {
456 #endif // KERN_CP_TIME
457 LOG(("sysctl kern.cp_time failed"));
458 return NS_ERROR_FAILURE;
459 }
460
461 const uint64_t cpu_times = cp_time[CP_NICE]
462 + cp_time[CP_SYS]
463 + cp_time[CP_INTR]
464 + cp_time[CP_USER];
465 const uint64_t total_times = cpu_times + cp_time[CP_IDLE];
466
467 UpdateCpuLoad(mTicksPerInterval,
468 total_times,
469 cpu_times,
470 &mSystemLoad);
471 return NS_OK;
472 #elif defined(XP_WIN)
473 float load;
474 nsresult rv = mSysMon.QuerySystemLoad(&load);
475
476 if (rv == NS_OK) {
477 mSystemLoad.mPrevLoad = load;
478 }
479
480 return rv;
481 #else
482 // Not implemented
483 return NS_OK;
484 #endif
485 }
486
487 nsresult RTCLoadInfo::UpdateProcessLoad() {
488 #if defined(XP_UNIX)
489 struct timeval tv;
490 gettimeofday(&tv, nullptr);
491 const uint64_t total_times = tv.tv_sec * PR_USEC_PER_SEC + tv.tv_usec;
492
493 rusage usage;
494 if (getrusage(RUSAGE_SELF, &usage) < 0) {
495 LOG(("getrusage failed"));
496 return NS_ERROR_FAILURE;
497 }
498
499 const uint64_t cpu_times =
500 (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * PR_USEC_PER_SEC +
501 usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
502
503 UpdateCpuLoad(PR_USEC_PER_MSEC * mLoadUpdateInterval,
504 total_times,
505 cpu_times,
506 &mProcessLoad);
507 #elif defined(XP_WIN)
508 FILETIME clk_time, sys_time, user_time;
509 uint64_t total_times, cpu_times;
510
511 GetSystemTimeAsFileTime(&clk_time);
512 total_times = (((uint64_t)clk_time.dwHighDateTime) << 32)
513 + (uint64_t)clk_time.dwLowDateTime;
514 BOOL ok = GetProcessTimes(mProcHandle, &clk_time, &clk_time, &sys_time, &user_time);
515
516 if (ok == 0) {
517 return NS_ERROR_FAILURE;
518 }
519
520 cpu_times = (((uint64_t)sys_time.dwHighDateTime
521 + (uint64_t)user_time.dwHighDateTime) << 32)
522 + (uint64_t)sys_time.dwLowDateTime
523 + (uint64_t)user_time.dwLowDateTime;
524
525 UpdateCpuLoad(mTicksPerInterval,
526 total_times,
527 cpu_times,
528 &mProcessLoad);
529 #endif
530 return NS_OK;
531 }
532
533 // Note: This class can't be in the anonymous namespace, because then we can't
534 // declare it as a friend of LoadMonitor.
535 class LoadInfoCollectRunner : public Runnable
536 {
537 public:
538 LoadInfoCollectRunner(RefPtr<LoadMonitor> loadMonitor,
539 RefPtr<RTCLoadInfo> loadInfo,
540 nsIThread *loadInfoThread)
541 : mThread(loadInfoThread),
542 mLoadUpdateInterval(loadMonitor->mLoadUpdateInterval),
543 mLoadNoiseCounter(0)
544 {
545 mLoadMonitor = loadMonitor;
546 mLoadInfo = loadInfo;
547 }
548
549 NS_IMETHOD Run() override
550 {
551 if (NS_IsMainThread()) {
552 if (mThread) {
553 // Don't leak threads!
554 mThread->Shutdown(); // can't Shutdown from the thread itself, darn
555 // Don't null out mThread!
556 // See bug 999104. We must hold a ref to the thread across Dispatch()
557 // since the internal mThread ref could be released while processing
558 // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it
559 // assumes the caller does.
560 }
561 return NS_OK;
562 }
563
564 MutexAutoLock lock(mLoadMonitor->mLock);
565 while (!mLoadMonitor->mShutdownPending) {
566 mLoadInfo->UpdateSystemLoad();
567 mLoadInfo->UpdateProcessLoad();
568 float sysLoad = mLoadInfo->GetSystemLoad();
569 float procLoad = mLoadInfo->GetProcessLoad();
570
571 if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) {
572 LOG(("System Load: %f Process Load: %f", sysLoad, procLoad));
573 mLoadNoiseCounter = 0;
574 }
575 mLoadMonitor->SetSystemLoad(sysLoad);
576 mLoadMonitor->SetProcessLoad(procLoad);
577 mLoadMonitor->FireCallbacks();
578
579 mLoadMonitor->mCondVar.Wait(PR_MillisecondsToInterval(mLoadUpdateInterval));
580 }
581 // ok, we need to exit safely and can't shut ourselves down (DARN)
582 NS_DispatchToMainThread(this);
583 return NS_OK;
584 }
585
586 private:
587 nsCOMPtr<nsIThread> mThread;
588 RefPtr<RTCLoadInfo> mLoadInfo;
589 RefPtr<LoadMonitor> mLoadMonitor;
590 int mLoadUpdateInterval;
591 int mLoadNoiseCounter;
592 };
593
594 void
595 LoadMonitor::SetProcessLoad(float load) {
596 mLock.AssertCurrentThreadOwns();
597 mProcessLoad = load;
598 }
599
600 void
601 LoadMonitor::SetSystemLoad(float load) {
602 mLock.AssertCurrentThreadOwns();
603 mSystemLoad = load;
604 }
605
606 float
607 LoadMonitor::GetProcessLoad() {
608 MutexAutoLock lock(mLock);
609 float load = mProcessLoad;
610 return load;
611 }
612
613 void
614 LoadMonitor::FireCallbacks() {
615 if (mLoadNotificationCallback) {
616 mLoadNotificationCallback->LoadChanged(mSystemLoad, mProcessLoad);
617 }
618 }
619
620 float
621 LoadMonitor::GetSystemLoad() {
622 MutexAutoLock lock(mLock);
623 float load = mSystemLoad;
624 return load;
625 }
626
627 nsresult
628 LoadMonitor::Init(RefPtr<LoadMonitor> &self)
629 {
630 LOG(("Initializing LoadMonitor"));
631
632 RefPtr<RTCLoadInfo> load_info = new RTCLoadInfo();
633 nsresult rv = load_info->Init(mLoadUpdateInterval);
634
635 if (NS_FAILED(rv)) {
636 LOG(("RTCLoadInfo::Init error"));
637 return rv;
638 }
639
640 RefPtr<LoadMonitorAddObserver> addObsRunner = new LoadMonitorAddObserver(self);
641 NS_DispatchToMainThread(addObsRunner);
642
643 NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread));
644
645 RefPtr<LoadInfoCollectRunner> runner =
646 new LoadInfoCollectRunner(self, load_info, mLoadInfoThread);
647 mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL);
648
649 return NS_OK;
650 }
651
652 void
653 LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback)
654 {
655 mLoadNotificationCallback = aCallback;
656 }
657
658 }
659