diff options
Diffstat (limited to 'dom/media/systemservices/LoadMonitor.cpp')
-rw-r--r-- | dom/media/systemservices/LoadMonitor.cpp | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/dom/media/systemservices/LoadMonitor.cpp b/dom/media/systemservices/LoadMonitor.cpp new file mode 100644 index 000000000..7a64c4fb0 --- /dev/null +++ b/dom/media/systemservices/LoadMonitor.cpp @@ -0,0 +1,658 @@ +/* -*- 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 <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#endif + +#ifdef XP_MACOSX +#include <mach/mach_host.h> +#include <mach/mach_init.h> +#include <mach/host_info.h> +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/sysctl.h> +# if defined(__OpenBSD__) +#define KERN_CP_TIME KERN_CPTIME +# endif +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/sched.h> +#endif + +#ifdef XP_WIN +#include <pdh.h> +#include <tchar.h> +#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> loadMonitor) + { + mLoadMonitor = loadMonitor; + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIObserverService> 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<LoadMonitor> mLoadMonitor; +}; + +class LoadMonitorRemoveObserver : public Runnable +{ +public: + explicit LoadMonitorRemoveObserver(RefPtr<LoadMonitor> loadMonitor) + { + mLoadMonitor = loadMonitor; + } + + NS_IMETHOD Run() override + { + // remove xpcom shutdown observer + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) + observerService->RemoveObserver(mLoadMonitor, "xpcom-shutdown-threads"); + + return NS_OK; + } + +private: + RefPtr<LoadMonitor> 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<LoadMonitorRemoveObserver> 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<nsIFile> procStatFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + procStatFile->InitWithPath(NS_LITERAL_STRING("/proc/stat")); + + nsCOMPtr<nsIInputStream> fileInputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), + procStatFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILineInputStream> 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> loadMonitor, + RefPtr<RTCLoadInfo> 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<nsIThread> mThread; + RefPtr<RTCLoadInfo> mLoadInfo; + RefPtr<LoadMonitor> 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<LoadMonitor> &self) +{ + LOG(("Initializing LoadMonitor")); + + RefPtr<RTCLoadInfo> load_info = new RTCLoadInfo(); + nsresult rv = load_info->Init(mLoadUpdateInterval); + + if (NS_FAILED(rv)) { + LOG(("RTCLoadInfo::Init error")); + return rv; + } + + RefPtr<LoadMonitorAddObserver> addObsRunner = new LoadMonitorAddObserver(self); + NS_DispatchToMainThread(addObsRunner); + + NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread)); + + RefPtr<LoadInfoCollectRunner> runner = + new LoadInfoCollectRunner(self, load_info, mLoadInfoThread); + mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback) +{ + mLoadNotificationCallback = aCallback; +} + +} |