| // Copyright (c) 2012 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 "chrome/browser/performance_monitor/performance_monitor.h" |
| |
| #include <set> |
| #include <vector> |
| |
| #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" |
| #include "base/threading/worker_pool.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/browser_shutdown.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/crx_installer.h" |
| #include "chrome/browser/performance_monitor/constants.h" |
| #include "chrome/browser/performance_monitor/performance_monitor_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_iterator.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_constants.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" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/render_widget_host_iterator.h" |
| #include "content/public/browser/web_contents.h" |
| #include "net/url_request/url_request.h" |
| |
| using content::BrowserThread; |
| using extensions::Extension; |
| using extensions::UnloadedExtensionInfo; |
| |
| namespace performance_monitor { |
| |
| namespace { |
| |
| std::string TimeToString(base::Time time) { |
| int64 time_int64 = time.ToInternalValue(); |
| return base::Int64ToString(time_int64); |
| } |
| |
| bool StringToTime(std::string time, base::Time* output) { |
| int64 time_int64 = 0; |
| if (!base::StringToInt64(time, &time_int64)) |
| return false; |
| *output = base::Time::FromInternalValue(time_int64); |
| return true; |
| } |
| |
| // Try to get the URL for the RenderViewHost if the host does not correspond to |
| // an incognito profile (we don't store URLs from incognito sessions). Returns |
| // true if url has been populated, and false otherwise. |
| bool MaybeGetURLFromRenderView(const content::RenderViewHost* view, |
| std::string* url) { |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderViewHost(view); |
| |
| if (Profile::FromBrowserContext( |
| web_contents->GetBrowserContext())->IsOffTheRecord()) { |
| return false; |
| } |
| |
| *url = web_contents->GetURL().spec(); |
| return true; |
| } |
| |
| // Takes ownership of and deletes |database| on the background thread, to |
| // avoid destruction in the middle of an operation. |
| void DeleteDatabaseOnBackgroundThread(Database* database) { |
| delete database; |
| } |
| |
| } // namespace |
| |
| bool PerformanceMonitor::initialized_ = false; |
| |
| PerformanceMonitor::PerformanceDataForIOThread::PerformanceDataForIOThread() |
| : network_bytes_read(0) { |
| } |
| |
| 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( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind(&DeleteDatabaseOnBackgroundThread, database_.release())); |
| } |
| |
| bool PerformanceMonitor::SetDatabasePath(const base::FilePath& path) { |
| if (!database_.get()) { |
| database_path_ = path; |
| return true; |
| } |
| |
| // PerformanceMonitor already initialized with another path. |
| return false; |
| } |
| |
| // static |
| PerformanceMonitor* PerformanceMonitor::GetInstance() { |
| return Singleton<PerformanceMonitor>::get(); |
| } |
| |
| void PerformanceMonitor::Initialize() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| 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_); |
| |
| util::PostTaskToDatabaseThreadAndReply( |
| FROM_HERE, |
| base::Bind(&PerformanceMonitor::InitOnBackgroundThread, |
| base::Unretained(this)), |
| base::Bind(&PerformanceMonitor::FinishInit, |
| base::Unretained(this))); |
| } |
| |
| void PerformanceMonitor::InitOnBackgroundThread() { |
| CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (database_logging_enabled_) { |
| if (!database_) |
| 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)); |
| |
| // 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))); |
| } |
| |
| // 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 |
| // the reply being sent, since they use the same thread. |
| // |
| // Important! Make sure that methods in FinishInit() only rely on posting |
| // to the background thread, and do not rely upon a reply from the background |
| // thread; this is necessary for this notification to be valid. |
| util::PostTaskToDatabaseThreadAndReply( |
| FROM_HERE, |
| base::Bind(&base::DoNothing), |
| base::Bind(&PerformanceMonitor::NotifyInitialized, |
| base::Unretained(this))); |
| } |
| |
| void PerformanceMonitor::StartGatherCycle() { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Start our periodic gathering of metrics. |
| if (!disable_timer_autostart_for_testing_) |
| timer_.Reset(); |
| } |
| |
| void PerformanceMonitor::RegisterForNotifications() { |
| DCHECK(database_logging_enabled_); |
| |
| // Extensions |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_ENABLED, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED, |
| content::NotificationService::AllSources()); |
| |
| // Crashes |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_HANG, |
| content::NotificationService::AllSources()); |
| registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, |
| content::NotificationService::AllSources()); |
| |
| // Profiles (for unclean exit) |
| registrar_.Add(this, chrome::NOTIFICATION_PROFILE_ADDED, |
| content::NotificationService::AllSources()); |
| |
| // Page load times |
| registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, |
| content::NotificationService::AllSources()); |
| } |
| |
| // We check if profiles exited cleanly initialization time in case they were |
| // 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(); |
| |
| for (std::vector<Profile*>::const_iterator iter = profiles.begin(); |
| iter != profiles.end(); ++iter) { |
| if ((*iter)->GetLastSessionExitType() == Profile::EXIT_CRASHED) { |
| BrowserThread::PostBlockingPoolSequencedTask( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind(&PerformanceMonitor::AddUncleanExitEventOnBackgroundThread, |
| base::Unretained(this), |
| (*iter)->GetDebugName())); |
| } |
| } |
| } |
| |
| void PerformanceMonitor::AddUncleanExitEventOnBackgroundThread( |
| 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); |
| |
| // Check if there was no previous time; this should only happen if the profile |
| // was last used prior to PerformanceMonitor's integration. Do nothing in this |
| // case, since the event was prior to the beginning of our recording. |
| if (last_active_string.empty()) |
| return; |
| |
| base::Time last_active_time; |
| CHECK(StringToTime(last_active_string, &last_active_time)); |
| |
| scoped_ptr<Event> event = |
| util::CreateUncleanExitEvent(last_active_time, profile_name); |
| |
| database_->AddEvent(*event.get()); |
| } |
| |
| void PerformanceMonitor::CheckForVersionUpdateOnBackgroundThread() { |
| CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(database_logging_enabled_); |
| |
| chrome::VersionInfo version; |
| DCHECK(version.is_valid()); |
| std::string current_version = version.Version(); |
| |
| std::string previous_version = database_->GetStateValue(kStateChromeVersion); |
| |
| // We should never have a current_version which is older than the |
| // previous_version. |
| DCHECK(current_version >= previous_version); |
| |
| // If this is the first run, there will not be a stored value for Chrome |
| // version; we insert the current version and will insert an event for the |
| // next update of Chrome. If the previous version is older than the current |
| // version, update the state in the database and insert an event. |
| if (current_version > previous_version) { |
| database_->AddStateValue(kStateChromeVersion, current_version); |
| if (!previous_version.empty()) { |
| scoped_ptr<Event> event = util::CreateChromeUpdateEvent( |
| base::Time::Now(), previous_version, current_version); |
| database_->AddEvent(*event.get()); |
| } |
| } |
| } |
| |
| void PerformanceMonitor::AddEvent(scoped_ptr<Event> event) { |
| DCHECK(database_logging_enabled_); |
| |
| BrowserThread::PostBlockingPoolSequencedTask( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind(&PerformanceMonitor::AddEventOnBackgroundThread, |
| base::Unretained(this), |
| base::Passed(&event))); |
| } |
| |
| void PerformanceMonitor::AddEventOnBackgroundThread(scoped_ptr<Event> event) { |
| database_->AddEvent(*event.get()); |
| } |
| |
| void PerformanceMonitor::AddMetricOnBackgroundThread(const Metric& metric) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(database_logging_enabled_); |
| |
| database_->AddMetric(metric); |
| } |
| |
| void PerformanceMonitor::NotifyInitialized() { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_PERFORMANCE_MONITOR_INITIALIZED, |
| content::Source<PerformanceMonitor>(this), |
| content::NotificationService::NoDetails()); |
| |
| initialized_ = true; |
| } |
| |
| void PerformanceMonitor::DoTimedCollections() { |
| #if !defined(OS_ANDROID) |
| // The profile list is only useful for the logged events. |
| if (database_logging_enabled_) |
| UpdateLiveProfiles(); |
| #endif |
| |
| GatherMetricsMapOnUIThread(); |
| } |
| |
| 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); |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&PerformanceMonitor::GatherMetricsMapOnIOThread, |
| base::Unretained(this), |
| current_update_sequence)); |
| } |
| |
| void PerformanceMonitor::MarkProcessAsAlive(const base::ProcessHandle& handle, |
| int process_type, |
| int current_update_sequence) { |
| |
| if (handle == 0) { |
| // Process may not be valid yet. |
| return; |
| } |
| |
| 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( |
| new std::set<std::string>()); |
| |
| for (chrome::BrowserIterator it; !it.done(); it.Next()) |
| active_profiles->insert(it->profile()->GetDebugName()); |
| |
| BrowserThread::PostBlockingPoolSequencedTask( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind(&PerformanceMonitor::UpdateLiveProfilesHelper, |
| base::Unretained(this), |
| base::Passed(&active_profiles), |
| time)); |
| } |
| |
| 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::GatherMetricsMapOnIOThread( |
| int current_update_sequence) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| // 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); |
| } |
| |
| // Add the current (browser) process. |
| MarkProcessAsAlive(base::GetCurrentProcessHandle(), |
| content::PROCESS_TYPE_BROWSER, current_update_sequence); |
| |
| BrowserThread::PostBlockingPoolSequencedTask( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind(&PerformanceMonitor::StoreMetricsOnBackgroundThread, |
| base::Unretained(this), current_update_sequence, |
| performance_data_for_io_thread_)); |
| } |
| |
| void PerformanceMonitor::StoreMetricsOnBackgroundThread( |
| int current_update_sequence, |
| const PerformanceDataForIOThread& performance_data_for_io_thread) { |
| CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| 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::StartGatherCycle, |
| base::Unretained(this))); |
| } |
| |
| void PerformanceMonitor::BytesReadOnIOThread(const net::URLRequest& request, |
| const int bytes_read) { |
| CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| if (initialized_ && !request.url().SchemeIsFile()) |
| performance_data_for_io_thread_.network_bytes_read += bytes_read; |
| } |
| |
| 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( |
| EVENT_EXTENSION_INSTALL, |
| content::Details<const extensions::InstalledExtensionInfo>(details)-> |
| extension); |
| break; |
| } |
| case chrome::NOTIFICATION_EXTENSION_ENABLED: { |
| AddExtensionEvent(EVENT_EXTENSION_ENABLE, |
| content::Details<Extension>(details).ptr()); |
| break; |
| } |
| case chrome::NOTIFICATION_EXTENSION_UNLOADED: { |
| const UnloadedExtensionInfo* info = |
| content::Details<UnloadedExtensionInfo>(details).ptr(); |
| |
| // Check if the extension was unloaded because it was disabled. |
| if (info->reason == UnloadedExtensionInfo::REASON_DISABLE) { |
| AddExtensionEvent(EVENT_EXTENSION_DISABLE, |
| info->extension); |
| } |
| break; |
| } |
| case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { |
| const extensions::CrxInstaller* installer = |
| content::Source<extensions::CrxInstaller>(source).ptr(); |
| const extensions::Extension* extension = |
| content::Details<Extension>(details).ptr(); |
| |
| // Check if the reason for the install was due to a successful |
| // extension update. |extension| is NULL in case of install failure. |
| if (extension && |
| installer->install_cause() == extension_misc::INSTALL_CAUSE_UPDATE) { |
| AddExtensionEvent(EVENT_EXTENSION_UPDATE, extension); |
| } |
| break; |
| } |
| case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { |
| AddExtensionEvent(EVENT_EXTENSION_UNINSTALL, |
| content::Details<Extension>(details).ptr()); |
| break; |
| } |
| case content::NOTIFICATION_RENDERER_PROCESS_HANG: { |
| std::string url; |
| content::RenderWidgetHost* widget = |
| content::Source<content::RenderWidgetHost>(source).ptr(); |
| if (widget->IsRenderView()) { |
| content::RenderViewHost* view = content::RenderViewHost::From(widget); |
| MaybeGetURLFromRenderView(view, &url); |
| } |
| AddEvent(util::CreateRendererFailureEvent(base::Time::Now(), |
| EVENT_RENDERER_HANG, |
| url)); |
| break; |
| } |
| case content::NOTIFICATION_RENDERER_PROCESS_CLOSED: { |
| AddRendererClosedEvent( |
| content::Source<content::RenderProcessHost>(source).ptr(), |
| *content::Details<content::RenderProcessHost::RendererClosedDetails>( |
| details).ptr()); |
| break; |
| } |
| case chrome::NOTIFICATION_PROFILE_ADDED: { |
| Profile* profile = content::Source<Profile>(source).ptr(); |
| if (profile->GetLastSessionExitType() == Profile::EXIT_CRASHED) { |
| BrowserThread::PostBlockingPoolSequencedTask( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind( |
| &PerformanceMonitor::AddUncleanExitEventOnBackgroundThread, |
| base::Unretained(this), |
| profile->GetDebugName())); |
| } |
| break; |
| } |
| case content::NOTIFICATION_LOAD_STOP: { |
| const content::LoadNotificationDetails* load_details = |
| content::Details<content::LoadNotificationDetails>(details).ptr(); |
| if (!load_details) |
| break; |
| BrowserThread::PostBlockingPoolSequencedTask( |
| Database::kDatabaseSequenceToken, |
| FROM_HERE, |
| base::Bind( |
| &PerformanceMonitor::AddMetricOnBackgroundThread, |
| base::Unretained(this), |
| Metric(METRIC_PAGE_LOAD_TIME, |
| base::Time::Now(), |
| static_cast<double>( |
| load_details->load_time.ToInternalValue())))); |
| break; |
| } |
| default: { |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| |
| void PerformanceMonitor::AddExtensionEvent(EventType type, |
| const Extension* extension) { |
| DCHECK(type == EVENT_EXTENSION_INSTALL || |
| type == EVENT_EXTENSION_UNINSTALL || |
| type == EVENT_EXTENSION_UPDATE || |
| type == EVENT_EXTENSION_ENABLE || |
| type == EVENT_EXTENSION_DISABLE); |
| AddEvent(util::CreateExtensionEvent(type, |
| base::Time::Now(), |
| extension->id(), |
| extension->name(), |
| extension->url().spec(), |
| extension->location(), |
| extension->VersionString(), |
| extension->description())); |
| } |
| |
| void PerformanceMonitor::AddRendererClosedEvent( |
| content::RenderProcessHost* host, |
| const content::RenderProcessHost::RendererClosedDetails& details) { |
| // We only care if this is an invalid termination. |
| if (details.status == base::TERMINATION_STATUS_NORMAL_TERMINATION || |
| details.status == base::TERMINATION_STATUS_STILL_RUNNING) |
| return; |
| |
| // Determine the type of crash. |
| EventType type = |
| details.status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ? |
| EVENT_RENDERER_KILLED : EVENT_RENDERER_CRASH; |
| |
| // A RenderProcessHost may contain multiple render views - for each valid |
| // render view, extract the url, and append it to the string, comma-separating |
| // the entries. |
| std::string url_list; |
| scoped_ptr<content::RenderWidgetHostIterator> widgets( |
| content::RenderWidgetHost::GetRenderWidgetHosts()); |
| while (content::RenderWidgetHost* widget = widgets->GetNextHost()) { |
| if (widget->GetProcess()->GetID() != host->GetID()) |
| continue; |
| if (!widget->IsRenderView()) |
| continue; |
| |
| content::RenderViewHost* view = content::RenderViewHost::From(widget); |
| std::string url; |
| if (!MaybeGetURLFromRenderView(view, &url)) |
| continue; |
| |
| if (!url_list.empty()) |
| url_list += ", "; |
| |
| url_list += url; |
| } |
| |
| AddEvent(util::CreateRendererFailureEvent(base::Time::Now(), type, url_list)); |
| } |
| |
| } // namespace performance_monitor |