[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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 | |
[email protected] | 8e2b647 | 2010-12-15 22:19:48 | [diff] [blame^] | 11 | #include <set> |
| 12 | |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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" |
[email protected] | be1ce6a7 | 2010-08-03 14:35:22 | [diff] [blame] | 17 | #include "base/utf_string_conversions.h" |
[email protected] | d27893f6 | 2010-07-03 05:47:42 | [diff] [blame] | 18 | #include "chrome/browser/browser_child_process_host.h" |
[email protected] | e3671727 | 2010-10-12 12:07:13 | [diff] [blame] | 19 | #include "chrome/browser/browser_thread.h" |
[email protected] | 21fc8e7 | 2010-03-09 21:25:19 | [diff] [blame] | 20 | #include "chrome/browser/zygote_host_linux.h" |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 21 | #include "chrome/common/chrome_constants.h" |
| 22 | #include "chrome/common/url_constants.h" |
| 23 | #include "grit/chromium_strings.h" |
| 24 | |
| 25 | // Known browsers which we collect details for. |
| 26 | enum BrowserType { |
| 27 | CHROME = 0, |
| 28 | FIREFOX, |
[email protected] | f3f032f8 | 2010-07-07 17:32:53 | [diff] [blame] | 29 | ICEWEASEL, |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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. |
| 39 | static const char kBrowserPrettyNames[][10] = { |
| 40 | "Chrome", |
| 41 | "Firefox", |
[email protected] | f3f032f8 | 2010-07-07 17:32:53 | [diff] [blame] | 42 | "Iceweasel", |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 43 | "Opera", |
| 44 | "Konqueror", |
| 45 | "Epiphany", |
| 46 | "Midori", |
| 47 | }; |
| 48 | |
| 49 | // A mapping from process name to the type of browser. |
| 50 | static const struct { |
[email protected] | 57678648 | 2010-04-05 18:54:32 | [diff] [blame] | 51 | const char process_name[16]; |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 52 | BrowserType browser; |
| 53 | } kBrowserBinaryNames[] = { |
| 54 | { "firefox", FIREFOX }, |
| 55 | { "firefox-3.5", FIREFOX }, |
| 56 | { "firefox-3.0", FIREFOX }, |
[email protected] | 57678648 | 2010-04-05 18:54:32 | [diff] [blame] | 57 | { "firefox-bin", FIREFOX }, |
[email protected] | f3f032f8 | 2010-07-07 17:32:53 | [diff] [blame] | 58 | { "iceweasel", ICEWEASEL }, |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 59 | { "opera", OPERA }, |
| 60 | { "konqueror", KONQUEROR }, |
[email protected] | 57678648 | 2010-04-05 18:54:32 | [diff] [blame] | 61 | { "epiphany-browse", EPIPHANY }, |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 62 | { "epiphany", EPIPHANY }, |
| 63 | { "midori", MIDORI }, |
| 64 | { "", MAX_BROWSERS }, |
| 65 | }; |
| 66 | |
[email protected] | 6fad263 | 2009-11-02 05:59:37 | [diff] [blame] | 67 | MemoryDetails::MemoryDetails() { |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | ProcessData* MemoryDetails::ChromeBrowser() { |
| 71 | return &process_data_[0]; |
| 72 | } |
| 73 | |
| 74 | struct 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. |
| 81 | static 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)); |
[email protected] | 19cb929 | 2010-04-16 23:00:15 | [diff] [blame] | 110 | if (HANDLE_EINTR(close(fd)) < 0) |
| 111 | PLOG(ERROR) << "close"; |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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. |
| 137 | static 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| |
| 148 | static 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. |
| 172 | static void GetAllChildren(const std::vector<Process>& processes, |
[email protected] | 21fc8e7 | 2010-03-09 21:25:19 | [diff] [blame] | 173 | const pid_t root, const pid_t zygote, |
| 174 | std::vector<pid_t>* out) { |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 175 | out->clear(); |
| 176 | out->push_back(root); |
| 177 | |
| 178 | std::set<pid_t> wavefront, next_wavefront; |
| 179 | wavefront.insert(root); |
[email protected] | 21fc8e7 | 2010-03-09 21:25:19 | [diff] [blame] | 180 | bool zygote_found = zygote ? false : true; |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 181 | |
| 182 | while (wavefront.size()) { |
| 183 | for (std::vector<Process>::const_iterator |
| 184 | i = processes.begin(); i != processes.end(); ++i) { |
[email protected] | 21fc8e7 | 2010-03-09 21:25:19 | [diff] [blame] | 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)) { |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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 | |
| 202 | void MemoryDetails::CollectProcessData( |
| 203 | std::vector<ProcessMemoryInformation> child_info) { |
[email protected] | f8b3ef8 | 2010-10-11 02:45:52 | [diff] [blame] | 204 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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; |
[email protected] | d3c6c0d7 | 2010-12-09 08:15:04 | [diff] [blame] | 239 | const pid_t zygote = ZygoteHost::GetInstance()->pid(); |
[email protected] | 21fc8e7 | 2010-03-09 21:25:19 | [diff] [blame] | 240 | GetAllChildren(processes, getpid(), zygote, ¤t_browser_processes); |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 241 | ProcessData current_browser; |
| 242 | GetProcessDataMemoryInformation(current_browser_processes, ¤t_browser); |
| 243 | current_browser.name = chrome::kBrowserAppName; |
| 244 | current_browser.process_name = L"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; |
[email protected] | 21fc8e7 | 2010-03-09 21:25:19 | [diff] [blame] | 252 | GetAllChildren(processes, *i, 0, &browser_processes); |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 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 = ASCIIToWide(kBrowserPrettyNames[type]); |
| 262 | break; |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | process_data_.push_back(browser); |
| 267 | } |
| 268 | |
| 269 | // Finally return to the browser thread. |
[email protected] | f8b3ef8 | 2010-10-11 02:45:52 | [diff] [blame] | 270 | BrowserThread::PostTask( |
| 271 | BrowserThread::UI, FROM_HERE, |
[email protected] | 54fd1d3 | 2009-09-01 00:12:58 | [diff] [blame] | 272 | NewRunnableMethod(this, &MemoryDetails::CollectChildInfoOnUIThread)); |
| 273 | } |