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