• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors
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>
8 #include <fcntl.h>
9 #include <inttypes.h>
10 #include <stddef.h>
11 #include <stdint.h>
12 #include <sys/stat.h>
13 #include <sys/time.h>
14 #include <sys/types.h>
15 #include <unistd.h>
16 
17 #include <utility>
18 
19 #include "base/cpu.h"
20 #include "base/files/dir_reader_posix.h"
21 #include "base/files/file_util.h"
22 #include "base/logging.h"
23 #include "base/memory/ptr_util.h"
24 #include "base/notreached.h"
25 #include "base/numerics/safe_conversions.h"
26 #include "base/process/internal_linux.h"
27 #include "base/process/process_metrics_iocounters.h"
28 #include "base/strings/string_number_conversions.h"
29 #include "base/strings/string_piece.h"
30 #include "base/strings/string_split.h"
31 #include "base/strings/string_tokenizer.h"
32 #include "base/strings/string_util.h"
33 #include "base/system/sys_info.h"
34 #include "base/threading/thread_restrictions.h"
35 #include "base/values.h"
36 #include "build/build_config.h"
37 #include "third_party/abseil-cpp/absl/types/optional.h"
38 
39 namespace base {
40 
41 class ScopedAllowBlockingForProcessMetrics : public ScopedAllowBlocking {};
42 
43 namespace {
44 
TrimKeyValuePairs(StringPairs * pairs)45 void TrimKeyValuePairs(StringPairs* pairs) {
46   for (auto& pair : *pairs) {
47     TrimWhitespaceASCII(pair.first, TRIM_ALL, &pair.first);
48     TrimWhitespaceASCII(pair.second, TRIM_ALL, &pair.second);
49   }
50 }
51 
52 #if BUILDFLAG(IS_CHROMEOS)
53 // Read a file with a single number string and return the number as a uint64_t.
ReadFileToUint64(const FilePath & file)54 uint64_t ReadFileToUint64(const FilePath& file) {
55   std::string file_contents;
56   if (!ReadFileToString(file, &file_contents))
57     return 0;
58   TrimWhitespaceASCII(file_contents, TRIM_ALL, &file_contents);
59   uint64_t file_contents_uint64 = 0;
60   if (!StringToUint64(file_contents, &file_contents_uint64))
61     return 0;
62   return file_contents_uint64;
63 }
64 #endif
65 
66 // Read |filename| in /proc/<pid>/, split the entries into key/value pairs, and
67 // trim the key and value. On success, return true and write the trimmed
68 // key/value pairs into |key_value_pairs|.
ReadProcFileToTrimmedStringPairs(pid_t pid,StringPiece filename,StringPairs * key_value_pairs)69 bool ReadProcFileToTrimmedStringPairs(pid_t pid,
70                                       StringPiece filename,
71                                       StringPairs* key_value_pairs) {
72   std::string status_data;
73   FilePath status_file = internal::GetProcPidDir(pid).Append(filename);
74   if (!internal::ReadProcFile(status_file, &status_data))
75     return false;
76   SplitStringIntoKeyValuePairs(status_data, ':', '\n', key_value_pairs);
77   TrimKeyValuePairs(key_value_pairs);
78   return true;
79 }
80 
81 // Read /proc/<pid>/status and return the value for |field|, or 0 on failure.
82 // Only works for fields in the form of "Field: value kB".
ReadProcStatusAndGetFieldAsSizeT(pid_t pid,StringPiece field)83 size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, StringPiece field) {
84   StringPairs pairs;
85   if (!ReadProcFileToTrimmedStringPairs(pid, "status", &pairs))
86     return 0;
87 
88   for (const auto& pair : pairs) {
89     const std::string& key = pair.first;
90     const std::string& value_str = pair.second;
91     if (key != field)
92       continue;
93 
94     std::vector<StringPiece> split_value_str =
95         SplitStringPiece(value_str, " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
96     if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
97       NOTREACHED();
98       return 0;
99     }
100     size_t value;
101     if (!StringToSizeT(split_value_str[0], &value)) {
102       NOTREACHED();
103       return 0;
104     }
105     return value;
106   }
107   // This can be reached if the process dies when proc is read -- in that case,
108   // the kernel can return missing fields.
109   return 0;
110 }
111 
112 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)
113 // Read /proc/<pid>/status and look for |field|. On success, return true and
114 // write the value for |field| into |result|.
115 // Only works for fields in the form of "field    :     uint_value"
ReadProcStatusAndGetFieldAsUint64(pid_t pid,StringPiece field,uint64_t * result)116 bool ReadProcStatusAndGetFieldAsUint64(pid_t pid,
117                                        StringPiece field,
118                                        uint64_t* result) {
119   StringPairs pairs;
120   if (!ReadProcFileToTrimmedStringPairs(pid, "status", &pairs))
121     return false;
122 
123   for (const auto& pair : pairs) {
124     const std::string& key = pair.first;
125     const std::string& value_str = pair.second;
126     if (key != field)
127       continue;
128 
129     uint64_t value;
130     if (!StringToUint64(value_str, &value))
131       return false;
132     *result = value;
133     return true;
134   }
135   return false;
136 }
137 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)
138 
139 // Get the total CPU from a proc stat buffer.  Return value is number of jiffies
140 // on success or 0 if parsing failed.
ParseTotalCPUTimeFromStats(const std::vector<std::string> & proc_stats)141 int64_t ParseTotalCPUTimeFromStats(const std::vector<std::string>& proc_stats) {
142   return internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_UTIME) +
143          internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_STIME);
144 }
145 
146 // Get the total CPU of a single process.  Return value is number of jiffies
147 // on success or -1 on error.
GetProcessCPU(pid_t pid)148 int64_t GetProcessCPU(pid_t pid) {
149   std::string buffer;
150   std::vector<std::string> proc_stats;
151   if (!internal::ReadProcStats(pid, &buffer) ||
152       !internal::ParseProcStats(buffer, &proc_stats)) {
153     return -1;
154   }
155 
156   return ParseTotalCPUTimeFromStats(proc_stats);
157 }
158 
159 }  // namespace
160 
161 // static
CreateProcessMetrics(ProcessHandle process)162 std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
163     ProcessHandle process) {
164   return WrapUnique(new ProcessMetrics(process));
165 }
166 
GetResidentSetSize() const167 size_t ProcessMetrics::GetResidentSetSize() const {
168   return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
169          checked_cast<size_t>(getpagesize());
170 }
171 
GetCumulativeCPUUsage()172 TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
173   return internal::ClockTicksToTimeDelta(GetProcessCPU(process_));
174 }
175 
GetCumulativeCPUUsagePerThread(CPUUsagePerThread & cpu_per_thread)176 bool ProcessMetrics::GetCumulativeCPUUsagePerThread(
177     CPUUsagePerThread& cpu_per_thread) {
178   cpu_per_thread.clear();
179 
180   internal::ForEachProcessTask(
181       process_,
182       [&cpu_per_thread](PlatformThreadId tid, const FilePath& task_path) {
183         FilePath thread_stat_path = task_path.Append("stat");
184 
185         std::string buffer;
186         std::vector<std::string> proc_stats;
187         if (!internal::ReadProcFile(thread_stat_path, &buffer) ||
188             !internal::ParseProcStats(buffer, &proc_stats)) {
189           return;
190         }
191 
192         TimeDelta thread_time = internal::ClockTicksToTimeDelta(
193             ParseTotalCPUTimeFromStats(proc_stats));
194         cpu_per_thread.emplace_back(tid, thread_time);
195       });
196 
197   return !cpu_per_thread.empty();
198 }
199 
200 // For the /proc/self/io file to exist, the Linux kernel must have
201 // CONFIG_TASK_IO_ACCOUNTING enabled.
GetIOCounters(IoCounters * io_counters) const202 bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
203   StringPairs pairs;
204   if (!ReadProcFileToTrimmedStringPairs(process_, "io", &pairs))
205     return false;
206 
207   io_counters->OtherOperationCount = 0;
208   io_counters->OtherTransferCount = 0;
209 
210   for (const auto& pair : pairs) {
211     const std::string& key = pair.first;
212     const std::string& value_str = pair.second;
213     uint64_t* target_counter = nullptr;
214     if (key == "syscr")
215       target_counter = &io_counters->ReadOperationCount;
216     else if (key == "syscw")
217       target_counter = &io_counters->WriteOperationCount;
218     else if (key == "rchar")
219       target_counter = &io_counters->ReadTransferCount;
220     else if (key == "wchar")
221       target_counter = &io_counters->WriteTransferCount;
222     if (!target_counter)
223       continue;
224     bool converted = StringToUint64(value_str, target_counter);
225     DCHECK(converted);
226   }
227   return true;
228 }
229 
230 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
GetVmSwapBytes() const231 uint64_t ProcessMetrics::GetVmSwapBytes() const {
232   return ReadProcStatusAndGetFieldAsSizeT(process_, "VmSwap") * 1024;
233 }
234 
GetPageFaultCounts(PageFaultCounts * counts) const235 bool ProcessMetrics::GetPageFaultCounts(PageFaultCounts* counts) const {
236   // We are not using internal::ReadStatsFileAndGetFieldAsInt64(), since it
237   // would read the file twice, and return inconsistent numbers.
238   std::string stats_data;
239   if (!internal::ReadProcStats(process_, &stats_data))
240     return false;
241   std::vector<std::string> proc_stats;
242   if (!internal::ParseProcStats(stats_data, &proc_stats))
243     return false;
244 
245   counts->minor =
246       internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_MINFLT);
247   counts->major =
248       internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_MAJFLT);
249   return true;
250 }
251 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) ||
252         // BUILDFLAG(IS_ANDROID)
253 
GetOpenFdCount() const254 int ProcessMetrics::GetOpenFdCount() const {
255   // Use /proc/<pid>/fd to count the number of entries there.
256   FilePath fd_path = internal::GetProcPidDir(process_).Append("fd");
257 
258   DirReaderPosix dir_reader(fd_path.value().c_str());
259   if (!dir_reader.IsValid())
260     return -1;
261 
262   int total_count = 0;
263   for (; dir_reader.Next(); ) {
264     const char* name = dir_reader.name();
265     if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
266       ++total_count;
267   }
268 
269   return total_count;
270 }
271 
GetOpenFdSoftLimit() const272 int ProcessMetrics::GetOpenFdSoftLimit() const {
273   // Use /proc/<pid>/limits to read the open fd limit.
274   FilePath fd_path = internal::GetProcPidDir(process_).Append("limits");
275 
276   std::string limits_contents;
277   if (!ReadFileToStringNonBlocking(fd_path, &limits_contents))
278     return -1;
279 
280   for (const auto& line : SplitStringPiece(
281            limits_contents, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
282     if (!StartsWith(line, "Max open files"))
283       continue;
284 
285     auto tokens =
286         SplitStringPiece(line, " ", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
287     if (tokens.size() > 3) {
288       int limit = -1;
289       if (!StringToInt(tokens[3], &limit))
290         return -1;
291       return limit;
292     }
293   }
294   return -1;
295 }
296 
297 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)
ProcessMetrics(ProcessHandle process)298 ProcessMetrics::ProcessMetrics(ProcessHandle process)
299     : process_(process), last_absolute_idle_wakeups_(0) {}
300 #else
ProcessMetrics(ProcessHandle process)301 ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process) {}
302 #endif
303 
GetSystemCommitCharge()304 size_t GetSystemCommitCharge() {
305   SystemMemoryInfoKB meminfo;
306   if (!GetSystemMemoryInfo(&meminfo))
307     return 0;
308   return checked_cast<size_t>(meminfo.total - meminfo.free - meminfo.buffers -
309                               meminfo.cached);
310 }
311 
ParseProcStatCPU(StringPiece input)312 int ParseProcStatCPU(StringPiece input) {
313   // |input| may be empty if the process disappeared somehow.
314   // e.g. http://crbug.com/145811.
315   if (input.empty())
316     return -1;
317 
318   size_t start = input.find_last_of(')');
319   if (start == input.npos)
320     return -1;
321 
322   // Number of spaces remaining until reaching utime's index starting after the
323   // last ')'.
324   int num_spaces_remaining = internal::VM_UTIME - 1;
325 
326   size_t i = start;
327   while ((i = input.find(' ', i + 1)) != input.npos) {
328     // Validate the assumption that there aren't any contiguous spaces
329     // in |input| before utime.
330     DCHECK_NE(input[i - 1], ' ');
331     if (--num_spaces_remaining == 0) {
332       int utime = 0;
333       int stime = 0;
334       if (sscanf(&input.data()[i], "%d %d", &utime, &stime) != 2)
335         return -1;
336 
337       return utime + stime;
338     }
339   }
340 
341   return -1;
342 }
343 
GetNumberOfThreads(ProcessHandle process)344 int64_t GetNumberOfThreads(ProcessHandle process) {
345   return internal::ReadProcStatsAndGetFieldAsInt64(process,
346                                                    internal::VM_NUMTHREADS);
347 }
348 
349 const char kProcSelfExe[] = "/proc/self/exe";
350 
351 namespace {
352 
353 // The format of /proc/diskstats is:
354 //  Device major number
355 //  Device minor number
356 //  Device name
357 //  Field  1 -- # of reads completed
358 //      This is the total number of reads completed successfully.
359 //  Field  2 -- # of reads merged, field 6 -- # of writes merged
360 //      Reads and writes which are adjacent to each other may be merged for
361 //      efficiency.  Thus two 4K reads may become one 8K read before it is
362 //      ultimately handed to the disk, and so it will be counted (and queued)
363 //      as only one I/O.  This field lets you know how often this was done.
364 //  Field  3 -- # of sectors read
365 //      This is the total number of sectors read successfully.
366 //  Field  4 -- # of milliseconds spent reading
367 //      This is the total number of milliseconds spent by all reads (as
368 //      measured from __make_request() to end_that_request_last()).
369 //  Field  5 -- # of writes completed
370 //      This is the total number of writes completed successfully.
371 //  Field  6 -- # of writes merged
372 //      See the description of field 2.
373 //  Field  7 -- # of sectors written
374 //      This is the total number of sectors written successfully.
375 //  Field  8 -- # of milliseconds spent writing
376 //      This is the total number of milliseconds spent by all writes (as
377 //      measured from __make_request() to end_that_request_last()).
378 //  Field  9 -- # of I/Os currently in progress
379 //      The only field that should go to zero. Incremented as requests are
380 //      given to appropriate struct request_queue and decremented as they
381 //      finish.
382 //  Field 10 -- # of milliseconds spent doing I/Os
383 //      This field increases so long as field 9 is nonzero.
384 //  Field 11 -- weighted # of milliseconds spent doing I/Os
385 //      This field is incremented at each I/O start, I/O completion, I/O
386 //      merge, or read of these stats by the number of I/Os in progress
387 //      (field 9) times the number of milliseconds spent doing I/O since the
388 //      last update of this field.  This can provide an easy measure of both
389 //      I/O completion time and the backlog that may be accumulating.
390 
391 const size_t kDiskDriveName = 2;
392 const size_t kDiskReads = 3;
393 const size_t kDiskReadsMerged = 4;
394 const size_t kDiskSectorsRead = 5;
395 const size_t kDiskReadTime = 6;
396 const size_t kDiskWrites = 7;
397 const size_t kDiskWritesMerged = 8;
398 const size_t kDiskSectorsWritten = 9;
399 const size_t kDiskWriteTime = 10;
400 const size_t kDiskIO = 11;
401 const size_t kDiskIOTime = 12;
402 const size_t kDiskWeightedIOTime = 13;
403 
404 }  // namespace
405 
ToDict() const406 Value::Dict SystemMemoryInfoKB::ToDict() const {
407   Value::Dict res;
408   res.Set("total", total);
409   res.Set("free", free);
410   res.Set("available", available);
411   res.Set("buffers", buffers);
412   res.Set("cached", cached);
413   res.Set("active_anon", active_anon);
414   res.Set("inactive_anon", inactive_anon);
415   res.Set("active_file", active_file);
416   res.Set("inactive_file", inactive_file);
417   res.Set("swap_total", swap_total);
418   res.Set("swap_free", swap_free);
419   res.Set("swap_used", swap_total - swap_free);
420   res.Set("dirty", dirty);
421   res.Set("reclaimable", reclaimable);
422 #if BUILDFLAG(IS_CHROMEOS)
423   res.Set("shmem", shmem);
424   res.Set("slab", slab);
425 #endif
426 
427   return res;
428 }
429 
ParseProcMeminfo(StringPiece meminfo_data,SystemMemoryInfoKB * meminfo)430 bool ParseProcMeminfo(StringPiece meminfo_data, SystemMemoryInfoKB* meminfo) {
431   // The format of /proc/meminfo is:
432   //
433   // MemTotal:      8235324 kB
434   // MemFree:       1628304 kB
435   // Buffers:        429596 kB
436   // Cached:        4728232 kB
437   // ...
438   // There is no guarantee on the ordering or position
439   // though it doesn't appear to change very often
440 
441   // As a basic sanity check at the end, make sure the MemTotal value will be at
442   // least non-zero. So start off with a zero total.
443   meminfo->total = 0;
444 
445   for (const StringPiece& line : SplitStringPiece(
446            meminfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
447     std::vector<StringPiece> tokens = SplitStringPiece(
448         line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
449     // HugePages_* only has a number and no suffix so there may not be exactly 3
450     // tokens.
451     if (tokens.size() <= 1) {
452       DLOG(WARNING) << "meminfo: tokens: " << tokens.size()
453                     << " malformed line: " << line;
454       continue;
455     }
456 
457     int* target = nullptr;
458     if (tokens[0] == "MemTotal:")
459       target = &meminfo->total;
460     else if (tokens[0] == "MemFree:")
461       target = &meminfo->free;
462     else if (tokens[0] == "MemAvailable:")
463       target = &meminfo->available;
464     else if (tokens[0] == "Buffers:")
465       target = &meminfo->buffers;
466     else if (tokens[0] == "Cached:")
467       target = &meminfo->cached;
468     else if (tokens[0] == "Active(anon):")
469       target = &meminfo->active_anon;
470     else if (tokens[0] == "Inactive(anon):")
471       target = &meminfo->inactive_anon;
472     else if (tokens[0] == "Active(file):")
473       target = &meminfo->active_file;
474     else if (tokens[0] == "Inactive(file):")
475       target = &meminfo->inactive_file;
476     else if (tokens[0] == "SwapTotal:")
477       target = &meminfo->swap_total;
478     else if (tokens[0] == "SwapFree:")
479       target = &meminfo->swap_free;
480     else if (tokens[0] == "Dirty:")
481       target = &meminfo->dirty;
482     else if (tokens[0] == "SReclaimable:")
483       target = &meminfo->reclaimable;
484 #if BUILDFLAG(IS_CHROMEOS)
485     // Chrome OS has a tweaked kernel that allows querying Shmem, which is
486     // usually video memory otherwise invisible to the OS.
487     else if (tokens[0] == "Shmem:")
488       target = &meminfo->shmem;
489     else if (tokens[0] == "Slab:")
490       target = &meminfo->slab;
491 #endif
492     if (target)
493       StringToInt(tokens[1], target);
494   }
495 
496   // Make sure the MemTotal is valid.
497   return meminfo->total > 0;
498 }
499 
ParseProcVmstat(StringPiece vmstat_data,VmStatInfo * vmstat)500 bool ParseProcVmstat(StringPiece vmstat_data, VmStatInfo* vmstat) {
501   // The format of /proc/vmstat is:
502   //
503   // nr_free_pages 299878
504   // nr_inactive_anon 239863
505   // nr_active_anon 1318966
506   // nr_inactive_file 2015629
507   // ...
508   //
509   // Iterate through the whole file because the position of the
510   // fields are dependent on the kernel version and configuration.
511 
512   // Returns true if all of these 3 fields are present.
513   bool has_pswpin = false;
514   bool has_pswpout = false;
515   bool has_pgmajfault = false;
516 
517   // The oom_kill field is optional. The vmstat oom_kill field is available on
518   // upstream kernel 4.13. It's backported to Chrome OS kernel 3.10.
519   bool has_oom_kill = false;
520   vmstat->oom_kill = 0;
521 
522   for (const StringPiece& line : SplitStringPiece(
523            vmstat_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
524     std::vector<StringPiece> tokens = SplitStringPiece(
525         line, " ", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
526     if (tokens.size() != 2)
527       continue;
528 
529     uint64_t val;
530     if (!StringToUint64(tokens[1], &val))
531       continue;
532 
533     if (tokens[0] == "pswpin") {
534       vmstat->pswpin = val;
535       DCHECK(!has_pswpin);
536       has_pswpin = true;
537     } else if (tokens[0] == "pswpout") {
538       vmstat->pswpout = val;
539       DCHECK(!has_pswpout);
540       has_pswpout = true;
541     } else if (tokens[0] == "pgmajfault") {
542       vmstat->pgmajfault = val;
543       DCHECK(!has_pgmajfault);
544       has_pgmajfault = true;
545     } else if (tokens[0] == "oom_kill") {
546       vmstat->oom_kill = val;
547       DCHECK(!has_oom_kill);
548       has_oom_kill = true;
549     }
550   }
551 
552   return has_pswpin && has_pswpout && has_pgmajfault;
553 }
554 
GetSystemMemoryInfo(SystemMemoryInfoKB * meminfo)555 bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
556   // Used memory is: total - free - buffers - caches
557   // ReadFileToStringNonBlocking doesn't require ScopedAllowIO, and reading
558   // /proc/meminfo is fast. See crbug.com/1160988 for details.
559   FilePath meminfo_file("/proc/meminfo");
560   std::string meminfo_data;
561   if (!ReadFileToStringNonBlocking(meminfo_file, &meminfo_data)) {
562     DLOG(WARNING) << "Failed to open " << meminfo_file.value();
563     return false;
564   }
565 
566   if (!ParseProcMeminfo(meminfo_data, meminfo)) {
567     DLOG(WARNING) << "Failed to parse " << meminfo_file.value();
568     return false;
569   }
570 
571   return true;
572 }
573 
ToDict() const574 Value::Dict VmStatInfo::ToDict() const {
575   Value::Dict res;
576   // TODO(crbug.com/1334256): Make base::Value able to hold uint64_t and remove
577   // casts below.
578   res.Set("pswpin", static_cast<int>(pswpin));
579   res.Set("pswpout", static_cast<int>(pswpout));
580   res.Set("pgmajfault", static_cast<int>(pgmajfault));
581   return res;
582 }
583 
GetVmStatInfo(VmStatInfo * vmstat)584 bool GetVmStatInfo(VmStatInfo* vmstat) {
585   // Synchronously reading files in /proc is safe.
586   ScopedAllowBlockingForProcessMetrics allow_blocking;
587 
588   FilePath vmstat_file("/proc/vmstat");
589   std::string vmstat_data;
590   if (!ReadFileToStringNonBlocking(vmstat_file, &vmstat_data)) {
591     DLOG(WARNING) << "Failed to open " << vmstat_file.value();
592     return false;
593   }
594   if (!ParseProcVmstat(vmstat_data, vmstat)) {
595     DLOG(WARNING) << "Failed to parse " << vmstat_file.value();
596     return false;
597   }
598   return true;
599 }
600 
SystemDiskInfo()601 SystemDiskInfo::SystemDiskInfo() {
602   reads = 0;
603   reads_merged = 0;
604   sectors_read = 0;
605   read_time = 0;
606   writes = 0;
607   writes_merged = 0;
608   sectors_written = 0;
609   write_time = 0;
610   io = 0;
611   io_time = 0;
612   weighted_io_time = 0;
613 }
614 
615 SystemDiskInfo::SystemDiskInfo(const SystemDiskInfo&) = default;
616 
617 SystemDiskInfo& SystemDiskInfo::operator=(const SystemDiskInfo&) = default;
618 
ToDict() const619 Value::Dict SystemDiskInfo::ToDict() const {
620   Value::Dict res;
621 
622   // Write out uint64_t variables as doubles.
623   // Note: this may discard some precision, but for JS there's no other option.
624   res.Set("reads", static_cast<double>(reads));
625   res.Set("reads_merged", static_cast<double>(reads_merged));
626   res.Set("sectors_read", static_cast<double>(sectors_read));
627   res.Set("read_time", static_cast<double>(read_time));
628   res.Set("writes", static_cast<double>(writes));
629   res.Set("writes_merged", static_cast<double>(writes_merged));
630   res.Set("sectors_written", static_cast<double>(sectors_written));
631   res.Set("write_time", static_cast<double>(write_time));
632   res.Set("io", static_cast<double>(io));
633   res.Set("io_time", static_cast<double>(io_time));
634   res.Set("weighted_io_time", static_cast<double>(weighted_io_time));
635 
636   return res;
637 }
638 
IsValidDiskName(StringPiece candidate)639 bool IsValidDiskName(StringPiece candidate) {
640   if (candidate.length() < 3)
641     return false;
642 
643   if (candidate[1] == 'd' &&
644       (candidate[0] == 'h' || candidate[0] == 's' || candidate[0] == 'v')) {
645     // [hsv]d[a-z]+ case
646     for (size_t i = 2; i < candidate.length(); ++i) {
647       if (!islower(candidate[i]))
648         return false;
649     }
650     return true;
651   }
652 
653   const char kMMCName[] = "mmcblk";
654   if (!StartsWith(candidate, kMMCName))
655     return false;
656 
657   // mmcblk[0-9]+ case
658   for (size_t i = strlen(kMMCName); i < candidate.length(); ++i) {
659     if (!isdigit(candidate[i]))
660       return false;
661   }
662   return true;
663 }
664 
GetSystemDiskInfo(SystemDiskInfo * diskinfo)665 bool GetSystemDiskInfo(SystemDiskInfo* diskinfo) {
666   // Synchronously reading files in /proc does not hit the disk.
667   ScopedAllowBlockingForProcessMetrics allow_blocking;
668 
669   FilePath diskinfo_file("/proc/diskstats");
670   std::string diskinfo_data;
671   if (!ReadFileToStringNonBlocking(diskinfo_file, &diskinfo_data)) {
672     DLOG(WARNING) << "Failed to open " << diskinfo_file.value();
673     return false;
674   }
675 
676   std::vector<StringPiece> diskinfo_lines = SplitStringPiece(
677       diskinfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
678   if (diskinfo_lines.empty()) {
679     DLOG(WARNING) << "No lines found";
680     return false;
681   }
682 
683   diskinfo->reads = 0;
684   diskinfo->reads_merged = 0;
685   diskinfo->sectors_read = 0;
686   diskinfo->read_time = 0;
687   diskinfo->writes = 0;
688   diskinfo->writes_merged = 0;
689   diskinfo->sectors_written = 0;
690   diskinfo->write_time = 0;
691   diskinfo->io = 0;
692   diskinfo->io_time = 0;
693   diskinfo->weighted_io_time = 0;
694 
695   uint64_t reads = 0;
696   uint64_t reads_merged = 0;
697   uint64_t sectors_read = 0;
698   uint64_t read_time = 0;
699   uint64_t writes = 0;
700   uint64_t writes_merged = 0;
701   uint64_t sectors_written = 0;
702   uint64_t write_time = 0;
703   uint64_t io = 0;
704   uint64_t io_time = 0;
705   uint64_t weighted_io_time = 0;
706 
707   for (const StringPiece& line : diskinfo_lines) {
708     std::vector<StringPiece> disk_fields = SplitStringPiece(
709         line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
710 
711     // Fields may have overflowed and reset to zero.
712     if (!IsValidDiskName(disk_fields[kDiskDriveName]))
713       continue;
714 
715     StringToUint64(disk_fields[kDiskReads], &reads);
716     StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged);
717     StringToUint64(disk_fields[kDiskSectorsRead], &sectors_read);
718     StringToUint64(disk_fields[kDiskReadTime], &read_time);
719     StringToUint64(disk_fields[kDiskWrites], &writes);
720     StringToUint64(disk_fields[kDiskWritesMerged], &writes_merged);
721     StringToUint64(disk_fields[kDiskSectorsWritten], &sectors_written);
722     StringToUint64(disk_fields[kDiskWriteTime], &write_time);
723     StringToUint64(disk_fields[kDiskIO], &io);
724     StringToUint64(disk_fields[kDiskIOTime], &io_time);
725     StringToUint64(disk_fields[kDiskWeightedIOTime], &weighted_io_time);
726 
727     diskinfo->reads += reads;
728     diskinfo->reads_merged += reads_merged;
729     diskinfo->sectors_read += sectors_read;
730     diskinfo->read_time += read_time;
731     diskinfo->writes += writes;
732     diskinfo->writes_merged += writes_merged;
733     diskinfo->sectors_written += sectors_written;
734     diskinfo->write_time += write_time;
735     diskinfo->io += io;
736     diskinfo->io_time += io_time;
737     diskinfo->weighted_io_time += weighted_io_time;
738   }
739 
740   return true;
741 }
742 
GetUserCpuTimeSinceBoot()743 TimeDelta GetUserCpuTimeSinceBoot() {
744   return internal::GetUserCpuTimeSinceBoot();
745 }
746 
747 #if BUILDFLAG(IS_CHROMEOS)
ToDict() const748 Value::Dict SwapInfo::ToDict() const {
749   Value::Dict res;
750 
751   // Write out uint64_t variables as doubles.
752   // Note: this may discard some precision, but for JS there's no other option.
753   res.Set("num_reads", static_cast<double>(num_reads));
754   res.Set("num_writes", static_cast<double>(num_writes));
755   res.Set("orig_data_size", static_cast<double>(orig_data_size));
756   res.Set("compr_data_size", static_cast<double>(compr_data_size));
757   res.Set("mem_used_total", static_cast<double>(mem_used_total));
758   double ratio = compr_data_size ? static_cast<double>(orig_data_size) /
759                                        static_cast<double>(compr_data_size)
760                                  : 0;
761   res.Set("compression_ratio", ratio);
762 
763   return res;
764 }
765 
ToDict() const766 Value::Dict GraphicsMemoryInfoKB::ToDict() const {
767   Value::Dict res;
768 
769   res.Set("gpu_objects", gpu_objects);
770   res.Set("gpu_memory_size", static_cast<double>(gpu_memory_size));
771 
772   return res;
773 }
774 
ParseZramMmStat(StringPiece mm_stat_data,SwapInfo * swap_info)775 bool ParseZramMmStat(StringPiece mm_stat_data, SwapInfo* swap_info) {
776   // There are 7 columns in /sys/block/zram0/mm_stat,
777   // split by several spaces. The first three columns
778   // are orig_data_size, compr_data_size and mem_used_total.
779   // Example:
780   // 17715200 5008166 566062  0 1225715712  127 183842
781   //
782   // For more details:
783   // https://www.kernel.org/doc/Documentation/blockdev/zram.txt
784 
785   std::vector<StringPiece> tokens = SplitStringPiece(
786       mm_stat_data, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
787   if (tokens.size() < 7) {
788     DLOG(WARNING) << "zram mm_stat: tokens: " << tokens.size()
789                   << " malformed line: " << mm_stat_data;
790     return false;
791   }
792 
793   if (!StringToUint64(tokens[0], &swap_info->orig_data_size))
794     return false;
795   if (!StringToUint64(tokens[1], &swap_info->compr_data_size))
796     return false;
797   if (!StringToUint64(tokens[2], &swap_info->mem_used_total))
798     return false;
799 
800   return true;
801 }
802 
ParseZramStat(StringPiece stat_data,SwapInfo * swap_info)803 bool ParseZramStat(StringPiece stat_data, SwapInfo* swap_info) {
804   // There are 11 columns in /sys/block/zram0/stat,
805   // split by several spaces. The first column is read I/Os
806   // and fifth column is write I/Os.
807   // Example:
808   // 299    0    2392    0    1    0    8    0    0    0    0
809   //
810   // For more details:
811   // https://www.kernel.org/doc/Documentation/blockdev/zram.txt
812 
813   std::vector<StringPiece> tokens = SplitStringPiece(
814       stat_data, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
815   if (tokens.size() < 11) {
816     DLOG(WARNING) << "zram stat: tokens: " << tokens.size()
817                   << " malformed line: " << stat_data;
818     return false;
819   }
820 
821   if (!StringToUint64(tokens[0], &swap_info->num_reads))
822     return false;
823   if (!StringToUint64(tokens[4], &swap_info->num_writes))
824     return false;
825 
826   return true;
827 }
828 
829 namespace {
830 
IgnoreZramFirstPage(uint64_t orig_data_size,SwapInfo * swap_info)831 bool IgnoreZramFirstPage(uint64_t orig_data_size, SwapInfo* swap_info) {
832   if (orig_data_size <= 4096) {
833     // A single page is compressed at startup, and has a high compression
834     // ratio. Ignore this as it doesn't indicate any real swapping.
835     swap_info->orig_data_size = 0;
836     swap_info->num_reads = 0;
837     swap_info->num_writes = 0;
838     swap_info->compr_data_size = 0;
839     swap_info->mem_used_total = 0;
840     return true;
841   }
842   return false;
843 }
844 
ParseZramPath(SwapInfo * swap_info)845 void ParseZramPath(SwapInfo* swap_info) {
846   FilePath zram_path("/sys/block/zram0");
847   uint64_t orig_data_size =
848       ReadFileToUint64(zram_path.Append("orig_data_size"));
849   if (IgnoreZramFirstPage(orig_data_size, swap_info))
850     return;
851 
852   swap_info->orig_data_size = orig_data_size;
853   swap_info->num_reads = ReadFileToUint64(zram_path.Append("num_reads"));
854   swap_info->num_writes = ReadFileToUint64(zram_path.Append("num_writes"));
855   swap_info->compr_data_size =
856       ReadFileToUint64(zram_path.Append("compr_data_size"));
857   swap_info->mem_used_total =
858       ReadFileToUint64(zram_path.Append("mem_used_total"));
859 }
860 
GetSwapInfoImpl(SwapInfo * swap_info)861 bool GetSwapInfoImpl(SwapInfo* swap_info) {
862   // Synchronously reading files in /sys/block/zram0 does not hit the disk.
863   ScopedAllowBlockingForProcessMetrics allow_blocking;
864 
865   // Since ZRAM update, it shows the usage data in different places.
866   // If file "/sys/block/zram0/mm_stat" exists, use the new way, otherwise,
867   // use the old way.
868   static absl::optional<bool> use_new_zram_interface;
869   FilePath zram_mm_stat_file("/sys/block/zram0/mm_stat");
870   if (!use_new_zram_interface.has_value()) {
871     use_new_zram_interface = PathExists(zram_mm_stat_file);
872   }
873 
874   if (!use_new_zram_interface.value()) {
875     ParseZramPath(swap_info);
876     return true;
877   }
878 
879   std::string mm_stat_data;
880   if (!ReadFileToStringNonBlocking(zram_mm_stat_file, &mm_stat_data)) {
881     DLOG(WARNING) << "Failed to open " << zram_mm_stat_file.value();
882     return false;
883   }
884   if (!ParseZramMmStat(mm_stat_data, swap_info)) {
885     DLOG(WARNING) << "Failed to parse " << zram_mm_stat_file.value();
886     return false;
887   }
888   if (IgnoreZramFirstPage(swap_info->orig_data_size, swap_info))
889     return true;
890 
891   FilePath zram_stat_file("/sys/block/zram0/stat");
892   std::string stat_data;
893   if (!ReadFileToStringNonBlocking(zram_stat_file, &stat_data)) {
894     DLOG(WARNING) << "Failed to open " << zram_stat_file.value();
895     return false;
896   }
897   if (!ParseZramStat(stat_data, swap_info)) {
898     DLOG(WARNING) << "Failed to parse " << zram_stat_file.value();
899     return false;
900   }
901 
902   return true;
903 }
904 
905 }  // namespace
906 
GetSwapInfo(SwapInfo * swap_info)907 bool GetSwapInfo(SwapInfo* swap_info) {
908   if (!GetSwapInfoImpl(swap_info)) {
909     *swap_info = SwapInfo();
910     return false;
911   }
912   return true;
913 }
914 
GetGraphicsMemoryInfo(GraphicsMemoryInfoKB * gpu_meminfo)915 bool GetGraphicsMemoryInfo(GraphicsMemoryInfoKB* gpu_meminfo) {
916 #if defined(ARCH_CPU_X86_FAMILY)
917   // Reading i915_gem_objects on intel platform with kernel 5.4 is slow and is
918   // prohibited.
919   // TODO(b/170397975): Update if i915_gem_objects reading time is improved.
920   static bool is_newer_kernel =
921       base::StartsWith(base::SysInfo::KernelVersion(), "5.");
922   static bool is_intel_cpu = base::CPU().vendor_name() == "GenuineIntel";
923   if (is_newer_kernel && is_intel_cpu)
924     return false;
925 #endif
926 
927 #if defined(ARCH_CPU_ARM_FAMILY)
928   const FilePath geminfo_path("/run/debugfs_gpu/exynos_gem_objects");
929 #else
930   const FilePath geminfo_path("/run/debugfs_gpu/i915_gem_objects");
931 #endif
932   std::string geminfo_data;
933   gpu_meminfo->gpu_objects = -1;
934   gpu_meminfo->gpu_memory_size = -1;
935   if (ReadFileToStringNonBlocking(geminfo_path, &geminfo_data)) {
936     int gpu_objects = -1;
937     int64_t gpu_memory_size = -1;
938     int num_res = sscanf(geminfo_data.c_str(), "%d objects, %" SCNd64 " bytes",
939                          &gpu_objects, &gpu_memory_size);
940     if (num_res == 2) {
941       gpu_meminfo->gpu_objects = gpu_objects;
942       gpu_meminfo->gpu_memory_size = gpu_memory_size;
943     }
944   }
945 
946 #if defined(ARCH_CPU_ARM_FAMILY)
947   // Incorporate Mali graphics memory if present.
948   FilePath mali_memory_file("/sys/class/misc/mali0/device/memory");
949   std::string mali_memory_data;
950   if (ReadFileToStringNonBlocking(mali_memory_file, &mali_memory_data)) {
951     int64_t mali_size = -1;
952     int num_res =
953         sscanf(mali_memory_data.c_str(), "%" SCNd64 " bytes", &mali_size);
954     if (num_res == 1)
955       gpu_meminfo->gpu_memory_size += mali_size;
956   }
957 #endif  // defined(ARCH_CPU_ARM_FAMILY)
958 
959   return gpu_meminfo->gpu_memory_size != -1;
960 }
961 
962 #endif  // BUILDFLAG(IS_CHROMEOS)
963 
964 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)
GetIdleWakeupsPerSecond()965 int ProcessMetrics::GetIdleWakeupsPerSecond() {
966   uint64_t num_switches;
967   static const char kSwitchStat[] = "voluntary_ctxt_switches";
968   return ReadProcStatusAndGetFieldAsUint64(process_, kSwitchStat, &num_switches)
969              ? CalculateIdleWakeupsPerSecond(num_switches)
970              : 0;
971 }
972 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_AIX)
973 
974 }  // namespace base
975