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