Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(82)

Side by Side Diff: base/process_util_linux.cc

Issue 9568046: Linux: Stop using /proc/pid/smaps to calculate memory usage. It's too slow. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: grammar Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/process_util.h" 5 #include "base/process_util.h"
6 6
7 #include <ctype.h> 7 #include <ctype.h>
8 #include <dirent.h> 8 #include <dirent.h>
9 #include <dlfcn.h> 9 #include <dlfcn.h>
10 #include <errno.h> 10 #include <errno.h>
11 #include <fcntl.h> 11 #include <fcntl.h>
12 #include <sys/time.h> 12 #include <sys/time.h>
13 #include <sys/types.h> 13 #include <sys/types.h>
14 #include <sys/wait.h> 14 #include <sys/wait.h>
15 #include <time.h> 15 #include <time.h>
16 #include <unistd.h> 16 #include <unistd.h>
17 17
18 #include "base/file_util.h" 18 #include "base/file_util.h"
19 #include "base/logging.h" 19 #include "base/logging.h"
20 #include "base/stringprintf.h"
21 #include "base/string_number_conversions.h" 20 #include "base/string_number_conversions.h"
22 #include "base/string_split.h" 21 #include "base/string_split.h"
23 #include "base/string_tokenizer.h" 22 #include "base/string_tokenizer.h"
24 #include "base/string_util.h" 23 #include "base/string_util.h"
25 #include "base/sys_info.h" 24 #include "base/sys_info.h"
26 #include "base/threading/thread_restrictions.h" 25 #include "base/threading/thread_restrictions.h"
27 26
28 namespace { 27 namespace {
29 28
30 // Max score for the old oom_adj range. Used for conversion of new
31 // values to old values.
32 const int kMaxOldOomScore = 15;
33
34 enum ParsingState { 29 enum ParsingState {
35 KEY_NAME, 30 KEY_NAME,
36 KEY_VALUE 31 KEY_VALUE
37 }; 32 };
38 33
34 const char kProcDir[] = "/proc";
35
36 // Returns a FilePath to "/proc/pid".
37 FilePath GetProcPidDir(pid_t pid) {
38 return FilePath(kProcDir).Append(base::Int64ToString(pid));
39 }
40
39 // Reads /proc/<pid>/stat and populates |proc_stats| with the values split by 41 // Reads /proc/<pid>/stat and populates |proc_stats| with the values split by
40 // spaces. Returns true if successful. 42 // spaces. Returns true if successful.
41 bool GetProcStats(pid_t pid, std::vector<std::string>* proc_stats) { 43 bool GetProcStats(pid_t pid, std::vector<std::string>* proc_stats) {
42 // Synchronously reading files in /proc is safe. 44 // Synchronously reading files in /proc is safe.
43 base::ThreadRestrictions::ScopedAllowIO allow_io; 45 base::ThreadRestrictions::ScopedAllowIO allow_io;
44 46
45 FilePath stat_file("/proc"); 47 FilePath stat_file = GetProcPidDir(pid).Append("stat");
46 stat_file = stat_file.Append(base::IntToString(pid));
47 stat_file = stat_file.Append("stat");
48 std::string mem_stats; 48 std::string mem_stats;
49 if (!file_util::ReadFileToString(stat_file, &mem_stats)) 49 if (!file_util::ReadFileToString(stat_file, &mem_stats))
50 return false; 50 return false;
51 base::SplitString(mem_stats, ' ', proc_stats); 51 base::SplitString(mem_stats, ' ', proc_stats);
52 return true; 52 return true;
53 } 53 }
54 54
55 // Reads /proc/<pid>/cmdline and populates |proc_cmd_line_args| with the command 55 // Reads /proc/<pid>/cmdline and populates |proc_cmd_line_args| with the command
56 // line arguments. Returns true if successful. 56 // line arguments. Returns true if successful.
57 // Note: /proc/<pid>/cmdline contains command line arguments separated by single 57 // Note: /proc/<pid>/cmdline contains command line arguments separated by single
58 // null characters. We tokenize it into a vector of strings using '\0' as a 58 // null characters. We tokenize it into a vector of strings using '\0' as a
59 // delimiter. 59 // delimiter.
60 bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) { 60 bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) {
61 // Synchronously reading files in /proc is safe. 61 // Synchronously reading files in /proc is safe.
62 base::ThreadRestrictions::ScopedAllowIO allow_io; 62 base::ThreadRestrictions::ScopedAllowIO allow_io;
63 63
64 FilePath cmd_line_file("/proc"); 64 FilePath cmd_line_file = GetProcPidDir(pid).Append("cmdline");
65 cmd_line_file = cmd_line_file.Append(base::IntToString(pid));
66 cmd_line_file = cmd_line_file.Append("cmdline");
67 std::string cmd_line; 65 std::string cmd_line;
68 if (!file_util::ReadFileToString(cmd_line_file, &cmd_line)) 66 if (!file_util::ReadFileToString(cmd_line_file, &cmd_line))
69 return false; 67 return false;
70 std::string delimiters; 68 std::string delimiters;
71 delimiters.push_back('\0'); 69 delimiters.push_back('\0');
72 Tokenize(cmd_line, delimiters, proc_cmd_line_args); 70 Tokenize(cmd_line, delimiters, proc_cmd_line_args);
73 return true; 71 return true;
74 } 72 }
75 73
76 // Get the total CPU of a single process. Return value is number of jiffies 74 // Get the total CPU of a single process. Return value is number of jiffies
77 // on success or -1 on error. 75 // on success or -1 on error.
78 int GetProcessCPU(pid_t pid) { 76 int GetProcessCPU(pid_t pid) {
79 // Synchronously reading files in /proc is safe. 77 // Synchronously reading files in /proc is safe.
80 base::ThreadRestrictions::ScopedAllowIO allow_io; 78 base::ThreadRestrictions::ScopedAllowIO allow_io;
81 79
82 // Use /proc/<pid>/task to find all threads and parse their /stat file. 80 // Use /proc/<pid>/task to find all threads and parse their /stat file.
83 FilePath path = FilePath(base::StringPrintf("/proc/%d/task/", pid)); 81 FilePath path = GetProcPidDir(pid).Append("task");
84 82
85 DIR* dir = opendir(path.value().c_str()); 83 DIR* dir = opendir(path.value().c_str());
86 if (!dir) { 84 if (!dir) {
87 DPLOG(ERROR) << "opendir(" << path.value() << ")"; 85 DPLOG(ERROR) << "opendir(" << path.value() << ")";
88 return -1; 86 return -1;
89 } 87 }
90 88
91 int total_cpu = 0; 89 int total_cpu = 0;
92 while (struct dirent* ent = readdir(dir)) { 90 while (struct dirent* ent = readdir(dir)) {
93 if (ent->d_name[0] == '.') 91 if (ent->d_name[0] == '.')
(...skipping 13 matching lines...) Expand all
107 } 105 }
108 106
109 } // namespace 107 } // namespace
110 108
111 namespace base { 109 namespace base {
112 110
113 ProcessId GetParentProcessId(ProcessHandle process) { 111 ProcessId GetParentProcessId(ProcessHandle process) {
114 // Synchronously reading files in /proc is safe. 112 // Synchronously reading files in /proc is safe.
115 base::ThreadRestrictions::ScopedAllowIO allow_io; 113 base::ThreadRestrictions::ScopedAllowIO allow_io;
116 114
117 FilePath stat_file("/proc"); 115 FilePath stat_file = GetProcPidDir(process).Append("status");
118 stat_file = stat_file.Append(base::IntToString(process));
119 stat_file = stat_file.Append("status");
120 std::string status; 116 std::string status;
121 if (!file_util::ReadFileToString(stat_file, &status)) 117 if (!file_util::ReadFileToString(stat_file, &status))
122 return -1; 118 return -1;
123 119
124 StringTokenizer tokenizer(status, ":\n"); 120 StringTokenizer tokenizer(status, ":\n");
125 ParsingState state = KEY_NAME; 121 ParsingState state = KEY_NAME;
126 StringPiece last_key_name; 122 StringPiece last_key_name;
127 while (tokenizer.GetNext()) { 123 while (tokenizer.GetNext()) {
128 switch (state) { 124 switch (state) {
129 case KEY_NAME: 125 case KEY_NAME:
130 last_key_name = tokenizer.token_piece(); 126 last_key_name = tokenizer.token_piece();
131 state = KEY_VALUE; 127 state = KEY_VALUE;
132 break; 128 break;
133 case KEY_VALUE: 129 case KEY_VALUE:
134 DCHECK(!last_key_name.empty()); 130 DCHECK(!last_key_name.empty());
135 if (last_key_name == "PPid") { 131 if (last_key_name == "PPid") {
136 int ppid; 132 int ppid;
137 base::StringToInt(tokenizer.token_piece(), &ppid); 133 base::StringToInt(tokenizer.token_piece(), &ppid);
138 return ppid; 134 return ppid;
139 } 135 }
140 state = KEY_NAME; 136 state = KEY_NAME;
141 break; 137 break;
142 } 138 }
143 } 139 }
144 NOTREACHED(); 140 NOTREACHED();
145 return -1; 141 return -1;
146 } 142 }
147 143
148 FilePath GetProcessExecutablePath(ProcessHandle process) { 144 FilePath GetProcessExecutablePath(ProcessHandle process) {
149 FilePath stat_file("/proc"); 145 FilePath stat_file = GetProcPidDir(process).Append("exe");
150 stat_file = stat_file.Append(base::IntToString(process));
151 stat_file = stat_file.Append("exe");
152 FilePath exe_name; 146 FilePath exe_name;
153 if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) { 147 if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) {
154 // No such process. Happens frequently in e.g. TerminateAllChromeProcesses 148 // No such process. Happens frequently in e.g. TerminateAllChromeProcesses
155 return FilePath(); 149 return FilePath();
156 } 150 }
157 return exe_name; 151 return exe_name;
158 } 152 }
159 153
160 ProcessIterator::ProcessIterator(const ProcessFilter* filter) 154 ProcessIterator::ProcessIterator(const ProcessFilter* filter)
161 : filter_(filter) { 155 : filter_(filter) {
162 procfs_dir_ = opendir("/proc"); 156 procfs_dir_ = opendir(kProcDir);
163 } 157 }
164 158
165 ProcessIterator::~ProcessIterator() { 159 ProcessIterator::~ProcessIterator() {
166 if (procfs_dir_) { 160 if (procfs_dir_) {
167 closedir(procfs_dir_); 161 closedir(procfs_dir_);
168 procfs_dir_ = NULL; 162 procfs_dir_ = NULL;
169 } 163 }
170 } 164 }
171 165
172 bool ProcessIterator::CheckForNextProcess() { 166 bool ProcessIterator::CheckForNextProcess() {
(...skipping 30 matching lines...) Expand all
203 197
204 // Read the process's command line. 198 // Read the process's command line.
205 std::string pid_string(slot->d_name); 199 std::string pid_string(slot->d_name);
206 int pid; 200 int pid;
207 if (StringToInt(pid_string, &pid) && !GetProcCmdline(pid, &cmd_line_args)) 201 if (StringToInt(pid_string, &pid) && !GetProcCmdline(pid, &cmd_line_args))
208 continue; 202 continue;
209 203
210 // Read the process's status. 204 // Read the process's status.
211 char buf[NAME_MAX + 12]; 205 char buf[NAME_MAX + 12];
212 sprintf(buf, "/proc/%s/stat", slot->d_name); 206 sprintf(buf, "/proc/%s/stat", slot->d_name);
213 FILE *fp = fopen(buf, "r"); 207 FILE* fp = fopen(buf, "r");
214 if (!fp) 208 if (!fp)
215 continue; 209 continue;
216 const char* result = fgets(buf, sizeof(buf), fp); 210 const char* result = fgets(buf, sizeof(buf), fp);
217 fclose(fp); 211 fclose(fp);
218 if (!result) 212 if (!result)
219 continue; 213 continue;
220 214
221 // Parse the status. It is formatted like this: 215 // Parse the status. It is formatted like this:
222 // %d (%s) %c %d %d ... 216 // %d (%s) %c %d %d ...
223 // pid (name) runstate ppid gid 217 // pid (name) runstate ppid gid
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
333 327
334 if (private_bytes) 328 if (private_bytes)
335 *private_bytes = ws_usage.priv << 10; 329 *private_bytes = ws_usage.priv << 10;
336 330
337 if (shared_bytes) 331 if (shared_bytes)
338 *shared_bytes = ws_usage.shared * 1024; 332 *shared_bytes = ws_usage.shared * 1024;
339 333
340 return true; 334 return true;
341 } 335 }
342 336
343 // Private and Shared working set sizes are obtained from /proc/<pid>/smaps. 337 // Private and Shared working set sizes are obtained from /proc/<pid>/statm.
344 // When that's not available, use the values from /proc<pid>/statm as a
345 // close approximation.
346 // See http://www.pixelbeat.org/scripts/ps_mem.py
347 bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { 338 bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
348 // Synchronously reading files in /proc is safe. 339 // Use statm instead of smaps because smaps is:
349 base::ThreadRestrictions::ScopedAllowIO allow_io; 340 // a) Large and slow to parse.
341 // b) Unavailable in the SUID sandbox.
350 342
351 FilePath proc_dir = FilePath("/proc").Append(base::IntToString(process_)); 343 // First we need to get the page size, since everything is measured in pages.
352 std::string smaps; 344 // For details, see: man 5 proc.
353 int private_kb = 0; 345 const int page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024;
354 int pss_kb = 0; 346 if (page_size_kb <= 0)
355 bool have_pss = false; 347 return false;
356 bool ret;
357 348
349 std::string statm;
358 { 350 {
359 FilePath smaps_file = proc_dir.Append("smaps"); 351 FilePath statm_file = GetProcPidDir(process_).Append("statm");
360 // Synchronously reading files in /proc is safe. 352 // Synchronously reading files in /proc is safe.
361 base::ThreadRestrictions::ScopedAllowIO allow_io; 353 base::ThreadRestrictions::ScopedAllowIO allow_io;
362 ret = file_util::ReadFileToString(smaps_file, &smaps); 354 bool ret = file_util::ReadFileToString(statm_file, &statm);
363 }
364 if (ret && smaps.length() > 0) {
365 const std::string private_prefix = "Private_";
366 const std::string pss_prefix = "Pss";
367 StringTokenizer tokenizer(smaps, ":\n");
368 StringPiece last_key_name;
369 ParsingState state = KEY_NAME;
370 while (tokenizer.GetNext()) {
371 switch (state) {
372 case KEY_NAME:
373 last_key_name = tokenizer.token_piece();
374 state = KEY_VALUE;
375 break;
376 case KEY_VALUE:
377 if (last_key_name.empty()) {
378 NOTREACHED();
379 return false;
380 }
381 if (last_key_name.starts_with(private_prefix)) {
382 int cur;
383 base::StringToInt(tokenizer.token_piece(), &cur);
384 private_kb += cur;
385 } else if (last_key_name.starts_with(pss_prefix)) {
386 have_pss = true;
387 int cur;
388 base::StringToInt(tokenizer.token_piece(), &cur);
389 pss_kb += cur;
390 }
391 state = KEY_NAME;
392 break;
393 }
394 }
395 } else {
396 // Try statm if smaps is empty because of the SUID sandbox.
397 // First we need to get the page size though.
398 int page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024;
399 if (page_size_kb <= 0)
400 return false;
401
402 std::string statm;
403 {
404 FilePath statm_file = proc_dir.Append("statm");
405 // Synchronously reading files in /proc is safe.
406 base::ThreadRestrictions::ScopedAllowIO allow_io;
407 ret = file_util::ReadFileToString(statm_file, &statm);
408 }
409 if (!ret || statm.length() == 0) 355 if (!ret || statm.length() == 0)
410 return false; 356 return false;
357 }
411 358
412 std::vector<std::string> statm_vec; 359 std::vector<std::string> statm_vec;
413 base::SplitString(statm, ' ', &statm_vec); 360 base::SplitString(statm, ' ', &statm_vec);
414 if (statm_vec.size() != 7) 361 if (statm_vec.size() != 7)
415 return false; // Not the format we expect. 362 return false; // Not the format we expect.
416 363
417 int statm1, statm2; 364 int statm_rss, statm_shared;
418 base::StringToInt(statm_vec[1], &statm1); 365 base::StringToInt(statm_vec[1], &statm_rss);
419 base::StringToInt(statm_vec[2], &statm2); 366 base::StringToInt(statm_vec[2], &statm_shared);
420 private_kb = (statm1 - statm2) * page_size_kb; 367
421 } 368 ws_usage->priv = (statm_rss - statm_shared) * page_size_kb;
422 ws_usage->priv = private_kb; 369 ws_usage->shared = statm_shared * page_size_kb;
370
423 // Sharable is not calculated, as it does not provide interesting data. 371 // Sharable is not calculated, as it does not provide interesting data.
424 ws_usage->shareable = 0; 372 ws_usage->shareable = 0;
425 373
426 ws_usage->shared = 0;
427 if (have_pss)
428 ws_usage->shared = pss_kb;
429 return true; 374 return true;
430 } 375 }
431 376
432 double ProcessMetrics::GetCPUUsage() { 377 double ProcessMetrics::GetCPUUsage() {
433 // This queries the /proc-specific scaling factor which is 378 // This queries the /proc-specific scaling factor which is
434 // conceptually the system hertz. To dump this value on another 379 // conceptually the system hertz. To dump this value on another
435 // system, try 380 // system, try
436 // od -t dL /proc/self/auxv 381 // od -t dL /proc/self/auxv
437 // and look for the number after 17 in the output; mine is 382 // and look for the number after 17 in the output; mine is
438 // 0000040 17 100 3 134512692 383 // 0000040 17 100 3 134512692
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
472 return percentage; 417 return percentage;
473 } 418 }
474 419
475 // To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING 420 // To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING
476 // in your kernel configuration. 421 // in your kernel configuration.
477 bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { 422 bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
478 // Synchronously reading files in /proc is safe. 423 // Synchronously reading files in /proc is safe.
479 base::ThreadRestrictions::ScopedAllowIO allow_io; 424 base::ThreadRestrictions::ScopedAllowIO allow_io;
480 425
481 std::string proc_io_contents; 426 std::string proc_io_contents;
482 FilePath io_file("/proc"); 427 FilePath io_file = GetProcPidDir(process_).Append("io");
483 io_file = io_file.Append(base::IntToString(process_));
484 io_file = io_file.Append("io");
485 if (!file_util::ReadFileToString(io_file, &proc_io_contents)) 428 if (!file_util::ReadFileToString(io_file, &proc_io_contents))
486 return false; 429 return false;
487 430
488 (*io_counters).OtherOperationCount = 0; 431 (*io_counters).OtherOperationCount = 0;
489 (*io_counters).OtherTransferCount = 0; 432 (*io_counters).OtherTransferCount = 0;
490 433
491 StringTokenizer tokenizer(proc_io_contents, ": \n"); 434 StringTokenizer tokenizer(proc_io_contents, ": \n");
492 ParsingState state = KEY_NAME; 435 ParsingState state = KEY_NAME;
493 StringPiece last_key_name; 436 StringPiece last_key_name;
494 while (tokenizer.GetNext()) { 437 while (tokenizer.GetNext()) {
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after
738 #endif 681 #endif
739 } 682 }
740 683
741 // NOTE: This is not the only version of this function in the source: 684 // NOTE: This is not the only version of this function in the source:
742 // the setuid sandbox (in process_util_linux.c, in the sandbox source) 685 // the setuid sandbox (in process_util_linux.c, in the sandbox source)
743 // also has it's own C version. 686 // also has it's own C version.
744 bool AdjustOOMScore(ProcessId process, int score) { 687 bool AdjustOOMScore(ProcessId process, int score) {
745 if (score < 0 || score > kMaxOomScore) 688 if (score < 0 || score > kMaxOomScore)
746 return false; 689 return false;
747 690
748 FilePath oom_path("/proc"); 691 FilePath oom_path(GetProcPidDir(process));
749 oom_path = oom_path.Append(base::Int64ToString(process));
750 692
751 // Attempt to write the newer oom_score_adj file first. 693 // Attempt to write the newer oom_score_adj file first.
752 FilePath oom_file = oom_path.AppendASCII("oom_score_adj"); 694 FilePath oom_file = oom_path.AppendASCII("oom_score_adj");
753 if (file_util::PathExists(oom_file)) { 695 if (file_util::PathExists(oom_file)) {
754 std::string score_str = base::IntToString(score); 696 std::string score_str = base::IntToString(score);
755 DVLOG(1) << "Adjusting oom_score_adj of " << process << " to " 697 DVLOG(1) << "Adjusting oom_score_adj of " << process << " to "
756 << score_str; 698 << score_str;
757 int score_len = static_cast<int>(score_str.length()); 699 int score_len = static_cast<int>(score_str.length());
758 return (score_len == file_util::WriteFile(oom_file, 700 return (score_len == file_util::WriteFile(oom_file,
759 score_str.c_str(), 701 score_str.c_str(),
760 score_len)); 702 score_len));
761 } 703 }
762 704
763 // If the oom_score_adj file doesn't exist, then we write the old 705 // If the oom_score_adj file doesn't exist, then we write the old
764 // style file and translate the oom_adj score to the range 0-15. 706 // style file and translate the oom_adj score to the range 0-15.
765 oom_file = oom_path.AppendASCII("oom_adj"); 707 oom_file = oom_path.AppendASCII("oom_adj");
766 if (file_util::PathExists(oom_file)) { 708 if (file_util::PathExists(oom_file)) {
767 std::string score_str = base::IntToString( 709 // Max score for the old oom_adj range. Used for conversion of new
768 score * kMaxOldOomScore / kMaxOomScore); 710 // values to old values.
711 const int kMaxOldOomScore = 15;
712
713 int converted_score = score * kMaxOldOomScore / kMaxOomScore;
714 std::string score_str = base::IntToString(converted_score);
769 DVLOG(1) << "Adjusting oom_adj of " << process << " to " << score_str; 715 DVLOG(1) << "Adjusting oom_adj of " << process << " to " << score_str;
770 int score_len = static_cast<int>(score_str.length()); 716 int score_len = static_cast<int>(score_str.length());
771 return (score_len == file_util::WriteFile(oom_file, 717 return (score_len == file_util::WriteFile(oom_file,
772 score_str.c_str(), 718 score_str.c_str(),
773 score_len)); 719 score_len));
774 } 720 }
775 721
776 return false; 722 return false;
777 } 723 }
778 724
779 } // namespace base 725 } // namespace base
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698