542af12d10
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
256 lines
7.7 KiB
C++
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
|