// Copyright (c) 2011 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/task_manager/task_manager.h"
#include "base/compiler_specific.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/rtl.h"
#include "base/process_util.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/threading/thread.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/url_request_tracking.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/task_manager/task_manager_resource_providers.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/render_process_host.h"
#include "content/browser/renderer_host/resource_dispatcher_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/result_codes.h"
#include "grit/app_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_job.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "unicode/coll.h"
#if defined(OS_MACOSX)
#include "chrome/browser/mach_broker_mac.h"
#endif
namespace {
// The delay between updates of the information (in ms).
#if defined(OS_MACOSX)
// Match Activity Monitor's default refresh rate.
const int kUpdateTimeMs = 2000;
#else
const int kUpdateTimeMs = 1000;
#endif
template <class T>
int ValueCompare(T value1, T value2) {
if (value1 < value2)
return -1;
if (value1 == value2)
return 0;
return 1;
}
string16 FormatStatsSize(const WebKit::WebCache::ResourceTypeStat& stat) {
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_CACHE_SIZE_CELL_TEXT,
FormatBytes(stat.size, DATA_UNITS_KIBIBYTE, false),
FormatBytes(stat.liveSize, DATA_UNITS_KIBIBYTE, false));
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// TaskManagerModel class
////////////////////////////////////////////////////////////////////////////////
TaskManagerModel::TaskManagerModel(TaskManager* task_manager)
: update_requests_(0),
update_state_(IDLE),
goat_salt_(rand()) {
AddResourceProvider(
new TaskManagerBrowserProcessResourceProvider(task_manager));
AddResourceProvider(
new TaskManagerBackgroundContentsResourceProvider(task_manager));
AddResourceProvider(new TaskManagerTabContentsResourceProvider(task_manager));
AddResourceProvider(new TaskManagerPrerenderResourceProvider(task_manager));
AddResourceProvider(
new TaskManagerChildProcessResourceProvider(task_manager));
AddResourceProvider(
new TaskManagerExtensionProcessResourceProvider(task_manager));
AddResourceProvider(
new TaskManagerNotificationResourceProvider(task_manager));
}
TaskManagerModel::~TaskManagerModel() {
for (ResourceProviderList::iterator iter = providers_.begin();
iter != providers_.end(); ++iter) {
(*iter)->Release();
}
}
int TaskManagerModel::ResourceCount() const {
return resources_.size();
}
void TaskManagerModel::AddObserver(TaskManagerModelObserver* observer) {
observer_list_.AddObserver(observer);
}
void TaskManagerModel::RemoveObserver(TaskManagerModelObserver* observer) {
observer_list_.RemoveObserver(observer);
}
string16 TaskManagerModel::GetResourceTitle(int index) const {
CHECK_LT(index, ResourceCount());
return resources_[index]->GetTitle();
}
int64 TaskManagerModel::GetNetworkUsage(int index) const {
CHECK_LT(index, ResourceCount());
return GetNetworkUsage(resources_[index]);
}
string16 TaskManagerModel::GetResourceNetworkUsage(int index) const {
int64 net_usage = GetNetworkUsage(index);
if (net_usage == -1)
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NA_CELL_TEXT);
if (net_usage == 0)
return ASCIIToUTF16("0");
string16 net_byte = FormatSpeed(net_usage, GetByteDisplayUnits(net_usage),
true);
// Force number string to have LTR directionality.
return base::i18n::GetDisplayStringInLTRDirectionality(net_byte);
}
double TaskManagerModel::GetCPUUsage(int index) const {
CHECK_LT(index, ResourceCount());
return GetCPUUsage(resources_[index]);
}
string16 TaskManagerModel::GetResourceCPUUsage(int index) const {
CHECK_LT(index, ResourceCount());
return UTF8ToUTF16(StringPrintf(
#if defined(OS_MACOSX)
// Activity Monitor shows %cpu with one decimal digit -- be
// consistent with that.
"%.1f",
#else
"%.0f",
#endif
GetCPUUsage(resources_[index])));
}
string16 TaskManagerModel::GetResourcePrivateMemory(int index) const {
size_t private_mem;
if (!GetPrivateMemory(index, &private_mem))
return ASCIIToUTF16("N/A");
return GetMemCellText(private_mem);
}
string16 TaskManagerModel::GetResourceSharedMemory(int index) const {
size_t shared_mem;
if (!GetSharedMemory(index, &shared_mem))
return ASCIIToUTF16("N/A");
return GetMemCellText(shared_mem);
}
string16 TaskManagerModel::GetResourcePhysicalMemory(int index) const {
size_t phys_mem;
GetPhysicalMemory(index, &phys_mem);
return GetMemCellText(phys_mem);
}
int TaskManagerModel::GetProcessId(int index) const {
CHECK_LT(index, ResourceCount());
return base::GetProcId(resources_[index]->GetProcess());
}
string16 TaskManagerModel::GetResourceProcessId(int index) const {
return base::IntToString16(GetProcessId(index));
}
string16 TaskManagerModel::GetResourceGoatsTeleported(int index) const {
CHECK_LT(index, ResourceCount());
return base::FormatNumber(GetGoatsTeleported(index));
}
string16 TaskManagerModel::GetResourceWebCoreImageCacheSize(
int index) const {
CHECK_LT(index, ResourceCount());
if (!resources_[index]->ReportsCacheStats())
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NA_CELL_TEXT);
const WebKit::WebCache::ResourceTypeStats stats(
resources_[index]->GetWebCoreCacheStats());
return FormatStatsSize(stats.images);
}
string16 TaskManagerModel::GetResourceWebCoreScriptsCacheSize(
int index) const {
CHECK_LT(index, ResourceCount());
if (!resources_[index]->ReportsCacheStats())
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NA_CELL_TEXT);
const WebKit::WebCache::ResourceTypeStats stats(
resources_[index]->GetWebCoreCacheStats());
return FormatStatsSize(stats.scripts);
}
string16 TaskManagerModel::GetResourceWebCoreCSSCacheSize(
int index) const {
CHECK_LT(index, ResourceCount());
if (!resources_[index]->ReportsCacheStats())
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NA_CELL_TEXT);
const WebKit::WebCache::ResourceTypeStats stats(
resources_[index]->GetWebCoreCacheStats());
return FormatStatsSize(stats.cssStyleSheets);
}
string16 TaskManagerModel::GetResourceSqliteMemoryUsed(int index) const {
CHECK_LT(index, ResourceCount());
if (!resources_[index]->ReportsSqliteMemoryUsed())
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NA_CELL_TEXT);
return GetMemCellText(resources_[index]->SqliteMemoryUsedBytes());
}
string16 TaskManagerModel::GetResourceV8MemoryAllocatedSize(
int index) const {
if (!resources_[index]->ReportsV8MemoryStats())
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NA_CELL_TEXT);
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_CACHE_SIZE_CELL_TEXT,
FormatBytes(resources_[index]->GetV8MemoryAllocated(),
DATA_UNITS_KIBIBYTE,
false),
FormatBytes(resources_[index]->GetV8MemoryUsed(),
DATA_UNITS_KIBIBYTE,
false));
}
bool TaskManagerModel::IsResourceFirstInGroup(int index) const {
CHECK_LT(index, ResourceCount());
TaskManager::Resource* resource = resources_[index];
GroupMap::const_iterator iter = group_map_.find(resource->GetProcess());
DCHECK(iter != group_map_.end());
const ResourceList* group = iter->second;
return ((*group)[0] == resource);
}
bool TaskManagerModel::IsBackgroundResource(int index) const {
CHECK_LT(index, ResourceCount());
return resources_[index]->IsBackground();
}
SkBitmap TaskManagerModel::GetResourceIcon(int index) const {
CHECK_LT(index, ResourceCount());
SkBitmap icon = resources_[index]->GetIcon();
if (!icon.isNull())
return icon;
static SkBitmap* default_icon = ResourceBundle::GetSharedInstance().
GetBitmapNamed(IDR_DEFAULT_FAVICON);
return *default_icon;
}
std::pair<int, int> TaskManagerModel::GetGroupRangeForResource(int index)
const {
CHECK_LT(index, ResourceCount());
TaskManager::Resource* resource = resources_[index];
GroupMap::const_iterator group_iter =
group_map_.find(resource->GetProcess());
DCHECK(group_iter != group_map_.end());
ResourceList* group = group_iter->second;
DCHECK(group);
if (group->size() == 1) {
return std::make_pair(index, 1);
} else {
for (int i = index; i >= 0; --i) {
if (resources_[i] == (*group)[0])
return std::make_pair(i, group->size());
}
NOTREACHED();
return std::make_pair(-1, -1);
}
}
int TaskManagerModel::CompareValues(int row1, int row2, int col_id) const {
CHECK(row1 < ResourceCount() && row2 < ResourceCount());
if (col_id == IDS_TASK_MANAGER_PAGE_COLUMN) {
// Let's do the default, string compare on the resource title.
static icu::Collator* collator = NULL;
if (!collator) {
UErrorCode create_status = U_ZERO_ERROR;
collator = icu::Collator::createInstance(create_status);
if (!U_SUCCESS(create_status)) {
collator = NULL;
NOTREACHED();
}
}
string16 title1 = GetResourceTitle(row1);
string16 title2 = GetResourceTitle(row2);
UErrorCode compare_status = U_ZERO_ERROR;
UCollationResult compare_result = collator->compare(
static_cast<const UChar*>(title1.c_str()),
static_cast<int>(title1.length()),
static_cast<const UChar*>(title2.c_str()),
static_cast<int>(title2.length()),
compare_status);
DCHECK(U_SUCCESS(compare_status));
return compare_result;
} else if (col_id == IDS_TASK_MANAGER_NET_COLUMN) {
return ValueCompare<int64>(GetNetworkUsage(resources_[row1]),
GetNetworkUsage(resources_[row2]));
} else if (col_id == IDS_TASK_MANAGER_CPU_COLUMN) {
return ValueCompare<double>(GetCPUUsage(resources_[row1]),
GetCPUUsage(resources_[row2]));
} else if (col_id == IDS_TASK_MANAGER_PRIVATE_MEM_COLUMN) {
size_t value1;
size_t value2;
if (!GetPrivateMemory(row1, &value1) || !GetPrivateMemory(row2, &value2))
return 0;
return ValueCompare<size_t>(value1, value2);
} else if (col_id == IDS_TASK_MANAGER_SHARED_MEM_COLUMN) {
size_t value1;
size_t value2;
if (!GetSharedMemory(row1, &value1) || !GetSharedMemory(row2, &value2))
return 0;
return ValueCompare<size_t>(value1, value2);
} else if (col_id == IDS_TASK_MANAGER_PHYSICAL_MEM_COLUMN) {
size_t value1;
size_t value2;
if (!GetPhysicalMemory(row1, &value1) ||
!GetPhysicalMemory(row2, &value2))
return 0;
return ValueCompare<size_t>(value1, value2);
} else if (col_id == IDS_TASK_MANAGER_PROCESS_ID_COLUMN) {
int proc1_id = base::GetProcId(resources_[row1]->GetProcess());
int proc2_id = base::GetProcId(resources_[row2]->GetProcess());
return ValueCompare<int>(proc1_id, proc2_id);
} else if (col_id == IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN ||
col_id == IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN ||
col_id == IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN) {
WebKit::WebCache::ResourceTypeStats stats1 = { { 0 } };
WebKit::WebCache::ResourceTypeStats stats2 = { { 0 } };
if (resources_[row1]->ReportsCacheStats())
stats1 = resources_[row1]->GetWebCoreCacheStats();
if (resources_[row2]->ReportsCacheStats())
stats2 = resources_[row2]->GetWebCoreCacheStats();
if (IDS_TASK_MANAGER_WEBCORE_IMAGE_CACHE_COLUMN == col_id)
return ValueCompare<size_t>(stats1.images.size, stats2.images.size);
if (IDS_TASK_MANAGER_WEBCORE_SCRIPTS_CACHE_COLUMN == col_id)
return ValueCompare<size_t>(stats1.scripts.size, stats2.scripts.size);
DCHECK_EQ(IDS_TASK_MANAGER_WEBCORE_CSS_CACHE_COLUMN, col_id);
return ValueCompare<size_t>(stats1.cssStyleSheets.size,
stats2.cssStyleSheets.size);
} else if (col_id == IDS_TASK_MANAGER_GOATS_TELEPORTED_COLUMN) {
return ValueCompare<int>(GetGoatsTeleported(row1),
GetGoatsTeleported(row2));
} else if (col_id == IDS_TASK_MANAGER_JAVASCRIPT_MEMORY_ALLOCATED_COLUMN) {
size_t value1;
size_t value2;
bool reports_v8_memory1 = GetV8Memory(row1, &value1);
bool reports_v8_memory2 = GetV8Memory(row2, &value2);
if (reports_v8_memory1 == reports_v8_memory2)
return ValueCompare<size_t>(value1, value2);
else
return reports_v8_memory1 ? 1 : -1;
} else {
NOTREACHED();
return 0;
}
}
base::ProcessHandle TaskManagerModel::GetResourceProcessHandle(int index)
const {
CHECK_LT(index, ResourceCount());
return resources_[index]->GetProcess();
}
TaskManager::Resource::Type TaskManagerModel::GetResourceType(int index) const {
CHECK_LT(index, ResourceCount());
return resources_[index]->GetType();
}
TabContentsWrapper* TaskManagerModel::GetResourceTabContents(int index) const {
CHECK_LT(index, ResourceCount());
return resources_[index]->GetTabContents();
}
const Extension* TaskManagerModel::GetResourceExtension(int index) const {
CHECK_LT(index, ResourceCount());
return resources_[index]->GetExtension();
}
int64 TaskManagerModel::GetNetworkUsage(TaskManager::Resource* resource)
const {
int64 net_usage = GetNetworkUsageForResource(resource);
if (net_usage == 0 && !resource->SupportNetworkUsage())
return -1;
return net_usage;
}
double TaskManagerModel::GetCPUUsage(TaskManager::Resource* resource) const {
CPUUsageMap::const_iterator iter =
cpu_usage_map_.find(resource->GetProcess());
if (iter == cpu_usage_map_.end())
return 0;
return iter->second;
}
bool TaskManagerModel::GetPrivateMemory(int index, size_t* result) const {
base::ProcessHandle handle = resources_[index]->GetProcess();
MemoryUsageMap::const_iterator iter = memory_usage_map_.find(handle);
if (iter == memory_usage_map_.end()) {
std::pair<size_t, size_t> usage;
if (!GetAndCacheMemoryMetrics(handle, &usage))
return false;
*result = usage.first;
} else {
*result = iter->second.first;
}
return true;
}
bool TaskManagerModel::GetSharedMemory(int index, size_t* result) const {
base::ProcessHandle handle = resources_[index]->GetProcess();
MemoryUsageMap::const_iterator iter = memory_usage_map_.find(handle);
if (iter == memory_usage_map_.end()) {
std::pair<size_t, size_t> usage;
if (!GetAndCacheMemoryMetrics(handle, &usage))
return false;
*result = usage.second;
} else {
*result = iter->second.second;
}
return true;
}
bool TaskManagerModel::GetPhysicalMemory(int index, size_t* result) const {
*result = 0;
base::ProcessMetrics* process_metrics;
if (!GetProcessMetricsForRow(index, &process_metrics))
return false;
base::WorkingSetKBytes ws_usage;
if (!process_metrics->GetWorkingSetKBytes(&ws_usage))
return false;
// Memory = working_set.private + working_set.shareable.
// We exclude the shared memory.
size_t total_bytes = process_metrics->GetWorkingSetSize();
total_bytes -= ws_usage.shared * 1024;
*result = total_bytes;
return true;
}
bool TaskManagerModel::GetV8Memory(int index, size_t* result) const {
*result = 0;
if (!resources_[index]->ReportsV8MemoryStats())
return false;
*result = resources_[index]->GetV8MemoryAllocated();
return true;
}
int TaskManagerModel::GetGoatsTeleported(int index) const {
int seed = goat_salt_ * (index + 1);
return (seed >> 16) & 255;
}
string16 TaskManagerModel::GetMemCellText(int64 number) const {
#if !defined(OS_MACOSX)
string16 str = base::FormatNumber(number / 1024);
// Adjust number string if necessary.
base::i18n::AdjustStringForLocaleDirection(&str);
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_MEM_CELL_TEXT, str);
#else
// System expectation is to show "100 KB", "200 MB", etc.
// TODO(thakis): Switch to metric units (as opposed to powers of two).
return FormatBytes(number, GetByteDisplayUnits(number), /*show_units=*/true);
#endif
}
void TaskManagerModel::StartUpdating() {
// Multiple StartUpdating requests may come in, and we only need to take
// action the first time.
update_requests_++;
if (update_requests_ > 1)
return;
DCHECK_EQ(1, update_requests_);
DCHECK_NE(TASK_PENDING, update_state_);
// If update_state_ is STOPPING, it means a task is still pending. Setting
// it to TASK_PENDING ensures the tasks keep being posted (by Refresh()).
if (update_state_ == IDLE) {
MessageLoop::current()->PostDelayedTask(FROM_HERE,
NewRunnableMethod(this, &TaskManagerModel::Refresh),
kUpdateTimeMs);
}
update_state_ = TASK_PENDING;
// Register jobs notifications so we can compute network usage (it must be
// done from the IO thread).
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(
this, &TaskManagerModel::RegisterForJobDoneNotifications));
// Notify resource providers that we are updating.
for (ResourceProviderList::iterator iter = providers_.begin();
iter != providers_.end(); ++iter) {
(*iter)->StartUpdating();
}
}
void TaskManagerModel::StopUpdating() {
// Don't actually stop updating until we have heard as many calls as those
// to StartUpdating.
update_requests_--;
if (update_requests_ > 0)
return;
// Make sure that update_requests_ cannot go negative.
CHECK_EQ(0, update_requests_);
DCHECK_EQ(TASK_PENDING, update_state_);
update_state_ = STOPPING;
// Notify resource providers that we are done updating.
for (ResourceProviderList::const_iterator iter = providers_.begin();
iter != providers_.end(); ++iter) {
(*iter)->StopUpdating();
}
// Unregister jobs notification (must be done from the IO thread).
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
NewRunnableMethod(
this, &TaskManagerModel::UnregisterForJobDoneNotifications));
// Must clear the resources before the next attempt to start updating.
Clear();
}
void TaskManagerModel::AddResourceProvider(
TaskManager::ResourceProvider* provider) {
DCHECK(provider);
// AddRef matched with Release in destructor.
provider->AddRef();
providers_.push_back(provider);
}
void TaskManagerModel::RegisterForJobDoneNotifications() {
net::g_url_request_job_tracker.AddObserver(this);
}
void TaskManagerModel::UnregisterForJobDoneNotifications() {
net::g_url_request_job_tracker.RemoveObserver(this);
}
void TaskManagerModel::AddResource(TaskManager::Resource* resource) {
base::ProcessHandle process = resource->GetProcess();
ResourceList* group_entries = NULL;
GroupMap::const_iterator group_iter = group_map_.find(process);
int new_entry_index = 0;
if (group_iter == group_map_.end()) {
group_entries = new ResourceList();
group_map_[process] = group_entries;
group_entries->push_back(resource);
// Not part of a group, just put at the end of the list.
resources_.push_back(resource);
new_entry_index = static_cast<int>(resources_.size() - 1);
} else {
group_entries = group_iter->second;
group_entries->push_back(resource);
// Insert the new entry right after the last entry of its group.
ResourceList::iterator iter =
std::find(resources_.begin(),
resources_.end(),
(*group_entries)[group_entries->size() - 2]);
DCHECK(iter != resources_.end());
new_entry_index = static_cast<int>(iter - resources_.begin()) + 1;
resources_.insert(++iter, resource);
}
// Create the ProcessMetrics for this process if needed (not in map).
if (metrics_map_.find(process) == metrics_map_.end()) {
base::ProcessMetrics* pm =
#if !defined(OS_MACOSX)
base::ProcessMetrics::CreateProcessMetrics(process);
#else
base::ProcessMetrics::CreateProcessMetrics(process,
MachBroker::GetInstance());
#endif
metrics_map_[process] = pm;
}
// Notify the table that the contents have changed for it to redraw.
FOR_EACH_OBSERVER(TaskManagerModelObserver, observer_list_,
OnItemsAdded(new_entry_index, 1));
}
void TaskManagerModel::RemoveResource(TaskManager::Resource* resource) {
base::ProcessHandle process = resource->GetProcess();
// Find the associated group.
GroupMap::iterator group_iter = group_map_.find(process);
DCHECK(group_iter != group_map_.end());
ResourceList* group_entries = group_iter->second;
// Remove the entry from the group map.
ResourceList::iterator iter = std::find(group_entries->begin(),
group_entries->end(),
resource);
DCHECK(iter != group_entries->end());
group_entries->erase(iter);
// If there are no more entries for that process, do the clean-up.
if (group_entries->empty()) {
delete group_entries;
group_map_.erase(process);
// Nobody is using this process, we don't need the process metrics anymore.
MetricsMap::iterator pm_iter = metrics_map_.find(process);
DCHECK(pm_iter != metrics_map_.end());
if (pm_iter != metrics_map_.end()) {
delete pm_iter->second;
metrics_map_.erase(process);
}
// And we don't need the CPU usage anymore either.
CPUUsageMap::iterator cpu_iter = cpu_usage_map_.find(process);
if (cpu_iter != cpu_usage_map_.end())
cpu_usage_map_.erase(cpu_iter);
}
// Remove the entry from the model list.
iter = std::find(resources_.begin(), resources_.end(), resource);
DCHECK(iter != resources_.end());
int index = static_cast<int>(iter - resources_.begin());
resources_.erase(iter);
// Remove the entry from the network maps.
ResourceValueMap::iterator net_iter =
current_byte_count_map_.find(resource);
if (net_iter != current_byte_count_map_.end())
current_byte_count_map_.erase(net_iter);
net_iter = displayed_network_usage_map_.find(resource);
if (net_iter != displayed_network_usage_map_.end())
displayed_network_usage_map_.erase(net_iter);
// Notify the table that the contents have changed.
FOR_EACH_OBSERVER(TaskManagerModelObserver, observer_list_,
OnItemsRemoved(index, 1));
}
void TaskManagerModel::Clear() {
int size = ResourceCount();
if (size > 0) {
resources_.clear();
// Clear the groups.
for (GroupMap::iterator iter = group_map_.begin();
iter != group_map_.end(); ++iter) {
delete iter->second;
}
group_map_.clear();
// Clear the process related info.
for (MetricsMap::iterator iter = metrics_map_.begin();
iter != metrics_map_.end(); ++iter) {
delete iter->second;
}
metrics_map_.clear();
cpu_usage_map_.clear();
// Clear the network maps.
current_byte_count_map_.clear();
displayed_network_usage_map_.clear();
FOR_EACH_OBSERVER(TaskManagerModelObserver, observer_list_,
OnItemsRemoved(0, size));
}
}
void TaskManagerModel::ModelChanged() {
// Notify the table that the contents have changed for it to redraw.
FOR_EACH_OBSERVER(TaskManagerModelObserver, observer_list_, OnModelChanged());
}
void TaskManagerModel::NotifyResourceTypeStats(
base::ProcessId renderer_id,
const WebKit::WebCache::ResourceTypeStats& stats) {
for (ResourceList::iterator it = resources_.begin();
it != resources_.end(); ++it) {
if (base::GetProcId((*it)->GetProcess()) == renderer_id) {
(*it)->NotifyResourceTypeStats(stats);
}
}
}
void TaskManagerModel::NotifyV8HeapStats(base::ProcessId renderer_id,
size_t v8_memory_allocated,
size_t v8_memory_used) {
for (ResourceList::iterator it = resources_.begin();
it != resources_.end(); ++it) {
if (base::GetProcId((*it)->GetProcess()) == renderer_id) {
(*it)->NotifyV8HeapStats(v8_memory_allocated, v8_memory_used);
}
}
}
void TaskManagerModel::Refresh() {
DCHECK_NE(IDLE, update_state_);
if (update_state_ == STOPPING) {
// We have been asked to stop.
update_state_ = IDLE;
return;
}
goat_salt_ = rand();
// Compute the CPU usage values.
// Note that we compute the CPU usage for all resources (instead of doing it
// lazily) as process_util::GetCPUUsage() returns the CPU usage since the last
// time it was called, and not calling it everytime would skew the value the
// next time it is retrieved (as it would be for more than 1 cycle).
cpu_usage_map_.clear();
for (ResourceList::iterator iter = resources_.begin();
iter != resources_.end(); ++iter) {
base::ProcessHandle process = (*iter)->GetProcess();
CPUUsageMap::iterator cpu_iter = cpu_usage_map_.find(process);
if (cpu_iter != cpu_usage_map_.end())
continue; // Already computed.
MetricsMap::iterator metrics_iter = metrics_map_.find(process);
DCHECK(metrics_iter != metrics_map_.end());
cpu_usage_map_[process] = metrics_iter->second->GetCPUUsage();
}
// Clear the memory values so they can be querried lazily.
memory_usage_map_.clear();
// Compute the new network usage values.
displayed_network_usage_map_.clear();
for (ResourceValueMap::iterator iter = current_byte_count_map_.begin();
iter != current_byte_count_map_.end(); ++iter) {
if (kUpdateTimeMs > 1000) {
int divider = (kUpdateTimeMs / 1000);
displayed_network_usage_map_[iter->first] = iter->second / divider;
} else {
displayed_network_usage_map_[iter->first] = iter->second *
(1000 / kUpdateTimeMs);
}
// Then we reset the current byte count.
iter->second = 0;
}
// Let resources update themselves if they need to.
for (ResourceList::iterator iter = resources_.begin();
iter != resources_.end(); ++iter) {
(*iter)->Refresh();
}
if (!resources_.empty()) {
FOR_EACH_OBSERVER(TaskManagerModelObserver, observer_list_,
OnItemsChanged(0, ResourceCount()));
}
// Schedule the next update.
MessageLoop::current()->PostDelayedTask(FROM_HERE,
NewRunnableMethod(this, &TaskManagerModel::Refresh),
kUpdateTimeMs);
}
int64 TaskManagerModel::GetNetworkUsageForResource(
TaskManager::Resource* resource) const {
ResourceValueMap::const_iterator iter =
displayed_network_usage_map_.find(resource);
if (iter == displayed_network_usage_map_.end())
return 0;
return iter->second;
}
void TaskManagerModel::BytesRead(BytesReadParam param) {
if (update_state_ != TASK_PENDING) {
// A notification sneaked in while we were stopping the updating, just
// ignore it.
return;
}
if (param.byte_count == 0) {
// Nothing to do if no bytes were actually read.
return;
}
// TODO(jcampan): this should be improved once we have a better way of
// linking a network notification back to the object that initiated it.
TaskManager::Resource* resource = NULL;
for (ResourceProviderList::iterator iter = providers_.begin();
iter != providers_.end(); ++iter) {
resource = (*iter)->GetResource(param.origin_pid,
param.render_process_host_child_id,
param.routing_id);
if (resource)
break;
}
if (resource == NULL) {
// We can't match a resource to the notification. That might mean the
// tab that started a download was closed, or the request may have had
// no originating resource associated with it in the first place.
// We attribute orphaned/unaccounted activity to the Browser process.
CHECK(param.origin_pid || (param.render_process_host_child_id != -1));
param.origin_pid = 0;
param.render_process_host_child_id = param.routing_id = -1;
BytesRead(param);
return;
}
// We do support network usage, mark the resource as such so it can report 0
// instead of N/A.
if (!resource->SupportNetworkUsage())
resource->SetSupportNetworkUsage();
ResourceValueMap::const_iterator iter_res =
current_byte_count_map_.find(resource);
if (iter_res == current_byte_count_map_.end())
current_byte_count_map_[resource] = param.byte_count;
else
current_byte_count_map_[resource] = iter_res->second + param.byte_count;
}
// In order to retrieve the network usage, we register for net::URLRequestJob
// notifications. Every time we get notified some bytes were read we bump a
// counter of read bytes for the associated resource. When the timer ticks,
// we'll compute the actual network usage (see the Refresh method).
void TaskManagerModel::OnJobAdded(net::URLRequestJob* job) {
}
void TaskManagerModel::OnJobRemoved(net::URLRequestJob* job) {
}
void TaskManagerModel::OnJobDone(net::URLRequestJob* job,
const net::URLRequestStatus& status) {
}
void TaskManagerModel::OnJobRedirect(net::URLRequestJob* job,
const GURL& location,
int status_code) {
}
void TaskManagerModel::OnBytesRead(net::URLRequestJob* job, const char* buf,
int byte_count) {
// Only net::URLRequestJob instances created by the ResourceDispatcherHost
// have a render view associated. All other jobs will have -1 returned for
// the render process child and routing ids - the jobs may still match a
// resource based on their origin id, otherwise BytesRead() will attribute
// the activity to the Browser resource.
int render_process_host_child_id = -1, routing_id = -1;
ResourceDispatcherHost::RenderViewForRequest(job->request(),
&render_process_host_child_id,
&routing_id);
// Get the origin PID of the request's originator. This will only be set for
// plugins - for renderer or browser initiated requests it will be zero.
int origin_pid =
chrome_browser_net::GetOriginPIDForRequest(job->request());
// This happens in the IO thread, post it to the UI thread.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(
this,
&TaskManagerModel::BytesRead,
BytesReadParam(origin_pid,
render_process_host_child_id,
routing_id, byte_count)));
}
bool TaskManagerModel::GetProcessMetricsForRow(
int row, base::ProcessMetrics** proc_metrics) const {
DCHECK(row < ResourceCount());
*proc_metrics = NULL;
MetricsMap::const_iterator iter =
metrics_map_.find(resources_[row]->GetProcess());
if (iter == metrics_map_.end())
return false;
*proc_metrics = iter->second;
return true;
}
////////////////////////////////////////////////////////////////////////////////
// TaskManager class
////////////////////////////////////////////////////////////////////////////////
// static
void TaskManager::RegisterPrefs(PrefService* prefs) {
prefs->RegisterDictionaryPref(prefs::kTaskManagerWindowPlacement);
}
TaskManager::TaskManager()
: ALLOW_THIS_IN_INITIALIZER_LIST(model_(new TaskManagerModel(this))) {
}
TaskManager::~TaskManager() {
}
bool TaskManager::IsBrowserProcess(int index) const {
// If some of the selection is out of bounds, ignore. This may happen when
// killing a process that manages several pages.
return index < model_->ResourceCount() &&
model_->GetResourceProcessHandle(index) ==
base::GetCurrentProcessHandle();
}
void TaskManager::KillProcess(int index) {
base::ProcessHandle process = model_->GetResourceProcessHandle(index);
DCHECK(process);
if (process != base::GetCurrentProcessHandle())
base::KillProcess(process, ResultCodes::KILLED, false);
}
void TaskManager::ActivateProcess(int index) {
// GetResourceTabContents returns a pointer to the relevant tab contents for
// the resource. If the index doesn't correspond to a Tab (i.e. refers to
// the Browser process or a plugin), GetTabContents will return NULL.
TabContentsWrapper* chosen_tab_contents =
model_->GetResourceTabContents(index);
if (chosen_tab_contents)
chosen_tab_contents->tab_contents()->Activate();
}
void TaskManager::AddResource(Resource* resource) {
model_->AddResource(resource);
}
void TaskManager::RemoveResource(Resource* resource) {
model_->RemoveResource(resource);
}
void TaskManager::OnWindowClosed() {
model_->StopUpdating();
}
void TaskManager::ModelChanged() {
model_->ModelChanged();
}
// static
TaskManager* TaskManager::GetInstance() {
return Singleton<TaskManager>::get();
}
void TaskManager::OpenAboutMemory() {
Browser* browser = BrowserList::GetLastActive();
if (!browser) {
// On OS X, the task manager can be open without any open browser windows.
if (!g_browser_process || !g_browser_process->profile_manager())
return;
Profile* profile =
g_browser_process->profile_manager()->GetDefaultProfile();
if (!profile)
return;
browser = Browser::Create(profile);
browser->OpenURL(GURL(chrome::kAboutMemoryURL), GURL(), NEW_FOREGROUND_TAB,
PageTransition::LINK);
browser->window()->Show();
} else {
browser->OpenURL(GURL(chrome::kAboutMemoryURL), GURL(), NEW_FOREGROUND_TAB,
PageTransition::LINK);
// In case the browser window is minimzed, show it. If |browser| is a
// non-tabbed window, the call to OpenURL above will have opened a
// TabContents in a tabbed browser, so we need to grab it with GetLastActive
// before the call to show().
if (browser->type() & (Browser::TYPE_APP |
Browser::TYPE_DEVTOOLS |
Browser::TYPE_POPUP)) {
browser = BrowserList::GetLastActive();
DCHECK(browser);
}
browser->window()->Show();
}
}
bool TaskManagerModel::GetAndCacheMemoryMetrics(
base::ProcessHandle handle,
std::pair<size_t, size_t>* usage) const {
MetricsMap::const_iterator iter = metrics_map_.find(handle);
if (iter == metrics_map_.end())
return false;
if (!iter->second->GetMemoryBytes(&usage->first, &usage->second))
return false;
memory_usage_map_.insert(std::make_pair(handle, *usage));
return true;
}