1// Copyright (c) 2012 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/memory_details.h"
6
7#include <sys/types.h>
8#include <unistd.h>
9
10#include <map>
11#include <set>
12
13#include "base/bind.h"
14#include "base/file_util.h"
15#include "base/process/process_iterator.h"
16#include "base/process/process_metrics.h"
17#include "base/strings/string_number_conversions.h"
18#include "base/strings/string_util.h"
19#include "base/strings/utf_string_conversions.h"
20#include "chrome/common/chrome_constants.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/common/process_type.h"
23#include "grit/chromium_strings.h"
24#include "ui/base/l10n/l10n_util.h"
25
26using base::ProcessEntry;
27using content::BrowserThread;
28
29// Known browsers which we collect details for.
30enum BrowserType {
31  CHROME = 0,
32  FIREFOX,
33  ICEWEASEL,
34  OPERA,
35  KONQUEROR,
36  EPIPHANY,
37  MIDORI,
38  MAX_BROWSERS
39} BrowserProcess;
40
41// The pretty printed names of those browsers. Matches up with enum
42// BrowserType.
43static const char kBrowserPrettyNames[][10] = {
44  "Chrome",
45  "Firefox",
46  "Iceweasel",
47  "Opera",
48  "Konqueror",
49  "Epiphany",
50  "Midori",
51};
52
53// A mapping from process name to the type of browser.
54static const struct {
55  const char process_name[16];
56  BrowserType browser;
57} kBrowserBinaryNames[] = {
58  { "firefox", FIREFOX },
59  { "firefox-3.5", FIREFOX },
60  { "firefox-3.0", FIREFOX },
61  { "firefox-bin", FIREFOX },
62  { "iceweasel", ICEWEASEL },
63  { "opera", OPERA },
64  { "konqueror", KONQUEROR },
65  { "epiphany-browse", EPIPHANY },
66  { "epiphany", EPIPHANY },
67  { "midori", MIDORI },
68  { "", MAX_BROWSERS },
69};
70
71MemoryDetails::MemoryDetails()
72    : user_metrics_mode_(UPDATE_USER_METRICS) {
73}
74
75ProcessData* MemoryDetails::ChromeBrowser() {
76  return &process_data_[0];
77}
78
79struct Process {
80  pid_t pid;
81  pid_t parent;
82  std::string name;
83};
84
85typedef std::map<pid_t, Process> ProcessMap;
86
87// Get information on all the processes running on the system.
88static ProcessMap GetProcesses() {
89  ProcessMap map;
90
91  base::ProcessIterator process_iter(NULL);
92  while (const ProcessEntry* process_entry = process_iter.NextProcessEntry()) {
93    Process process;
94    process.pid = process_entry->pid();
95    process.parent = process_entry->parent_pid();
96    process.name = process_entry->exe_file();
97    map.insert(std::make_pair(process.pid, process));
98  }
99  return map;
100}
101
102// Given a process name, return the type of the browser which created that
103// process, or |MAX_BROWSERS| if we don't know about it.
104static BrowserType GetBrowserType(const std::string& process_name) {
105  for (unsigned i = 0; kBrowserBinaryNames[i].process_name[0]; ++i) {
106    if (strcmp(process_name.c_str(), kBrowserBinaryNames[i].process_name) == 0)
107      return kBrowserBinaryNames[i].browser;
108  }
109
110  return MAX_BROWSERS;
111}
112
113// For each of a list of pids, collect memory information about that process.
114static ProcessData GetProcessDataMemoryInformation(
115    const std::vector<pid_t>& pids) {
116  ProcessData process_data;
117  for (std::vector<pid_t>::const_iterator iter = pids.begin();
118       iter != pids.end();
119       ++iter) {
120    ProcessMemoryInformation pmi;
121
122    pmi.pid = *iter;
123    pmi.num_processes = 1;
124
125    if (pmi.pid == base::GetCurrentProcId())
126      pmi.process_type = content::PROCESS_TYPE_BROWSER;
127    else
128      pmi.process_type = content::PROCESS_TYPE_UNKNOWN;
129
130    base::ProcessMetrics* metrics =
131        base::ProcessMetrics::CreateProcessMetrics(*iter);
132    metrics->GetWorkingSetKBytes(&pmi.working_set);
133    delete metrics;
134
135    process_data.processes.push_back(pmi);
136  }
137  return process_data;
138}
139
140// Find all children of the given process with pid |root|.
141static std::vector<pid_t> GetAllChildren(const ProcessMap& processes,
142                                         const pid_t root) {
143  std::vector<pid_t> children;
144  children.push_back(root);
145
146  std::set<pid_t> wavefront, next_wavefront;
147  wavefront.insert(root);
148
149  while (wavefront.size()) {
150    for (ProcessMap::const_iterator iter = processes.begin();
151         iter != processes.end();
152         ++iter) {
153      const Process& process = iter->second;
154      if (wavefront.count(process.parent)) {
155        children.push_back(process.pid);
156        next_wavefront.insert(process.pid);
157      }
158    }
159
160    wavefront.clear();
161    wavefront.swap(next_wavefront);
162  }
163  return children;
164}
165
166#if defined(OS_CHROMEOS)
167static uint64 ReadFileToUint64(const base::FilePath file) {
168  std::string file_as_string;
169  if (!file_util::ReadFileToString(file, &file_as_string))
170    return 0;
171  TrimWhitespaceASCII(file_as_string, TRIM_ALL, &file_as_string);
172  uint64 file_as_uint64 = 0;
173  if (!base::StringToUint64(file_as_string, &file_as_uint64))
174    return 0;
175  return file_as_uint64;
176}
177
178static void GetSwapData(SwapData* swap_data) {
179  base::FilePath zram_path("/sys/block/zram0");
180  uint64 orig_data_size = ReadFileToUint64(zram_path.Append("orig_data_size"));
181  if (orig_data_size <= 4096) {
182    // A single page is compressed at startup, and has a high compression
183    // ratio. We ignore this as it doesn't indicate any real swapping.
184    swap_data->orig_data_size = 0;
185    swap_data->num_reads = 0;
186    swap_data->num_writes = 0;
187    swap_data->compr_data_size = 0;
188    swap_data->mem_used_total = 0;
189    return;
190  }
191  swap_data->orig_data_size = orig_data_size;
192  swap_data->num_reads = ReadFileToUint64(zram_path.Append("num_reads"));
193  swap_data->num_writes = ReadFileToUint64(zram_path.Append("num_writes"));
194  swap_data->compr_data_size =
195      ReadFileToUint64(zram_path.Append("compr_data_size"));
196  swap_data->mem_used_total =
197      ReadFileToUint64(zram_path.Append("mem_used_total"));
198}
199#endif
200
201void MemoryDetails::CollectProcessData(
202    const std::vector<ProcessMemoryInformation>& child_info) {
203  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
204
205  ProcessMap process_map = GetProcesses();
206  std::set<pid_t> browsers_found;
207
208  // For each process on the system, if it appears to be a browser process and
209  // it's parent isn't a browser process, then record it in |browsers_found|.
210  for (ProcessMap::const_iterator iter = process_map.begin();
211       iter != process_map.end();
212       ++iter) {
213    const Process& current_process = iter->second;
214    const BrowserType type = GetBrowserType(current_process.name);
215    if (type == MAX_BROWSERS)
216      continue;
217
218    ProcessMap::const_iterator parent_iter =
219        process_map.find(current_process.parent);
220    if (parent_iter == process_map.end()) {
221      browsers_found.insert(current_process.pid);
222      continue;
223    }
224
225    if (GetBrowserType(parent_iter->second.name) != type) {
226      // We found a process whose type is diffent from its parent's type.
227      // That means it is the root process of the browser.
228      browsers_found.insert(current_process.pid);
229      break;
230    }
231  }
232
233  ProcessData current_browser =
234      GetProcessDataMemoryInformation(GetAllChildren(process_map, getpid()));
235  current_browser.name = l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME);
236  current_browser.process_name = ASCIIToUTF16("chrome");
237
238  for (std::vector<ProcessMemoryInformation>::iterator
239       i = current_browser.processes.begin();
240       i != current_browser.processes.end(); ++i) {
241    // Check if this is one of the child processes whose data we collected
242    // on the IO thread, and if so copy over that data.
243    for (size_t child = 0; child < child_info.size(); child++) {
244      if (child_info[child].pid != i->pid)
245        continue;
246      i->titles = child_info[child].titles;
247      i->process_type = child_info[child].process_type;
248      break;
249    }
250  }
251
252  process_data_.push_back(current_browser);
253
254  // For each browser process, collect a list of its children and get the
255  // memory usage of each.
256  for (std::set<pid_t>::const_iterator iter = browsers_found.begin();
257       iter != browsers_found.end();
258       ++iter) {
259    std::vector<pid_t> browser_processes = GetAllChildren(process_map, *iter);
260    ProcessData browser = GetProcessDataMemoryInformation(browser_processes);
261
262    ProcessMap::const_iterator process_iter = process_map.find(*iter);
263    if (process_iter == process_map.end())
264      continue;
265    BrowserType type = GetBrowserType(process_iter->second.name);
266    if (type != MAX_BROWSERS)
267      browser.name = ASCIIToUTF16(kBrowserPrettyNames[type]);
268    process_data_.push_back(browser);
269  }
270
271#if defined(OS_CHROMEOS)
272  GetSwapData(&swap_data_);
273#endif
274
275  // Finally return to the browser thread.
276  BrowserThread::PostTask(
277      BrowserThread::UI, FROM_HERE,
278      base::Bind(&MemoryDetails::CollectChildInfoOnUIThread, this));
279}
280