1// Copyright (c) 2006-2008 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 <unistd.h>
8#include <fcntl.h>
9#include <dirent.h>
10
11#include <set>
12
13#include "base/eintr_wrapper.h"
14#include "base/file_version_info.h"
15#include "base/string_util.h"
16#include "base/process_util.h"
17#include "base/utf_string_conversions.h"
18#include "chrome/common/chrome_constants.h"
19#include "chrome/common/url_constants.h"
20#include "content/browser/browser_child_process_host.h"
21#include "content/browser/browser_thread.h"
22#include "content/browser/zygote_host_linux.h"
23#include "grit/chromium_strings.h"
24
25// Known browsers which we collect details for.
26enum BrowserType {
27  CHROME = 0,
28  FIREFOX,
29  ICEWEASEL,
30  OPERA,
31  KONQUEROR,
32  EPIPHANY,
33  MIDORI,
34  MAX_BROWSERS
35} BrowserProcess;
36
37// The pretty printed names of those browsers. Matches up with enum
38// BrowserType.
39static const char kBrowserPrettyNames[][10] = {
40  "Chrome",
41  "Firefox",
42  "Iceweasel",
43  "Opera",
44  "Konqueror",
45  "Epiphany",
46  "Midori",
47};
48
49// A mapping from process name to the type of browser.
50static const struct {
51  const char process_name[16];
52  BrowserType browser;
53  } kBrowserBinaryNames[] = {
54  { "firefox", FIREFOX },
55  { "firefox-3.5", FIREFOX },
56  { "firefox-3.0", FIREFOX },
57  { "firefox-bin", FIREFOX },
58  { "iceweasel", ICEWEASEL },
59  { "opera", OPERA },
60  { "konqueror", KONQUEROR },
61  { "epiphany-browse", EPIPHANY },
62  { "epiphany", EPIPHANY },
63  { "midori", MIDORI },
64  { "", MAX_BROWSERS },
65};
66
67MemoryDetails::MemoryDetails() {
68}
69
70ProcessData* MemoryDetails::ChromeBrowser() {
71  return &process_data_[0];
72}
73
74struct Process {
75  pid_t pid;
76  pid_t parent;
77  std::string name;
78};
79
80// Walk /proc and get information on all the processes running on the system.
81static bool GetProcesses(std::vector<Process>* processes) {
82  processes->clear();
83
84  DIR* dir = opendir("/proc");
85  if (!dir)
86    return false;
87
88  struct dirent* dent;
89  while ((dent = readdir(dir))) {
90    bool candidate = true;
91
92    // Filter out names which aren't ^[0-9]*$
93    for (const char* p = dent->d_name; *p; ++p) {
94      if (*p < '0' || *p > '9') {
95        candidate = false;
96        break;
97      }
98    }
99
100    if (!candidate)
101      continue;
102
103    char buf[256];
104    snprintf(buf, sizeof(buf), "/proc/%s/stat", dent->d_name);
105    const int fd = open(buf, O_RDONLY);
106    if (fd < 0)
107      continue;
108
109    const ssize_t len = HANDLE_EINTR(read(fd, buf, sizeof(buf) - 1));
110    if (HANDLE_EINTR(close(fd)) < 0)
111      PLOG(ERROR) << "close";
112    if (len < 1)
113      continue;
114    buf[len] = 0;
115
116    // The start of the file looks like:
117    //   <pid> (<name>) R <parent pid>
118    unsigned pid, ppid;
119    char *process_name;
120    if (sscanf(buf, "%u (%a[^)]) %*c %u", &pid, &process_name, &ppid) != 3)
121      continue;
122
123    Process process;
124    process.pid = pid;
125    process.parent = ppid;
126    process.name = process_name;
127    free(process_name);
128    processes->push_back(process);
129  }
130
131  closedir(dir);
132  return true;
133}
134
135// Given a process name, return the type of the browser which created that
136// process, or |MAX_BROWSERS| if we don't know about it.
137static BrowserType GetBrowserType(const std::string& process_name) {
138  for (unsigned i = 0; kBrowserBinaryNames[i].process_name[0]; ++i) {
139    if (strcmp(process_name.c_str(), kBrowserBinaryNames[i].process_name) == 0)
140      return kBrowserBinaryNames[i].browser;
141  }
142
143  return MAX_BROWSERS;
144}
145
146// For each of a list of pids, collect memory information about that process
147// and append a record to |out|
148static void GetProcessDataMemoryInformation(
149    const std::vector<pid_t>& pids, ProcessData* out) {
150  for (std::vector<pid_t>::const_iterator
151       i = pids.begin(); i != pids.end(); ++i) {
152    ProcessMemoryInformation pmi;
153
154    pmi.pid = *i;
155    pmi.num_processes = 1;
156
157    if (pmi.pid == base::GetCurrentProcId())
158      pmi.type = ChildProcessInfo::BROWSER_PROCESS;
159    else
160      pmi.type = ChildProcessInfo::UNKNOWN_PROCESS;
161
162    base::ProcessMetrics* metrics =
163        base::ProcessMetrics::CreateProcessMetrics(*i);
164    metrics->GetWorkingSetKBytes(&pmi.working_set);
165    delete metrics;
166
167    out->processes.push_back(pmi);
168  }
169}
170
171// Find all children of the given process.
172static void GetAllChildren(const std::vector<Process>& processes,
173                           const pid_t root, const pid_t zygote,
174                           std::vector<pid_t>* out) {
175  out->clear();
176  out->push_back(root);
177
178  std::set<pid_t> wavefront, next_wavefront;
179  wavefront.insert(root);
180  bool zygote_found = zygote ? false : true;
181
182  while (wavefront.size()) {
183    for (std::vector<Process>::const_iterator
184         i = processes.begin(); i != processes.end(); ++i) {
185      // Handle the zygote separately. With the SUID sandbox and a separate
186      // pid namespace, the zygote's parent process is not the browser.
187      if (!zygote_found && zygote == i->pid) {
188        zygote_found = true;
189        out->push_back(i->pid);
190        next_wavefront.insert(i->pid);
191      } else if (wavefront.count(i->parent)) {
192        out->push_back(i->pid);
193        next_wavefront.insert(i->pid);
194      }
195    }
196
197    wavefront.clear();
198    wavefront.swap(next_wavefront);
199  }
200}
201
202void MemoryDetails::CollectProcessData(
203    const std::vector<ProcessMemoryInformation>& child_info) {
204  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
205
206  std::vector<Process> processes;
207  GetProcesses(&processes);
208  std::set<pid_t> browsers_found;
209
210  // For each process on the system, if it appears to be a browser process and
211  // it's parent isn't a browser process, then record it in |browsers_found|.
212  for (std::vector<Process>::const_iterator
213       i = processes.begin(); i != processes.end(); ++i) {
214    const BrowserType type = GetBrowserType(i->name);
215    if (type != MAX_BROWSERS) {
216      bool found_parent = false;
217
218      // Find the parent of |i|
219      for (std::vector<Process>::const_iterator
220           j = processes.begin(); j != processes.end(); ++j) {
221        if (j->pid == i->parent) {
222          found_parent = true;
223
224          if (GetBrowserType(j->name) != type) {
225            // We went too far and ended up with something else, which means
226            // that |i| is a browser.
227            browsers_found.insert(i->pid);
228            break;
229          }
230        }
231      }
232
233      if (!found_parent)
234        browsers_found.insert(i->pid);
235    }
236  }
237
238  std::vector<pid_t> current_browser_processes;
239  const pid_t zygote = ZygoteHost::GetInstance()->pid();
240  GetAllChildren(processes, getpid(), zygote, &current_browser_processes);
241  ProcessData current_browser;
242  GetProcessDataMemoryInformation(current_browser_processes, &current_browser);
243  current_browser.name = WideToUTF16(chrome::kBrowserAppName);
244  current_browser.process_name = ASCIIToUTF16("chrome");
245  process_data_.push_back(current_browser);
246
247  // For each browser process, collect a list of its children and get the
248  // memory usage of each.
249  for (std::set<pid_t>::const_iterator
250       i = browsers_found.begin(); i != browsers_found.end(); ++i) {
251    std::vector<pid_t> browser_processes;
252    GetAllChildren(processes, *i, 0, &browser_processes);
253    ProcessData browser;
254    GetProcessDataMemoryInformation(browser_processes, &browser);
255
256    for (std::vector<Process>::const_iterator
257         j = processes.begin(); j != processes.end(); ++j) {
258      if (j->pid == *i) {
259        BrowserType type = GetBrowserType(j->name);
260        if (type != MAX_BROWSERS)
261          browser.name = ASCIIToUTF16(kBrowserPrettyNames[type]);
262        break;
263      }
264    }
265
266    process_data_.push_back(browser);
267  }
268
269  // Finally return to the browser thread.
270  BrowserThread::PostTask(
271      BrowserThread::UI, FROM_HERE,
272      NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnUIThread));
273}
274