blob: 24b2d931ad0e47b4b5d536f0f7920d455658a6f7 [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/ui/webui/memory_internals/memory_internals_proxy.h"
#include <set>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/strings/string16.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/memory_details.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
#include "chrome/browser/ui/android/tab_model/tab_model.h"
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
#include "chrome/browser/ui/webui/memory_internals/memory_internals_handler.h"
#include "chrome/common/render_messages.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#if defined(ENABLE_PRINT_PREVIEW)
#include "chrome/browser/printing/background_printing_manager.h"
#endif
using content::BrowserThread;
class Profile;
namespace {
class ProcessDetails : public MemoryDetails {
public:
typedef base::Callback<void(const ProcessData&)> DataCallback;
explicit ProcessDetails(const DataCallback& callback)
: callback_(callback) {}
// MemoryDetails:
void OnDetailsAvailable() override {
const std::vector<ProcessData>& browser_processes = processes();
// [0] means Chrome.
callback_.Run(browser_processes[0]);
}
private:
~ProcessDetails() override {}
DataCallback callback_;
DISALLOW_COPY_AND_ASSIGN(ProcessDetails);
};
base::DictionaryValue* FindProcessFromPid(base::ListValue* processes,
base::ProcessId pid) {
const size_t n = processes->GetSize();
for (size_t i = 0; i < n; ++i) {
base::DictionaryValue* process;
if (!processes->GetDictionary(i, &process))
return NULL;
int id;
if (process->GetInteger("pid", &id) && id == static_cast<int>(pid))
return process;
}
return NULL;
}
void GetAllWebContents(std::set<content::WebContents*>* web_contents) {
// Add all the existing WebContentses.
#if defined(OS_ANDROID)
for (TabModelList::const_iterator iter = TabModelList::begin();
iter != TabModelList::end(); ++iter) {
TabModel* model = *iter;
for (int i = 0; i < model->GetTabCount(); ++i) {
content::WebContents* tab_web_contents = model->GetWebContentsAt(i);
if (tab_web_contents)
web_contents->insert(tab_web_contents);
}
}
#else
for (TabContentsIterator iter; !iter.done(); iter.Next())
web_contents->insert(*iter);
#endif
// Add all the prerender pages.
std::vector<Profile*> profiles(
g_browser_process->profile_manager()->GetLoadedProfiles());
for (size_t i = 0; i < profiles.size(); ++i) {
prerender::PrerenderManager* prerender_manager =
prerender::PrerenderManagerFactory::GetForProfile(profiles[i]);
if (!prerender_manager)
continue;
const std::vector<content::WebContents*> contentses =
prerender_manager->GetAllPrerenderingContents();
web_contents->insert(contentses.begin(), contentses.end());
}
#if defined(ENABLE_PRINT_PREVIEW)
// Add all the pages being background printed.
printing::BackgroundPrintingManager* printing_manager =
g_browser_process->background_printing_manager();
std::set<content::WebContents*> printing_contents =
printing_manager->CurrentContentSet();
web_contents->insert(printing_contents.begin(), printing_contents.end());
#endif
}
} // namespace
class RendererDetails : public content::NotificationObserver {
public:
typedef base::Callback<void(const base::ProcessId pid,
const size_t v8_allocated,
const size_t v8_used)> V8DataCallback;
explicit RendererDetails(const V8DataCallback& callback)
: callback_(callback) {
registrar_.Add(this, chrome::NOTIFICATION_RENDERER_V8_HEAP_STATS_COMPUTED,
content::NotificationService::AllSources());
}
~RendererDetails() override {}
void Request() {
for (std::set<content::WebContents*>::iterator iter = web_contents_.begin();
iter != web_contents_.end(); ++iter)
(*iter)->GetRenderViewHost()->Send(new ChromeViewMsg_GetV8HeapStats);
}
void AddWebContents(content::WebContents* content) {
web_contents_.insert(content);
}
void Clear() {
web_contents_.clear();
}
void RemoveWebContents(base::ProcessId) {
// We don't have to detect which content is the caller of this method.
if (!web_contents_.empty())
web_contents_.erase(web_contents_.begin());
}
int IsClean() {
return web_contents_.empty();
}
private:
// NotificationObserver:
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
const base::ProcessId* pid =
content::Source<const base::ProcessId>(source).ptr();
const ChromeRenderMessageFilter::V8HeapStatsDetails* v8_heap =
content::Details<const ChromeRenderMessageFilter::V8HeapStatsDetails>(
details).ptr();
callback_.Run(*pid,
v8_heap->v8_memory_allocated(),
v8_heap->v8_memory_used());
}
V8DataCallback callback_;
content::NotificationRegistrar registrar_;
std::set<content::WebContents*> web_contents_; // This class does not own
DISALLOW_COPY_AND_ASSIGN(RendererDetails);
};
MemoryInternalsProxy::MemoryInternalsProxy()
: information_(new base::DictionaryValue()),
renderer_details_(new RendererDetails(
base::Bind(&MemoryInternalsProxy::OnRendererAvailable, this))) {}
void MemoryInternalsProxy::Attach(MemoryInternalsHandler* handler) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
handler_ = handler;
}
void MemoryInternalsProxy::Detach() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
handler_ = NULL;
}
void MemoryInternalsProxy::StartFetch(const base::ListValue* list) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Clear previous information before fetching new information.
information_->Clear();
scoped_refptr<ProcessDetails> process(new ProcessDetails(
base::Bind(&MemoryInternalsProxy::OnProcessAvailable, this)));
process->StartFetch(MemoryDetails::SKIP_USER_METRICS);
}
MemoryInternalsProxy::~MemoryInternalsProxy() {}
void MemoryInternalsProxy::RequestRendererDetails() {
renderer_details_->Clear();
#if defined(OS_ANDROID)
for (TabModelList::const_iterator iter = TabModelList::begin();
iter != TabModelList::end(); ++iter) {
TabModel* model = *iter;
for (int i = 0; i < model->GetTabCount(); ++i) {
content::WebContents* tab_web_contents = model->GetWebContentsAt(i);
if (tab_web_contents)
renderer_details_->AddWebContents(tab_web_contents);
}
}
#else
for (TabContentsIterator iter; !iter.done(); iter.Next())
renderer_details_->AddWebContents(*iter);
#endif
renderer_details_->Request();
}
void MemoryInternalsProxy::OnProcessAvailable(const ProcessData& browser) {
base::ListValue* process_info = new base::ListValue();
base::ListValue* extension_info = new base::ListValue();
information_->Set("processes", process_info);
information_->Set("extensions", extension_info);
for (PMIIterator iter = browser.processes.begin();
iter != browser.processes.end(); ++iter) {
base::DictionaryValue* process = new base::DictionaryValue();
if (iter->renderer_type == ProcessMemoryInformation::RENDERER_EXTENSION)
extension_info->Append(process);
else
process_info->Append(process);
// From MemoryDetails.
process->SetInteger("pid", iter->pid);
process->SetString("type",
ProcessMemoryInformation::GetFullTypeNameInEnglish(
iter->process_type, iter->renderer_type));
process->SetInteger("memory_private", iter->working_set.priv);
base::ListValue* titles = new base::ListValue();
process->Set("titles", titles);
for (size_t i = 0; i < iter->titles.size(); ++i)
titles->AppendString(iter->titles[i]);
}
std::set<content::WebContents*> web_contents;
GetAllWebContents(&web_contents);
ConvertTabsInformation(web_contents, process_info);
RequestRendererDetails();
}
void MemoryInternalsProxy::OnRendererAvailable(const base::ProcessId pid,
const size_t v8_allocated,
const size_t v8_used) {
// Do not update while no renderers are registered.
if (renderer_details_->IsClean())
return;
base::ListValue* processes;
if (!information_->GetList("processes", &processes))
return;
const size_t size = processes->GetSize();
for (size_t i = 0; i < size; ++i) {
base::DictionaryValue* process;
processes->GetDictionary(i, &process);
int id;
if (!process->GetInteger("pid", &id) || id != static_cast<int>(pid))
continue;
// Convert units from Bytes to KiB.
process->SetInteger("v8_alloc", v8_allocated / 1024);
process->SetInteger("v8_used", v8_used / 1024);
break;
}
renderer_details_->RemoveWebContents(pid);
if (renderer_details_->IsClean())
FinishCollection();
}
void MemoryInternalsProxy::ConvertTabsInformation(
const std::set<content::WebContents*>& web_contents,
base::ListValue* processes) {
for (std::set<content::WebContents*>::const_iterator
iter = web_contents.begin(); iter != web_contents.end(); ++iter) {
content::WebContents* web = *iter;
content::RenderProcessHost* process_host = web->GetRenderProcessHost();
if (!process_host)
continue;
// Find which process renders the web contents.
const base::ProcessId pid = base::GetProcId(process_host->GetHandle());
base::DictionaryValue* process = FindProcessFromPid(processes, pid);
if (!process)
continue;
// Prepare storage to register navigation histories.
base::ListValue* tabs;
if (!process->GetList("history", &tabs)) {
tabs = new base::ListValue();
process->Set("history", tabs);
}
base::DictionaryValue* tab = new base::DictionaryValue();
tabs->Append(tab);
base::ListValue* histories = new base::ListValue();
tab->Set("history", histories);
const content::NavigationController& controller = web->GetController();
const int entry_size = controller.GetEntryCount();
for (int i = 0; i < entry_size; ++i) {
content::NavigationEntry *entry = controller.GetEntryAtIndex(i);
base::DictionaryValue* history = new base::DictionaryValue();
histories->Append(history);
history->SetString("url", entry->GetURL().spec());
history->SetString("title", entry->GetTitle());
history->SetInteger("time", (base::Time::Now() -
entry->GetTimestamp()).InSeconds());
}
tab->SetInteger("index", controller.GetCurrentEntryIndex());
}
}
void MemoryInternalsProxy::FinishCollection() {
information_->SetInteger("uptime", base::SysInfo::Uptime());
information_->SetString("os", base::SysInfo::OperatingSystemName());
information_->SetString("os_version",
base::SysInfo::OperatingSystemVersion());
CallJavaScriptFunctionOnUIThread("g_main_view.onSetSnapshot", *information_);
}
void MemoryInternalsProxy::CallJavaScriptFunctionOnUIThread(
const std::string& function, const base::Value& args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::vector<const base::Value*> args_vector(1, &args);
base::string16 update =
content::WebUI::GetJavascriptCall(function, args_vector);
// Don't forward updates to a destructed UI.
if (handler_)
handler_->OnUpdate(update);
}