summaryrefslogtreecommitdiffstats
path: root/dom/media/systemservices/LoadMonitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/systemservices/LoadMonitor.cpp')
-rw-r--r--dom/media/systemservices/LoadMonitor.cpp658
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;
+}
+
+}