// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "base/bind.h" #include "base/file_util.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "chrome/browser/chromeos/power/cpu_data_collector.h" #include "chrome/browser/chromeos/power/power_data_collector.h" #include "content/public/browser/browser_thread.h" namespace chromeos { namespace { // The sampling of CPU idle or CPU freq data should not take more than this // limit. const int kSamplingDurationLimitMs = 500; // The CPU data is sampled every |kCpuDataSamplePeriodSec| seconds. const int kCpuDataSamplePeriodSec = 30; // The value in the file /sys/devices/system/cpu/cpu/online which indicates // that CPU-n is online. const int kCpuOnlineStatus = 1; // The base of the path to the files and directories which contain CPU data in // the sysfs. const char kCpuDataPathBase[] = "/sys/devices/system/cpu"; // Suffix of the path to the file listing the range of possible CPUs on the // system. const char kPossibleCpuPathSuffix[] = "/possible"; // Format of the suffix of the path to the file which contains information // about a particular CPU being online or offline. const char kCpuOnlinePathSuffixFormat[] = "/cpu%d/online"; // Format of the suffix of the path to the file which contains freq state // information of a CPU. const char kCpuFreqTimeInStatePathSuffixFormat[] = "/cpu%d/cpufreq/stats/time_in_state"; // Format of the suffix of the path to the directory which contains information // about an idle state of a CPU on the system. const char kCpuIdleStateDirPathSuffixFormat[] = "/cpu%d/cpuidle/state%d"; // Format of the suffix of the path to the file which contains the name of an // idle state of a CPU. const char kCpuIdleStateNamePathSuffixFormat[] = "/cpu%d/cpuidle/state%d/name"; // Format of the suffix of the path which contains information about time spent // in an idle state on a CPU. const char kCpuIdleStateTimePathSuffixFormat[] = "/cpu%d/cpuidle/state%d/time"; // Returns the index at which |str| is in |vector|. If |str| is not present in // |vector|, then it is added to it before its index is returned. size_t IndexInVector(const std::string& str, std::vector* vector) { for (size_t i = 0; i < vector->size(); ++i) { if (str == (*vector)[i]) return i; } // If this is reached, then it means |str| is not present in vector. Add it. vector->push_back(str); return vector->size() - 1; } // Returns true if the |i|-th CPU is online; false otherwise. bool CpuIsOnline(const int i) { const std::string online_file_format = base::StringPrintf( "%s%s", kCpuDataPathBase, kCpuOnlinePathSuffixFormat); const std::string cpu_online_file = base::StringPrintf( online_file_format.c_str(), i); if (!base::PathExists(base::FilePath(cpu_online_file))) { // If the 'online' status file is missing, then it means that the CPU is // not hot-pluggable and hence is always online. return true; } int online; std::string cpu_online_string; if (base::ReadFileToString(base::FilePath(cpu_online_file), &cpu_online_string)) { base::TrimWhitespace(cpu_online_string, base::TRIM_ALL, &cpu_online_string); if (base::StringToInt(cpu_online_string, &online)) return online == kCpuOnlineStatus; } LOG(ERROR) << "Bad format or error reading " << cpu_online_file << ". " << "Assuming offline."; return false; } // Samples the CPU idle state information from sysfs. |cpu_count| is the number // of possible CPUs on the system. Sample at index i in |idle_samples| // corresponds to the idle state information of the i-th CPU. void SampleCpuIdleData( int cpu_count, std::vector* cpu_idle_state_names, std::vector* idle_samples) { base::Time start_time = base::Time::Now(); for (int cpu = 0; cpu < cpu_count; ++cpu) { CpuDataCollector::StateOccupancySample idle_sample; idle_sample.time = base::Time::Now(); idle_sample.time_in_state.reserve(cpu_idle_state_names->size()); if (!CpuIsOnline(cpu)) { idle_sample.cpu_online = false; } else { idle_sample.cpu_online = true; const std::string idle_state_dir_format = base::StringPrintf( "%s%s", kCpuDataPathBase, kCpuIdleStateDirPathSuffixFormat); for (int state_count = 0; ; ++state_count) { std::string idle_state_dir = base::StringPrintf( idle_state_dir_format.c_str(), cpu, state_count); // This insures us from the unlikely case wherein the 'cpuidle_stats' // kernel module is not loaded. This could happen on a VM. if (!base::DirectoryExists(base::FilePath(idle_state_dir))) break; const std::string name_file_format = base::StringPrintf( "%s%s", kCpuDataPathBase, kCpuIdleStateNamePathSuffixFormat); const std::string name_file_path = base::StringPrintf( name_file_format.c_str(), cpu, state_count); DCHECK(base::PathExists(base::FilePath(name_file_path))); const std::string time_file_format = base::StringPrintf( "%s%s", kCpuDataPathBase, kCpuIdleStateTimePathSuffixFormat); const std::string time_file_path = base::StringPrintf( time_file_format.c_str(), cpu, state_count); DCHECK(base::PathExists(base::FilePath(time_file_path))); std::string state_name, occupancy_time_string; int64 occupancy_time_usec; if (!base::ReadFileToString(base::FilePath(name_file_path), &state_name) || !base::ReadFileToString(base::FilePath(time_file_path), &occupancy_time_string)) { // If an error occurs reading/parsing single state data, drop all the // samples as an incomplete sample can mislead consumers of this // sample. LOG(ERROR) << "Error reading idle state from " << idle_state_dir << ". Dropping sample."; idle_samples->clear(); return; } base::TrimWhitespace(state_name, base::TRIM_ALL, &state_name); base::TrimWhitespace( occupancy_time_string, base::TRIM_ALL, &occupancy_time_string); if (base::StringToInt64(occupancy_time_string, &occupancy_time_usec)) { // idle state occupancy time in sysfs is recorded in microseconds. int64 time_in_state_ms = occupancy_time_usec / 1000; size_t index = IndexInVector(state_name, cpu_idle_state_names); if (index >= idle_sample.time_in_state.size()) idle_sample.time_in_state.resize(index + 1); idle_sample.time_in_state[index] = time_in_state_ms; } else { LOG(ERROR) << "Bad format in " << time_file_path << ". " << "Dropping sample."; idle_samples->clear(); return; } } } idle_samples->push_back(idle_sample); } // If there was an interruption in sampling (like system suspended), // discard the samples! int64 delay = base::TimeDelta(base::Time::Now() - start_time).InMilliseconds(); if (delay > kSamplingDurationLimitMs) { idle_samples->clear(); LOG(WARNING) << "Dropped an idle state sample due to excessive time delay: " << delay << "milliseconds."; } } // Samples the CPU freq state information from sysfs. |cpu_count| is the number // of possible CPUs on the system. Sample at index i in |freq_samples| // corresponds to the freq state information of the i-th CPU. void SampleCpuFreqData( int cpu_count, std::vector* cpu_freq_state_names, std::vector* freq_samples) { base::Time start_time = base::Time::Now(); for (int cpu = 0; cpu < cpu_count; ++cpu) { CpuDataCollector::StateOccupancySample freq_sample; freq_sample.time_in_state.reserve(cpu_freq_state_names->size()); if (!CpuIsOnline(cpu)) { freq_sample.time = base::Time::Now(); freq_sample.cpu_online = false; } else { freq_sample.cpu_online = true; const std::string time_in_state_path_format = base::StringPrintf( "%s%s", kCpuDataPathBase, kCpuFreqTimeInStatePathSuffixFormat); const std::string time_in_state_path = base::StringPrintf( time_in_state_path_format.c_str(), cpu); if (!base::PathExists(base::FilePath(time_in_state_path))) { // If the path to the 'time_in_state' for a single CPU is missing, // then 'time_in_state' for all CPUs is missing. This could happen // on a VM where the 'cpufreq_stats' kernel module is not loaded. LOG(ERROR) << "CPU freq stats not available in sysfs."; freq_samples->clear(); return; } std::string time_in_state_string; // Note time as close to reading the file as possible. This is not // possible for idle state samples as the information for each state there // is recorded in different files. base::Time now = base::Time::Now(); if (!base::ReadFileToString(base::FilePath(time_in_state_path), &time_in_state_string)) { LOG(ERROR) << "Error reading " << time_in_state_path << ". " << "Dropping sample."; freq_samples->clear(); return; } freq_sample.time = now; std::vector lines; base::SplitString(time_in_state_string, '\n', &lines); // The last line could end with '\n'. Ignore the last empty string in // such cases. size_t state_count = lines.size(); if (state_count > 0 && lines.back().empty()) state_count -= 1; for (size_t state = 0; state < state_count; ++state) { std::vector pair; int freq_in_khz; int64 occupancy_time_centisecond; // Occupancy of each state is in the format "