summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsivachandra@chromium.org <sivachandra@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 15:05:14 +0000
committersivachandra@chromium.org <sivachandra@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 15:05:14 +0000
commitf29dd9fd3b43f62bd1ed095c1e13332dc52bab00 (patch)
tree811ca1b9a4b3789021e5d34243479f06d58e622c
parent142479ba17118dba683118db72a4a4dafe84b813 (diff)
downloadchromium_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.grdp45
-rw-r--r--chrome/browser/chromeos/power/cpu_data_collector.cc421
-rw-r--r--chrome/browser/chromeos/power/cpu_data_collector.h113
-rw-r--r--chrome/browser/chromeos/power/power_data_collector.cc12
-rw-r--r--chrome/browser/chromeos/power/power_data_collector.h11
-rw-r--r--chrome/browser/chromeos/power/power_data_collector_unittest.cc2
-rw-r--r--chrome/browser/resources/chromeos/power.css60
-rw-r--r--chrome/browser/resources/chromeos/power.html76
-rw-r--r--chrome/browser/resources/chromeos/power.js542
-rw-r--r--chrome/browser/ui/webui/chromeos/power_ui.cc134
-rw-r--r--chrome/chrome_browser_chromeos.gypi2
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',