blob: 019bcf45e82f4bfa1f69d9b980e82f5bbcf35247 [file] [log] [blame]
// 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 "chrome/browser/task_manager/child_process_resource_provider.h"
#include <stddef.h>
#include <utility>
#include <vector>
#include "base/i18n/rtl.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "chrome/browser/process_resource_usage.h"
#include "chrome/browser/task_manager/resource_provider.h"
#include "chrome/browser/task_manager/task_manager.h"
#include "chrome/grit/generated_resources.h"
#include "components/nacl/common/nacl_process_type.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/common/service_registry.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"
using content::BrowserChildProcessHostIterator;
using content::BrowserThread;
using content::WebContents;
namespace task_manager {
class ChildProcessResource : public Resource {
public:
ChildProcessResource(int process_type,
const base::string16& name,
base::ProcessHandle handle,
int unique_process_id);
~ChildProcessResource() override;
// Resource methods:
base::string16 GetTitle() const override;
base::string16 GetProfileName() const override;
gfx::ImageSkia GetIcon() const override;
base::ProcessHandle GetProcess() const override;
int GetUniqueChildProcessId() const override;
Type GetType() const override;
bool SupportNetworkUsage() const override;
void SetSupportNetworkUsage() override;
void Refresh() override;
bool ReportsV8MemoryStats() const override;
size_t GetV8MemoryAllocated() const override;
size_t GetV8MemoryUsed() const override;
// Returns the pid of the child process.
int process_id() const { return pid_; }
private:
// Returns a localized title for the child process. For example, a plugin
// process would be "Plugin: Flash" when name is "Flash".
base::string16 GetLocalizedTitle() const;
static void ConnectResourceReporterOnIOThread(
int id, mojo::InterfaceRequest<ResourceUsageReporter> req);
int process_type_;
base::string16 name_;
base::ProcessHandle handle_;
int pid_;
int unique_process_id_;
mutable base::string16 title_;
bool network_usage_support_;
scoped_ptr<ProcessResourceUsage> resource_usage_;
// The icon painted for the child processs.
// TODO(jcampan): we should have plugin specific icons for well-known
// plugins.
static gfx::ImageSkia* default_icon_;
DISALLOW_COPY_AND_ASSIGN(ChildProcessResource);
};
gfx::ImageSkia* ChildProcessResource::default_icon_ = NULL;
// static
void ChildProcessResource::ConnectResourceReporterOnIOThread(
int id, mojo::InterfaceRequest<ResourceUsageReporter> req) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
content::BrowserChildProcessHost* host =
content::BrowserChildProcessHost::FromID(id);
if (!host)
return;
content::ServiceRegistry* registry = host->GetServiceRegistry();
if (!registry)
return;
registry->ConnectToRemoteService(std::move(req));
}
ChildProcessResource::ChildProcessResource(int process_type,
const base::string16& name,
base::ProcessHandle handle,
int unique_process_id)
: process_type_(process_type),
name_(name),
handle_(handle),
unique_process_id_(unique_process_id),
network_usage_support_(false) {
// We cache the process id because it's not cheap to calculate, and it won't
// be available when we get the plugin disconnected notification.
pid_ = base::GetProcId(handle);
if (!default_icon_) {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
default_icon_ = rb.GetImageSkiaNamed(IDR_PLUGINS_FAVICON);
// TODO(jabdelmalek): use different icon for web workers.
}
ResourceUsageReporterPtr service;
mojo::InterfaceRequest<ResourceUsageReporter> request =
mojo::GetProxy(&service);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&ChildProcessResource::ConnectResourceReporterOnIOThread,
unique_process_id, base::Passed(&request)));
resource_usage_.reset(new ProcessResourceUsage(std::move(service)));
}
ChildProcessResource::~ChildProcessResource() {
}
// Resource methods:
base::string16 ChildProcessResource::GetTitle() const {
if (title_.empty())
title_ = GetLocalizedTitle();
return title_;
}
base::string16 ChildProcessResource::GetProfileName() const {
return base::string16();
}
gfx::ImageSkia ChildProcessResource::GetIcon() const {
return *default_icon_;
}
base::ProcessHandle ChildProcessResource::GetProcess() const {
return handle_;
}
int ChildProcessResource::GetUniqueChildProcessId() const {
return unique_process_id_;
}
Resource::Type ChildProcessResource::GetType() const {
// Translate types to Resource::Type, since ChildProcessData's type
// is not available for all TaskManager resources.
switch (process_type_) {
case content::PROCESS_TYPE_PPAPI_PLUGIN:
case content::PROCESS_TYPE_PPAPI_BROKER:
return Resource::PLUGIN;
case content::PROCESS_TYPE_UTILITY:
return Resource::UTILITY;
case content::PROCESS_TYPE_ZYGOTE:
return Resource::ZYGOTE;
case content::PROCESS_TYPE_SANDBOX_HELPER:
return Resource::SANDBOX_HELPER;
case content::PROCESS_TYPE_GPU:
return Resource::GPU;
case PROCESS_TYPE_NACL_LOADER:
case PROCESS_TYPE_NACL_BROKER:
return Resource::NACL;
default:
return Resource::UNKNOWN;
}
}
bool ChildProcessResource::SupportNetworkUsage() const {
return network_usage_support_;
}
void ChildProcessResource::SetSupportNetworkUsage() {
network_usage_support_ = true;
}
base::string16 ChildProcessResource::GetLocalizedTitle() const {
base::string16 title = name_;
if (title.empty()) {
switch (process_type_) {
case content::PROCESS_TYPE_PPAPI_PLUGIN:
case content::PROCESS_TYPE_PPAPI_BROKER:
title = l10n_util::GetStringUTF16(IDS_TASK_MANAGER_UNKNOWN_PLUGIN_NAME);
break;
default:
// Nothing to do for non-plugin processes.
break;
}
}
// Explicitly mark name as LTR if there is no strong RTL character,
// to avoid the wrong concatenation result similar to "!Yahoo Mail: the
// best web-based Email: NIGULP", in which "NIGULP" stands for the Hebrew
// or Arabic word for "plugin".
base::i18n::AdjustStringForLocaleDirection(&title);
switch (process_type_) {
case content::PROCESS_TYPE_UTILITY:
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_UTILITY_PREFIX, title);
case content::PROCESS_TYPE_GPU:
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_GPU_PREFIX);
case content::PROCESS_TYPE_PPAPI_PLUGIN:
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_PREFIX, title);
case content::PROCESS_TYPE_PPAPI_BROKER:
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_BROKER_PREFIX,
title);
case PROCESS_TYPE_NACL_BROKER:
return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_NACL_BROKER_PREFIX);
case PROCESS_TYPE_NACL_LOADER:
return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_NACL_PREFIX, title);
// These types don't need display names or get them from elsewhere.
case content::PROCESS_TYPE_BROWSER:
case content::PROCESS_TYPE_RENDERER:
case content::PROCESS_TYPE_ZYGOTE:
case content::PROCESS_TYPE_SANDBOX_HELPER:
case content::PROCESS_TYPE_MAX:
NOTREACHED();
break;
case content::PROCESS_TYPE_UNKNOWN:
NOTREACHED() << "Need localized name for child process type.";
}
return title;
}
void ChildProcessResource::Refresh() {
if (resource_usage_)
resource_usage_->Refresh(base::Closure());
}
bool ChildProcessResource::ReportsV8MemoryStats() const {
if (resource_usage_)
return resource_usage_->ReportsV8MemoryStats();
return false;
}
size_t ChildProcessResource::GetV8MemoryAllocated() const {
if (resource_usage_)
return resource_usage_->GetV8MemoryAllocated();
return 0;
}
size_t ChildProcessResource::GetV8MemoryUsed() const {
if (resource_usage_)
return resource_usage_->GetV8MemoryUsed();
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// ChildProcessResourceProvider class
////////////////////////////////////////////////////////////////////////////////
ChildProcessResourceProvider::
ChildProcessResourceProvider(TaskManager* task_manager)
: task_manager_(task_manager),
updating_(false) {
}
ChildProcessResourceProvider::~ChildProcessResourceProvider() {
}
Resource* ChildProcessResourceProvider::GetResource(
int origin_pid,
int child_id,
int route_id) {
PidResourceMap::iterator iter = pid_to_resources_.find(origin_pid);
if (iter != pid_to_resources_.end())
return iter->second;
else
return NULL;
}
void ChildProcessResourceProvider::StartUpdating() {
DCHECK(!updating_);
updating_ = true;
// Get the existing child processes.
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(
&ChildProcessResourceProvider::RetrieveChildProcessData,
this));
BrowserChildProcessObserver::Add(this);
}
void ChildProcessResourceProvider::StopUpdating() {
DCHECK(updating_);
updating_ = false;
// Delete all the resources.
STLDeleteContainerPairSecondPointers(resources_.begin(), resources_.end());
resources_.clear();
pid_to_resources_.clear();
BrowserChildProcessObserver::Remove(this);
}
void ChildProcessResourceProvider::BrowserChildProcessHostConnected(
const content::ChildProcessData& data) {
DCHECK(updating_);
if (resources_.count(data.handle)) {
// The case may happen that we have added a child_process_info as part of
// the iteration performed during StartUpdating() call but the notification
// that it has connected was not fired yet. So when the notification
// happens, we already know about this plugin and just ignore it.
return;
}
AddToTaskManager(data);
}
void ChildProcessResourceProvider::
BrowserChildProcessHostDisconnected(
const content::ChildProcessData& data) {
DCHECK(updating_);
ChildProcessMap::iterator iter = resources_.find(data.handle);
if (iter == resources_.end()) {
// ChildProcessData disconnection notifications are asynchronous, so we
// might be notified for a plugin we don't know anything about (if it was
// closed before the task manager was shown and destroyed after that).
return;
}
// Remove the resource from the Task Manager.
ChildProcessResource* resource = iter->second;
task_manager_->RemoveResource(resource);
// Remove it from the provider.
resources_.erase(iter);
// Remove it from our pid map.
PidResourceMap::iterator pid_iter =
pid_to_resources_.find(resource->process_id());
DCHECK(pid_iter != pid_to_resources_.end());
if (pid_iter != pid_to_resources_.end())
pid_to_resources_.erase(pid_iter);
// Finally, delete the resource.
delete resource;
}
void ChildProcessResourceProvider::AddToTaskManager(
const content::ChildProcessData& child_process_data) {
ChildProcessResource* resource =
new ChildProcessResource(
child_process_data.process_type,
child_process_data.name,
child_process_data.handle,
child_process_data.id);
resources_[child_process_data.handle] = resource;
pid_to_resources_[resource->process_id()] = resource;
task_manager_->AddResource(resource);
}
// The ChildProcessData::Iterator has to be used from the IO thread.
void ChildProcessResourceProvider::RetrieveChildProcessData() {
std::vector<content::ChildProcessData> child_processes;
for (BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
// Only add processes which are already started, since we need their handle.
if (iter.GetData().handle == base::kNullProcessHandle)
continue;
child_processes.push_back(iter.GetData());
}
// Now notify the UI thread that we have retrieved information about child
// processes.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&ChildProcessResourceProvider::ChildProcessDataRetreived,
this, child_processes));
}
// This is called on the UI thread.
void ChildProcessResourceProvider::ChildProcessDataRetreived(
const std::vector<content::ChildProcessData>& child_processes) {
for (size_t i = 0; i < child_processes.size(); ++i)
AddToTaskManager(child_processes[i]);
task_manager_->model()->NotifyDataReady();
}
} // namespace task_manager