/* * Copyright (C) 2019 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 "perfstatsd_cpu" #include "cpu_usage.h" #include #include using namespace android::pixel::perfstatsd; static bool cDebug = false; static constexpr char FMT_CPU_TOTAL[] = "[CPU: %lld.%03llds][T:%.2f%%,U:%.2f%%,S:%.2f%%,IO:%.2f%%]"; static constexpr char TOP_HEADER[] = "[CPU_TOP] PID, PROCESS_NAME, USR_TIME, SYS_TIME\n"; static constexpr char FMT_TOP_PROFILE[] = "%6.2f%% %5d %s %" PRIu64 " %" PRIu64 "\n"; CpuUsage::CpuUsage(void) { std::string procstat; if (android::base::ReadFileToString("/proc/stat", &procstat)) { std::istringstream stream(procstat); std::string line; while (getline(stream, line)) { std::vector fields = android::base::Split(line, " "); if (fields[0].find("cpu") != std::string::npos && fields[0] != "cpu") { CpuData data; mPrevCoresUsage.push_back(data); } } } mCores = mPrevCoresUsage.size(); mProfileThreshold = CPU_USAGE_PROFILE_THRESHOLD; mTopcount = TOP_PROCESS_COUNT; } void CpuUsage::setOptions(const std::string &key, const std::string &value) { if (key == PROCPROF_THRESHOLD || key == CPU_DISABLED || key == CPU_DEBUG || key == CPU_TOPCOUNT) { uint32_t val = 0; if (!base::ParseUint(value, &val)) { LOG_TO(SYSTEM, ERROR) << "Invalid value: " << value; return; } if (key == PROCPROF_THRESHOLD) { mProfileThreshold = val; LOG_TO(SYSTEM, INFO) << "set profile threshold " << mProfileThreshold; } else if (key == CPU_DISABLED) { mDisabled = (val != 0); LOG_TO(SYSTEM, INFO) << "set disabled " << mDisabled; } else if (key == CPU_DEBUG) { cDebug = (val != 0); LOG_TO(SYSTEM, INFO) << "set debug " << cDebug; } else if (key == CPU_TOPCOUNT) { mTopcount = val; LOG_TO(SYSTEM, INFO) << "set top count " << mTopcount; } } } void CpuUsage::profileProcess(std::string *out) { // Read cpu usage per process and find the top ones DIR *dir; struct dirent *ent; std::unordered_map procUsage; std::priority_queue, ProcdataCompare> procList; if ((dir = opendir("/proc/")) != NULL) { while ((ent = readdir(dir)) != NULL) { if (ent->d_type == DT_DIR) { std::string pidStr = ent->d_name; std::string::const_iterator it = pidStr.begin(); while (it != pidStr.end() && isdigit(*it)) ++it; if (!pidStr.empty() && it == pidStr.end()) { std::string pidStat; if (android::base::ReadFileToString("/proc/" + pidStr + "/stat", &pidStat)) { std::vector fields = android::base::Split(pidStat, " "); uint32_t pid = 0; uint64_t utime = 0; uint64_t stime = 0; uint64_t cutime = 0; uint64_t cstime = 0; if (!base::ParseUint(fields[0], &pid) || !base::ParseUint(fields[13], &utime) || !base::ParseUint(fields[14], &stime) || !base::ParseUint(fields[15], &cutime) || !base::ParseUint(fields[16], &cstime)) { LOG_TO(SYSTEM, ERROR) << "Invalid proc data\n" << pidStat; continue; } std::string proc = fields[1]; std::string name = proc.length() > 2 ? proc.substr(1, proc.length() - 2) : ""; uint64_t user = utime + cutime; uint64_t system = stime + cstime; uint64_t totalUsage = user + system; uint64_t diffUser = user - mPrevProcdata[pid].user; uint64_t diffSystem = system - mPrevProcdata[pid].system; uint64_t diffUsage = totalUsage - mPrevProcdata[pid].usage; ProcData ldata; ldata.user = user; ldata.system = system; ldata.usage = totalUsage; procUsage[pid] = ldata; float usageRatio = (float)(diffUsage * 100.0 / mDiffCpu); if (cDebug && usageRatio > 100) { LOG_TO(SYSTEM, INFO) << "pid: " << pid << " , ratio: " << usageRatio << " , prev usage: " << mPrevProcdata[pid].usage << " , cur usage: " << totalUsage << " , total cpu diff: " << mDiffCpu; } ProcData data; data.pid = pid; data.name = name; data.usageRatio = usageRatio; data.user = diffUser; data.system = diffSystem; procList.push(data); } } } } mPrevProcdata = std::move(procUsage); out->append(TOP_HEADER); for (uint32_t count = 0; !procList.empty() && count < mTopcount; count++) { ProcData data = procList.top(); out->append(android::base::StringPrintf(FMT_TOP_PROFILE, data.usageRatio, data.pid, data.name.c_str(), data.user, data.system)); procList.pop(); } closedir(dir); } else { LOG_TO(SYSTEM, ERROR) << "Fail to open /proc/"; } } void CpuUsage::getOverallUsage(std::chrono::system_clock::time_point &now, std::string *out) { mDiffCpu = 0; mTotalRatio = 0.0f; std::string procStat; // Get overall cpu usage if (android::base::ReadFileToString("/proc/stat", &procStat)) { std::istringstream stream(procStat); std::string line; while (getline(stream, line)) { std::vector fields = android::base::Split(line, " "); if (fields[0].find("cpu") != std::string::npos) { std::string cpuStr = fields[0]; std::string core = cpuStr.length() > 3 ? cpuStr.substr(3, cpuStr.length() - 3) : ""; uint64_t user = 0; uint64_t nice = 0; uint64_t system = 0; uint64_t idle = 0; uint64_t iowait = 0; uint64_t irq = 0; uint64_t softirq = 0; uint64_t steal = 0; // cpu 6013 3243 6311 92390 517 693 319 0 0 0 <-- (fields[1] = "") // cpu0 558 139 568 12135 67 121 50 0 0 0 uint32_t base = core.compare("") ? 1 : 2; if (!base::ParseUint(fields[base], &user) || !base::ParseUint(fields[base + 1], &nice) || !base::ParseUint(fields[base + 2], &system) || !base::ParseUint(fields[base + 3], &idle) || !base::ParseUint(fields[base + 4], &iowait) || !base::ParseUint(fields[base + 5], &irq) || !base::ParseUint(fields[base + 6], &softirq) || !base::ParseUint(fields[base + 7], &steal)) { LOG_TO(SYSTEM, ERROR) << "Invalid /proc/stat data\n" << line; continue; } uint64_t cpuTime = user + nice + system + idle + iowait + irq + softirq + steal; uint64_t cpuUsage = cpuTime - idle - iowait; uint64_t userUsage = user + nice; if (!core.compare("")) { uint64_t diffUsage = cpuUsage - mPrevUsage.cpuUsage; mDiffCpu = cpuTime - mPrevUsage.cpuTime; uint64_t diffUser = userUsage - mPrevUsage.userUsage; uint64_t diffSys = system - mPrevUsage.sysUsage; uint64_t diffIo = iowait - mPrevUsage.ioUsage; mTotalRatio = (float)(diffUsage * 100.0 / mDiffCpu); float userRatio = (float)(diffUser * 100.0 / mDiffCpu); float sysRatio = (float)(diffSys * 100.0 / mDiffCpu); float ioRatio = (float)(diffIo * 100.0 / mDiffCpu); if (cDebug) { LOG_TO(SYSTEM, INFO) << "prev total: " << mPrevUsage.cpuUsage << " , cur total: " << cpuUsage << " , diffusage: " << diffUsage << " , diffcpu: " << mDiffCpu << " , ratio: " << mTotalRatio; } mPrevUsage.cpuUsage = cpuUsage; mPrevUsage.cpuTime = cpuTime; mPrevUsage.userUsage = userUsage; mPrevUsage.sysUsage = system; mPrevUsage.ioUsage = iowait; auto ms = std::chrono::duration_cast(now - mLast); out->append(android::base::StringPrintf(FMT_CPU_TOTAL, ms.count() / 1000, ms.count() % 1000, mTotalRatio, userRatio, sysRatio, ioRatio)); } else { // calculate total cpu usage of each core uint32_t c = 0; if (!base::ParseUint(core, &c)) { LOG_TO(SYSTEM, ERROR) << "Invalid core: " << core; continue; } uint64_t diffUsage = cpuUsage - mPrevCoresUsage[c].cpuUsage; float coreTotalRatio = (float)(diffUsage * 100.0 / mDiffCpu); if (cDebug) { LOG_TO(SYSTEM, INFO) << "core " << c << " , prev cpu usage: " << mPrevCoresUsage[c].cpuUsage << " , cur cpu usage: " << cpuUsage << " , diffusage: " << diffUsage << " , difftotalcpu: " << mDiffCpu << " , ratio: " << coreTotalRatio; } mPrevCoresUsage[c].cpuUsage = cpuUsage; char buf[64]; sprintf(buf, "%.2f%%]", coreTotalRatio); out->append("[" + core + ":" + std::string(buf)); } } } out->append("\n"); } else { LOG_TO(SYSTEM, ERROR) << "Fail to read /proc/stat"; } } void CpuUsage::refresh(void) { if (mDisabled) return; std::string out; std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); getOverallUsage(now, &out); if (mTotalRatio >= mProfileThreshold) { if (cDebug) LOG_TO(SYSTEM, INFO) << "Total CPU usage over " << mProfileThreshold << "%"; std::string profileResult; profileProcess(&profileResult); if (mProfileProcess) { // Dump top processes once met threshold continuously at least twice. out.append(profileResult); } else mProfileProcess = true; } else mProfileProcess = false; append(now, out); mLast = now; if (cDebug) { auto ms = std::chrono::duration_cast( std::chrono::system_clock::now() - now); LOG_TO(SYSTEM, INFO) << "Took " << ms.count() << " ms, data bytes: " << out.length(); } }