diff options
author | sivachandra@chromium.org <sivachandra@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-13 15:05:14 +0000 |
---|---|---|
committer | sivachandra@chromium.org <sivachandra@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-13 15:05:14 +0000 |
commit | f29dd9fd3b43f62bd1ed095c1e13332dc52bab00 (patch) | |
tree | 811ca1b9a4b3789021e5d34243479f06d58e622c | |
parent | 142479ba17118dba683118db72a4a4dafe84b813 (diff) | |
download | chromium_src-f29dd9fd3b43f62bd1ed095c1e13332dc52bab00.zip chromium_src-f29dd9fd3b43f62bd1ed095c1e13332dc52bab00.tar.gz chromium_src-f29dd9fd3b43f62bd1ed095c1e13332dc52bab00.tar.bz2 |
Reland "[chromeos/about:power] Collect cpuidle and cpufreq stats"
Original: https://chromiumcodereview.appspot.com/149973002
Revert: https://codereview.chromium.org/196613002/
The difference between this and the original CL is that the sysfs is
read on the blocking pool via a non-member function. This avoids passing
the weak ptr to blocking pool from the UI thread (as was the case in the
original CL).
This reverts commit 92e40737ec9a1cf806761b4c67caf688d3a1ef5e.
BUG=335816
TBR=arv
Review URL: https://codereview.chromium.org/197813006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256828 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/app/chromeos_strings.grdp | 45 | ||||
-rw-r--r-- | chrome/browser/chromeos/power/cpu_data_collector.cc | 421 | ||||
-rw-r--r-- | chrome/browser/chromeos/power/cpu_data_collector.h | 113 | ||||
-rw-r--r-- | chrome/browser/chromeos/power/power_data_collector.cc | 12 | ||||
-rw-r--r-- | chrome/browser/chromeos/power/power_data_collector.h | 11 | ||||
-rw-r--r-- | chrome/browser/chromeos/power/power_data_collector_unittest.cc | 2 | ||||
-rw-r--r-- | chrome/browser/resources/chromeos/power.css | 60 | ||||
-rw-r--r-- | chrome/browser/resources/chromeos/power.html | 76 | ||||
-rw-r--r-- | chrome/browser/resources/chromeos/power.js | 542 | ||||
-rw-r--r-- | chrome/browser/ui/webui/chromeos/power_ui.cc | 134 | ||||
-rw-r--r-- | chrome/chrome_browser_chromeos.gypi | 2 |
11 files changed, 1284 insertions, 134 deletions
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index b69c829..4ef6ad5 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp @@ -5021,27 +5021,54 @@ All users must sign out to continue. </message> <!-- About power UI display strings --> - <message name="IDS_ABOUT_POWER_TITLE" desc="Title of the page"> + <message name="IDS_ABOUT_POWER_TITLE" desc="Title of the about:power page"> Power </message> - <message name="IDS_ABOUT_POWER_RELOAD_BUTTON" desc="Text on the reload button"> + <message name="IDS_ABOUT_POWER_SHOW_BUTTON" desc="Text on the 'Show' buttons of the about:power page"> + Show + </message> + <message name="IDS_ABOUT_POWER_HIDE_BUTTON" desc="Text on the 'Hide' buttons of the about:power page"> + Hide + </message> + <message name="IDS_ABOUT_POWER_RELOAD_BUTTON" desc="Text on the 'Reload' buttons of the about:power page "> Reload </message> - <message name="IDS_ABOUT_POWER_BATTERY_CHARGE_PERCENTAGE_HEADER" desc="Header of the battery charge plot"> + <message name="IDS_ABOUT_POWER_BATTERY_CHARGE_SECTION_TITLE" desc="Title for the battery charge related plots on the about:power page"> + Battery Charge + </message> + <message name="IDS_ABOUT_POWER_BATTERY_CHARGE_PERCENTAGE_HEADER" desc="Header of the battery charge plot on the about:power page"> Battery Charge Percentage </message> - <message name="IDS_ABOUT_POWER_BATTERY_DISCHARGE_RATE_HEADER" desc="Header of the battery discharge rate plot"> - Battery Discharge Rate in Watts + <message name="IDS_ABOUT_POWER_BATTERY_DISCHARGE_RATE_HEADER" desc="Header of the battery discharge rate plot on the about:power page"> + Battery Discharge Rate in Watts (Negative value means battery is charging) + </message> + <message name="IDS_ABOUT_POWER_DISCHARGE_RATE_LEGEND_TEXT" desc="Text to be displayed for the discharge rate legend entry of the discharge rate plot on the about:power page"> + Discharge Rate in Watts + </message> + <message name="IDS_ABOUT_POWER_CPU_IDLE_SECTION_TITLE" desc="Title for the CPU idle state data section on the about:power page"> + Idle State Data </message> - <message name="IDS_ABOUT_POWER_NEGATIVE_DISCHARGE_RATE_INFO" desc="Describes what a negative discharge rate means"> - Negative discharge rate means the battery is charging + <message name="IDS_ABOUT_POWER_CPU_IDLE_STATE_OCCUPANCY_PERCENTAGE" desc="Header for idle state plots on the about:power page"> + Idle State Occupancy Percentage </message> - <message name="IDS_ABOUT_POWER_NOT_ENOUGH_DATA" desc="String to display when enough plot data is not available"> + <message name="IDS_ABOUT_POWER_CPU_FREQ_SECTION_TITLE" desc="Title for the CPU frequency state data section on the about:power page"> + Frequency State Data + </message> + <message name="IDS_ABOUT_POWER_CPU_FREQ_STATE_OCCUPANCY_PERCENTAGE" desc="Header for frequency state plots on the about:power page"> + Frequency State Occupancy Percentage + </message> + <message name="IDS_ABOUT_POWER_NOT_ENOUGH_DATA" desc="String to display on the plots of the about:power page when enough plot data is not available"> Not enough data available yet. </message> - <message name="IDS_ABOUT_POWER_SYSTEM_SUSPENDED" desc="String to display on plots for sleep duration"> + <message name="IDS_ABOUT_POWER_SYSTEM_SUSPENDED" desc="String to display on the plots of the about:power page for sleep duration"> Suspended </message> + <message name="IDS_ABOUT_POWER_INVALID" desc="String to display on the plots of the about:power page for invalid data"> + Invalid + </message> + <message name="IDS_ABOUT_POWER_OFFLINE" desc="String to display on the plots of the about:power page for offline CPUs"> + Offline + </message> <!-- Strings for the new-style OAuth-based enterprise enrollment page --> <message name="IDS_ENTERPRISE_DEVICE_REQUISITION_PROMPT_CANCEL" desc="Cancel button text in the device requisition poup."> diff --git a/chrome/browser/chromeos/power/cpu_data_collector.cc b/chrome/browser/chromeos/power/cpu_data_collector.cc new file mode 100644 index 0000000..365347c --- /dev/null +++ b/chrome/browser/chromeos/power/cpu_data_collector.cc @@ -0,0 +1,421 @@ +// 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 <vector> + +#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<n>/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<std::string>* 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<std::string>* cpu_idle_state_names, + std::vector<CpuDataCollector::StateOccupancySample>* 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); + 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<std::string>* cpu_freq_state_names, + std::vector<CpuDataCollector::StateOccupancySample>* 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); + DCHECK(base::PathExists(base::FilePath(time_in_state_path))); + + 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<std::string> 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<std::string> pair; + int freq_in_khz; + int64 occupancy_time_centisecond; + + // Occupancy of each state is in the format "<state> <time>" + base::SplitString(lines[state], ' ', &pair); + for (size_t s = 0; s < pair.size(); ++s) + base::TrimWhitespace(pair[s], base::TRIM_ALL, &pair[s]); + if (pair.size() == 2 && + base::StringToInt(pair[0], &freq_in_khz) && + base::StringToInt64(pair[1], &occupancy_time_centisecond)) { + const std::string state_name = base::IntToString(freq_in_khz / 1000); + size_t index = IndexInVector(state_name, cpu_freq_state_names); + if (index >= freq_sample.time_in_state.size()) + freq_sample.time_in_state.resize(index + 1); + // The occupancy time is in units of centiseconds. + freq_sample.time_in_state[index] = occupancy_time_centisecond * 10; + } else { + LOG(ERROR) << "Bad format in " << time_in_state_path << ". " + << "Dropping sample."; + freq_samples->clear(); + return; + } + } + } + + freq_samples->push_back(freq_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) { + freq_samples->clear(); + LOG(WARNING) << "Dropped a freq state sample due to excessive time delay: " + << delay << "milliseconds."; + } +} + +// Samples CPU idle and CPU freq data from sysfs. This function should run on +// the blocking pool as reading from sysfs is a blocking task. Elements at +// index i in |idle_samples| and |freq_samples| correspond to the idle and +// freq samples of CPU i. This also function reads the number of CPUs from +// sysfs if *|cpu_count| < 0. +void SampleCpuStateOnBlockingPool( + int* cpu_count, + std::vector<std::string>* cpu_idle_state_names, + std::vector<CpuDataCollector::StateOccupancySample>* idle_samples, + std::vector<std::string>* cpu_freq_state_names, + std::vector<CpuDataCollector::StateOccupancySample>* freq_samples) { + DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + if (*cpu_count < 0) { + // Set |cpu_count_| to 1. If it is something else, it will get corrected + // later. A system will at least have one CPU. Hence, a value of 1 here + // will serve as a default value in case of errors. + *cpu_count = 1; + const std::string possible_cpu_path = base::StringPrintf( + "%s%s", kCpuDataPathBase, kPossibleCpuPathSuffix); + if (!base::PathExists(base::FilePath(possible_cpu_path))) { + LOG(ERROR) << "File listing possible CPUs missing. " + << "Defaulting CPU count to 1."; + } else { + std::string possible_string; + if (base::ReadFileToString(base::FilePath(possible_cpu_path), + &possible_string)) { + int max_cpu; + // The possible CPUs are listed in the format "0-N". Hence, N is present + // in the substring starting at offset 2. + base::TrimWhitespace(possible_string, base::TRIM_ALL, &possible_string); + if (possible_string.find("-") != std::string::npos && + possible_string.length() > 2 && + base::StringToInt(possible_string.substr(2), &max_cpu)) { + *cpu_count = max_cpu + 1; + } else { + LOG(ERROR) << "Unknown format in the file listing possible CPUs. " + << "Defaulting CPU count to 1."; + } + } else { + LOG(ERROR) << "Error reading the file listing possible CPUs. " + << "Defaulting CPU count to 1."; + } + } + } + + // Initialize the deques in the data vectors. + SampleCpuIdleData(*cpu_count, cpu_idle_state_names, idle_samples); + SampleCpuFreqData(*cpu_count, cpu_freq_state_names, freq_samples); +} + +} // namespace + +// Set |cpu_count_| to -1 and let SampleCpuStateOnBlockingPool discover the +// correct number of CPUs. +CpuDataCollector::CpuDataCollector() : cpu_count_(-1), weak_ptr_factory_(this) { +} + +CpuDataCollector::~CpuDataCollector() { +} + +void CpuDataCollector::Start() { + timer_.Start(FROM_HERE, + base::TimeDelta::FromSeconds(kCpuDataSamplePeriodSec), + this, + &CpuDataCollector::PostSampleCpuState); +} + +void CpuDataCollector::PostSampleCpuState() { + int* cpu_count = new int(cpu_count_); + std::vector<std::string>* cpu_idle_state_names = + new std::vector<std::string>(cpu_idle_state_names_); + std::vector<StateOccupancySample>* idle_samples = + new std::vector<StateOccupancySample>; + std::vector<std::string>* cpu_freq_state_names = + new std::vector<std::string>(cpu_freq_state_names_); + std::vector<StateOccupancySample>* freq_samples = + new std::vector<StateOccupancySample>; + + content::BrowserThread::PostBlockingPoolTaskAndReply( + FROM_HERE, + base::Bind(&SampleCpuStateOnBlockingPool, + base::Unretained(cpu_count), + base::Unretained(cpu_idle_state_names), + base::Unretained(idle_samples), + base::Unretained(cpu_freq_state_names), + base::Unretained(freq_samples)), + base::Bind(&CpuDataCollector::SaveCpuStateSamplesOnUIThread, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(cpu_count), + base::Owned(cpu_idle_state_names), + base::Owned(idle_samples), + base::Owned(cpu_freq_state_names), + base::Owned(freq_samples))); +} + +void CpuDataCollector::SaveCpuStateSamplesOnUIThread( + const int* cpu_count, + const std::vector<std::string>* cpu_idle_state_names, + const std::vector<CpuDataCollector::StateOccupancySample>* idle_samples, + const std::vector<std::string>* cpu_freq_state_names, + const std::vector<CpuDataCollector::StateOccupancySample>* freq_samples) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + cpu_count_ = *cpu_count; + + // |idle_samples| or |freq_samples| could be empty sometimes (for example, if + // sampling was interrupted due to system suspension). Iff they are not empty, + // they will have one sample each for each of the CPUs. + + if (!idle_samples->empty()) { + // When committing the first sample, resize the data vector to the number of + // CPUs on the system. This number should be the same as the number of + // samples in |idle_samples|. + if (cpu_idle_state_data_.empty()) { + cpu_idle_state_data_.resize(idle_samples->size()); + } else { + DCHECK_EQ(idle_samples->size(), cpu_idle_state_data_.size()); + } + for (size_t i = 0; i < cpu_idle_state_data_.size(); ++i) + AddSample(&cpu_idle_state_data_[i], (*idle_samples)[i]); + + cpu_idle_state_names_ = *cpu_idle_state_names; + } + + if (!freq_samples->empty()) { + // As with idle samples, resize the data vector before committing the first + // sample. + if (cpu_freq_state_data_.empty()) { + cpu_freq_state_data_.resize(freq_samples->size()); + } else { + DCHECK_EQ(freq_samples->size(), cpu_freq_state_data_.size()); + } + for (size_t i = 0; i < cpu_freq_state_data_.size(); ++i) + AddSample(&cpu_freq_state_data_[i], (*freq_samples)[i]); + + cpu_freq_state_names_ = *cpu_freq_state_names; + } +} + +CpuDataCollector::StateOccupancySample::StateOccupancySample() + : cpu_online(false) { +} + +CpuDataCollector::StateOccupancySample::~StateOccupancySample() { +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/power/cpu_data_collector.h b/chrome/browser/chromeos/power/cpu_data_collector.h new file mode 100644 index 0000000..d0734c6 --- /dev/null +++ b/chrome/browser/chromeos/power/cpu_data_collector.h @@ -0,0 +1,113 @@ +// 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. + +#ifndef CHROME_BROWSER_CHROMEOS_POWER_CPU_DATA_COLLECTOR_H_ +#define CHROME_BROWSER_CHROMEOS_POWER_CPU_DATA_COLLECTOR_H_ + +#include <deque> +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/timer/timer.h" + +namespace chromeos { + +// A class to sample CPU idle state occupancy and freq state occupancy. +// Collects raw data from sysfs and does not convert it to percentage +// occupancy. As CPUs can be offline at times, or the system can be suspended at +// other times, it is best for the consumer of this data to calculate percentage +// occupancy information using suspend time data from +// PowerDataCollector::system_resumed_data. +class CpuDataCollector { + public: + struct StateOccupancySample { + StateOccupancySample(); + ~StateOccupancySample(); + + // The time when the data was sampled. + base::Time time; + + // Indicates whether the CPU is online. + bool cpu_online; + + // A mapping from a CPU state to time spent in that state in milliseconds. + // For idle state samples, the name of the state at index i in this vector + // is the name at index i in the vector returned by cpu_idle_state_names(). + // Similarly, for freq state occupancy, similar information is in the vector + // returned by cpu_freq_state_names(). + std::vector<int64> time_in_state; + }; + + typedef std::deque<StateOccupancySample> StateOccupancySampleDeque; + + const std::vector<std::string>& cpu_idle_state_names() const { + return cpu_idle_state_names_; + } + + const std::vector<StateOccupancySampleDeque>& cpu_idle_state_data() const { + return cpu_idle_state_data_; + } + + const std::vector<std::string>& cpu_freq_state_names() const { + return cpu_freq_state_names_; + } + + const std::vector<StateOccupancySampleDeque>& cpu_freq_state_data() const { + return cpu_freq_state_data_; + } + + CpuDataCollector(); + ~CpuDataCollector(); + + // Starts a repeating timer which periodically runs a callback to collect + // CPU state occupancy samples. + void Start(); + + private: + // Posts callbacks to the blocking pool which collect CPU state occupancy + // samples from the sysfs. + void PostSampleCpuState(); + + // This function commits the CPU count and samples read by + // SampleCpuStateOnBlockingPool to |cpu_idle_state_data_| and + // |cpu_freq_state_data_|. Since UI is the consumer of CPU idle and freq data, + // this function should run on the UI thread. + void SaveCpuStateSamplesOnUIThread( + const int* cpu_count, + const std::vector<std::string>* cpu_idle_state_names, + const std::vector<StateOccupancySample>* idle_samples, + const std::vector<std::string>* cpu_freq_state_names, + const std::vector<StateOccupancySample>* freq_samples); + + base::RepeatingTimer<CpuDataCollector> timer_; + + // Names of the idle states. + std::vector<std::string> cpu_idle_state_names_; + + // The deque at index <i> in the vector corresponds to the idle state + // occupancy data of CPU<i>. + std::vector<StateOccupancySampleDeque> cpu_idle_state_data_; + + // Names of the freq states. + std::vector<std::string> cpu_freq_state_names_; + + // The deque at index <i> in the vector corresponds to the frequency state + // occupancy data of CPU<i>. + std::vector<StateOccupancySampleDeque> cpu_freq_state_data_; + + // The number of CPUs on the system. This value is read from the sysfs, and + // hence should be read/written to only from the blocking pool. + int cpu_count_; + + base::WeakPtrFactory<CpuDataCollector> weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(CpuDataCollector); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_POWER_CPU_DATA_COLLECTOR_H_ diff --git a/chrome/browser/chromeos/power/power_data_collector.cc b/chrome/browser/chromeos/power/power_data_collector.cc index b997bb0..a6ffe8f 100644 --- a/chrome/browser/chromeos/power/power_data_collector.cc +++ b/chrome/browser/chromeos/power/power_data_collector.cc @@ -25,7 +25,13 @@ void PowerDataCollector::Initialize() { // DBusThreadManager is initialized. CHECK(DBusThreadManager::Get()); CHECK(g_power_data_collector == NULL); - g_power_data_collector = new PowerDataCollector(); + g_power_data_collector = new PowerDataCollector(true); +} + +void PowerDataCollector::InitializeForTesting() { + CHECK(DBusThreadManager::Get()); + CHECK(g_power_data_collector == NULL); + g_power_data_collector = new PowerDataCollector(false); } // static @@ -60,8 +66,10 @@ void PowerDataCollector::SystemResumed(const base::TimeDelta& sleep_duration) { AddSample(&system_resumed_data_, sample); } -PowerDataCollector::PowerDataCollector() { +PowerDataCollector::PowerDataCollector(const bool start_cpu_data_collector) { DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this); + if (start_cpu_data_collector) + cpu_data_collector_.Start(); } PowerDataCollector::~PowerDataCollector() { diff --git a/chrome/browser/chromeos/power/power_data_collector.h b/chrome/browser/chromeos/power/power_data_collector.h index db68342..16ee5f7 100644 --- a/chrome/browser/chromeos/power/power_data_collector.h +++ b/chrome/browser/chromeos/power/power_data_collector.h @@ -10,6 +10,7 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/time/time.h" +#include "chrome/browser/chromeos/power/cpu_data_collector.h" #include "chromeos/chromeos_export.h" #include "chromeos/dbus/power_manager_client.h" @@ -63,9 +64,16 @@ class CHROMEOS_EXPORT PowerDataCollector : public PowerManagerClient::Observer { return system_resumed_data_; } + const CpuDataCollector& cpu_data_collector() const { + return cpu_data_collector_; + } + // Can be called only after DBusThreadManager is initialized. static void Initialize(); + // Same as Initialize, but does not start the CpuDataCollector. + static void InitializeForTesting(); + // Can be called only if initialized via Initialize, and before // DBusThreadManager is destroyed. static void Shutdown(); @@ -83,12 +91,13 @@ class CHROMEOS_EXPORT PowerDataCollector : public PowerManagerClient::Observer { static const int kSampleTimeLimitSec; private: - PowerDataCollector(); + explicit PowerDataCollector(const bool start_cpu_data_collector); virtual ~PowerDataCollector(); std::deque<PowerSupplySample> power_supply_data_; std::deque<SystemResumedSample> system_resumed_data_; + CpuDataCollector cpu_data_collector_; DISALLOW_COPY_AND_ASSIGN(PowerDataCollector); }; diff --git a/chrome/browser/chromeos/power/power_data_collector_unittest.cc b/chrome/browser/chromeos/power/power_data_collector_unittest.cc index ed7f2d8..b45e43d 100644 --- a/chrome/browser/chromeos/power/power_data_collector_unittest.cc +++ b/chrome/browser/chromeos/power/power_data_collector_unittest.cc @@ -19,7 +19,7 @@ class PowerDataCollectorTest : public testing::Test { FakeDBusThreadManager* fake_dbus_thread_manager = new FakeDBusThreadManager; fake_dbus_thread_manager->SetFakeClients(); DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager); - PowerDataCollector::Initialize(); + PowerDataCollector::InitializeForTesting(); power_data_collector_ = PowerDataCollector::Get(); } diff --git a/chrome/browser/resources/chromeos/power.css b/chrome/browser/resources/chromeos/power.css index 2ebbdb0..a675d89 100644 --- a/chrome/browser/resources/chromeos/power.css +++ b/chrome/browser/resources/chromeos/power.css @@ -4,24 +4,68 @@ * found in the LICENSE file. */ -#main-layout { +#main-table { + border-collapse: collapse; + border-width: 0; margin-left: auto; margin-right: auto; - width: 600px; + table-layout: fixed; + width: 1000px; } -hr.section-boundary { - background: #000; - border: 0; - height: 2px; +tr.section-row { + border-bottom-width: 2px; + border-color: #000; + border-left-width: 0; + border-right-width: 0; + border-style: solid; + border-top-width: 2px; width: 100%; } -div.plot-canvas-div { - overflow: auto; +td.title-cell { + border-width: 0; + text-align: right; + vertical-align: top; + width: 15%; +} + +p.title-text { + font-weight: bold; + margin-right: 10px; +} + +td.show-button-cell { + border-bottom-width: 0; + border-color: #aaa; + border-left-width: 1px; + border-right-width: 1px; + border-style: solid; + border-top-width: 0; + text-align: center; + vertical-align: top; + width: 10%; +} + +td.plots-cell { + border-width: 0; + padding: 10px; + text-align: left; + width: 75%; +} + +div.section-div { + width: 100%; +} + +div.plots-div { width: 100%; } +button.show-button { + margin: 10px; +} + button.reload-button { margin-bottom: 10px; margin-top: 10px; diff --git a/chrome/browser/resources/chromeos/power.html b/chrome/browser/resources/chromeos/power.html index 3eefcdf..fd34144 100644 --- a/chrome/browser/resources/chromeos/power.html +++ b/chrome/browser/resources/chromeos/power.html @@ -10,28 +10,64 @@ <script src="chrome://power/power.js"></script> </head> <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize"> - <div id="main-layout"> - <div id="battery-charge-section"> - <h3 i18n-content="batteryChargePercentageHeader"></h3> - <div class="plot-canvas-div"> - <canvas id="battery-charge-percentage-canvas" width="600" height="200"> - </canvas> - </div> + <table id="main-table"> + <tr class="section-row"> + <td class="title-cell"> + <p i18n-content="batteryChargeSectionTitle" class="title-text"></p> + </td> + <td class="show-button-cell"> + <button id="battery-charge-show-button" class="show-button" + i18n-content="showButton"> + </button> + </td> + <td id="battery-charge-cell" class="plots-cell"> + <div id="battery-charge-section" class="section-div"> + <div class="plots-div" id="battery-charge-plots-div"></div> + <button id="battery-charge-reload-button" class="reload-button" + i18n-content="reloadButton"> + </button> + </div> + </td> + </tr> - <h3 i18n-content="batteryDischargeRateHeader"></h3> - <p i18n-content="negativeDischargeRateInfo"></p> - <div class="plot-canvas-div"> - <canvas id="battery-discharge-rate-canvas" width="600" height="200"> - </canvas> - </div> + <tr class="section-row"> + <td class="title-cell"> + <p i18n-content="cpuIdleSectionTitle" class="title-text"></p> + </td> + <td class="show-button-cell"> + <button id="cpu-idle-show-button" class="show-button" + i18n-content="showButton"> + </button> + </td> + <td id="cpu-idle-cell" class="plots-cell"> + <div id="cpu-idle-section" class="section-div"> + <div class="plots-div" id="cpu-idle-plots-div"></div> + <button id="cpu-idle-reload-button" class="reload-button" + i18n-content="reloadButton"> + </button> + </div> + </td> + </tr> - <button id="battery-charge-reload-button" class="reload-button" - i18n-content="reloadButton"> - </button> - </div> - - <hr class="section-boundary"> - </div> + <tr class="section-row"> + <td class="title-cell"> + <p i18n-content="cpuFreqSectionTitle" class="title-text"></p> + </td> + <td class="show-button-cell"> + <button id="cpu-freq-show-button" class="show-button" + i18n-content="showButton"> + </button> + </td> + <td id="cpu-freq-cell" class="plots-cell"> + <div id="cpu-freq-section" class="section-div"> + <div class="plots-div" id="cpu-freq-plots-div"></div> + <button id="cpu-freq-reload-button" class="reload-button" + i18n-content="reloadButton"> + </button> + </div> + </td> + </tr> + </table> <script src="chrome://resources/js/i18n_template2.js"></script> </body> </html> diff --git a/chrome/browser/resources/chromeos/power.js b/chrome/browser/resources/chromeos/power.js index 2d2f2df..a61bd966 100644 --- a/chrome/browser/resources/chromeos/power.js +++ b/chrome/browser/resources/chromeos/power.js @@ -5,8 +5,10 @@ /** * Plot a line graph of data versus time on a HTML canvas element. * - * @param {HTMLCanvasElement} canvas The canvas on which the line graph is + * @param {HTMLCanvasElement} plotCanvas The canvas on which the line graph is * drawn. + * @param {HTMLCanvasElement} legendCanvas The canvas on which the legend for + * the line graph is drawn. * @param {Array.<number>} tData The time (in seconds) in the past when the * corresponding data in plots was sampled. * @param {Array.<{data: Array.<number>, color: string}>} plots An @@ -21,28 +23,30 @@ * @param {integer} yPrecision An integer value representing the number of * digits of precision the y-axis data should be printed with. */ -function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { +function plotLineGraph( + plotCanvas, legendCanvas, tData, plots, yMin, yMax, yPrecision) { var textFont = '12px Arial'; var textHeight = 12; var padding = 5; // Pixels var errorOffsetPixels = 15; var gridColor = '#ccc'; - var ctx = canvas.getContext('2d'); + var plotCtx = plotCanvas.getContext('2d'); var size = tData.length; - function drawText(text, x, y) { + function drawText(ctx, text, x, y) { ctx.font = textFont; ctx.fillStyle = '#000'; ctx.fillText(text, x, y); } - function printErrorText(text) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - drawText(text, errorOffsetPixels, errorOffsetPixels); + function printErrorText(ctx, text) { + ctx.clearRect(0, 0, plotCanvas.width, plotCanvas.height); + drawText(ctx, text, errorOffsetPixels, errorOffsetPixels); } if (size < 2) { - printErrorText(loadTimeData.getString('notEnoughDataAvailableYet')); + printErrorText(plotCtx, + loadTimeData.getString('notEnoughDataAvailableYet')); return; } @@ -53,10 +57,14 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { } function valueToString(value) { - return Number(value).toPrecision(yPrecision); + if (Math.abs(value) < 1) { + return Number(value).toFixed(yPrecision - 1); + } else { + return Number(value).toPrecision(yPrecision); + } } - function getTextWidth(text) { + function getTextWidth(ctx, text) { ctx.font = textFont; // For now, all text is drawn to the left of vertical lines, or centered. // Add a 2 pixel padding so that there is some spacing between the text @@ -64,16 +72,16 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { return Math.round(ctx.measureText(text).width) + 2; } - function drawHighlightText(text, x, y, color) { + function drawHighlightText(ctx, text, x, y, color) { ctx.strokeStyle = '#000'; - ctx.strokeRect(x, y - textHeight, getTextWidth(text), textHeight); + ctx.strokeRect(x, y - textHeight, getTextWidth(ctx, text), textHeight); ctx.fillStyle = color; - ctx.fillRect(x, y - textHeight, getTextWidth(text), textHeight); + ctx.fillRect(x, y - textHeight, getTextWidth(ctx, text), textHeight); ctx.fillStyle = '#fff'; ctx.fillText(text, x, y); } - function drawLine(x1, y1, x2, y2, color) { + function drawLine(ctx, x1, y1, x2, y2, color) { ctx.save(); ctx.beginPath(); ctx.moveTo(x1, y1); @@ -83,66 +91,138 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { ctx.restore(); } - // The strokeRect method of the 2d context of a canvas draws a bounding + // The strokeRect method of the 2d context of a plotCanvas draws a bounding // rectangle with an offset origin and greater dimensions. Hence, use this // function to draw a rect at the desired location with desired dimensions. - function drawRect(x, y, width, height, color) { - drawLine(x, y, x + width - 1, y, color); - drawLine(x, y, x, y + height - 1, color); - drawLine(x, y + height - 1, x + width - 1, y + height - 1, color); - drawLine(x + width - 1, y, x + width - 1, y + height - 1, color); + function drawRect(ctx, x, y, width, height, color) { + drawLine(ctx, x, y, x + width - 1, y, color); + drawLine(ctx, x, y, x, y + height - 1, color); + drawLine(ctx, x, y + height - 1, x + width - 1, y + height - 1, color); + drawLine(ctx, x + width - 1, y, x + width - 1, y + height - 1, color); + } + + function drawLegend() { + // Show a legend only if at least one individual plot has a name. + var valid = false; + for (var i = 0; i < plots.length; i++) { + if (plots[i].name != null) { + valid = true; + break; + } + } + if (!valid) { + legendCanvas.hidden = true; + return; + } + + var padding = 2; + var legendSquareSide = 12; + var legendCtx = legendCanvas.getContext('2d'); + var xLoc = padding; + var yLoc = padding; + // Adjust the height of the canvas before drawing on it. + for (var i = 0; i < plots.length; i++) { + if (plots[i].name == null) { + continue; + } + var legendText = ' - ' + plots[i].name; + xLoc += legendSquareSide + getTextWidth(legendCtx, legendText) + + 2 * padding; + if (i < plots.length - 1) { + var xLocNext = xLoc + + getTextWidth(legendCtx, ' - ' + plots[i + 1].name) + + legendSquareSide; + if (xLocNext >= legendCanvas.width) { + xLoc = padding; + yLoc = yLoc + 2 * padding + textHeight; + } + } + } + + legendCanvas.height = yLoc + textHeight + padding; + + xLoc = padding; + yLoc = padding; + // Go over the plots again, this time drawing the legends. + for (var i = 0; i < plots.length; i++) { + legendCtx.fillStyle = plots[i].color; + legendCtx.fillRect(xLoc, yLoc, legendSquareSide, legendSquareSide); + xLoc += legendSquareSide; + + var legendText = ' - ' + plots[i].name; + drawText(legendCtx, legendText, xLoc, yLoc + textHeight - 1); + xLoc += getTextWidth(legendCtx, legendText) + 2 * padding; + + if (i < plots.length - 1) { + var xLocNext = xLoc + + getTextWidth(legendCtx, ' - ' + plots[i + 1].name) + + legendSquareSide; + if (xLocNext >= legendCanvas.width) { + xLoc = padding; + yLoc = yLoc + 2 * padding + textHeight; + } + } + } } var yMinStr = valueToString(yMin); var yMaxStr = valueToString(yMax); var yHalfStr = valueToString((yMax + yMin) / 2); - var yMinWidth = getTextWidth(yMinStr); - var yMaxWidth = getTextWidth(yMaxStr); - var yHalfWidth = getTextWidth(yHalfStr); + var yMinWidth = getTextWidth(plotCtx, yMinStr); + var yMaxWidth = getTextWidth(plotCtx, yMaxStr); + var yHalfWidth = getTextWidth(plotCtx, yHalfStr); var xMinStr = tData[0]; var xMaxStr = tData[size - 1]; - var xMinWidth = getTextWidth(xMinStr); - var xMaxWidth = getTextWidth(xMaxStr); + var xMinWidth = getTextWidth(plotCtx, xMinStr); + var xMaxWidth = getTextWidth(plotCtx, xMaxStr); var xOrigin = padding + Math.max(yMinWidth, yMaxWidth, Math.round(xMinWidth / 2)); var yOrigin = padding + textHeight; - var width = canvas.width - xOrigin - Math.floor(xMaxWidth / 2) - padding; + var width = plotCanvas.width - xOrigin - Math.floor(xMaxWidth / 2) - padding; if (width < size) { - canvas.width += size - width; + plotCanvas.width += size - width; width = size; } - var height = canvas.height - yOrigin - textHeight - padding; + var height = plotCanvas.height - yOrigin - textHeight - padding; function drawPlots() { // Start fresh. - ctx.clearRect(0, 0, canvas.width, canvas.height); + plotCtx.clearRect(0, 0, plotCanvas.width, plotCanvas.height); // Draw the bounding rectangle. - drawRect(xOrigin, yOrigin, width, height, gridColor); + drawRect(plotCtx, xOrigin, yOrigin, width, height, gridColor); // Draw the x and y bound values. - drawText(yMaxStr, xOrigin - yMaxWidth, yOrigin + textHeight); - drawText(yMinStr, xOrigin - yMinWidth, yOrigin + height); - drawText(xMinStr, xOrigin - xMinWidth / 2, yOrigin + height + textHeight); - drawText(xMaxStr, + drawText(plotCtx, yMaxStr, xOrigin - yMaxWidth, yOrigin + textHeight); + drawText(plotCtx, yMinStr, xOrigin - yMinWidth, yOrigin + height); + drawText(plotCtx, + xMinStr, + xOrigin - xMinWidth / 2, + yOrigin + height + textHeight); + drawText(plotCtx, + xMaxStr, xOrigin + width - xMaxWidth / 2, yOrigin + height + textHeight); // Draw y-level (horizontal) lines. - drawLine(xOrigin + 1, yOrigin + height / 4, + drawLine(plotCtx, + xOrigin + 1, yOrigin + height / 4, xOrigin + width - 2, yOrigin + height / 4, gridColor); - drawLine(xOrigin + 1, yOrigin + height / 2, + drawLine(plotCtx, + xOrigin + 1, yOrigin + height / 2, xOrigin + width - 2, yOrigin + height / 2, gridColor); - drawLine(xOrigin + 1, yOrigin + 3 * height / 4, + drawLine(plotCtx, + xOrigin + 1, yOrigin + 3 * height / 4, xOrigin + width - 2, yOrigin + 3 * height / 4, gridColor); // Draw half-level value. - drawText(yHalfStr, + drawText(plotCtx, + yHalfStr, xOrigin - yHalfWidth, yOrigin + height / 2 + textHeight / 2); @@ -151,15 +231,15 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { for (var count = 0; count < plots.length; count++) { var plot = plots[count]; var yData = plot.data; - ctx.strokeStyle = plot.color; - ctx.beginPath(); + plotCtx.strokeStyle = plot.color; + plotCtx.beginPath(); var beginPath = true; for (var i = 0; i < size; i++) { var val = yData[i]; if (typeof val === 'string') { // Stroke the plot drawn so far and begin a fresh plot. - ctx.stroke(); - ctx.beginPath(); + plotCtx.stroke(); + plotCtx.beginPath(); beginPath = true; continue; } @@ -167,27 +247,35 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { var yPos = yOrigin + height - 1 - Math.round((val - yMin) / yValRange * (height - 1)); if (beginPath) { - ctx.moveTo(xPos, yPos); + plotCtx.moveTo(xPos, yPos); // A simple move to does not print anything. Hence, draw a little // square here to mark a beginning. - ctx.fillStyle = '#000'; - ctx.fillRect(xPos - 1, yPos - 1, 2, 2); + plotCtx.fillStyle = '#000'; + plotCtx.fillRect(xPos - 1, yPos - 1, 2, 2); beginPath = false; } else { - ctx.lineTo(xPos, yPos); + plotCtx.lineTo(xPos, yPos); if (i === size - 1 || typeof yData[i + 1] === 'string') { // Draw a little square to mark an end to go with the start // markers from above. - ctx.fillStyle = '#000'; - ctx.fillRect(xPos - 1, yPos - 1, 2, 2); + plotCtx.fillStyle = '#000'; + plotCtx.fillRect(xPos - 1, yPos - 1, 2, 2); } } } - ctx.stroke(); + plotCtx.stroke(); } // Paint the missing time intervals with |gridColor|. // Pick one of the plots to look for missing time intervals. + function drawMissingRect(start, end) { + var xLeft = xOrigin + Math.floor(start / (size - 1) * (width - 1)); + var xRight = xOrigin + Math.floor(end / (size - 1) * (width - 1)); + plotCtx.fillStyle = gridColor; + // The x offsets below are present so that the blank space starts + // and ends between two valid samples. + plotCtx.fillRect(xLeft + 1, yOrigin, xRight - xLeft - 2, height - 1); + } var inMissingInterval = false; var intervalStart; for (var i = 0; i < size; i++) { @@ -198,24 +286,24 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { // sample. intervalStart = Math.max(i - 1, 0); } + + if (i == size - 1) { + // If this is the last sample, just draw missing rect. + drawMissingRect(intervalStart, i); + } } else if (inMissingInterval) { inMissingInterval = false; - var xLeft = xOrigin + - Math.floor(intervalStart / (size - 1) * (width - 1)); - var xRight = xOrigin + Math.floor(i / (size - 1) * (width - 1)); - ctx.fillStyle = gridColor; - // The x offsets below are present so that the blank space starts - // and ends between two valid samples. - ctx.fillRect(xLeft + 1, yOrigin, xRight - xLeft - 2, height - 1); + drawMissingRect(intervalStart, i); } } } function drawTimeGuide(tDataIndex) { var x = xOrigin + tDataIndex / (size - 1) * (width - 1); - drawLine(x, yOrigin, x, yOrigin + height - 1, '#000'); - drawText(tData[tDataIndex], - x - getTextWidth(tData[tDataIndex]) / 2, + drawLine(plotCtx, x, yOrigin, x, yOrigin + height - 1, '#000'); + drawText(plotCtx, + tData[tDataIndex], + x - getTextWidth(plotCtx, tData[tDataIndex]) / 2, yOrigin - 2); for (var count = 0; count < plots.length; count++) { @@ -233,8 +321,8 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { Math.round((val - yMin) / (yMax - yMin) * (height - 1)); valStr = valueToString(val); } - ctx.fillStyle = '#000'; - ctx.fillRect(x - 2, yPos - 2, 4, 4); + plotCtx.fillStyle = '#000'; + plotCtx.fillRect(x - 2, yPos - 2, 4, 4); // Draw the val to right of the intersection. var yLoc; @@ -245,16 +333,16 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { } else { yLoc = yPos + textHeight / 2; } - drawHighlightText(valStr, x + 5, yLoc, plots[count].color); + drawHighlightText(plotCtx, valStr, x + 5, yLoc, plots[count].color); } } function onMouseOverOrMove(event) { drawPlots(); - var boundingRect = canvas.getBoundingClientRect(); - var x = event.clientX - boundingRect.left; - var y = event.clientY - boundingRect.top; + var boundingRect = plotCanvas.getBoundingClientRect(); + var x = Math.round(event.clientX - boundingRect.left); + var y = Math.round(event.clientY - boundingRect.top); if (x < xOrigin || x >= xOrigin + width || y < yOrigin || y >= yOrigin + height) { return; @@ -271,14 +359,65 @@ function plotLineGraph(canvas, tData, plots, yMin, yMax, yPrecision) { drawPlots(); } + drawLegend(); drawPlots(); - canvas.addEventListener('mouseover', onMouseOverOrMove); - canvas.addEventListener('mousemove', onMouseOverOrMove); - canvas.addEventListener('mouseout', onMouseOut); + plotCanvas.addEventListener('mouseover', onMouseOverOrMove); + plotCanvas.addEventListener('mousemove', onMouseOverOrMove); + plotCanvas.addEventListener('mouseout', onMouseOut); } var sleepSampleInterval = 30 * 1000; // in milliseconds. var sleepText = loadTimeData.getString('systemSuspended'); +var invalidDataText = loadTimeData.getString('invalidData'); +var offlineText = loadTimeData.getString('offlineText'); + +var plotColors = ['Red', 'Blue', 'Green', 'Gold', 'CadetBlue', 'LightCoral', + 'LightSlateGray', 'Peru', 'DarkRed', 'LawnGreen', 'Tan']; + +/** + * Add canvases for plotting to |plotsDiv|. For every header in |headerArray|, + * one canvas for the plot and one for its legend are added. + * + * @param {Array.<string>} headerArray Headers for the different plots to be + * added to |plotsDiv|. + * @param {HTMLDivElement} plotsDiv The div element into which the canvases + * are added. + * @return {<string>: {plotCanvas: <HTMLCanvasElement>, + * legendCanvas: <HTMLCanvasElement>} Returns an object + * with the headers as 'keys'. Each element is an object containing the + * legend canvas and the plot canvas that have been added to |plotsDiv|. + */ +function addCanvases(headerArray, plotsDiv) { + // Remove the contents before adding new ones. + while (plotsDiv.firstChild != null) { + plotsDiv.removeChild(plotsDiv.firstChild); + } + var width = Math.floor(plotsDiv.getBoundingClientRect().width); + var canvases = {}; + for (var i = 0; i < headerArray.length; i++) { + var header = document.createElement('h4'); + header.textContent = headerArray[i]; + plotsDiv.appendChild(header); + + var legendCanvas = document.createElement('canvas'); + legendCanvas.width = width; + legendCanvas.height = 5; + plotsDiv.appendChild(legendCanvas); + + var plotCanvasDiv = document.createElement('div'); + plotCanvasDiv.style.overflow = 'auto'; + plotCanvasDiv.style.maxWidth = String(width) + 'px'; + plotsDiv.appendChild(plotCanvasDiv); + + plotCanvas = document.createElement('canvas'); + plotCanvas.width = width; + plotCanvas.height = 200; + plotCanvasDiv.appendChild(plotCanvas); + + canvases[headerArray[i]] = {plot: plotCanvas, legend: legendCanvas}; + } + return canvases; +} /** * Add samples in |sampleArray| to individual plots in |plots|. If the system @@ -306,7 +445,7 @@ var sleepText = loadTimeData.getString('systemSuspended'); * 'sleepDuration' field is for the time in milliseconds the system spent * in sleep/suspend state. */ -function addTimeDataSample(plots, tData, sampleArray, +function addTimeDataSample(plots, tData, absTime, sampleArray, sampleTime, previousSampleTime, systemResumedArray) { for (var i = 0; i < plots.length; i++) { @@ -318,6 +457,7 @@ function addTimeDataSample(plots, tData, sampleArray, var time; if (tData.length == 0) { time = new Date(sampleTime); + absTime[0] = sampleTime; tData[0] = time.toLocaleTimeString(); for (var i = 0; i < plots.length; i++) { plots[i].data[0] = sampleArray[i]; @@ -329,11 +469,17 @@ function addTimeDataSample(plots, tData, sampleArray, var resumeTime = systemResumedArray[i].time; var sleepDuration = systemResumedArray[i].sleepDuration; var sleepStartTime = resumeTime - sleepDuration; - if (resumeTime < sampleTime && sleepStartTime > previousSampleTime) { + if (resumeTime < sampleTime) { + if (sleepStartTime < previousSampleTime) { + // This can happen if pending callbacks were handled before actually + // suspending. + sleepStartTime = previousSampleTime + 1000; + } // Add sleep samples for every |sleepSampleInterval|. var sleepSampleTime = sleepStartTime; while (sleepSampleTime < resumeTime) { time = new Date(sleepSampleTime); + absTime.push(sleepSampleTime); tData.push(time.toLocaleTimeString()); for (var j = 0; j < plots.length; j++) { plots[j].data.push(sleepText); @@ -344,6 +490,7 @@ function addTimeDataSample(plots, tData, sampleArray, } time = new Date(sampleTime); + absTime.push(sampleTime); tData.push(time.toLocaleTimeString()); for (var i = 0; i < plots.length; i++) { plots[i].data.push(sampleArray[i]); @@ -369,15 +516,19 @@ function addTimeDataSample(plots, tData, sampleArray, */ function showBatteryChargeData(powerSupplyArray, systemResumedArray) { var chargeTimeData = []; + var chargeAbsTime = []; var chargePlot = [ { + name: loadTimeData.getString('batteryChargePercentageHeader'), color: '#0000FF', data: [] } ]; var dischargeRateTimeData = []; + var dischargeRateAbsTime = []; var dischargeRatePlot = [ { + name: loadTimeData.getString('dischargeRateLegendText'), color: '#FF0000', data: [] } @@ -387,7 +538,9 @@ function showBatteryChargeData(powerSupplyArray, systemResumedArray) { for (var i = 0; i < powerSupplyArray.length; i++) { var j = Math.max(i - 1, 0); - addTimeDataSample(chargePlot, chargeTimeData, + addTimeDataSample(chargePlot, + chargeTimeData, + chargeAbsTime, [powerSupplyArray[i].batteryPercent], powerSupplyArray[i].time, powerSupplyArray[j].time, @@ -398,6 +551,7 @@ function showBatteryChargeData(powerSupplyArray, systemResumedArray) { maxDischargeRate = Math.max(dischargeRate, maxDischargeRate); addTimeDataSample(dischargeRatePlot, dischargeRateTimeData, + dischargeRateAbsTime, [dischargeRate], powerSupplyArray[i].time, powerSupplyArray[j].time, @@ -410,26 +564,250 @@ function showBatteryChargeData(powerSupplyArray, systemResumedArray) { maxDischargeRate += 1; } - var chargeCanvas = $('battery-charge-percentage-canvas'); - var dischargeRateCanvas = $('battery-discharge-rate-canvas'); - plotLineGraph(chargeCanvas, chargeTimeData, chargePlot, 0.00, 100.00, 3); - plotLineGraph(dischargeRateCanvas, - dischargeRateTimeData, - dischargeRatePlot, - minDischargeRate, - maxDischargeRate, - 3); + plotsDiv = $('battery-charge-plots-div'); + + canvases = addCanvases( + [loadTimeData.getString('batteryChargePercentageHeader'), + loadTimeData.getString('batteryDischargeRateHeader')], + plotsDiv); + + batteryChargeCanvases = canvases[ + loadTimeData.getString('batteryChargePercentageHeader')]; + plotLineGraph( + batteryChargeCanvases['plot'], + batteryChargeCanvases['legend'], + chargeTimeData, + chargePlot, + 0.00, + 100.00, + 3); + + dischargeRateCanvases = canvases[ + loadTimeData.getString('batteryDischargeRateHeader')]; + plotLineGraph( + dischargeRateCanvases['plot'], + dischargeRateCanvases['legend'], + dischargeRateTimeData, + dischargeRatePlot, + minDischargeRate, + maxDischargeRate, + 3); +} + +/** + * Shows state occupancy data (CPU idle or CPU freq state occupancy) on a set of + * plots on the about:power UI. + * + * @param {Array.<{Array.<{ + * time: number, + * cpuOnline:boolean, + * timeInState: {<string>: number}>}>} timeInStateData Array of arrays + * where each array corresponds to a CPU on the system. The elements of the + * individual arrays contain state occupancy samples. + * @param {Array.<{time: ?, sleepDuration: ?}>} systemResumedArray An array + * objects with fields 'time' and 'sleepDuration'. Each object corresponds + * to a system resume event. The 'time' field is for the time in + * milliseconds since the epoch when the system resumed. The 'sleepDuration' + * field is for the time in milliseconds the system spent in sleep/suspend + * state. + * @param {string} i18nHeaderString The header string to be displayed with each + * plot. For example, CPU idle data will have its own header format, and CPU + * freq data will have its header format. + * @param {string} unitString This is the string capturing the unit, if any, + * for the different states. Note that this is not the unit of the data + * being plotted. + * @param {HTMLDivElement} plotsDivId The div element in which the plots should + * be added. + */ +function showStateOccupancyData(timeInStateData, + systemResumedArray, + i18nHeaderString, + unitString, + plotsDivId) { + var cpuPlots = []; + for (var cpu = 0; cpu < timeInStateData.length; cpu++) { + var cpuData = timeInStateData[cpu]; + if (cpuData.length == 0) { + cpuPlots[cpu] = {plots: [], tData: []}; + continue; + } + tData = []; + absTime = []; + // Each element of |plots| is an array of samples, one for each of the CPU + // states. The number of states is dicovered by looking at the first + // sample for which the CPU is online. + var plots = []; + var stateIndexMap = []; + var stateCount = 0; + for (var i = 0; i < cpuData.length; i++) { + if (cpuData[i].cpuOnline) { + for (var state in cpuData[i].timeInState) { + var stateName = state; + if (unitString != null) { + stateName += ' ' + unitString; + } + plots.push({ + name: stateName, + data: [], + color: plotColors[stateCount] + }); + stateIndexMap.push(state); + stateCount += 1; + } + break; + } + } + // If stateCount is 0, then it means the CPU has been offline + // throughout. Just add a single plot for such a case. + if (stateCount == 0) { + plots.push({ + name: null, + data: [], + color: null + }); + stateCount = 1; // Some invalid state! + } + + // Pass the samples through the function addTimeDataSample to add 'sleep' + // samples. + for (var i = 0; i < cpuData.length; i++) { + var sample = cpuData[i]; + var valArray = []; + for (var j = 0; j < stateCount; j++) { + if (sample.cpuOnline) { + valArray[j] = sample.timeInState[stateIndexMap[j]]; + } else { + valArray[j] = offlineText; + } + } + + var k = Math.max(i - 1, 0); + addTimeDataSample(plots, + tData, + absTime, + valArray, + sample.time, + cpuData[k].time, + systemResumedArray); + } + + // Calculate the percentage occupancy of each state. A valid number is + // possible only if two consecutive samples are valid/numbers. + for (var k = 0; k < stateCount; k++) { + var stateData = plots[k].data; + // Skip the first sample as there is no previous sample. + for (var i = stateData.length - 1; i > 0; i--) { + if (typeof stateData[i] === 'number') { + if (typeof stateData[i - 1] === 'number') { + stateData[i] = (stateData[i] - stateData[i - 1]) / + (absTime[i] - absTime[i - 1]) * 100; + } else { + stateData[i] = invalidDataText; + } + } + } + } + + // Remove the first sample from the time and data arrays. + tData.shift(); + for (var k = 0; k < stateCount; k++) { + plots[k].data.shift(); + } + cpuPlots[cpu] = {plots: plots, tData: tData}; + } + + headers = []; + for (var cpu = 0; cpu < timeInStateData.length; cpu++) { + headers[cpu] = + 'CPU ' + cpu + ' ' + loadTimeData.getString(i18nHeaderString); + } + + canvases = addCanvases(headers, $(plotsDivId)); + for (var cpu = 0; cpu < timeInStateData.length; cpu++) { + cpuCanvases = canvases[headers[cpu]]; + plotLineGraph(cpuCanvases['plot'], + cpuCanvases['legend'], + cpuPlots[cpu]['tData'], + cpuPlots[cpu]['plots'], + 0, + 100, + 3); + } +} + +function showCpuIdleData(idleStateData, systemResumedArray) { + showStateOccupancyData(idleStateData, + systemResumedArray, + 'idleStateOccupancyPercentageHeader', + null, + 'cpu-idle-plots-div'); +} + +function showCpuFreqData(freqStateData, systemResumedArray) { + showStateOccupancyData(freqStateData, + systemResumedArray, + 'frequencyStateOccupancyPercentageHeader', + 'MHz', + 'cpu-freq-plots-div'); } function requestBatteryChargeData() { chrome.send('requestBatteryChargeData'); } +function requestCpuIdleData() { + chrome.send('requestCpuIdleData'); +} + +function requestCpuFreqData() { + chrome.send('requestCpuFreqData'); +} + +/** + * Return a callback for the 'Show'/'Hide' buttons for each section of the + * about:power page. + * + * @param {string} sectionId The ID of the section which is to be shown or + * hidden. + * @param {string} buttonId The ID of the 'Show'/'Hide' button. + * @param {function} requestFunction The function which should be invoked on + * 'Show' to request for data from chrome. + * @return {function} The button callback function. + */ +function showHideCallback(sectionId, buttonId, requestFunction) { + return function() { + if ($(sectionId).hidden) { + $(sectionId).hidden = false; + $(buttonId).textContent = loadTimeData.getString('hideButton'); + requestFunction(); + } else { + $(sectionId).hidden = true; + $(buttonId).textContent = loadTimeData.getString('showButton'); + } + } +} + var powerUI = { - showBatteryChargeData: showBatteryChargeData + showBatteryChargeData: showBatteryChargeData, + showCpuIdleData: showCpuIdleData, + showCpuFreqData: showCpuFreqData }; document.addEventListener('DOMContentLoaded', function() { - requestBatteryChargeData(); + $('battery-charge-section').hidden = true; + $('battery-charge-show-button').onclick = showHideCallback( + 'battery-charge-section', + 'battery-charge-show-button', + requestBatteryChargeData); $('battery-charge-reload-button').onclick = requestBatteryChargeData; + + $('cpu-idle-section').hidden = true; + $('cpu-idle-show-button').onclick = showHideCallback( + 'cpu-idle-section', 'cpu-idle-show-button', requestCpuIdleData); + $('cpu-idle-reload-button').onclick = requestCpuIdleData; + + $('cpu-freq-section').hidden = true; + $('cpu-freq-show-button').onclick = showHideCallback( + 'cpu-freq-section', 'cpu-freq-show-button', requestCpuFreqData); + $('cpu-freq-reload-button').onclick = requestCpuFreqData; }); diff --git a/chrome/browser/ui/webui/chromeos/power_ui.cc b/chrome/browser/ui/webui/chromeos/power_ui.cc index b7f35eb..6c8d7e7 100644 --- a/chrome/browser/ui/webui/chromeos/power_ui.cc +++ b/chrome/browser/ui/webui/chromeos/power_ui.cc @@ -24,10 +24,19 @@ namespace chromeos { namespace { const char kStringsJsFile[] = "strings.js"; + const char kRequestBatteryChargeDataCallback[] = "requestBatteryChargeData"; const char kOnRequestBatteryChargeDataFunction[] = "powerUI.showBatteryChargeData"; +const char kRequestCpuIdleDataCallback[] = "requestCpuIdleData"; +const char kOnRequestCpuIdleDataFunction[] = + "powerUI.showCpuIdleData"; + +const char kRequestCpuFreqDataCallback[] = "requestCpuFreqData"; +const char kOnRequestCpuFreqDataFunction[] = + "powerUI.showCpuFreqData"; + class PowerMessageHandler : public content::WebUIMessageHandler { public: PowerMessageHandler(); @@ -38,6 +47,13 @@ class PowerMessageHandler : public content::WebUIMessageHandler { private: void OnGetBatteryChargeData(const base::ListValue* value); + void OnGetCpuIdleData(const base::ListValue* value); + void OnGetCpuFreqData(const base::ListValue* value); + void GetJsStateOccupancyData( + const std::vector<CpuDataCollector::StateOccupancySampleDeque>& data, + const std::vector<std::string>& state_names, + base::ListValue* js_data); + void GetJsSystemResumedData(base::ListValue* value); }; PowerMessageHandler::PowerMessageHandler() { @@ -51,6 +67,14 @@ void PowerMessageHandler::RegisterMessages() { kRequestBatteryChargeDataCallback, base::Bind(&PowerMessageHandler::OnGetBatteryChargeData, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + kRequestCpuIdleDataCallback, + base::Bind(&PowerMessageHandler::OnGetCpuIdleData, + base::Unretained(this))); + web_ui()->RegisterMessageCallback( + kRequestCpuFreqDataCallback, + base::Bind(&PowerMessageHandler::OnGetCpuFreqData, + base::Unretained(this))); } void PowerMessageHandler::OnGetBatteryChargeData(const base::ListValue* value) { @@ -68,9 +92,57 @@ void PowerMessageHandler::OnGetBatteryChargeData(const base::ListValue* value) { js_power_supply_data.Append(element.release()); } + base::ListValue js_system_resumed_data; + GetJsSystemResumedData(&js_system_resumed_data); + + web_ui()->CallJavascriptFunction(kOnRequestBatteryChargeDataFunction, + js_power_supply_data, + js_system_resumed_data); +} + +void PowerMessageHandler::OnGetCpuIdleData(const base::ListValue* value) { + const CpuDataCollector& cpu_data_collector = + PowerDataCollector::Get()->cpu_data_collector(); + + const std::vector<CpuDataCollector::StateOccupancySampleDeque>& idle_data = + cpu_data_collector.cpu_idle_state_data(); + const std::vector<std::string>& idle_state_names = + cpu_data_collector.cpu_idle_state_names(); + base::ListValue js_idle_data; + GetJsStateOccupancyData(idle_data, idle_state_names, &js_idle_data); + + base::ListValue js_system_resumed_data; + GetJsSystemResumedData(&js_system_resumed_data); + + web_ui()->CallJavascriptFunction(kOnRequestCpuIdleDataFunction, + js_idle_data, + js_system_resumed_data); +} + +void PowerMessageHandler::OnGetCpuFreqData(const base::ListValue* value) { + const CpuDataCollector& cpu_data_collector = + PowerDataCollector::Get()->cpu_data_collector(); + + const std::vector<CpuDataCollector::StateOccupancySampleDeque>& freq_data = + cpu_data_collector.cpu_freq_state_data(); + const std::vector<std::string>& freq_state_names = + cpu_data_collector.cpu_freq_state_names(); + base::ListValue js_freq_data; + GetJsStateOccupancyData(freq_data, freq_state_names, &js_freq_data); + + base::ListValue js_system_resumed_data; + GetJsSystemResumedData(&js_system_resumed_data); + + web_ui()->CallJavascriptFunction(kOnRequestCpuFreqDataFunction, + js_freq_data, + js_system_resumed_data); +} + +void PowerMessageHandler::GetJsSystemResumedData(base::ListValue *data) { + DCHECK(data); + const std::deque<PowerDataCollector::SystemResumedSample>& system_resumed = PowerDataCollector::Get()->system_resumed_data(); - base::ListValue js_system_resumed_data; for (size_t i = 0; i < system_resumed.size(); ++i) { const PowerDataCollector::SystemResumedSample& sample = system_resumed[i]; scoped_ptr<base::DictionaryValue> element(new base::DictionaryValue); @@ -78,12 +150,34 @@ void PowerMessageHandler::OnGetBatteryChargeData(const base::ListValue* value) { sample.sleep_duration.InMillisecondsF()); element->SetDouble("time", sample.time.ToJsTime()); - js_system_resumed_data.Append(element.release()); + data->Append(element.release()); } +} - web_ui()->CallJavascriptFunction(kOnRequestBatteryChargeDataFunction, - js_power_supply_data, - js_system_resumed_data); +void PowerMessageHandler::GetJsStateOccupancyData( + const std::vector<CpuDataCollector::StateOccupancySampleDeque>& data, + const std::vector<std::string>& state_names, + base::ListValue *js_data) { + for (unsigned int cpu = 0; cpu < data.size(); ++cpu) { + const CpuDataCollector::StateOccupancySampleDeque& sample_deque = data[cpu]; + scoped_ptr<base::ListValue> js_sample_list(new base::ListValue); + for (unsigned int i = 0; i < sample_deque.size(); ++i) { + const CpuDataCollector::StateOccupancySample& sample = sample_deque[i]; + scoped_ptr<base::DictionaryValue> js_sample(new base::DictionaryValue); + js_sample->SetDouble("time", sample.time.ToJsTime()); + js_sample->SetBoolean("cpuOnline", sample.cpu_online); + + scoped_ptr<base::DictionaryValue> state_dict(new base::DictionaryValue); + for (size_t index = 0; index < sample.time_in_state.size(); ++index) { + state_dict->SetDouble(state_names[index], + static_cast<double>(sample.time_in_state[index])); + } + js_sample->Set("timeInState", state_dict.release()); + + js_sample_list->Append(js_sample.release()); + } + js_data->Append(js_sample_list.release()); + } } } // namespace @@ -96,17 +190,35 @@ PowerUI::PowerUI(content::WebUI* web_ui) : content::WebUIController(web_ui) { html->SetUseJsonJSFormatV2(); html->AddLocalizedString("titleText", IDS_ABOUT_POWER_TITLE); + html->AddLocalizedString("showButton", IDS_ABOUT_POWER_SHOW_BUTTON); + html->AddLocalizedString("hideButton", IDS_ABOUT_POWER_HIDE_BUTTON); html->AddLocalizedString("reloadButton", IDS_ABOUT_POWER_RELOAD_BUTTON); - html->AddLocalizedString("batteryChargePercentageHeader", - IDS_ABOUT_POWER_BATTERY_CHARGE_PERCENTAGE_HEADER); - html->AddLocalizedString("batteryDischargeRateHeader", - IDS_ABOUT_POWER_BATTERY_DISCHARGE_RATE_HEADER); - html->AddLocalizedString("negativeDischargeRateInfo", - IDS_ABOUT_POWER_NEGATIVE_DISCHARGE_RATE_INFO); html->AddLocalizedString("notEnoughDataAvailableYet", IDS_ABOUT_POWER_NOT_ENOUGH_DATA); html->AddLocalizedString("systemSuspended", IDS_ABOUT_POWER_SYSTEM_SUSPENDED); + html->AddLocalizedString("invalidData", IDS_ABOUT_POWER_INVALID); + html->AddLocalizedString("offlineText", IDS_ABOUT_POWER_OFFLINE); + + html->AddLocalizedString("batteryChargeSectionTitle", + IDS_ABOUT_POWER_BATTERY_CHARGE_SECTION_TITLE); + html->AddLocalizedString("batteryChargePercentageHeader", + IDS_ABOUT_POWER_BATTERY_CHARGE_PERCENTAGE_HEADER); + html->AddLocalizedString("batteryDischargeRateHeader", + IDS_ABOUT_POWER_BATTERY_DISCHARGE_RATE_HEADER); + html->AddLocalizedString("dischargeRateLegendText", + IDS_ABOUT_POWER_DISCHARGE_RATE_LEGEND_TEXT); + + html->AddLocalizedString("cpuIdleSectionTitle", + IDS_ABOUT_POWER_CPU_IDLE_SECTION_TITLE); + html->AddLocalizedString("idleStateOccupancyPercentageHeader", + IDS_ABOUT_POWER_CPU_IDLE_STATE_OCCUPANCY_PERCENTAGE); + + html->AddLocalizedString("cpuFreqSectionTitle", + IDS_ABOUT_POWER_CPU_FREQ_SECTION_TITLE); + html->AddLocalizedString("frequencyStateOccupancyPercentageHeader", + IDS_ABOUT_POWER_CPU_FREQ_STATE_OCCUPANCY_PERCENTAGE); + html->SetJsonPath(kStringsJsFile); html->AddResourcePath("power.css", IDR_ABOUT_POWER_CSS); diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi index 8dcb4f5..cbd7a81 100644 --- a/chrome/chrome_browser_chromeos.gypi +++ b/chrome/chrome_browser_chromeos.gypi @@ -772,6 +772,8 @@ 'browser/chromeos/policy/user_policy_token_loader.h', 'browser/chromeos/policy/wildcard_login_checker.cc', 'browser/chromeos/policy/wildcard_login_checker.h', + 'browser/chromeos/power/cpu_data_collector.cc', + 'browser/chromeos/power/cpu_data_collector.h', 'browser/chromeos/power/idle_action_warning_dialog_view.cc', 'browser/chromeos/power/idle_action_warning_dialog_view.h', 'browser/chromeos/power/idle_action_warning_observer.cc', |