| /* |
| * 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 <unistd.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 |