[go: nahoru, domu]

blob: 85ab16e5b0c552da888c6f2f7b87ac45648fde7d [file] [log] [blame]
rsesek@chromium.org32f5e9a2013-05-23 12:59:541// Copyright (c) 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 "base/process/process_metrics.h"
6
7#include <dirent.h>
davemoore@chromium.org7b9c98872013-05-29 15:42:048#include <fcntl.h>
9#include <sys/stat.h>
rsesek@chromium.org32f5e9a2013-05-23 12:59:5410#include <sys/time.h>
11#include <sys/types.h>
12#include <unistd.h>
13
14#include "base/file_util.h"
15#include "base/logging.h"
16#include "base/process/internal_linux.h"
17#include "base/string_util.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_split.h"
20#include "base/strings/string_tokenizer.h"
21#include "base/sys_info.h"
22#include "base/threading/thread_restrictions.h"
23
24namespace base {
25
26namespace {
27
28enum ParsingState {
29 KEY_NAME,
30 KEY_VALUE
31};
32
33// Read /proc/<pid>/status and returns the value for |field|, or 0 on failure.
34// Only works for fields in the form of "Field: value kB".
35size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, const std::string& field) {
36 FilePath stat_file = internal::GetProcPidDir(pid).Append("status");
37 std::string status;
38 {
39 // Synchronously reading files in /proc is safe.
40 ThreadRestrictions::ScopedAllowIO allow_io;
41 if (!file_util::ReadFileToString(stat_file, &status))
42 return 0;
43 }
44
45 StringTokenizer tokenizer(status, ":\n");
46 ParsingState state = KEY_NAME;
47 StringPiece last_key_name;
48 while (tokenizer.GetNext()) {
49 switch (state) {
50 case KEY_NAME:
51 last_key_name = tokenizer.token_piece();
52 state = KEY_VALUE;
53 break;
54 case KEY_VALUE:
55 DCHECK(!last_key_name.empty());
56 if (last_key_name == field) {
57 std::string value_str;
58 tokenizer.token_piece().CopyToString(&value_str);
59 std::string value_str_trimmed;
60 TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed);
61 std::vector<std::string> split_value_str;
62 SplitString(value_str_trimmed, ' ', &split_value_str);
63 if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
64 NOTREACHED();
65 return 0;
66 }
67 size_t value;
68 if (!StringToSizeT(split_value_str[0], &value)) {
69 NOTREACHED();
70 return 0;
71 }
72 return value;
73 }
74 state = KEY_NAME;
75 break;
76 }
77 }
78 NOTREACHED();
79 return 0;
80}
81
82// Get the total CPU of a single process. Return value is number of jiffies
83// on success or -1 on error.
84int GetProcessCPU(pid_t pid) {
85 // Use /proc/<pid>/task to find all threads and parse their /stat file.
86 FilePath task_path = internal::GetProcPidDir(pid).Append("task");
87
88 DIR* dir = opendir(task_path.value().c_str());
89 if (!dir) {
90 DPLOG(ERROR) << "opendir(" << task_path.value() << ")";
91 return -1;
92 }
93
94 int total_cpu = 0;
95 while (struct dirent* ent = readdir(dir)) {
96 pid_t tid = internal::ProcDirSlotToPid(ent->d_name);
97 if (!tid)
98 continue;
99
100 // Synchronously reading files in /proc is safe.
101 ThreadRestrictions::ScopedAllowIO allow_io;
102
103 std::string stat;
104 FilePath stat_path =
105 task_path.Append(ent->d_name).Append(internal::kStatFile);
106 if (file_util::ReadFileToString(stat_path, &stat)) {
107 int cpu = ParseProcStatCPU(stat);
108 if (cpu > 0)
109 total_cpu += cpu;
110 }
111 }
112 closedir(dir);
113
114 return total_cpu;
115}
116
117} // namespace
118
119// static
120ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) {
121 return new ProcessMetrics(process);
122}
123
124// On linux, we return vsize.
125size_t ProcessMetrics::GetPagefileUsage() const {
126 return internal::ReadProcStatsAndGetFieldAsSizeT(process_,
127 internal::VM_VSIZE);
128}
129
130// On linux, we return the high water mark of vsize.
131size_t ProcessMetrics::GetPeakPagefileUsage() const {
132 return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024;
133}
134
135// On linux, we return RSS.
136size_t ProcessMetrics::GetWorkingSetSize() const {
137 return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
138 getpagesize();
139}
140
141// On linux, we return the high water mark of RSS.
142size_t ProcessMetrics::GetPeakWorkingSetSize() const {
143 return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024;
144}
145
146bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes,
147 size_t* shared_bytes) {
148 WorkingSetKBytes ws_usage;
149 if (!GetWorkingSetKBytes(&ws_usage))
150 return false;
151
152 if (private_bytes)
153 *private_bytes = ws_usage.priv * 1024;
154
155 if (shared_bytes)
156 *shared_bytes = ws_usage.shared * 1024;
157
158 return true;
159}
160
rsesek@chromium.org32f5e9a2013-05-23 12:59:54161bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const {
davemoore@chromium.org7b9c98872013-05-29 15:42:04162#if defined(OS_CHROMEOS)
163 if (GetWorkingSetKBytesTotmaps(ws_usage))
164 return true;
165#endif
166 return GetWorkingSetKBytesStatm(ws_usage);
rsesek@chromium.org32f5e9a2013-05-23 12:59:54167}
168
169double ProcessMetrics::GetCPUUsage() {
170 // This queries the /proc-specific scaling factor which is
171 // conceptually the system hertz. To dump this value on another
172 // system, try
173 // od -t dL /proc/self/auxv
174 // and look for the number after 17 in the output; mine is
175 // 0000040 17 100 3 134512692
176 // which means the answer is 100.
177 // It may be the case that this value is always 100.
178 static const int kHertz = sysconf(_SC_CLK_TCK);
179
180 struct timeval now;
181 int retval = gettimeofday(&now, NULL);
182 if (retval)
183 return 0;
184 int64 time = TimeValToMicroseconds(now);
185
186 if (last_time_ == 0) {
187 // First call, just set the last values.
188 last_time_ = time;
189 last_cpu_ = GetProcessCPU(process_);
190 return 0;
191 }
192
193 int64 time_delta = time - last_time_;
194 DCHECK_NE(time_delta, 0);
195 if (time_delta == 0)
196 return 0;
197
198 int cpu = GetProcessCPU(process_);
199
200 // We have the number of jiffies in the time period. Convert to percentage.
201 // Note this means we will go *over* 100 in the case where multiple threads
202 // are together adding to more than one CPU's worth.
203 int percentage = 100 * (cpu - last_cpu_) /
204 (kHertz * TimeDelta::FromMicroseconds(time_delta).InSecondsF());
205
206 last_time_ = time;
207 last_cpu_ = cpu;
208
209 return percentage;
210}
211
212// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING
213// in your kernel configuration.
214bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
215 // Synchronously reading files in /proc is safe.
216 ThreadRestrictions::ScopedAllowIO allow_io;
217
218 std::string proc_io_contents;
219 FilePath io_file = internal::GetProcPidDir(process_).Append("io");
220 if (!file_util::ReadFileToString(io_file, &proc_io_contents))
221 return false;
222
223 (*io_counters).OtherOperationCount = 0;
224 (*io_counters).OtherTransferCount = 0;
225
226 StringTokenizer tokenizer(proc_io_contents, ": \n");
227 ParsingState state = KEY_NAME;
228 StringPiece last_key_name;
229 while (tokenizer.GetNext()) {
230 switch (state) {
231 case KEY_NAME:
232 last_key_name = tokenizer.token_piece();
233 state = KEY_VALUE;
234 break;
235 case KEY_VALUE:
236 DCHECK(!last_key_name.empty());
237 if (last_key_name == "syscr") {
238 StringToInt64(tokenizer.token_piece(),
239 reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount));
240 } else if (last_key_name == "syscw") {
241 StringToInt64(tokenizer.token_piece(),
242 reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount));
243 } else if (last_key_name == "rchar") {
244 StringToInt64(tokenizer.token_piece(),
245 reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount));
246 } else if (last_key_name == "wchar") {
247 StringToInt64(tokenizer.token_piece(),
248 reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount));
249 }
250 state = KEY_NAME;
251 break;
252 }
253 }
254 return true;
255}
256
257ProcessMetrics::ProcessMetrics(ProcessHandle process)
258 : process_(process),
259 last_time_(0),
260 last_system_time_(0),
261 last_cpu_(0) {
262 processor_count_ = base::SysInfo::NumberOfProcessors();
davemoore@chromium.orgc8db21c2013-05-28 20:16:49263}
264
davemoore@chromium.org7b9c98872013-05-29 15:42:04265#if defined(OS_CHROMEOS)
266// Private, Shared and Proportional working set sizes are obtained from
267// /proc/<pid>/totmaps
268bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage)
269 const {
270 // The format of /proc/<pid>/totmaps is:
271 //
272 // Rss: 6120 kB
273 // Pss: 3335 kB
274 // Shared_Clean: 1008 kB
275 // Shared_Dirty: 4012 kB
276 // Private_Clean: 4 kB
277 // Private_Dirty: 1096 kB
278 // ...
279 const size_t kPssIndex = 4;
280 const size_t kPrivate_CleanIndex = 13;
281 const size_t kPrivate_DirtyIndex = 16;
282
283 std::string totmaps_data;
284 {
285 FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps");
286 ThreadRestrictions::ScopedAllowIO allow_io;
287 bool ret = file_util::ReadFileToString(totmaps_file, &totmaps_data);
288 if (!ret || totmaps_data.length() == 0)
289 return false;
290 }
291
292 std::vector<std::string> totmaps_fields;
293 SplitStringAlongWhitespace(totmaps_data, &totmaps_fields);
294
295 DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]);
296 DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex-1]);
297 DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex-1]);
298
299 int pss, private_clean, private_dirty;
300 bool ret = true;
301 ret &= StringToInt(totmaps_fields[kPssIndex], &pss);
302 ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean);
303 ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty);
304
305 ws_usage->priv = private_clean + private_dirty;
306 ws_usage->shared = pss;
307 ws_usage->shareable = 0;
308
309 return ret;
310}
311#endif
312
313// Private and Shared working set sizes are obtained from /proc/<pid>/statm.
314bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage)
315 const {
316 // Use statm instead of smaps because smaps is:
317 // a) Large and slow to parse.
318 // b) Unavailable in the SUID sandbox.
319
320 // First we need to get the page size, since everything is measured in pages.
321 // For details, see: man 5 proc.
322 const int page_size_kb = getpagesize() / 1024;
323 if (page_size_kb <= 0)
324 return false;
325
326 std::string statm;
327 {
328 FilePath statm_file = internal::GetProcPidDir(process_).Append("statm");
329 // Synchronously reading files in /proc is safe.
330 ThreadRestrictions::ScopedAllowIO allow_io;
331 bool ret = file_util::ReadFileToString(statm_file, &statm);
332 if (!ret || statm.length() == 0)
333 return false;
334 }
335
336 std::vector<std::string> statm_vec;
337 SplitString(statm, ' ', &statm_vec);
338 if (statm_vec.size() != 7)
339 return false; // Not the format we expect.
340
341 int statm_rss, statm_shared;
342 bool ret = true;
343 ret &= StringToInt(statm_vec[1], &statm_rss);
344 ret &= StringToInt(statm_vec[2], &statm_shared);
345
346 ws_usage->priv = (statm_rss - statm_shared) * page_size_kb;
347 ws_usage->shared = statm_shared * page_size_kb;
348
349 // Sharable is not calculated, as it does not provide interesting data.
350 ws_usage->shareable = 0;
351
352 return ret;
353}
354
rsesek@chromium.org32f5e9a2013-05-23 12:59:54355size_t GetSystemCommitCharge() {
356 SystemMemoryInfoKB meminfo;
357 if (!GetSystemMemoryInfo(&meminfo))
358 return 0;
359 return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached;
360}
361
362// Exposed for testing.
363int ParseProcStatCPU(const std::string& input) {
364 std::vector<std::string> proc_stats;
365 if (!internal::ParseProcStats(input, &proc_stats))
366 return -1;
367
368 if (proc_stats.size() <= internal::VM_STIME)
369 return -1;
370 int utime = GetProcStatsFieldAsInt(proc_stats, internal::VM_UTIME);
371 int stime = GetProcStatsFieldAsInt(proc_stats, internal::VM_STIME);
372 return utime + stime;
373}
374
375namespace {
376
377// The format of /proc/meminfo is:
378//
379// MemTotal: 8235324 kB
380// MemFree: 1628304 kB
381// Buffers: 429596 kB
382// Cached: 4728232 kB
383// ...
384const size_t kMemTotalIndex = 1;
385const size_t kMemFreeIndex = 4;
386const size_t kMemBuffersIndex = 7;
387const size_t kMemCachedIndex = 10;
388const size_t kMemActiveAnonIndex = 22;
389const size_t kMemInactiveAnonIndex = 25;
390const size_t kMemActiveFileIndex = 28;
391const size_t kMemInactiveFileIndex = 31;
392
393} // namespace
394
395SystemMemoryInfoKB::SystemMemoryInfoKB()
396 : total(0),
397 free(0),
398 buffers(0),
399 cached(0),
400 active_anon(0),
401 inactive_anon(0),
402 active_file(0),
403 inactive_file(0),
404 shmem(0),
405 gem_objects(-1),
406 gem_size(-1) {
407}
408
409bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
410 // Synchronously reading files in /proc is safe.
411 ThreadRestrictions::ScopedAllowIO allow_io;
412
413 // Used memory is: total - free - buffers - caches
414 FilePath meminfo_file("/proc/meminfo");
415 std::string meminfo_data;
416 if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) {
417 DLOG(WARNING) << "Failed to open " << meminfo_file.value();
418 return false;
419 }
420 std::vector<std::string> meminfo_fields;
421 SplitStringAlongWhitespace(meminfo_data, &meminfo_fields);
422
423 if (meminfo_fields.size() < kMemCachedIndex) {
424 DLOG(WARNING) << "Failed to parse " << meminfo_file.value()
425 << ". Only found " << meminfo_fields.size() << " fields.";
426 return false;
427 }
428
429 DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:");
430 DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:");
431 DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:");
432 DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:");
433 DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):");
434 DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):");
435 DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):");
436 DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):");
437
438 StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total);
439 StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free);
440 StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers);
441 StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached);
442 StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon);
443 StringToInt(meminfo_fields[kMemInactiveAnonIndex],
444 &meminfo->inactive_anon);
445 StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file);
446 StringToInt(meminfo_fields[kMemInactiveFileIndex],
447 &meminfo->inactive_file);
448#if defined(OS_CHROMEOS)
449 // Chrome OS has a tweaked kernel that allows us to query Shmem, which is
450 // usually video memory otherwise invisible to the OS. Unfortunately, the
451 // meminfo format varies on different hardware so we have to search for the
452 // string. It always appears after "Cached:".
453 for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) {
454 if (meminfo_fields[i] == "Shmem:") {
455 StringToInt(meminfo_fields[i+1], &meminfo->shmem);
456 break;
457 }
458 }
459
460 // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a
461 // bind mount into /sys/kernel/debug and synchronously reading the in-memory
462 // files in /sys is fast.
463#if defined(ARCH_CPU_ARM_FAMILY)
464 FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects");
465#else
466 FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects");
467#endif
468 std::string geminfo_data;
469 meminfo->gem_objects = -1;
470 meminfo->gem_size = -1;
471 if (file_util::ReadFileToString(geminfo_file, &geminfo_data)) {
472 int gem_objects = -1;
473 long long gem_size = -1;
474 int num_res = sscanf(geminfo_data.c_str(),
475 "%d objects, %lld bytes",
476 &gem_objects, &gem_size);
477 if (num_res == 2) {
478 meminfo->gem_objects = gem_objects;
479 meminfo->gem_size = gem_size;
480 }
481 }
482
483#if defined(ARCH_CPU_ARM_FAMILY)
484 // Incorporate Mali graphics memory if present.
485 FilePath mali_memory_file("/sys/devices/platform/mali.0/memory");
486 std::string mali_memory_data;
487 if (file_util::ReadFileToString(mali_memory_file, &mali_memory_data)) {
488 long long mali_size = -1;
489 int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
490 if (num_res == 1)
491 meminfo->gem_size += mali_size;
492 }
493#endif // defined(ARCH_CPU_ARM_FAMILY)
494#endif // defined(OS_CHROMEOS)
495
496 return true;
497}
498
499} // namespace base