replicant-frameworks_native/libs/cpustats/ThreadCpuUsage.cpp
Glenn Kasten 542af12d10 Fix log spam about CPU frequency on one device
Previous kernels have allowed opening the CPU frequency file regardless
whether the CPU is up or not.  This fixes some log spam on one device
with dynamic hot plug CPU feature, which does not allow opening the CPU
frequency file if CPU is down.

Also, since the file descriptors are global and have long lives, add
the close-on-exec flag.

Change-Id: Ia14a2b9e20038dfb96a573920176a47a96bd3f5a
2012-05-14 16:55:26 -07:00

256 lines
7.7 KiB
C++

/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "ThreadCpuUsage"
//#define LOG_NDEBUG 0
#include <errno.h>
#include <stdlib.h>
#include <time.h>
#include <utils/Debug.h>
#include <utils/Log.h>
#include <cpustats/ThreadCpuUsage.h>
namespace android {
bool ThreadCpuUsage::setEnabled(bool isEnabled)
{
bool wasEnabled = mIsEnabled;
// only do something if there is a change
if (isEnabled != wasEnabled) {
ALOGV("setEnabled(%d)", isEnabled);
int rc;
// enabling
if (isEnabled) {
rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mPreviousTs);
if (rc) {
ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
isEnabled = false;
} else {
mWasEverEnabled = true;
// record wall clock time at first enable
if (!mMonotonicKnown) {
rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
if (rc) {
ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
} else {
mMonotonicKnown = true;
}
}
}
// disabling
} else {
struct timespec ts;
rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
if (rc) {
ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
} else {
long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
(ts.tv_nsec - mPreviousTs.tv_nsec);
mAccumulator += delta;
#if 0
mPreviousTs = ts;
#endif
}
}
mIsEnabled = isEnabled;
}
return wasEnabled;
}
bool ThreadCpuUsage::sampleAndEnable(double& ns)
{
bool ret;
bool wasEverEnabled = mWasEverEnabled;
if (enable()) {
// already enabled, so add a new sample relative to previous
return sample(ns);
} else if (wasEverEnabled) {
// was disabled, but add sample for accumulated time while enabled
ns = (double) mAccumulator;
mAccumulator = 0;
ALOGV("sampleAndEnable %.0f", ns);
return true;
} else {
// first time called
ns = 0.0;
ALOGV("sampleAndEnable false");
return false;
}
}
bool ThreadCpuUsage::sample(double &ns)
{
if (mWasEverEnabled) {
if (mIsEnabled) {
struct timespec ts;
int rc;
rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
if (rc) {
ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
ns = 0.0;
return false;
} else {
long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
(ts.tv_nsec - mPreviousTs.tv_nsec);
mAccumulator += delta;
mPreviousTs = ts;
}
} else {
mWasEverEnabled = false;
}
ns = (double) mAccumulator;
ALOGV("sample %.0f", ns);
mAccumulator = 0;
return true;
} else {
ALOGW("Can't add sample because measurements have never been enabled");
ns = 0.0;
return false;
}
}
long long ThreadCpuUsage::elapsed() const
{
long long elapsed;
if (mMonotonicKnown) {
struct timespec ts;
int rc;
rc = clock_gettime(CLOCK_MONOTONIC, &ts);
if (rc) {
ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
elapsed = 0;
} else {
// mMonotonicTs is updated only at first enable and resetStatistics
elapsed = (ts.tv_sec - mMonotonicTs.tv_sec) * 1000000000LL +
(ts.tv_nsec - mMonotonicTs.tv_nsec);
}
} else {
ALOGW("Can't compute elapsed time because measurements have never been enabled");
elapsed = 0;
}
ALOGV("elapsed %lld", elapsed);
return elapsed;
}
void ThreadCpuUsage::resetElapsed()
{
ALOGV("resetElapsed");
if (mMonotonicKnown) {
int rc;
rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
if (rc) {
ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
mMonotonicKnown = false;
}
}
}
/*static*/
int ThreadCpuUsage::sScalingFds[ThreadCpuUsage::MAX_CPU];
pthread_once_t ThreadCpuUsage::sOnceControl = PTHREAD_ONCE_INIT;
int ThreadCpuUsage::sKernelMax;
pthread_mutex_t ThreadCpuUsage::sMutex = PTHREAD_MUTEX_INITIALIZER;
/*static*/
void ThreadCpuUsage::init()
{
// read the number of CPUs
sKernelMax = 1;
int fd = open("/sys/devices/system/cpu/kernel_max", O_RDONLY);
if (fd >= 0) {
#define KERNEL_MAX_SIZE 12
char kernelMax[KERNEL_MAX_SIZE];
ssize_t actual = read(fd, kernelMax, sizeof(kernelMax));
if (actual >= 2 && kernelMax[actual-1] == '\n') {
sKernelMax = atoi(kernelMax);
if (sKernelMax >= MAX_CPU - 1) {
ALOGW("kernel_max %d but MAX_CPU %d", sKernelMax, MAX_CPU);
sKernelMax = MAX_CPU;
} else if (sKernelMax < 0) {
ALOGW("kernel_max invalid %d", sKernelMax);
sKernelMax = 1;
} else {
++sKernelMax;
ALOGV("number of CPUs %d", sKernelMax);
}
} else {
ALOGW("Can't read number of CPUs");
}
(void) close(fd);
} else {
ALOGW("Can't open number of CPUs");
}
int i;
for (i = 0; i < MAX_CPU; ++i) {
sScalingFds[i] = -1;
}
}
uint32_t ThreadCpuUsage::getCpukHz(int cpuNum)
{
if (cpuNum < 0 || cpuNum >= MAX_CPU) {
ALOGW("getCpukHz called with invalid CPU %d", cpuNum);
return 0;
}
// double-checked locking idiom is not broken for atomic values such as fd
int fd = sScalingFds[cpuNum];
if (fd < 0) {
// some kernels can't open a scaling file until hot plug complete
pthread_mutex_lock(&sMutex);
fd = sScalingFds[cpuNum];
if (fd < 0) {
#define FREQ_SIZE 64
char freq_path[FREQ_SIZE];
#define FREQ_DIGIT 27
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(MAX_CPU <= 10);
#define FREQ_PATH "/sys/devices/system/cpu/cpu?/cpufreq/scaling_cur_freq"
strlcpy(freq_path, FREQ_PATH, sizeof(freq_path));
freq_path[FREQ_DIGIT] = cpuNum + '0';
fd = open(freq_path, O_RDONLY | O_CLOEXEC);
// keep this fd until process exit or exec
sScalingFds[cpuNum] = fd;
}
pthread_mutex_unlock(&sMutex);
if (fd < 0) {
ALOGW("getCpukHz can't open CPU %d", cpuNum);
return 0;
}
}
#define KHZ_SIZE 12
char kHz[KHZ_SIZE]; // kHz base 10
ssize_t actual = pread(fd, kHz, sizeof(kHz), (off_t) 0);
uint32_t ret;
if (actual >= 2 && kHz[actual-1] == '\n') {
ret = atoi(kHz);
} else {
ret = 0;
}
if (ret != mCurrentkHz[cpuNum]) {
if (ret > 0) {
ALOGV("CPU %d frequency %u kHz", cpuNum, ret);
} else {
ALOGW("Can't read CPU %d frequency", cpuNum);
}
mCurrentkHz[cpuNum] = ret;
}
return ret;
}
} // namespace android