1 // Copyright 2020 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 "ash/hud_display/memory_status.h"
6 
7 #include <unistd.h>
8 
9 #include "base/files/file_util.h"
10 #include "base/logging.h"
11 #include "base/process/internal_linux.h"
12 #include "base/process/process_iterator.h"
13 #include "base/process/process_metrics.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/system/sys_info.h"
17 #include "base/threading/thread_restrictions.h"
18 
19 namespace ash {
20 namespace hud_display {
21 namespace {
22 
23 constexpr char kProcDir[] = "/proc";
24 constexpr char kSysFsCgroupCpuDir[] = "/sys/fs/cgroup/cpu";
25 
26 // Fields from /proc/<pid>/statm, 0-based. See man 5 proc.
27 // If the ordering ever changes, carefully review functions that use these
28 // values.
29 enum class ProcStatMFields {
30   VM_SIZE = 0,    // Virtual memory size in bytes.
31   VM_RSS = 1,     // Resident Set Size in pages.
32   VM_SHARED = 2,  // number of resident shared pages
33 };
34 
GetProcPidDir(pid_t pid)35 base::FilePath GetProcPidDir(pid_t pid) {
36   return base::FilePath(kProcDir).Append(base::NumberToString(pid));
37 }
38 
ReadProcFile(const base::FilePath & path)39 std::string ReadProcFile(const base::FilePath& path) {
40   std::string result;
41   ReadFileToString(path, &result);
42   return result;
43 }
44 
45 // Reads and returns /proc/<pid>/cmdline
46 // Note: /proc/<pid>/cmdline contains command line arguments separated by single
47 // null characters.
GetProcCmdline(pid_t pid)48 std::string GetProcCmdline(pid_t pid) {
49   return ReadProcFile(GetProcPidDir(pid).Append("cmdline"));
50 }
51 
GetProcVM_RSS(pid_t pid)52 int64_t GetProcVM_RSS(pid_t pid) {
53   const std::string statm = ReadProcFile(GetProcPidDir(pid).Append("statm"));
54   const std::vector<base::StringPiece> parts = base::SplitStringPiece(
55       statm, " \n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
56 
57   if (parts.size() <= static_cast<size_t>(ProcStatMFields::VM_RSS)) {
58     DLOG(ERROR) << "GetProcVM_RSS(): No data in '" << statm << "'!";
59     return 0;
60   }
61   int64_t result;
62   base::StringToInt64(parts[static_cast<size_t>(ProcStatMFields::VM_RSS)],
63                       &result);
64   return result * getpagesize();
65 }
66 
GetProcVM_SHARED(pid_t pid)67 int64_t GetProcVM_SHARED(pid_t pid) {
68   const std::string statm = ReadProcFile(GetProcPidDir(pid).Append("statm"));
69   const std::vector<base::StringPiece> parts = base::SplitStringPiece(
70       statm, " \n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
71 
72   if (parts.size() <= static_cast<size_t>(ProcStatMFields::VM_SHARED)) {
73     DLOG(ERROR) << "GetProcVM_SHARED(): No data!";
74     return 0;
75   }
76   int64_t result;
77   base::StringToInt64(parts[static_cast<size_t>(ProcStatMFields::VM_SHARED)],
78                       &result);
79   return result * getpagesize();
80 }
81 
82 }  // namespace
83 
84 ////////////////////////////////////////////////////////////////////////////////
85 
86 // ProcessMemoryCountersByFlag
ProcessMemoryCountersByFlag(const std::string & cmd_line_flag)87 MemoryStatus::ProcessMemoryCountersByFlag::ProcessMemoryCountersByFlag(
88     const std::string& cmd_line_flag)
89     : flag_(cmd_line_flag) {}
90 MemoryStatus::ProcessMemoryCountersByFlag::~ProcessMemoryCountersByFlag() =
91     default;
92 
TryRead(const base::ProcessId & pid,const std::string & cmdline)93 bool MemoryStatus::ProcessMemoryCountersByFlag::TryRead(
94     const base::ProcessId& pid,
95     const std::string& cmdline) {
96   if (cmdline.find(flag_) == std::string::npos)
97     return false;
98 
99   rss_ += GetProcVM_RSS(pid);
100   rss_shared_ += GetProcVM_SHARED(pid);
101   return true;
102 }
103 
104 // ProcessMemoryCountersByCgroup
ProcessMemoryCountersByCgroup(const std::string & expected_cgroup)105 MemoryStatus::ProcessMemoryCountersByCgroup::ProcessMemoryCountersByCgroup(
106     const std::string& expected_cgroup) {
107   const base::FilePath pids_filename = base::FilePath(kSysFsCgroupCpuDir)
108                                            .Append(expected_cgroup)
109                                            .Append("cgroup.procs");
110   const std::string pids_list_str = ReadProcFile(pids_filename);
111   if (pids_list_str.empty()) {
112     // Ignore read failures.
113     return;
114   }
115   const std::vector<base::StringPiece> pids = base::SplitStringPiece(
116       pids_list_str, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
117   for (const auto& p : pids) {
118     int64_t pid;
119     if (base::StringToInt64(p, &pid))
120       pids_.insert(pid);
121   }
122 }
123 
124 MemoryStatus::ProcessMemoryCountersByCgroup::~ProcessMemoryCountersByCgroup() =
125     default;
126 
TryRead(const base::ProcessId & pid)127 bool MemoryStatus::ProcessMemoryCountersByCgroup::TryRead(
128     const base::ProcessId& pid) {
129   if (!pids_.contains(pid))
130     return false;
131 
132   rss_ += GetProcVM_RSS(pid);
133   rss_shared_ += GetProcVM_SHARED(pid);
134   return true;
135 }
136 
137 // MemoryStatus
MemoryStatus()138 MemoryStatus::MemoryStatus() {
139   UpdatePerProcessStat();
140   UpdateMeminfo();
141 }
142 
UpdatePerProcessStat()143 void MemoryStatus::UpdatePerProcessStat() {
144   // TODO: Can we remember process status in some way?
145   base::ProcessIterator process_iter(/*filter=*/nullptr);
146   while (const base::ProcessEntry* process_entry =
147              process_iter.NextProcessEntry()) {
148     const base::Process process(process_entry->pid());
149     if (process.is_current()) {
150       browser_rss_ = GetProcVM_RSS(process.Pid());
151       browser_rss_shared_ = GetProcVM_SHARED(process.Pid());
152       continue;
153     }
154     const std::string cmdline = GetProcCmdline(process.Pid());
155     if (gpu_.TryRead(process.Pid(), cmdline) ||
156         renderers_.TryRead(process.Pid(), cmdline)) {
157       continue;
158     }
159     arc_.TryRead(process.Pid());
160   }
161 }
162 
UpdateMeminfo()163 void MemoryStatus::UpdateMeminfo() {
164   base::SystemMemoryInfoKB meminfo;
165   base::GetSystemMemoryInfo(&meminfo);
166   total_ram_size_ = meminfo.total * 1024LL;
167   total_free_ = meminfo.free * 1024LL;
168 
169   base::GraphicsMemoryInfoKB gpu_meminfo;
170   if (base::GetGraphicsMemoryInfo(&gpu_meminfo))
171     gpu_kernel_ = gpu_meminfo.gpu_memory_size;
172   else
173     gpu_kernel_ = 0LL;
174 }
175 
176 }  // namespace hud_display
177 }  // namespace ash
178