gecko-dev/xpcom/threads/CPUUsageWatcher.cpp

244 строки
7.4 KiB
C++
Исходник Обычный вид История

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "mozilla/CPUUsageWatcher.h"
#include "prsystem.h"
#ifdef XP_MACOSX
#include <mach/mach_host.h>
#endif
// We only support OSX and Windows, because on Linux we're forced to read
// from /proc/stat in order to get global CPU values. We would prefer to not
// eat that cost for this.
#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX))
#define CPU_USAGE_WATCHER_ACTIVE
#endif
namespace mozilla {
// Even if the machine only has one processor, tolerate up to 50%
// external CPU usage.
static const float kTolerableExternalCPUUsageFloor = 0.5f;
struct CPUStats {
// The average CPU usage time, which can be summed across all cores in the
// system, or averaged between them. Whichever it is, it needs to be in the
// same units as updateTime.
uint64_t usageTime;
// A monotonically increasing value in the same units as usageTime, which can
// be used to determine the percentage of active vs idle time
uint64_t updateTime;
};
#ifdef XP_MACOSX
static const uint64_t kNanosecondsPerSecond = 1000000000LL;
static const uint64_t kCPUCheckInterval = kNanosecondsPerSecond / 2LL;
Result<uint64_t, CPUUsageWatcherError>
GetClockTime(clockid_t clockId) {
timespec clockResult;
bool success = !clock_gettime(clockId, &clockResult);
if (!success) {
return Err(ClockGetTimeError);
}
return ((uint64_t)clockResult.tv_sec) * kNanosecondsPerSecond +
(uint64_t)clockResult.tv_nsec;
}
Result<CPUStats, CPUUsageWatcherError>
GetProcessCPUStats(int32_t numCPUs) {
CPUStats result = {};
MOZ_TRY_VAR(result.usageTime, GetClockTime(CLOCK_PROCESS_CPUTIME_ID));
MOZ_TRY_VAR(result.updateTime, GetClockTime(CLOCK_MONOTONIC));
// CLOCK_PROCESS_CPUTIME_ID will give us the sum of the values across all
// of our cores. Divide by the number of CPUs to get an average.
result.usageTime /= numCPUs;
return result;
}
Result<CPUStats, CPUUsageWatcherError>
GetGlobalCPUStats() {
CPUStats result = {};
host_cpu_load_info_data_t loadInfo;
mach_msg_type_number_t loadInfoCount = HOST_CPU_LOAD_INFO_COUNT;
kern_return_t statsResult = host_statistics(mach_host_self(),
HOST_CPU_LOAD_INFO,
(host_info_t)&loadInfo,
&loadInfoCount);
if (statsResult != KERN_SUCCESS) {
return Err(HostStatisticsError);
}
result.usageTime = loadInfo.cpu_ticks[CPU_STATE_USER] +
loadInfo.cpu_ticks[CPU_STATE_NICE] +
loadInfo.cpu_ticks[CPU_STATE_SYSTEM];
result.updateTime = result.usageTime + loadInfo.cpu_ticks[CPU_STATE_IDLE];
return result;
}
#endif // XP_MACOSX
#ifdef XP_WIN
// A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC
static const uint64_t kFILETIMETicksPerSecond = 10000000;
static const uint64_t kCPUCheckInterval = kFILETIMETicksPerSecond / 2;
uint64_t
FiletimeToInteger(FILETIME filetime) {
return ((uint64_t)filetime.dwLowDateTime) |
(uint64_t)filetime.dwHighDateTime << 32;
}
Result<CPUStats, CPUUsageWatcherError> GetProcessCPUStats(int32_t numCPUs) {
CPUStats result = {};
FILETIME creationFiletime;
FILETIME exitFiletime;
FILETIME kernelFiletime;
FILETIME userFiletime;
bool success = GetProcessTimes(GetCurrentProcess(),
&creationFiletime,
&exitFiletime,
&kernelFiletime,
&userFiletime);
if (!success) {
return Err(GetProcessTimesError);
}
result.usageTime = FiletimeToInteger(kernelFiletime) +
FiletimeToInteger(userFiletime);
FILETIME nowFiletime;
GetSystemTimeAsFileTime(&nowFiletime);
result.updateTime = FiletimeToInteger(nowFiletime);
result.usageTime /= numCPUs;
return result;
}
Result<CPUStats, CPUUsageWatcherError>
GetGlobalCPUStats() {
CPUStats result = {};
FILETIME idleFiletime;
FILETIME kernelFiletime;
FILETIME userFiletime;
bool success = GetSystemTimes(&idleFiletime,
&kernelFiletime,
&userFiletime);
if (!success) {
return Err(GetSystemTimesError);
}
result.usageTime = FiletimeToInteger(kernelFiletime) +
FiletimeToInteger(userFiletime);
result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime);
return result;
}
#endif // XP_WIN
Result<Ok, CPUUsageWatcherError>
CPUUsageWatcher::Init()
{
mNumCPUs = PR_GetNumberOfProcessors();
if (mNumCPUs <= 0) {
mExternalUsageThreshold = 1.0f;
return Err(GetNumberOfProcessorsError);
}
mExternalUsageThreshold = std::max(1.0f - 1.0f / (float)mNumCPUs,
kTolerableExternalCPUUsageFloor);
#ifdef CPU_USAGE_WATCHER_ACTIVE
CPUStats processTimes;
MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
mProcessUpdateTime = processTimes.updateTime;
mProcessUsageTime = processTimes.usageTime;
CPUStats globalTimes;
MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
mGlobalUpdateTime = globalTimes.updateTime;
mGlobalUsageTime = globalTimes.usageTime;
mInitialized = true;
CPUUsageWatcher* self = this;
NS_DispatchToMainThread(
NS_NewRunnableFunction("CPUUsageWatcher::Init",
[=]() { HangMonitor::RegisterAnnotator(*self); }));
#endif // CPU_USAGE_WATCHER_ACTIVE
return Ok();
}
void
CPUUsageWatcher::Uninit()
{
mInitialized = false;
#ifdef CPU_USAGE_WATCHER_ACTIVE
HangMonitor::UnregisterAnnotator(*this);
#endif // CPU_USAGE_WATCHER_ACTIVE
}
Result<Ok, CPUUsageWatcherError>
CPUUsageWatcher::CollectCPUUsage()
{
if (!mInitialized) {
return Ok();
}
#ifdef CPU_USAGE_WATCHER_ACTIVE
mExternalUsageRatio = 0.0f;
CPUStats processTimes;
MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs));
CPUStats globalTimes;
MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats());
uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime;
uint64_t processUpdateDelta = processTimes.updateTime - mProcessUpdateTime;
float processUsageNormalized = processUsageDelta > 0 ?
(float)processUsageDelta / (float)processUpdateDelta :
0.0f;
uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime;
uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime;
float globalUsageNormalized = globalUsageDelta > 0 ?
(float)globalUsageDelta / (float)globalUpdateDelta :
0.0f;
mProcessUsageTime = processTimes.usageTime;
mProcessUpdateTime = processTimes.updateTime;
mGlobalUsageTime = globalTimes.usageTime;
mGlobalUpdateTime = globalTimes.updateTime;
mExternalUsageRatio = std::max(0.0f,
globalUsageNormalized - processUsageNormalized);
#endif // CPU_USAGE_WATCHER_ACTIVE
return Ok();
}
void
CPUUsageWatcher::AnnotateHang(HangMonitor::HangAnnotations& aAnnotations) {
if (!mInitialized) {
return;
}
if (mExternalUsageRatio > mExternalUsageThreshold) {
aAnnotations.AddAnnotation(NS_LITERAL_STRING("ExternalCPUHigh"), true);
}
}
} // namespace mozilla