/* * 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 <string.h> #include <time.h> #include <utils/Log.h> #include <cpustats/ThreadCpuUsage.h> // implemented by host, but not declared in <string.h> as FreeBSD does extern "C" { extern size_t strlcpy(char *dst, const char *src, size_t dstsize); } 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 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 static_assert(MAX_CPU <= 10, "MAX_CPU too large"); #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