/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "LoadMonitor.h" #include "LoadManager.h" #include "nsString.h" #include "mozilla/Logging.h" #include "prtime.h" #include "prinrval.h" #include "prsystem.h" #include "prprf.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsReadableUtils.h" #include "nsNetUtil.h" #include "nsIInputStream.h" #include "nsIFile.h" #include "nsILineInputStream.h" #include "nsIObserverService.h" #include "nsIServiceManager.h" #include "mozilla/TimeStamp.h" #include "mozilla/Services.h" #ifdef XP_UNIX #include #include #include #endif #ifdef XP_MACOSX #include #include #include #endif #if defined(__DragonFly__) || defined(__FreeBSD__) \ || defined(__NetBSD__) || defined(__OpenBSD__) #include # if defined(__OpenBSD__) #define KERN_CP_TIME KERN_CPTIME # endif #endif #if defined(__NetBSD__) || defined(__OpenBSD__) #include #endif #ifdef XP_WIN #include #include #pragma comment(lib, "pdh.lib") #endif // MOZ_LOG=LoadManager:5 #undef LOG #undef LOG_ENABLED #define LOG(args) MOZ_LOG(gLoadManagerLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Debug) #define LOG_MANY_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Verbose) namespace mozilla { NS_IMPL_ISUPPORTS(LoadMonitor, nsIObserver) LoadMonitor::LoadMonitor(int aLoadUpdateInterval) : mLoadUpdateInterval(aLoadUpdateInterval), mLock("LoadMonitor.mLock"), mCondVar(mLock, "LoadMonitor.mCondVar"), mShutdownPending(false), mLoadInfoThread(nullptr), mSystemLoad(0.0f), mProcessLoad(0.0f), mLoadNotificationCallback(nullptr) { } LoadMonitor::~LoadMonitor() { Shutdown(); } NS_IMETHODIMP LoadMonitor::Observe(nsISupports* /* aSubject */, const char* aTopic, const char16_t* /* aData */) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); Shutdown(); return NS_OK; } class LoadMonitorAddObserver : public Runnable { public: explicit LoadMonitorAddObserver(RefPtr loadMonitor) { mLoadMonitor = loadMonitor; } NS_IMETHOD Run() override { nsCOMPtr observerService = mozilla::services::GetObserverService(); if (!observerService) return NS_ERROR_FAILURE; nsresult rv = observerService->AddObserver(mLoadMonitor, "xpcom-shutdown-threads", false); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } private: RefPtr mLoadMonitor; }; class LoadMonitorRemoveObserver : public Runnable { public: explicit LoadMonitorRemoveObserver(RefPtr loadMonitor) { mLoadMonitor = loadMonitor; } NS_IMETHOD Run() override { // remove xpcom shutdown observer nsCOMPtr observerService = mozilla::services::GetObserverService(); if (observerService) observerService->RemoveObserver(mLoadMonitor, "xpcom-shutdown-threads"); return NS_OK; } private: RefPtr mLoadMonitor; }; void LoadMonitor::Shutdown() { if (mLoadInfoThread) { { MutexAutoLock lock(mLock); LOG(("LoadMonitor: shutting down")); mShutdownPending = true; mCondVar.Notify(); } // Note: can't just call ->Shutdown() from here; that spins the event // loop here, causing re-entrancy issues if we're invoked from cycle // collection. Argh. mLoadInfoThread = nullptr; RefPtr remObsRunner = new LoadMonitorRemoveObserver(this); if (!NS_IsMainThread()) { NS_DispatchToMainThread(remObsRunner); } else { remObsRunner->Run(); } } } #ifdef XP_WIN static LPCTSTR TotalCounterPath = _T("\\Processor(_Total)\\% Processor Time"); class WinProcMon { public: WinProcMon(): mQuery(0), mCounter(0) {}; ~WinProcMon(); nsresult Init(); nsresult QuerySystemLoad(float* load_percent); static const uint64_t TicksPerSec = 10000000; //100nsec tick (10MHz) private: PDH_HQUERY mQuery; PDH_HCOUNTER mCounter; }; WinProcMon::~WinProcMon() { if (mQuery != 0) { PdhCloseQuery(mQuery); mQuery = 0; } } nsresult WinProcMon::Init() { PDH_HQUERY query; PDH_HCOUNTER counter; // Get a query handle to the Performance Data Helper PDH_STATUS status = PdhOpenQuery( NULL, // No log file name: use real-time source 0, // zero out user data token: unsued &query); if (status != ERROR_SUCCESS) { LOG(("PdhOpenQuery error = %X", status)); return NS_ERROR_FAILURE; } // Add a pre-defined high performance counter to the query. // This one is for the total CPU usage. status = PdhAddCounter(query, TotalCounterPath, 0, &counter); if (status != ERROR_SUCCESS) { PdhCloseQuery(query); LOG(("PdhAddCounter (_Total) error = %X", status)); return NS_ERROR_FAILURE; } // Need to make an initial query call to set up data capture. status = PdhCollectQueryData(query); if (status != ERROR_SUCCESS) { PdhCloseQuery(query); LOG(("PdhCollectQueryData (init) error = %X", status)); return NS_ERROR_FAILURE; } mQuery = query; mCounter = counter; return NS_OK; } nsresult WinProcMon::QuerySystemLoad(float* load_percent) { *load_percent = 0; if (mQuery == 0) { return NS_ERROR_FAILURE; } // Update all counters associated with this query object. PDH_STATUS status = PdhCollectQueryData(mQuery); if (status != ERROR_SUCCESS) { LOG(("PdhCollectQueryData error = %X", status)); return NS_ERROR_FAILURE; } PDH_FMT_COUNTERVALUE counter; // maximum is 100% regardless of CPU core count. status = PdhGetFormattedCounterValue( mCounter, PDH_FMT_DOUBLE, (LPDWORD)NULL, &counter); if (ERROR_SUCCESS != status || // There are multiple success return values. !IsSuccessSeverity(counter.CStatus)) { LOG(("PdhGetFormattedCounterValue error")); return NS_ERROR_FAILURE; } // The result is a percent value, reduce to match expected scale. *load_percent = (float)(counter.doubleValue / 100.0f); return NS_OK; } #endif // Use a non-generic class name, because otherwise we can get name collisions // with other classes in the codebase. The normal way of dealing with that is // to put the class in an anonymous namespace, but this class is used as a // member of RTCLoadInfo, which can't be in the anonymous namespace, so it also // can't be in an anonymous namespace: gcc warns about that setup and this // directory is fail-on-warnings. class RTCLoadStats { public: RTCLoadStats() : mPrevTotalTimes(0), mPrevCpuTimes(0), mPrevLoad(0) {}; double GetLoad() { return (double)mPrevLoad; }; uint64_t mPrevTotalTimes; uint64_t mPrevCpuTimes; float mPrevLoad; // Previous load value. }; // Use a non-generic class name, because otherwise we can get name collisions // with other classes in the codebase. The normal way of dealing with that is // to put the class in an anonymous namespace, but this class is used as a // member of LoadInfoCollectRunner, which can't be in the anonymous namespace, // so it also can't be in an anonymous namespace: gcc warns about that setup // and this directory is fail-on-warnings. class RTCLoadInfo final { private: ~RTCLoadInfo() {} public: NS_INLINE_DECL_REFCOUNTING(RTCLoadInfo) RTCLoadInfo(): mLoadUpdateInterval(0) {}; nsresult Init(int aLoadUpdateInterval); double GetSystemLoad() { return mSystemLoad.GetLoad(); }; double GetProcessLoad() { return mProcessLoad.GetLoad(); }; nsresult UpdateSystemLoad(); nsresult UpdateProcessLoad(); private: void UpdateCpuLoad(uint64_t ticks_per_interval, uint64_t current_total_times, uint64_t current_cpu_times, RTCLoadStats* loadStat); #ifdef XP_WIN WinProcMon mSysMon; HANDLE mProcHandle; int mNumProcessors; #endif RTCLoadStats mSystemLoad; RTCLoadStats mProcessLoad; uint64_t mTicksPerInterval; int mLoadUpdateInterval; }; nsresult RTCLoadInfo::Init(int aLoadUpdateInterval) { mLoadUpdateInterval = aLoadUpdateInterval; #ifdef XP_WIN mTicksPerInterval = (WinProcMon::TicksPerSec /*Hz*/ * mLoadUpdateInterval /*msec*/) / 1000 ; mNumProcessors = PR_GetNumberOfProcessors(); mProcHandle = GetCurrentProcess(); return mSysMon.Init(); #else mTicksPerInterval = (sysconf(_SC_CLK_TCK) * mLoadUpdateInterval) / 1000; return NS_OK; #endif } void RTCLoadInfo::UpdateCpuLoad(uint64_t ticks_per_interval, uint64_t current_total_times, uint64_t current_cpu_times, RTCLoadStats *loadStat) { // Check if we get an inconsistent number of ticks. if (((current_total_times - loadStat->mPrevTotalTimes) > (ticks_per_interval * 10)) || current_total_times < loadStat->mPrevTotalTimes || current_cpu_times < loadStat->mPrevCpuTimes) { // Bug at least on the Nexus 4 and Galaxy S4 // https://code.google.com/p/android/issues/detail?id=41630 // We do need to update our previous times, or we can get stuck // when there is a blip upwards and then we get a bunch of consecutive // lower times. Just skip the load calculation. LOG(("Inconsistent time values are passed. ignored")); // Try to recover next tick loadStat->mPrevTotalTimes = current_total_times; loadStat->mPrevCpuTimes = current_cpu_times; return; } const uint64_t cpu_diff = current_cpu_times - loadStat->mPrevCpuTimes; const uint64_t total_diff = current_total_times - loadStat->mPrevTotalTimes; if (total_diff > 0) { #ifdef XP_WIN float result = (float)cpu_diff / (float)total_diff/ (float)mNumProcessors; #else float result = (float)cpu_diff / (float)total_diff; #endif loadStat->mPrevLoad = result; } loadStat->mPrevTotalTimes = current_total_times; loadStat->mPrevCpuTimes = current_cpu_times; } nsresult RTCLoadInfo::UpdateSystemLoad() { #if defined(LINUX) || defined(ANDROID) nsCOMPtr procStatFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); procStatFile->InitWithPath(NS_LITERAL_STRING("/proc/stat")); nsCOMPtr fileInputStream; nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), procStatFile); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr lineInputStream = do_QueryInterface(fileInputStream, &rv); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString buffer; bool isMore = true; lineInputStream->ReadLine(buffer, &isMore); uint64_t user; uint64_t nice; uint64_t system; uint64_t idle; if (PR_sscanf(buffer.get(), "cpu %llu %llu %llu %llu", &user, &nice, &system, &idle) != 4) { LOG(("Error parsing /proc/stat")); return NS_ERROR_FAILURE; } const uint64_t cpu_times = nice + system + user; const uint64_t total_times = cpu_times + idle; UpdateCpuLoad(mTicksPerInterval, total_times, cpu_times, &mSystemLoad); return NS_OK; #elif defined(XP_MACOSX) mach_msg_type_number_t info_cnt = HOST_CPU_LOAD_INFO_COUNT; host_cpu_load_info_data_t load_info; kern_return_t rv = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)(&load_info), &info_cnt); if (rv != KERN_SUCCESS || info_cnt != HOST_CPU_LOAD_INFO_COUNT) { LOG(("Error from mach/host_statistics call")); return NS_ERROR_FAILURE; } const uint64_t cpu_times = load_info.cpu_ticks[CPU_STATE_NICE] + load_info.cpu_ticks[CPU_STATE_SYSTEM] + load_info.cpu_ticks[CPU_STATE_USER]; const uint64_t total_times = cpu_times + load_info.cpu_ticks[CPU_STATE_IDLE]; UpdateCpuLoad(mTicksPerInterval, total_times, cpu_times, &mSystemLoad); return NS_OK; #elif defined(__DragonFly__) || defined(__FreeBSD__) \ || defined(__NetBSD__) || defined(__OpenBSD__) #if defined(__NetBSD__) uint64_t cp_time[CPUSTATES]; #else long cp_time[CPUSTATES]; #endif // __NetBSD__ size_t sz = sizeof(cp_time); #ifdef KERN_CP_TIME int mib[] = { CTL_KERN, KERN_CP_TIME, }; u_int miblen = sizeof(mib) / sizeof(mib[0]); if (sysctl(mib, miblen, &cp_time, &sz, nullptr, 0)) { #else if (sysctlbyname("kern.cp_time", &cp_time, &sz, nullptr, 0)) { #endif // KERN_CP_TIME LOG(("sysctl kern.cp_time failed")); return NS_ERROR_FAILURE; } const uint64_t cpu_times = cp_time[CP_NICE] + cp_time[CP_SYS] + cp_time[CP_INTR] + cp_time[CP_USER]; const uint64_t total_times = cpu_times + cp_time[CP_IDLE]; UpdateCpuLoad(mTicksPerInterval, total_times, cpu_times, &mSystemLoad); return NS_OK; #elif defined(XP_WIN) float load; nsresult rv = mSysMon.QuerySystemLoad(&load); if (rv == NS_OK) { mSystemLoad.mPrevLoad = load; } return rv; #else // Not implemented return NS_OK; #endif } nsresult RTCLoadInfo::UpdateProcessLoad() { #if defined(XP_UNIX) struct timeval tv; gettimeofday(&tv, nullptr); const uint64_t total_times = tv.tv_sec * PR_USEC_PER_SEC + tv.tv_usec; rusage usage; if (getrusage(RUSAGE_SELF, &usage) < 0) { LOG(("getrusage failed")); return NS_ERROR_FAILURE; } const uint64_t cpu_times = (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) * PR_USEC_PER_SEC + usage.ru_utime.tv_usec + usage.ru_stime.tv_usec; UpdateCpuLoad(PR_USEC_PER_MSEC * mLoadUpdateInterval, total_times, cpu_times, &mProcessLoad); #elif defined(XP_WIN) FILETIME clk_time, sys_time, user_time; uint64_t total_times, cpu_times; GetSystemTimeAsFileTime(&clk_time); total_times = (((uint64_t)clk_time.dwHighDateTime) << 32) + (uint64_t)clk_time.dwLowDateTime; BOOL ok = GetProcessTimes(mProcHandle, &clk_time, &clk_time, &sys_time, &user_time); if (ok == 0) { return NS_ERROR_FAILURE; } cpu_times = (((uint64_t)sys_time.dwHighDateTime + (uint64_t)user_time.dwHighDateTime) << 32) + (uint64_t)sys_time.dwLowDateTime + (uint64_t)user_time.dwLowDateTime; UpdateCpuLoad(mTicksPerInterval, total_times, cpu_times, &mProcessLoad); #endif return NS_OK; } // Note: This class can't be in the anonymous namespace, because then we can't // declare it as a friend of LoadMonitor. class LoadInfoCollectRunner : public Runnable { public: LoadInfoCollectRunner(RefPtr loadMonitor, RefPtr loadInfo, nsIThread *loadInfoThread) : mThread(loadInfoThread), mLoadUpdateInterval(loadMonitor->mLoadUpdateInterval), mLoadNoiseCounter(0) { mLoadMonitor = loadMonitor; mLoadInfo = loadInfo; } NS_IMETHOD Run() override { if (NS_IsMainThread()) { if (mThread) { // Don't leak threads! mThread->Shutdown(); // can't Shutdown from the thread itself, darn // Don't null out mThread! // See bug 999104. We must hold a ref to the thread across Dispatch() // since the internal mThread ref could be released while processing // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it // assumes the caller does. } return NS_OK; } MutexAutoLock lock(mLoadMonitor->mLock); while (!mLoadMonitor->mShutdownPending) { mLoadInfo->UpdateSystemLoad(); mLoadInfo->UpdateProcessLoad(); float sysLoad = mLoadInfo->GetSystemLoad(); float procLoad = mLoadInfo->GetProcessLoad(); if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) { LOG(("System Load: %f Process Load: %f", sysLoad, procLoad)); mLoadNoiseCounter = 0; } mLoadMonitor->SetSystemLoad(sysLoad); mLoadMonitor->SetProcessLoad(procLoad); mLoadMonitor->FireCallbacks(); mLoadMonitor->mCondVar.Wait(PR_MillisecondsToInterval(mLoadUpdateInterval)); } // ok, we need to exit safely and can't shut ourselves down (DARN) NS_DispatchToMainThread(this); return NS_OK; } private: nsCOMPtr mThread; RefPtr mLoadInfo; RefPtr mLoadMonitor; int mLoadUpdateInterval; int mLoadNoiseCounter; }; void LoadMonitor::SetProcessLoad(float load) { mLock.AssertCurrentThreadOwns(); mProcessLoad = load; } void LoadMonitor::SetSystemLoad(float load) { mLock.AssertCurrentThreadOwns(); mSystemLoad = load; } float LoadMonitor::GetProcessLoad() { MutexAutoLock lock(mLock); float load = mProcessLoad; return load; } void LoadMonitor::FireCallbacks() { if (mLoadNotificationCallback) { mLoadNotificationCallback->LoadChanged(mSystemLoad, mProcessLoad); } } float LoadMonitor::GetSystemLoad() { MutexAutoLock lock(mLock); float load = mSystemLoad; return load; } nsresult LoadMonitor::Init(RefPtr &self) { LOG(("Initializing LoadMonitor")); RefPtr load_info = new RTCLoadInfo(); nsresult rv = load_info->Init(mLoadUpdateInterval); if (NS_FAILED(rv)) { LOG(("RTCLoadInfo::Init error")); return rv; } RefPtr addObsRunner = new LoadMonitorAddObserver(self); NS_DispatchToMainThread(addObsRunner); NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread)); RefPtr runner = new LoadInfoCollectRunner(self, load_info, mLoadInfoThread); mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL); return NS_OK; } void LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback) { mLoadNotificationCallback = aCallback; } }