1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/webui/memory_internals/memory_internals_proxy.h"
6
7#include <set>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/strings/string16.h"
13#include "base/sys_info.h"
14#include "base/values.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/chrome_notification_types.h"
17#include "chrome/browser/memory_details.h"
18#include "chrome/browser/prerender/prerender_manager.h"
19#include "chrome/browser/prerender/prerender_manager_factory.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/profiles/profile_manager.h"
22#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
23#include "chrome/browser/ui/android/tab_model/tab_model.h"
24#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
25#include "chrome/browser/ui/browser.h"
26#include "chrome/browser/ui/browser_iterator.h"
27#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
28#include "chrome/browser/ui/webui/memory_internals/memory_internals_handler.h"
29#include "chrome/common/render_messages.h"
30#include "content/public/browser/navigation_controller.h"
31#include "content/public/browser/navigation_entry.h"
32#include "content/public/browser/notification_observer.h"
33#include "content/public/browser/notification_registrar.h"
34#include "content/public/browser/notification_service.h"
35#include "content/public/browser/render_process_host.h"
36#include "content/public/browser/render_view_host.h"
37#include "content/public/browser/web_contents.h"
38#include "content/public/browser/web_ui.h"
39
40#if defined(ENABLE_FULL_PRINTING)
41#include "chrome/browser/printing/background_printing_manager.h"
42#endif
43
44using content::BrowserThread;
45
46class Profile;
47
48namespace {
49
50class ProcessDetails : public MemoryDetails {
51 public:
52  typedef base::Callback<void(const ProcessData&)> DataCallback;
53  explicit ProcessDetails(const DataCallback& callback)
54      : callback_(callback) {}
55  // MemoryDetails:
56  virtual void OnDetailsAvailable() OVERRIDE {
57    const std::vector<ProcessData>& browser_processes = processes();
58    // [0] means Chrome.
59    callback_.Run(browser_processes[0]);
60  }
61
62 private:
63  virtual ~ProcessDetails() {}
64
65  DataCallback callback_;
66
67  DISALLOW_COPY_AND_ASSIGN(ProcessDetails);
68};
69
70base::DictionaryValue* FindProcessFromPid(base::ListValue* processes,
71                                          base::ProcessId pid) {
72  const size_t n = processes->GetSize();
73  for (size_t i = 0; i < n; ++i) {
74    base::DictionaryValue* process;
75    if (!processes->GetDictionary(i, &process))
76      return NULL;
77    int id;
78    if (process->GetInteger("pid", &id) && id == static_cast<int>(pid))
79      return process;
80  }
81  return NULL;
82}
83
84void GetAllWebContents(std::set<content::WebContents*>* web_contents) {
85  // Add all the existing WebContentses.
86#if defined(OS_ANDROID)
87  for (TabModelList::const_iterator iter = TabModelList::begin();
88       iter != TabModelList::end(); ++iter) {
89    TabModel* model = *iter;
90    for (int i = 0; i < model->GetTabCount(); ++i) {
91      content::WebContents* tab_web_contents = model->GetWebContentsAt(i);
92      if (tab_web_contents)
93        web_contents->insert(tab_web_contents);
94    }
95  }
96#else
97  for (TabContentsIterator iter; !iter.done(); iter.Next())
98    web_contents->insert(*iter);
99#endif
100  // Add all the prerender pages.
101  std::vector<Profile*> profiles(
102      g_browser_process->profile_manager()->GetLoadedProfiles());
103  for (size_t i = 0; i < profiles.size(); ++i) {
104    prerender::PrerenderManager* prerender_manager =
105        prerender::PrerenderManagerFactory::GetForProfile(profiles[i]);
106    if (!prerender_manager)
107      continue;
108    const std::vector<content::WebContents*> contentses =
109        prerender_manager->GetAllPrerenderingContents();
110    web_contents->insert(contentses.begin(), contentses.end());
111  }
112#if defined(ENABLE_FULL_PRINTING)
113  // Add all the pages being background printed.
114  printing::BackgroundPrintingManager* printing_manager =
115      g_browser_process->background_printing_manager();
116  std::set<content::WebContents*> printing_contents =
117      printing_manager->CurrentContentSet();
118  web_contents->insert(printing_contents.begin(), printing_contents.end());
119#endif
120}
121
122}  // namespace
123
124class RendererDetails : public content::NotificationObserver {
125 public:
126  typedef base::Callback<void(const base::ProcessId pid,
127                              const size_t v8_allocated,
128                              const size_t v8_used)> V8DataCallback;
129
130  explicit RendererDetails(const V8DataCallback& callback)
131      : callback_(callback) {
132    registrar_.Add(this, chrome::NOTIFICATION_RENDERER_V8_HEAP_STATS_COMPUTED,
133                   content::NotificationService::AllSources());
134  }
135  virtual ~RendererDetails() {}
136
137  void Request() {
138    for (std::set<content::WebContents*>::iterator iter = web_contents_.begin();
139         iter != web_contents_.end(); ++iter)
140      (*iter)->GetRenderViewHost()->Send(new ChromeViewMsg_GetV8HeapStats);
141  }
142
143  void AddWebContents(content::WebContents* content) {
144    web_contents_.insert(content);
145  }
146
147  void Clear() {
148    web_contents_.clear();
149  }
150
151  void RemoveWebContents(base::ProcessId) {
152    // We don't have to detect which content is the caller of this method.
153    if (!web_contents_.empty())
154      web_contents_.erase(web_contents_.begin());
155  }
156
157  int IsClean() {
158    return web_contents_.empty();
159  }
160
161 private:
162  // NotificationObserver:
163  virtual void Observe(int type,
164                       const content::NotificationSource& source,
165                       const content::NotificationDetails& details) OVERRIDE {
166    const base::ProcessId* pid =
167        content::Source<const base::ProcessId>(source).ptr();
168    const ChromeRenderMessageFilter::V8HeapStatsDetails* v8_heap =
169        content::Details<const ChromeRenderMessageFilter::V8HeapStatsDetails>(
170            details).ptr();
171    callback_.Run(*pid,
172                  v8_heap->v8_memory_allocated(),
173                  v8_heap->v8_memory_used());
174  }
175
176  V8DataCallback callback_;
177  content::NotificationRegistrar registrar_;
178  std::set<content::WebContents*> web_contents_;  // This class does not own
179
180  DISALLOW_COPY_AND_ASSIGN(RendererDetails);
181};
182
183MemoryInternalsProxy::MemoryInternalsProxy()
184    : information_(new base::DictionaryValue()),
185      renderer_details_(new RendererDetails(
186          base::Bind(&MemoryInternalsProxy::OnRendererAvailable, this))) {}
187
188void MemoryInternalsProxy::Attach(MemoryInternalsHandler* handler) {
189  DCHECK_CURRENTLY_ON(BrowserThread::UI);
190  handler_ = handler;
191}
192
193void MemoryInternalsProxy::Detach() {
194  DCHECK_CURRENTLY_ON(BrowserThread::UI);
195  handler_ = NULL;
196}
197
198void MemoryInternalsProxy::StartFetch(const base::ListValue* list) {
199  DCHECK_CURRENTLY_ON(BrowserThread::UI);
200
201  // Clear previous information before fetching new information.
202  information_->Clear();
203  scoped_refptr<ProcessDetails> process(new ProcessDetails(
204      base::Bind(&MemoryInternalsProxy::OnProcessAvailable, this)));
205  process->StartFetch(MemoryDetails::SKIP_USER_METRICS);
206}
207
208MemoryInternalsProxy::~MemoryInternalsProxy() {}
209
210void MemoryInternalsProxy::RequestRendererDetails() {
211  renderer_details_->Clear();
212
213#if defined(OS_ANDROID)
214  for (TabModelList::const_iterator iter = TabModelList::begin();
215       iter != TabModelList::end(); ++iter) {
216    TabModel* model = *iter;
217    for (int i = 0; i < model->GetTabCount(); ++i) {
218      content::WebContents* tab_web_contents = model->GetWebContentsAt(i);
219      if (tab_web_contents)
220        renderer_details_->AddWebContents(tab_web_contents);
221    }
222  }
223#else
224  for (TabContentsIterator iter; !iter.done(); iter.Next())
225    renderer_details_->AddWebContents(*iter);
226#endif
227
228  renderer_details_->Request();
229}
230
231void MemoryInternalsProxy::OnProcessAvailable(const ProcessData& browser) {
232  base::ListValue* process_info = new base::ListValue();
233  base::ListValue* extension_info = new base::ListValue();
234  information_->Set("processes", process_info);
235  information_->Set("extensions", extension_info);
236  for (PMIIterator iter = browser.processes.begin();
237       iter != browser.processes.end(); ++iter) {
238    base::DictionaryValue* process = new base::DictionaryValue();
239    if (iter->renderer_type == ProcessMemoryInformation::RENDERER_EXTENSION)
240      extension_info->Append(process);
241    else
242      process_info->Append(process);
243
244    // From MemoryDetails.
245    process->SetInteger("pid", iter->pid);
246    process->SetString("type",
247                       ProcessMemoryInformation::GetFullTypeNameInEnglish(
248                           iter->process_type, iter->renderer_type));
249    process->SetInteger("memory_private", iter->working_set.priv);
250
251    base::ListValue* titles = new base::ListValue();
252    process->Set("titles", titles);
253    for (size_t i = 0; i < iter->titles.size(); ++i)
254      titles->AppendString(iter->titles[i]);
255  }
256
257  std::set<content::WebContents*> web_contents;
258  GetAllWebContents(&web_contents);
259  ConvertTabsInformation(web_contents, process_info);
260
261  RequestRendererDetails();
262}
263
264void MemoryInternalsProxy::OnRendererAvailable(const base::ProcessId pid,
265                                               const size_t v8_allocated,
266                                               const size_t v8_used) {
267  // Do not update while no renderers are registered.
268  if (renderer_details_->IsClean())
269    return;
270
271  base::ListValue* processes;
272  if (!information_->GetList("processes", &processes))
273    return;
274
275  const size_t size = processes->GetSize();
276  for (size_t i = 0; i < size; ++i) {
277    base::DictionaryValue* process;
278    processes->GetDictionary(i, &process);
279    int id;
280    if (!process->GetInteger("pid", &id) || id != static_cast<int>(pid))
281      continue;
282    // Convert units from Bytes to KiB.
283    process->SetInteger("v8_alloc", v8_allocated / 1024);
284    process->SetInteger("v8_used", v8_used / 1024);
285    break;
286  }
287
288  renderer_details_->RemoveWebContents(pid);
289  if (renderer_details_->IsClean())
290    FinishCollection();
291}
292
293void MemoryInternalsProxy::ConvertTabsInformation(
294    const std::set<content::WebContents*>& web_contents,
295    base::ListValue* processes) {
296  for (std::set<content::WebContents*>::const_iterator
297           iter = web_contents.begin(); iter != web_contents.end(); ++iter) {
298    content::WebContents* web = *iter;
299    content::RenderProcessHost* process_host = web->GetRenderProcessHost();
300    if (!process_host)
301        continue;
302
303    // Find which process renders the web contents.
304    const base::ProcessId pid = base::GetProcId(process_host->GetHandle());
305    base::DictionaryValue* process = FindProcessFromPid(processes, pid);
306    if (!process)
307      continue;
308
309    // Prepare storage to register navigation histories.
310    base::ListValue* tabs;
311    if (!process->GetList("history", &tabs)) {
312      tabs = new base::ListValue();
313      process->Set("history", tabs);
314    }
315
316    base::DictionaryValue* tab = new base::DictionaryValue();
317    tabs->Append(tab);
318
319    base::ListValue* histories = new base::ListValue();
320    tab->Set("history", histories);
321
322    const content::NavigationController& controller = web->GetController();
323    const int entry_size = controller.GetEntryCount();
324    for (int i = 0; i < entry_size; ++i) {
325      content::NavigationEntry *entry = controller.GetEntryAtIndex(i);
326      base::DictionaryValue* history = new base::DictionaryValue();
327      histories->Append(history);
328      history->SetString("url", entry->GetURL().spec());
329      history->SetString("title", entry->GetTitle());
330      history->SetInteger("time", (base::Time::Now() -
331                                   entry->GetTimestamp()).InSeconds());
332    }
333    tab->SetInteger("index", controller.GetCurrentEntryIndex());
334  }
335}
336
337void MemoryInternalsProxy::FinishCollection() {
338  information_->SetInteger("uptime", base::SysInfo::Uptime());
339  information_->SetString("os", base::SysInfo::OperatingSystemName());
340  information_->SetString("os_version",
341                          base::SysInfo::OperatingSystemVersion());
342
343  CallJavaScriptFunctionOnUIThread("g_main_view.onSetSnapshot", *information_);
344}
345
346void MemoryInternalsProxy::CallJavaScriptFunctionOnUIThread(
347    const std::string& function, const base::Value& args) {
348  DCHECK_CURRENTLY_ON(BrowserThread::UI);
349
350  std::vector<const base::Value*> args_vector(1, &args);
351  base::string16 update =
352      content::WebUI::GetJavascriptCall(function, args_vector);
353  // Don't forward updates to a destructed UI.
354  if (handler_)
355    handler_->OnUpdate(update);
356}
357