michael@0: /* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "LoadMonitor.h" michael@0: #include "LoadManager.h" michael@0: #include "nsString.h" michael@0: #include "prlog.h" michael@0: #include "prtime.h" michael@0: #include "prinrval.h" michael@0: #include "prsystem.h" michael@0: #include "prprf.h" michael@0: michael@0: #include "nsString.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsILineInputStream.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIServiceManager.h" michael@0: michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: #if defined(ANDROID) || defined(LINUX) michael@0: #include michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: // NSPR_LOG_MODULES=LoadManager:5 michael@0: #undef LOG michael@0: #undef LOG_ENABLED michael@0: #if defined(PR_LOGGING) michael@0: #define LOG(args) PR_LOG(gLoadManagerLog, PR_LOG_DEBUG, args) michael@0: #define LOG_ENABLED() PR_LOG_TEST(gLoadManagerLog, 4) michael@0: #define LOG_MANY_ENABLED() PR_LOG_TEST(gLoadManagerLog, 5) michael@0: #else michael@0: #define LOG(args) michael@0: #define LOG_ENABLED() (false) michael@0: #define LOG_MANY_ENABLED() (false) michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: NS_IMPL_ISUPPORTS(LoadMonitor, nsIObserver) michael@0: michael@0: LoadMonitor::LoadMonitor(int aLoadUpdateInterval) michael@0: : mLoadUpdateInterval(aLoadUpdateInterval), michael@0: mLock("LoadMonitor.mLock"), michael@0: mCondVar(mLock, "LoadMonitor.mCondVar"), michael@0: mShutdownPending(false), michael@0: mLoadInfoThread(nullptr), michael@0: mSystemLoad(0.0f), michael@0: mProcessLoad(0.0f), michael@0: mLoadNotificationCallback(nullptr) michael@0: { michael@0: } michael@0: michael@0: LoadMonitor::~LoadMonitor() michael@0: { michael@0: Shutdown(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LoadMonitor::Observe(nsISupports* /* aSubject */, michael@0: const char* aTopic, michael@0: const char16_t* /* aData */) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); michael@0: Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: class LoadMonitorAddObserver : public nsRunnable michael@0: { michael@0: public: michael@0: LoadMonitorAddObserver(nsRefPtr loadMonitor) michael@0: { michael@0: mLoadMonitor = loadMonitor; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: if (!observerService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = observerService->AddObserver(mLoadMonitor, "xpcom-shutdown-threads", false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mLoadMonitor; michael@0: }; michael@0: michael@0: class LoadMonitorRemoveObserver : public nsRunnable michael@0: { michael@0: public: michael@0: LoadMonitorRemoveObserver(nsRefPtr loadMonitor) michael@0: { michael@0: mLoadMonitor = loadMonitor; michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // remove xpcom shutdown observer michael@0: nsCOMPtr observerService = michael@0: mozilla::services::GetObserverService(); michael@0: michael@0: if (observerService) michael@0: observerService->RemoveObserver(mLoadMonitor, "xpcom-shutdown-threads"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mLoadMonitor; michael@0: }; michael@0: michael@0: void LoadMonitor::Shutdown() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: if (mLoadInfoThread) { michael@0: mShutdownPending = true; michael@0: mCondVar.Notify(); michael@0: michael@0: mLoadInfoThread = nullptr; michael@0: michael@0: nsRefPtr remObsRunner = new LoadMonitorRemoveObserver(this); michael@0: if (!NS_IsMainThread()) { michael@0: NS_DispatchToMainThread(remObsRunner, NS_DISPATCH_NORMAL); michael@0: } else { michael@0: remObsRunner->Run(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: class LoadStats michael@0: { michael@0: public: michael@0: LoadStats() : michael@0: mPrevTotalTimes(0), michael@0: mPrevCpuTimes(0), michael@0: mPrevLoad(0) {}; michael@0: michael@0: double GetLoad() { return (double)mPrevLoad; }; michael@0: michael@0: uint64_t mPrevTotalTimes; michael@0: uint64_t mPrevCpuTimes; michael@0: float mPrevLoad; // Previous load value. michael@0: }; michael@0: michael@0: class LoadInfo : public mozilla::RefCounted michael@0: { michael@0: public: michael@0: MOZ_DECLARE_REFCOUNTED_TYPENAME(LoadInfo) michael@0: LoadInfo(int aLoadUpdateInterval); michael@0: double GetSystemLoad() { return mSystemLoad.GetLoad(); }; michael@0: double GetProcessLoad() { return mProcessLoad.GetLoad(); }; michael@0: nsresult UpdateSystemLoad(); michael@0: nsresult UpdateProcessLoad(); michael@0: michael@0: private: michael@0: void UpdateCpuLoad(uint64_t ticks_per_interval, michael@0: uint64_t current_total_times, michael@0: uint64_t current_cpu_times, michael@0: LoadStats* loadStat); michael@0: LoadStats mSystemLoad; michael@0: LoadStats mProcessLoad; michael@0: uint64_t mTicksPerInterval; michael@0: int mLoadUpdateInterval; michael@0: }; michael@0: michael@0: LoadInfo::LoadInfo(int aLoadUpdateInterval) michael@0: : mLoadUpdateInterval(aLoadUpdateInterval) michael@0: { michael@0: #if defined(ANDROID) || defined(LINUX) michael@0: mTicksPerInterval = (sysconf(_SC_CLK_TCK) * mLoadUpdateInterval) / 1000; michael@0: #endif michael@0: } michael@0: michael@0: void LoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval, michael@0: uint64_t current_total_times, michael@0: uint64_t current_cpu_times, michael@0: LoadStats *loadStat) { michael@0: michael@0: // Check if we get an inconsistent number of ticks. michael@0: if (((current_total_times - loadStat->mPrevTotalTimes) michael@0: > (ticks_per_interval * 10)) michael@0: || current_total_times < loadStat->mPrevTotalTimes michael@0: || current_cpu_times < loadStat->mPrevCpuTimes) { michael@0: // Bug at least on the Nexus 4 and Galaxy S4 michael@0: // https://code.google.com/p/android/issues/detail?id=41630 michael@0: // We do need to update our previous times, or we can get stuck michael@0: // when there is a blip upwards and then we get a bunch of consecutive michael@0: // lower times. Just skip the load calculation. michael@0: LOG(("Inconsistent time values are passed. ignored")); michael@0: // Try to recover next tick michael@0: loadStat->mPrevTotalTimes = current_total_times; michael@0: loadStat->mPrevCpuTimes = current_cpu_times; michael@0: return; michael@0: } michael@0: michael@0: const uint64_t cpu_diff = current_cpu_times - loadStat->mPrevCpuTimes; michael@0: const uint64_t total_diff = current_total_times - loadStat->mPrevTotalTimes; michael@0: if (total_diff > 0) { michael@0: float result = (float)cpu_diff / (float)total_diff; michael@0: loadStat->mPrevLoad = result; michael@0: } michael@0: loadStat->mPrevTotalTimes = current_total_times; michael@0: loadStat->mPrevCpuTimes = current_cpu_times; michael@0: } michael@0: michael@0: nsresult LoadInfo::UpdateSystemLoad() michael@0: { michael@0: #if defined(LINUX) || defined(ANDROID) michael@0: nsCOMPtr procStatFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); michael@0: procStatFile->InitWithPath(NS_LITERAL_STRING("/proc/stat")); michael@0: michael@0: nsCOMPtr fileInputStream; michael@0: nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), michael@0: procStatFile); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr lineInputStream = do_QueryInterface(fileInputStream, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString buffer; michael@0: bool isMore = true; michael@0: lineInputStream->ReadLine(buffer, &isMore); michael@0: michael@0: uint64_t user; michael@0: uint64_t nice; michael@0: uint64_t system; michael@0: uint64_t idle; michael@0: if (PR_sscanf(buffer.get(), "cpu %llu %llu %llu %llu", michael@0: &user, &nice, michael@0: &system, &idle) != 4) { michael@0: LOG(("Error parsing /proc/stat")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: const uint64_t cpu_times = nice + system + user; michael@0: const uint64_t total_times = cpu_times + idle; michael@0: michael@0: UpdateCpuLoad(mTicksPerInterval, michael@0: total_times, michael@0: cpu_times, michael@0: &mSystemLoad); michael@0: return NS_OK; michael@0: #else michael@0: // Not implemented michael@0: return NS_OK; michael@0: #endif michael@0: } michael@0: michael@0: nsresult LoadInfo::UpdateProcessLoad() { michael@0: #if defined(LINUX) || defined(ANDROID) michael@0: struct timeval tv; michael@0: gettimeofday(&tv, nullptr); michael@0: const uint64_t total_times = tv.tv_sec * PR_USEC_PER_SEC + tv.tv_usec; michael@0: michael@0: rusage usage; michael@0: if (getrusage(RUSAGE_SELF, &usage) < 0) { michael@0: LOG(("getrusage failed")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: const uint64_t cpu_times = michael@0: (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * PR_USEC_PER_SEC + michael@0: usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; michael@0: michael@0: UpdateCpuLoad(PR_USEC_PER_MSEC * mLoadUpdateInterval, michael@0: total_times, michael@0: cpu_times, michael@0: &mProcessLoad); michael@0: #endif // defined(LINUX) || defined(ANDROID) michael@0: return NS_OK; michael@0: } michael@0: michael@0: class LoadInfoCollectRunner : public nsRunnable michael@0: { michael@0: public: michael@0: LoadInfoCollectRunner(nsRefPtr loadMonitor, michael@0: int aLoadUpdateInterval) michael@0: : mLoadUpdateInterval(aLoadUpdateInterval), michael@0: mLoadNoiseCounter(0) michael@0: { michael@0: mLoadMonitor = loadMonitor; michael@0: mLoadInfo = new LoadInfo(mLoadUpdateInterval); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MutexAutoLock lock(mLoadMonitor->mLock); michael@0: while (!mLoadMonitor->mShutdownPending) { michael@0: mLoadInfo->UpdateSystemLoad(); michael@0: mLoadInfo->UpdateProcessLoad(); michael@0: float sysLoad = mLoadInfo->GetSystemLoad(); michael@0: float procLoad = mLoadInfo->GetProcessLoad(); michael@0: if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) { michael@0: LOG(("System Load: %f Process Load: %f", sysLoad, procLoad)); michael@0: mLoadNoiseCounter = 0; michael@0: } michael@0: mLoadMonitor->SetSystemLoad(sysLoad); michael@0: mLoadMonitor->SetProcessLoad(procLoad); michael@0: mLoadMonitor->FireCallbacks(); michael@0: michael@0: mLoadMonitor->mCondVar.Wait(PR_MillisecondsToInterval(mLoadUpdateInterval)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: RefPtr mLoadInfo; michael@0: nsRefPtr mLoadMonitor; michael@0: int mLoadUpdateInterval; michael@0: int mLoadNoiseCounter; michael@0: }; michael@0: michael@0: void michael@0: LoadMonitor::SetProcessLoad(float load) { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: mProcessLoad = load; michael@0: } michael@0: michael@0: void michael@0: LoadMonitor::SetSystemLoad(float load) { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: mSystemLoad = load; michael@0: } michael@0: michael@0: float michael@0: LoadMonitor::GetProcessLoad() { michael@0: MutexAutoLock lock(mLock); michael@0: float load = mProcessLoad; michael@0: return load; michael@0: } michael@0: michael@0: void michael@0: LoadMonitor::FireCallbacks() { michael@0: if (mLoadNotificationCallback) { michael@0: mLoadNotificationCallback->LoadChanged(mSystemLoad, mProcessLoad); michael@0: } michael@0: } michael@0: michael@0: float michael@0: LoadMonitor::GetSystemLoad() { michael@0: MutexAutoLock lock(mLock); michael@0: float load = mSystemLoad; michael@0: return load; michael@0: } michael@0: michael@0: nsresult michael@0: LoadMonitor::Init(nsRefPtr &self) michael@0: { michael@0: LOG(("Initializing LoadMonitor")); michael@0: michael@0: #if defined(ANDROID) || defined(LINUX) michael@0: nsRefPtr addObsRunner = new LoadMonitorAddObserver(self); michael@0: NS_DispatchToMainThread(addObsRunner, NS_DISPATCH_NORMAL); michael@0: michael@0: NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread)); michael@0: michael@0: nsRefPtr runner = michael@0: new LoadInfoCollectRunner(self, mLoadUpdateInterval); michael@0: mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback) michael@0: { michael@0: mLoadNotificationCallback = aCallback; michael@0: } michael@0: michael@0: }