diff options
author | oysteine@chromium.org <oysteine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-02 15:56:09 +0000 |
---|---|---|
committer | oysteine@chromium.org <oysteine@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-02 15:56:09 +0000 |
commit | 56fd0a89174b70b665e6c0267a05e81beb86d510 (patch) | |
tree | 5fe813a40793f0a01257a46630451836cb584f5d | |
parent | 22c808bb76ea5873bab7b39b9e4e53f1e11d208b (diff) | |
download | chromium_src-56fd0a89174b70b665e6c0267a05e81beb86d510.zip chromium_src-56fd0a89174b70b665e6c0267a05e81beb86d510.tar.gz chromium_src-56fd0a89174b70b665e6c0267a05e81beb86d510.tar.bz2 |
Modified the PerformanceMonitor to not use test/ code for enumerating Chrome processes.
Separated out basic performance metrics gathering in the PerformanceMonitor to run independently from the metrics/events database code. The former will now run even without the performance-monitor-gathering flag.
Added an UMA event which triggers when the browser process never runs below X CPU utilization over a period of Y seconds.
BUG=279660, 273320
Review URL: https://codereview.chromium.org/23825004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@226466 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chrome_browser_main.cc | 5 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/constants.h | 7 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/event.h | 34 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/event_type.h | 44 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/performance_monitor.cc | 370 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/performance_monitor.h | 77 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/performance_monitor_browsertest.cc | 25 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/process_metrics_history.cc | 88 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/process_metrics_history.h | 72 | ||||
-rw-r--r-- | chrome/browser/performance_monitor/startup_timer.cc | 11 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 10 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 2 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 5 | ||||
-rw-r--r-- | tools/metrics/actions/chromeactions.txt | 2 |
14 files changed, 511 insertions, 241 deletions
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc index 9589a51..a2dfd7a 100644 --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc @@ -1575,10 +1575,7 @@ bool ChromeBrowserMainParts::MainMessageLoopRun(int* result_code) { base::RunLoop run_loop; #endif - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kPerformanceMonitorGathering)) { - performance_monitor::PerformanceMonitor::GetInstance()->Start(); - } + performance_monitor::PerformanceMonitor::GetInstance()->Start(); run_loop.Run(); diff --git a/chrome/browser/performance_monitor/constants.h b/chrome/browser/performance_monitor/constants.h index 168baf2..84dfb8e 100644 --- a/chrome/browser/performance_monitor/constants.h +++ b/chrome/browser/performance_monitor/constants.h @@ -21,6 +21,8 @@ extern const char kProcessChromeAggregate[]; extern const char kStateChromeVersion[]; extern const char kStateProfilePrefix[]; +// The interval the watched processes are sampled for performance metrics. +const int kSampleIntervalInSeconds = 10; // The default interval at which PerformanceMonitor performs its timed // collections; this can be overridden by using the kPerformanceMonitorGathering // switch with an associated (positive integer) value. @@ -41,6 +43,11 @@ const int64 kBytesPerTerabyte = kBytesPerGigabyte * (1 << 10); const int64 kMicrosecondsPerMonth = base::Time::kMicrosecondsPerDay * 30; const int64 kMicrosecondsPerYear = base::Time::kMicrosecondsPerDay * 365; +// Performance alert thresholds + +// If a process is consistently above this CPU utilization percentage over time, +// we consider it as high and may take action. +const float kHighCPUUtilizationThreshold = 90.0f; } // namespace performance_monitor #endif // CHROME_BROWSER_PERFORMANCE_MONITOR_CONSTANTS_H_ diff --git a/chrome/browser/performance_monitor/event.h b/chrome/browser/performance_monitor/event.h index b7666b4..4bde259 100644 --- a/chrome/browser/performance_monitor/event.h +++ b/chrome/browser/performance_monitor/event.h @@ -9,39 +9,9 @@ #include "base/time/time.h" #include "base/values.h" -namespace performance_monitor { - -// IMPORTANT: To add new events, please -// - Place the new event above EVENT_NUMBER_OF_EVENTS. -// - Add a member to the EventKeyChar enum in key_builder.cc. -// - Add the appropriate messages in generated_resources.grd. -// - Add the appropriate functions in -// chrome/browser/ui/webui/performance_monitor/performance_monitor_l10n.h. -enum EventType { - EVENT_UNDEFINED, - - // Extension-Related events - EVENT_EXTENSION_INSTALL, - EVENT_EXTENSION_UNINSTALL, - EVENT_EXTENSION_UPDATE, - EVENT_EXTENSION_ENABLE, - EVENT_EXTENSION_DISABLE, - - // Chrome's version has changed. - EVENT_CHROME_UPDATE, +#include "chrome/browser/performance_monitor/event_type.h" - // Renderer-Failure related events; these correspond to the RENDERER_HANG - // event, and the two termination statuses ABNORMAL_EXIT and PROCESS_KILLED, - // respectively. - EVENT_RENDERER_HANG, - EVENT_RENDERER_CRASH, - EVENT_RENDERER_KILLED, - - // Chrome did not shut down correctly. - EVENT_UNCLEAN_EXIT, - - EVENT_NUMBER_OF_EVENTS -}; +namespace performance_monitor { const char* EventTypeToString(EventType event_type); diff --git a/chrome/browser/performance_monitor/event_type.h b/chrome/browser/performance_monitor/event_type.h new file mode 100644 index 0000000..fd19cac --- /dev/null +++ b/chrome/browser/performance_monitor/event_type.h @@ -0,0 +1,44 @@ +// Copyright 2013 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_PERFORMANCE_MONITOR_EVENT_TYPE_H_ +#define CHROME_BROWSER_PERFORMANCE_MONITOR_EVENT_TYPE_H_ + +namespace performance_monitor { + +// IMPORTANT: To add new events, please +// - Place the new event above EVENT_NUMBER_OF_EVENTS. +// - Add a member to the EventKeyChar enum in key_builder.cc. +// - Add the appropriate messages in generated_resources.grd. +// - Add the appropriate functions in +// chrome/browser/ui/webui/performance_monitor/performance_monitor_l10n.h. +enum EventType { + EVENT_UNDEFINED, + + // Extension-Related events + EVENT_EXTENSION_INSTALL, + EVENT_EXTENSION_UNINSTALL, + EVENT_EXTENSION_UPDATE, + EVENT_EXTENSION_ENABLE, + EVENT_EXTENSION_DISABLE, + + // Chrome's version has changed. + EVENT_CHROME_UPDATE, + + // Renderer-Failure related events; these correspond to the RENDERER_HANG + // event, and the two termination statuses ABNORMAL_EXIT and PROCESS_KILLED, + // respectively. + EVENT_RENDERER_HANG, + EVENT_RENDERER_CRASH, + EVENT_RENDERER_KILLED, + + // Chrome did not shut down correctly. + EVENT_UNCLEAN_EXIT, + + EVENT_NUMBER_OF_EVENTS +}; + +} // namespace performance_monitor + +#endif // CHROME_BROWSER_PERFORMANCE_MONITOR_EVENT_TYPE_H_ diff --git a/chrome/browser/performance_monitor/performance_monitor.cc b/chrome/browser/performance_monitor/performance_monitor.cc index b800e7f..55e5df1 100644 --- a/chrome/browser/performance_monitor/performance_monitor.cc +++ b/chrome/browser/performance_monitor/performance_monitor.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/memory/singleton.h" #include "base/process/process_iterator.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" @@ -29,9 +30,10 @@ #include "chrome/common/chrome_version_info.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" -#include "chrome/test/base/chrome_process_util.h" #include "content/public/browser/browser_child_process_host.h" +#include "content/public/browser/browser_child_process_host_iterator.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_data.h" #include "content/public/browser/load_notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" @@ -53,8 +55,6 @@ const uint32 kAccessFlags = base::kProcessAccessDuplicateHandle | base::kProcessAccessTerminate | base::kProcessAccessWaitForTermination; -bool g_started_initialization = false; - std::string TimeToString(base::Time time) { int64 time_int64 = time.ToInternalValue(); return base::Int64ToString(time_int64); @@ -99,7 +99,15 @@ PerformanceMonitor::PerformanceDataForIOThread::PerformanceDataForIOThread() : network_bytes_read(0) { } -PerformanceMonitor::PerformanceMonitor() : metrics_map_(new MetricsMap) {} +PerformanceMonitor::PerformanceMonitor() + : gather_interval_in_seconds_(kDefaultGatherIntervalInSeconds), + database_logging_enabled_(false), + timer_(FROM_HERE, + base::TimeDelta::FromSeconds(kSampleIntervalInSeconds), + this, + &PerformanceMonitor::DoTimedCollections), + disable_timer_autostart_for_testing_(false) { +} PerformanceMonitor::~PerformanceMonitor() { BrowserThread::PostBlockingPoolSequencedTask( @@ -126,11 +134,39 @@ PerformanceMonitor* PerformanceMonitor::GetInstance() { void PerformanceMonitor::Start() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // Avoid responding to multiple calls to Start(). - if (g_started_initialization) + static bool has_initialized = false; + if (has_initialized) { + NOTREACHED(); return; + } + has_initialized = true; + + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kPerformanceMonitorGathering)) { + database_logging_enabled_ = true; + + std::string switch_value = CommandLine::ForCurrentProcess()-> + GetSwitchValueASCII(switches::kPerformanceMonitorGathering); + + if (!switch_value.empty()) { + int specified_interval = 0; + if (!base::StringToInt(switch_value, &specified_interval) || + specified_interval <= 0) { + LOG(ERROR) << "Invalid value for switch: '" + << switches::kPerformanceMonitorGathering + << "'; please use an integer greater than 0."; + } else { + gather_interval_in_seconds_ = std::max(specified_interval, + kSampleIntervalInSeconds); + } + } + } + + DCHECK(gather_interval_in_seconds_ >= kSampleIntervalInSeconds); + + next_collection_time_ = base::Time::Now() + + base::TimeDelta::FromSeconds(gather_interval_in_seconds_); - g_started_initialization = true; util::PostTaskToDatabaseThreadAndReply( FROM_HERE, base::Bind(&PerformanceMonitor::InitOnBackgroundThread, @@ -141,59 +177,42 @@ void PerformanceMonitor::Start() { void PerformanceMonitor::InitOnBackgroundThread() { CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); - database_ = Database::Create(database_path_); - if (!database_) { - LOG(ERROR) << "Could not initialize database; aborting initialization."; - return; - } - // Initialize the io thread's performance data to the value in the database; - // if there isn't a recording in the database, the value stays at 0. - Metric metric; - if (database_->GetRecentStatsForActivityAndMetric(METRIC_NETWORK_BYTES_READ, - &metric)) { - performance_data_for_io_thread_.network_bytes_read = metric.value; + if (database_logging_enabled_) { + database_ = Database::Create(database_path_); + if (!database_) { + LOG(ERROR) << "Could not initialize database; aborting initialization."; + database_logging_enabled_ = false; + return; + } + + // Initialize the io thread's performance data to the value in the database; + // if there isn't a recording in the database, the value stays at 0. + Metric metric; + if (database_->GetRecentStatsForActivityAndMetric(METRIC_NETWORK_BYTES_READ, + &metric)) { + performance_data_for_io_thread_.network_bytes_read = metric.value; + } } } void PerformanceMonitor::FinishInit() { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // Short-circuit the initialization process if the database wasn't properly - // created. This will prevent PerformanceMonitor from performing any actions, - // including observing events. - if (!database_) - return; - - RegisterForNotifications(); - CheckForUncleanExits(); - BrowserThread::PostBlockingPoolSequencedTask( - Database::kDatabaseSequenceToken, - FROM_HERE, - base::Bind(&PerformanceMonitor::CheckForVersionUpdateOnBackgroundThread, - base::Unretained(this))); - int gather_interval_in_seconds = kDefaultGatherIntervalInSeconds; - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kPerformanceMonitorGathering) && - !CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kPerformanceMonitorGathering).empty()) { - int specified_interval = 0; - if (!base::StringToInt( - CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kPerformanceMonitorGathering), - &specified_interval) || specified_interval <= 0) { - LOG(ERROR) << "Invalid value for switch: '" - << switches::kPerformanceMonitorGathering - << "'; please use an integer greater than 0."; - } else { - gather_interval_in_seconds = specified_interval; - } + // Events and notifications are only useful if we're logging to the database. + if (database_logging_enabled_) { + RegisterForNotifications(); + CheckForUncleanExits(); + BrowserThread::PostBlockingPoolSequencedTask( + Database::kDatabaseSequenceToken, + FROM_HERE, + base::Bind(&PerformanceMonitor::CheckForVersionUpdateOnBackgroundThread, + base::Unretained(this))); } - timer_.Start(FROM_HERE, - base::TimeDelta::FromSeconds(gather_interval_in_seconds), - this, - &PerformanceMonitor::DoTimedCollections); + // Start our periodic gathering of metrics. + if (!disable_timer_autostart_for_testing_) + timer_.Reset(); // Post a task to the background thread to a function which does nothing. // This will force any tasks the database is performing to finish prior to @@ -210,6 +229,8 @@ void PerformanceMonitor::FinishInit() { } void PerformanceMonitor::RegisterForNotifications() { + DCHECK(database_logging_enabled_); + // Extensions registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, content::NotificationService::AllSources()); @@ -241,6 +262,8 @@ void PerformanceMonitor::RegisterForNotifications() { // loaded prior to PerformanceMonitor's initialization. Later profiles will be // checked through the PROFILE_ADDED notification. void PerformanceMonitor::CheckForUncleanExits() { + DCHECK(database_logging_enabled_); + std::vector<Profile*> profiles = g_browser_process->profile_manager()->GetLoadedProfiles(); @@ -258,7 +281,8 @@ void PerformanceMonitor::CheckForUncleanExits() { } void PerformanceMonitor::AddUncleanExitEventOnBackgroundThread( - const std::string& profile_name) { + const std::string& profile_name) { + DCHECK(database_logging_enabled_); std::string database_key = kStateProfilePrefix + profile_name; std::string last_active_string = database_->GetStateValue(database_key); @@ -279,6 +303,7 @@ void PerformanceMonitor::AddUncleanExitEventOnBackgroundThread( void PerformanceMonitor::CheckForVersionUpdateOnBackgroundThread() { CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(database_logging_enabled_); chrome::VersionInfo version; DCHECK(version.is_valid()); @@ -305,6 +330,8 @@ void PerformanceMonitor::CheckForVersionUpdateOnBackgroundThread() { } void PerformanceMonitor::AddEvent(scoped_ptr<Event> event) { + DCHECK(database_logging_enabled_); + BrowserThread::PostBlockingPoolSequencedTask( Database::kDatabaseSequenceToken, FROM_HERE, @@ -319,6 +346,8 @@ void PerformanceMonitor::AddEventOnBackgroundThread(scoped_ptr<Event> event) { void PerformanceMonitor::AddMetricOnBackgroundThread(const Metric& metric) { DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(database_logging_enabled_); + database_->AddMetric(metric); } @@ -331,88 +360,62 @@ void PerformanceMonitor::NotifyInitialized() { initialized_ = true; } -void PerformanceMonitor::GatherStatisticsOnBackgroundThread() { - CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); - - // Because the CPU usage is gathered as an average since the last time the - // function was called, while the memory usage is gathered as an instantaneous - // usage, the CPU usage is gathered before the metrics map is wiped. - GatherCPUUsageOnBackgroundThread(); - UpdateMetricsMapOnBackgroundThread(); - GatherMemoryUsageOnBackgroundThread(); -} - -void PerformanceMonitor::GatherCPUUsageOnBackgroundThread() { - if (metrics_map_->size()) { - double cpu_usage = 0; - for (MetricsMap::const_iterator iter = metrics_map_->begin(); - iter != metrics_map_->end(); ++iter) { - cpu_usage += iter->second->GetCPUUsage(); - } +void PerformanceMonitor::DoTimedCollections() { +#if !defined(OS_ANDROID) + // The profile list is only useful for the logged events. + if (database_logging_enabled_) + UpdateLiveProfiles(); +#endif - database_->AddMetric(Metric(METRIC_CPU_USAGE, - base::Time::Now(), - cpu_usage)); - } + GatherMetricsMapOnUIThread(); } -void PerformanceMonitor::GatherMemoryUsageOnBackgroundThread() { - size_t private_memory_sum = 0; - size_t shared_memory_sum = 0; - for (MetricsMap::const_iterator iter = metrics_map_->begin(); - iter != metrics_map_->end(); ++iter) { - size_t private_memory = 0; - size_t shared_memory = 0; - if (iter->second->GetMemoryBytes(&private_memory, &shared_memory)) { - private_memory_sum += private_memory; - shared_memory_sum += shared_memory; - } else { - LOG(WARNING) << "GetMemoryBytes returned NULL (platform-specific error)"; - } +void PerformanceMonitor::GatherMetricsMapOnUIThread() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + static int current_update_sequence = 0; + // Even in the "somewhat" unlikely event this wraps around, + // it doesn't matter. We just check it for inequality. + current_update_sequence++; + + // Find all render child processes; has to be done on the UI thread. + for (content::RenderProcessHost::iterator rph_iter = + content::RenderProcessHost::AllHostsIterator(); + !rph_iter.IsAtEnd(); rph_iter.Advance()) { + base::ProcessHandle handle = rph_iter.GetCurrentValue()->GetHandle(); + MarkProcessAsAlive(handle, content::PROCESS_TYPE_RENDERER, + current_update_sequence); } - database_->AddMetric(Metric(METRIC_PRIVATE_MEMORY_USAGE, - base::Time::Now(), - static_cast<double>(private_memory_sum))); - database_->AddMetric(Metric(METRIC_SHARED_MEMORY_USAGE, - base::Time::Now(), - static_cast<double>(shared_memory_sum))); -} - -void PerformanceMonitor::UpdateMetricsMapOnBackgroundThread() { - MetricsMap* new_map = new MetricsMap; - - base::ProcessId browser_pid = base::GetCurrentProcId(); - ChromeProcessList chrome_processes(GetRunningChromeProcesses(browser_pid)); - for (ChromeProcessList::const_iterator pid_iter = chrome_processes.begin(); - pid_iter != chrome_processes.end(); ++pid_iter) { - base::ProcessHandle handle; - if (base::OpenProcessHandleWithAccess(*pid_iter, kAccessFlags, &handle)) { - // If we were already watching this process, transfer it to the new map. - if (ContainsKey(*metrics_map_, handle)) { - (*new_map)[handle] = (*metrics_map_)[handle]; - continue; - } - - // Otherwise, gather information and prime the CPU usage to be gathered. -#if defined (OS_MACOSX) - linked_ptr<base::ProcessMetrics> process_metrics( - base::ProcessMetrics::CreateProcessMetrics( - handle, content::BrowserChildProcessHost::GetPortProvider())); -#else - linked_ptr<base::ProcessMetrics> process_metrics( - base::ProcessMetrics::CreateProcessMetrics(handle)); -#endif + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&PerformanceMonitor::GatherMetricsMapOnIOThread, + base::Unretained(this), + current_update_sequence)); +} - process_metrics->GetCPUUsage(); +void PerformanceMonitor::MarkProcessAsAlive(const base::ProcessHandle& handle, + int process_type, + int current_update_sequence) { - (*new_map)[handle] = process_metrics; - } + if (handle == 0) { + // Process may not be valid yet. + return; } - metrics_map_.reset(new_map); + MetricsMap::iterator process_metrics_iter = metrics_map_.find(handle); + if (process_metrics_iter == metrics_map_.end()) { + // If we're not already watching the process, let's initialize it. + metrics_map_[handle] + .Initialize(handle, process_type, current_update_sequence); + } else { + // If we are watching the process, touch it to keep it alive. + ProcessMetricsHistory& process_metrics = process_metrics_iter->second; + process_metrics.set_last_update_sequence(current_update_sequence); + } } +#if !defined(OS_ANDROID) void PerformanceMonitor::UpdateLiveProfiles() { std::string time = TimeToString(base::Time::Now()); scoped_ptr<std::set<std::string> > active_profiles( @@ -434,48 +437,119 @@ void PerformanceMonitor::UpdateLiveProfilesHelper( scoped_ptr<std::set<std::string> > active_profiles, std::string time) { CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(database_logging_enabled_); for (std::set<std::string>::const_iterator iter = active_profiles->begin(); iter != active_profiles->end(); ++iter) { database_->AddStateValue(kStateProfilePrefix + *iter, time); } } +#endif -void PerformanceMonitor::DoTimedCollections() { - UpdateLiveProfiles(); - - BrowserThread::PostBlockingPoolSequencedTask( - Database::kDatabaseSequenceToken, - FROM_HERE, - base::Bind(&PerformanceMonitor::GatherStatisticsOnBackgroundThread, - base::Unretained(this))); +void PerformanceMonitor::GatherMetricsMapOnIOThread( + int current_update_sequence) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(&PerformanceMonitor::CallInsertIOData, - base::Unretained(this))); -} + // Find all child processes (does not include renderers), which has to be + // done on the IO thread. + for (content::BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) { + const content::ChildProcessData& child_process_data = iter.GetData(); + base::ProcessHandle handle = child_process_data.handle; + MarkProcessAsAlive(handle, child_process_data.process_type, + current_update_sequence); + } -void PerformanceMonitor::CallInsertIOData() { - CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // Add the current (browser) process. + MarkProcessAsAlive(base::GetCurrentProcessHandle(), + content::PROCESS_TYPE_BROWSER, current_update_sequence); BrowserThread::PostBlockingPoolSequencedTask( Database::kDatabaseSequenceToken, FROM_HERE, - base::Bind(&PerformanceMonitor::InsertIOData, - base::Unretained(this), + base::Bind(&PerformanceMonitor::StoreMetricsOnBackgroundThread, + base::Unretained(this), current_update_sequence, performance_data_for_io_thread_)); } -void PerformanceMonitor::InsertIOData( +void PerformanceMonitor::StoreMetricsOnBackgroundThread( + int current_update_sequence, const PerformanceDataForIOThread& performance_data_for_io_thread) { CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); - database_->AddMetric( - Metric(METRIC_NETWORK_BYTES_READ, - base::Time::Now(), - static_cast<double>( - performance_data_for_io_thread.network_bytes_read))); + + base::Time time_now = base::Time::Now(); + + // The timing can be off by kSampleIntervalInSeconds during any one particular + // run, but will be correct over time. + bool end_of_cycle = time_now >= next_collection_time_; + if (end_of_cycle) { + next_collection_time_ += + base::TimeDelta::FromSeconds(gather_interval_in_seconds_); + } + + double cpu_usage = 0.0; + size_t private_memory_sum = 0; + size_t shared_memory_sum = 0; + + // Update metrics for all watched processes; remove dead entries from the map. + MetricsMap::iterator iter = metrics_map_.begin(); + while (iter != metrics_map_.end()) { + ProcessMetricsHistory& process_metrics = iter->second; + if (process_metrics.last_update_sequence() != current_update_sequence) { + // Not touched this iteration; let's get rid of it. + metrics_map_.erase(iter++); + } else { + process_metrics.SampleMetrics(); + + if (end_of_cycle) { + // Gather averages of previously sampled metrics. + cpu_usage += process_metrics.GetAverageCPUUsage(); + + size_t private_memory = 0; + size_t shared_memory = 0; + process_metrics.GetAverageMemoryBytes(&private_memory, &shared_memory); + private_memory_sum += private_memory; + shared_memory_sum += shared_memory; + + process_metrics.EndOfCycle(); + } + + ++iter; + } + } + + // Store previously-sampled metrics. + if (end_of_cycle && database_logging_enabled_) { + if (!metrics_map_.empty()) { + database_->AddMetric(Metric(METRIC_CPU_USAGE, time_now, cpu_usage)); + database_->AddMetric(Metric(METRIC_PRIVATE_MEMORY_USAGE, + time_now, + static_cast<double>(private_memory_sum))); + database_->AddMetric(Metric(METRIC_SHARED_MEMORY_USAGE, + time_now, + static_cast<double>(shared_memory_sum))); + } + + database_->AddMetric( + Metric(METRIC_NETWORK_BYTES_READ, + time_now, + static_cast<double>( + performance_data_for_io_thread.network_bytes_read))); + } + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&PerformanceMonitor::ResetMetricsTimerOnUIThread, + base::Unretained(this))); +} + +void PerformanceMonitor::ResetMetricsTimerOnUIThread() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // Start up the timer again; we avoid the use of a repeating timer + // to not have concurrent metrics gathering running. + if (!disable_timer_autostart_for_testing_) + timer_.Reset(); } void PerformanceMonitor::BytesReadOnIOThread(const net::URLRequest& request, @@ -489,6 +563,8 @@ void PerformanceMonitor::BytesReadOnIOThread(const net::URLRequest& request, void PerformanceMonitor::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { + DCHECK(database_logging_enabled_); + switch (type) { case chrome::NOTIFICATION_EXTENSION_INSTALLED: { AddExtensionEvent( diff --git a/chrome/browser/performance_monitor/performance_monitor.h b/chrome/browser/performance_monitor/performance_monitor.h index a054f58..ff14abb 100644 --- a/chrome/browser/performance_monitor/performance_monitor.h +++ b/chrome/browser/performance_monitor/performance_monitor.h @@ -6,23 +6,23 @@ #define CHROME_BROWSER_PERFORMANCE_MONITOR_PERFORMANCE_MONITOR_H_ #include <map> +#include <set> #include <string> -#include "base/callback.h" #include "base/files/file_path.h" -#include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" -#include "base/memory/singleton.h" -#include "base/process/process_metrics.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" #include "base/timer/timer.h" -#include "chrome/browser/performance_monitor/database.h" -#include "chrome/browser/performance_monitor/event.h" -#include "content/public/browser/notification_details.h" +#include "chrome/browser/performance_monitor/event_type.h" +#include "chrome/browser/performance_monitor/process_metrics_history.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" -#include "content/public/browser/notification_source.h" #include "content/public/browser/render_process_host.h" +template <typename Type> +struct DefaultSingletonTraits; + namespace extensions { class Extension; } @@ -33,6 +33,8 @@ class URLRequest; namespace performance_monitor { class Database; +class Event; +struct Metric; // PerformanceMonitor is a tool which will allow the user to view information // about Chrome's performance over a period of time. It will gather statistics @@ -56,8 +58,7 @@ class PerformanceMonitor : public content::NotificationObserver { uint64 network_bytes_read; }; - typedef std::map<base::ProcessHandle, - linked_ptr<base::ProcessMetrics> > MetricsMap; + typedef std::map<base::ProcessHandle, ProcessMetricsHistory> MetricsMap; // Set the path which the PerformanceMonitor should use for the database files // constructed. This must be done prior to the initialization of the @@ -66,6 +67,8 @@ class PerformanceMonitor : public content::NotificationObserver { // time of the call). bool SetDatabasePath(const base::FilePath& path); + bool database_logging_enabled() const { return database_logging_enabled_; } + // Returns the current PerformanceMonitor instance if one exists; otherwise // constructs a new PerformanceMonitor. static PerformanceMonitor* GetInstance(); @@ -141,22 +144,32 @@ class PerformanceMonitor : public content::NotificationObserver { // Update the database record of the last time the active profiles were // running; this is used in determining when an unclean exit occurred. +#if !defined(OS_ANDROID) void UpdateLiveProfiles(); void UpdateLiveProfilesHelper( scoped_ptr<std::set<std::string> > active_profiles, std::string time); +#endif - // Gathers CPU usage and memory usage of all Chrome processes in order to. - void GatherStatisticsOnBackgroundThread(); + // Stores CPU/memory usage metrics to the database. + void StoreMetricsOnBackgroundThread( + int current_update_sequence, + const PerformanceDataForIOThread& performance_data_for_io_thread); - // Gathers the CPU usage of every Chrome process that has been running since - // the last call to GatherStatistics(). - void GatherCPUUsageOnBackgroundThread(); + // Mark the given process as alive in the current update iteration. + // This means adding an entry to the map of watched processes if it's not + // already present. + void MarkProcessAsAlive(const base::ProcessHandle& handle, + int process_type, + int current_update_sequence); - // Gathers the memory usage of every process in the current list of processes. - void GatherMemoryUsageOnBackgroundThread(); + // Updates the ProcessMetrics map with the current list of processes and + // gathers metrics from each entry. + void GatherMetricsMapOnUIThread(); + void GatherMetricsMapOnIOThread(int current_update_sequence); - // Updates the ProcessMetrics map with the current list of processes. - void UpdateMetricsMapOnBackgroundThread(); + // Called at the end of the metrics gathering process, to start + // our timer for the next run. + void ResetMetricsTimerOnUIThread(); // Generate an appropriate ExtensionEvent for an extension-related occurrance // and insert it in the database. @@ -169,15 +182,6 @@ class PerformanceMonitor : public content::NotificationObserver { content::RenderProcessHost* host, const content::RenderProcessHost::RendererClosedDetails& details); - // Called on the IO thread, this will call InsertIOData on the background - // thread with a copy of the PerformanceDataForIOThread object to prevent - // any possible race conditions. - void CallInsertIOData(); - - // Insert the collected IO data into the database. - void InsertIOData( - const PerformanceDataForIOThread& performance_data_for_io_thread); - // The store for all performance data that must be gathered from the IO // thread. PerformanceDataForIOThread performance_data_for_io_thread_; @@ -189,10 +193,20 @@ class PerformanceMonitor : public content::NotificationObserver { scoped_ptr<Database> database_; // A map of currently running ProcessHandles to ProcessMetrics. - scoped_ptr<MetricsMap> metrics_map_; + MetricsMap metrics_map_; + + // The next time we should collect averages from the performance metrics + // and act on them. + base::Time next_collection_time_; + + // How long to wait between collections. + int gather_interval_in_seconds_; + + // Enable persistent logging of performance metrics to a database. + bool database_logging_enabled_; // The timer to signal PerformanceMonitor to perform its timed collections. - base::RepeatingTimer<PerformanceMonitor> timer_; + base::DelayTimer<PerformanceMonitor> timer_; content::NotificationRegistrar registrar_; @@ -202,6 +216,9 @@ class PerformanceMonitor : public content::NotificationObserver { // flag. static bool initialized_; + // Disable auto-starting the collection timer; used for tests. + bool disable_timer_autostart_for_testing_; + DISALLOW_COPY_AND_ASSIGN(PerformanceMonitor); }; diff --git a/chrome/browser/performance_monitor/performance_monitor_browsertest.cc b/chrome/browser/performance_monitor/performance_monitor_browsertest.cc index a343e04..0535418 100644 --- a/chrome/browser/performance_monitor/performance_monitor_browsertest.cc +++ b/chrome/browser/performance_monitor/performance_monitor_browsertest.cc @@ -161,25 +161,24 @@ class PerformanceMonitorBrowserTest : public ExtensionBrowserTest { chrome::NOTIFICATION_PERFORMANCE_MONITOR_INITIALIZED, content::NotificationService::AllSources()); + // We stop the timer in charge of doing timed collections so that we can + // enforce when, and how many times, we do these collections. + performance_monitor_->disable_timer_autostart_for_testing_ = true; + // Force metrics to be stored, regardless of switches used. + performance_monitor_->database_logging_enabled_ = true; performance_monitor_->Start(); windowed_observer.Wait(); - - // We stop the timer in charge of doing timed collections so that we can - // enforce when, and how many times, we do these collections. - performance_monitor_->timer_.Stop(); } // A handle for gathering statistics from the database, which must be done on // the background thread. Since we are testing, we can mock synchronicity with // FlushForTesting(). void GatherStatistics() { - content::BrowserThread::PostBlockingPoolSequencedTask( - Database::kDatabaseSequenceToken, - FROM_HERE, - base::Bind(&PerformanceMonitor::GatherStatisticsOnBackgroundThread, - base::Unretained(performance_monitor()))); + performance_monitor_->next_collection_time_ = base::Time::Now(); + performance_monitor_->GatherMetricsMapOnUIThread(); + RunAllPendingInMessageLoop(content::BrowserThread::IO); content::BrowserThread::GetBlockingPool()->FlushForTesting(); } @@ -772,11 +771,7 @@ IN_PROC_BROWSER_TEST_F(PerformanceMonitorBrowserTest, NetworkBytesRead) { browser(), test_server()->GetURL(std::string("files/").append("title1.html"))); - performance_monitor()->DoTimedCollections(); - - // Since network bytes are read and set on the IO thread, we must flush this - // additional thread to be sure that all messages are run. - RunAllPendingInMessageLoop(content::BrowserThread::IO); + GatherStatistics(); Database::MetricVector metrics = GetStats(METRIC_NETWORK_BYTES_READ); ASSERT_EQ(1u, metrics.size()); @@ -789,7 +784,7 @@ IN_PROC_BROWSER_TEST_F(PerformanceMonitorBrowserTest, NetworkBytesRead) { browser(), test_server()->GetURL(std::string("files/").append("title2.html"))); - performance_monitor()->DoTimedCollections(); + GatherStatistics(); metrics = GetStats(METRIC_NETWORK_BYTES_READ); ASSERT_EQ(2u, metrics.size()); diff --git a/chrome/browser/performance_monitor/process_metrics_history.cc b/chrome/browser/performance_monitor/process_metrics_history.cc new file mode 100644 index 0000000..84ffcac --- /dev/null +++ b/chrome/browser/performance_monitor/process_metrics_history.cc @@ -0,0 +1,88 @@ +// Copyright 2013 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 <limits> + +#include "base/logging.h" +#include "base/process/process_metrics.h" + +#include "chrome/browser/performance_monitor/constants.h" +#include "chrome/browser/performance_monitor/process_metrics_history.h" +#if defined(OS_MACOSX) +#include "content/public/browser/browser_child_process_host.h" +#endif +#include "content/public/browser/user_metrics.h" +#include "content/public/common/process_type.h" + +namespace performance_monitor { + +ProcessMetricsHistory::ProcessMetricsHistory() + : process_handle_(0), + process_type_(content::PROCESS_TYPE_UNKNOWN), + last_update_sequence_(0) { + ResetCounters(); +} + +ProcessMetricsHistory::~ProcessMetricsHistory() {} + +void ProcessMetricsHistory::ResetCounters() { + min_cpu_usage_ = std::numeric_limits<double>::max(); + accumulated_cpu_usage_ = 0.0; + accumulated_private_bytes_ = 0; + accumulated_shared_bytes_ = 0; + sample_count_ = 0; +} + +void ProcessMetricsHistory::Initialize(base::ProcessHandle process_handle, + int process_type, + int initial_update_sequence) { + DCHECK(process_handle_ == 0); + process_handle_ = process_handle; + process_type_ = process_type; + last_update_sequence_ = initial_update_sequence; + +#if defined(OS_MACOSX) + process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics( + process_handle_, content::BrowserChildProcessHost::GetPortProvider())); +#else + process_metrics_.reset( + base::ProcessMetrics::CreateProcessMetrics(process_handle_)); +#endif +} + +void ProcessMetricsHistory::SampleMetrics() { + double cpu_usage = process_metrics_->GetCPUUsage(); + min_cpu_usage_ = std::min(min_cpu_usage_, cpu_usage); + accumulated_cpu_usage_ += cpu_usage; + + size_t private_bytes = 0; + size_t shared_bytes = 0; + if (!process_metrics_->GetMemoryBytes(&private_bytes, &shared_bytes)) + LOG(WARNING) << "GetMemoryBytes returned NULL (platform-specific error)"; + + accumulated_private_bytes_ += private_bytes; + accumulated_shared_bytes_ += shared_bytes; + + sample_count_++; +} + +void ProcessMetricsHistory::EndOfCycle() { + RunPerformanceTriggers(); + ResetCounters(); +} + +void ProcessMetricsHistory::RunPerformanceTriggers() { + // As an initial step, we only care about browser processes. + if (process_type_ != content::PROCESS_TYPE_BROWSER) + return; + + // If CPU usage has consistently been above our threshold, + // we *may* have an issue. + if (min_cpu_usage_ > kHighCPUUtilizationThreshold) { + content::RecordAction( + content::UserMetricsAction("PerformanceMonitor.HighCPU.Browser")); + } +} + +} // namespace performance_monitor diff --git a/chrome/browser/performance_monitor/process_metrics_history.h b/chrome/browser/performance_monitor/process_metrics_history.h new file mode 100644 index 0000000..8837e1f --- /dev/null +++ b/chrome/browser/performance_monitor/process_metrics_history.h @@ -0,0 +1,72 @@ +// Copyright 2013 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_PERFORMANCE_MONITOR_PROCESS_METRICS_HISTORY_H_ +#define CHROME_BROWSER_PERFORMANCE_MONITOR_PROCESS_METRICS_HISTORY_H_ + +#include "base/memory/linked_ptr.h" +#include "base/process/process_handle.h" + +namespace base { +class ProcessMetrics; +} + +namespace performance_monitor { + +class ProcessMetricsHistory { + public: + ProcessMetricsHistory(); + ~ProcessMetricsHistory(); + + // Configure this to monitor a specific process. + void Initialize(base::ProcessHandle process_handle, + int process_type, + int initial_update_sequence); + + // End of a measurement cycle; check for performance issues and reset + // accumulated statistics. + void EndOfCycle(); + + // Gather metrics for the process and accumulate with past data. + void SampleMetrics(); + + // Used to mark when this object was last updated, so we can cull + // dead ones. + void set_last_update_sequence(int new_update_sequence) { + last_update_sequence_ = new_update_sequence; + } + + int last_update_sequence() const { return last_update_sequence_; } + + // Average CPU over all the past sampling points since last reset. + double GetAverageCPUUsage() const { + return accumulated_cpu_usage_ / sample_count_; + } + + // Average mem usage over all the past sampling points since last reset. + void GetAverageMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) const { + *private_bytes = accumulated_private_bytes_ / sample_count_; + *shared_bytes = accumulated_shared_bytes_ / sample_count_; + } + + private: + void ResetCounters(); + void RunPerformanceTriggers(); + + base::ProcessHandle process_handle_; + int process_type_; + linked_ptr<base::ProcessMetrics> process_metrics_; + int last_update_sequence_; + + double accumulated_cpu_usage_; + double min_cpu_usage_; + size_t accumulated_private_bytes_; + size_t accumulated_shared_bytes_; + int sample_count_; +}; + +} // namespace performance_monitor + +#endif // CHROME_BROWSER_PERFORMANCE_MONITOR_PROCESS_METRICS_HISTORY_H_ diff --git a/chrome/browser/performance_monitor/startup_timer.cc b/chrome/browser/performance_monitor/startup_timer.cc index d6aee73..175ab6d 100644 --- a/chrome/browser/performance_monitor/startup_timer.cc +++ b/chrome/browser/performance_monitor/startup_timer.cc @@ -90,10 +90,13 @@ void StartupTimer::Observe(int type, const content::NotificationDetails& details) { CHECK(type == chrome::NOTIFICATION_PERFORMANCE_MONITOR_INITIALIZED); performance_monitor_initialized_ = true; - if (elapsed_startup_time_ != base::TimeDelta()) - InsertElapsedStartupTime(); - if (elapsed_session_restore_times_.size()) - InsertElapsedSessionRestoreTime(); + + if (PerformanceMonitor::GetInstance()->database_logging_enabled()) { + if (elapsed_startup_time_ != base::TimeDelta()) + InsertElapsedStartupTime(); + if (elapsed_session_restore_times_.size()) + InsertElapsedSessionRestoreTime(); + } } // static diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 6ffea01..a0198d8 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1390,6 +1390,8 @@ 'browser/performance_monitor/performance_monitor.h', 'browser/performance_monitor/performance_monitor_util.cc', 'browser/performance_monitor/performance_monitor_util.h', + 'browser/performance_monitor/process_metrics_history.cc', + 'browser/performance_monitor/process_metrics_history.h', 'browser/performance_monitor/startup_timer.cc', 'browser/performance_monitor/startup_timer.h', 'browser/platform_util.h', @@ -2566,14 +2568,6 @@ 'browser/webdata/web_intents_table.cc', 'browser/webdata/web_intents_table.h', - # These files are needed by performance_monitor.cc and this dependency - # should be removed (crbug.com/279660) - 'test/base/test_switches.cc', - 'test/base/test_switches.h', - 'test/base/chrome_process_util.cc', - 'test/base/chrome_process_util.h', - 'test/base/chrome_process_util_mac.cc', - # These files are generated by GRIT. '<(grit_out_dir)/grit/component_extension_resources_map.cc', '<(grit_out_dir)/grit/devtools_discovery_page_resources_map.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 519dafa..e4bf877 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1490,12 +1490,12 @@ 'test/base/chrome_test_launcher.cc', 'test/base/web_ui_browsertest.cc', 'test/base/web_ui_browsertest.h', - 'test/data/chromeos/oobe_webui_browsertest.js', 'test/base/in_process_browser_test_browsertest.cc', 'test/base/tracing_browsertest.cc', 'test/base/test_chrome_web_ui_controller_factory.cc', 'test/base/test_chrome_web_ui_controller_factory.h', 'test/base/test_chrome_web_ui_controller_factory_browsertest.cc', + 'test/data/chromeos/oobe_webui_browsertest.js', 'test/data/webui/accessibility_audit_browsertest.js', 'test/data/webui/assertions.js', 'test/data/webui/async_gen.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 70af84d..fb7068d 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -218,6 +218,9 @@ 'test/automation/tab_proxy.h', 'test/automation/window_proxy.cc', 'test/automation/window_proxy.h', + 'test/base/chrome_process_util.cc', + 'test/base/chrome_process_util.h', + 'test/base/chrome_process_util_mac.cc', 'test/base/chrome_render_view_host_test_harness.cc', 'test/base/chrome_render_view_host_test_harness.h', 'test/base/chrome_test_suite.cc', @@ -244,6 +247,8 @@ 'test/base/test_launcher_utils.h', 'test/base/test_location_bar.cc', 'test/base/test_location_bar.h', + 'test/base/test_switches.cc', + 'test/base/test_switches.h', 'test/base/test_tab_strip_model_observer.cc', 'test/base/test_tab_strip_model_observer.h', 'test/base/testing_browser_process.cc', diff --git a/tools/metrics/actions/chromeactions.txt b/tools/metrics/actions/chromeactions.txt index cee69a1..c3793e7 100644 --- a/tools/metrics/actions/chromeactions.txt +++ b/tools/metrics/actions/chromeactions.txt @@ -1440,6 +1440,7 @@ 0xa7613e3a71f2d755 PasswordManager_RemoveSavedPassword 0x36bb6559696dc912 Paste 0x5d0e6942f354a06c PasteAndMatchStyle +0xb0c1b1bb1cef8c89 PerformanceMonitor.HighCPU.Browser 0x01c52e891f36d4fe PlatformVerificationAccepted 0x1929087478be7017 PlatformVerificationRejected 0xca471265efb33961 PluginContextMenu_RotateClockwise @@ -1537,6 +1538,7 @@ 0x2d9491c68a4ec635 SelectTab5 0x8fa9d8e88271c82d SelectTab6 0xdaa611aa645914e5 SelectTab7 +0x7eb3241cff786567 SettingsResetBubble.Show 0xbc7eb334ae9f29d0 ShowAccessibilitySettings 0x1da022468e27685c ShowAppLauncherPage 0x0d1d6961dd313b79 ShowAppMenu |