1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <inttypes.h>
20 #include <linux/kernel-page-flags.h>
21 #include <stdio.h>
22 #include <unistd.h>
23
24 #include <atomic>
25 #include <fstream>
26 #include <iostream>
27 #include <memory>
28 #include <string>
29 #include <utility>
30 #include <vector>
31
32 #include <android-base/file.h>
33 #include <android-base/logging.h>
34 #include <android-base/stringprintf.h>
35 #include <android-base/strings.h>
36 #include <android-base/unique_fd.h>
37 #include <procinfo/process_map.h>
38
39 #include "meminfo_private.h"
40
41 namespace android {
42 namespace meminfo {
43
44 // List of VMA names that we don't want to process:
45 // - On ARM32, [vectors] is a special VMA that is outside of pagemap range.
46 static const std::vector<std::string> g_blacklisted_vmas = {"[vectors]"};
47
add_mem_usage(MemUsage * to,const MemUsage & from)48 static void add_mem_usage(MemUsage* to, const MemUsage& from) {
49 to->vss += from.vss;
50 to->rss += from.rss;
51 to->pss += from.pss;
52 to->uss += from.uss;
53
54 to->swap += from.swap;
55
56 to->private_clean += from.private_clean;
57 to->private_dirty += from.private_dirty;
58
59 to->shared_clean += from.shared_clean;
60 to->shared_dirty += from.shared_dirty;
61 }
62
63 // Returns true if the line was valid smaps stats line false otherwise.
parse_smaps_field(const char * line,MemUsage * stats)64 static bool parse_smaps_field(const char* line, MemUsage* stats) {
65 char field[64];
66 int len;
67 if (sscanf(line, "%63s %n", field, &len) == 1 && *field && field[strlen(field) - 1] == ':') {
68 const char* c = line + len;
69 switch (field[0]) {
70 case 'P':
71 if (strncmp(field, "Pss:", 4) == 0) {
72 stats->pss = strtoull(c, nullptr, 10);
73 } else if (strncmp(field, "Private_Clean:", 14) == 0) {
74 uint64_t prcl = strtoull(c, nullptr, 10);
75 stats->private_clean = prcl;
76 stats->uss += prcl;
77 } else if (strncmp(field, "Private_Dirty:", 14) == 0) {
78 uint64_t prdi = strtoull(c, nullptr, 10);
79 stats->private_dirty = prdi;
80 stats->uss += prdi;
81 }
82 break;
83 case 'S':
84 if (strncmp(field, "Size:", 5) == 0) {
85 stats->vss = strtoull(c, nullptr, 10);
86 } else if (strncmp(field, "Shared_Clean:", 13) == 0) {
87 stats->shared_clean = strtoull(c, nullptr, 10);
88 } else if (strncmp(field, "Shared_Dirty:", 13) == 0) {
89 stats->shared_dirty = strtoull(c, nullptr, 10);
90 } else if (strncmp(field, "Swap:", 5) == 0) {
91 stats->swap = strtoull(c, nullptr, 10);
92 } else if (strncmp(field, "SwapPss:", 8) == 0) {
93 stats->swap_pss = strtoull(c, nullptr, 10);
94 }
95 break;
96 case 'R':
97 if (strncmp(field, "Rss:", 4) == 0) {
98 stats->rss = strtoull(c, nullptr, 10);
99 }
100 break;
101 }
102 return true;
103 }
104
105 return false;
106 }
107
ResetWorkingSet(pid_t pid)108 bool ProcMemInfo::ResetWorkingSet(pid_t pid) {
109 std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid);
110 if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) {
111 PLOG(ERROR) << "Failed to write to " << clear_refs_path;
112 return false;
113 }
114
115 return true;
116 }
117
ProcMemInfo(pid_t pid,bool get_wss,uint64_t pgflags,uint64_t pgflags_mask)118 ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask)
119 : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) {}
120
Maps()121 const std::vector<Vma>& ProcMemInfo::Maps() {
122 if (maps_.empty() && !ReadMaps(get_wss_)) {
123 LOG(ERROR) << "Failed to read maps for Process " << pid_;
124 }
125
126 return maps_;
127 }
128
MapsWithPageIdle()129 const std::vector<Vma>& ProcMemInfo::MapsWithPageIdle() {
130 if (maps_.empty() && !ReadMaps(get_wss_, true)) {
131 LOG(ERROR) << "Failed to read maps with page idle for Process " << pid_;
132 }
133
134 return maps_;
135 }
136
MapsWithoutUsageStats()137 const std::vector<Vma>& ProcMemInfo::MapsWithoutUsageStats() {
138 if (maps_.empty() && !ReadMaps(get_wss_, false, false)) {
139 LOG(ERROR) << "Failed to read maps for Process " << pid_;
140 }
141
142 return maps_;
143 }
144
Smaps(const std::string & path)145 const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path) {
146 if (!maps_.empty()) {
147 return maps_;
148 }
149
150 auto collect_vmas = [&](const Vma& vma) {
151 if (std::find(g_blacklisted_vmas.begin(), g_blacklisted_vmas.end(), vma.name) ==
152 g_blacklisted_vmas.end()) {
153 maps_.emplace_back(vma);
154 }
155 };
156 if (path.empty() && !ForEachVma(collect_vmas)) {
157 LOG(ERROR) << "Failed to read smaps for Process " << pid_;
158 maps_.clear();
159 }
160
161 if (!path.empty() && !ForEachVmaFromFile(path, collect_vmas)) {
162 LOG(ERROR) << "Failed to read smaps from file " << path;
163 maps_.clear();
164 }
165
166 return maps_;
167 }
168
Usage()169 const MemUsage& ProcMemInfo::Usage() {
170 if (get_wss_) {
171 LOG(WARNING) << "Trying to read process memory usage for " << pid_
172 << " using invalid object";
173 return usage_;
174 }
175
176 if (maps_.empty() && !ReadMaps(get_wss_)) {
177 LOG(ERROR) << "Failed to get memory usage for Process " << pid_;
178 }
179
180 return usage_;
181 }
182
Wss()183 const MemUsage& ProcMemInfo::Wss() {
184 if (!get_wss_) {
185 LOG(WARNING) << "Trying to read process working set for " << pid_
186 << " using invalid object";
187 return usage_;
188 }
189
190 if (maps_.empty() && !ReadMaps(get_wss_)) {
191 LOG(ERROR) << "Failed to get working set for Process " << pid_;
192 }
193
194 return usage_;
195 }
196
ForEachVma(const VmaCallback & callback)197 bool ProcMemInfo::ForEachVma(const VmaCallback& callback) {
198 std::string path = ::android::base::StringPrintf("/proc/%d/smaps", pid_);
199 return ForEachVmaFromFile(path, callback);
200 }
201
SmapsOrRollup(MemUsage * stats) const202 bool ProcMemInfo::SmapsOrRollup(MemUsage* stats) const {
203 std::string path = ::android::base::StringPrintf(
204 "/proc/%d/%s", pid_, IsSmapsRollupSupported(pid_) ? "smaps_rollup" : "smaps");
205 return SmapsOrRollupFromFile(path, stats);
206 }
207
SmapsOrRollupPss(uint64_t * pss) const208 bool ProcMemInfo::SmapsOrRollupPss(uint64_t* pss) const {
209 std::string path = ::android::base::StringPrintf(
210 "/proc/%d/%s", pid_, IsSmapsRollupSupported(pid_) ? "smaps_rollup" : "smaps");
211 return SmapsOrRollupPssFromFile(path, pss);
212 }
213
SwapOffsets()214 const std::vector<uint64_t>& ProcMemInfo::SwapOffsets() {
215 if (get_wss_) {
216 LOG(WARNING) << "Trying to read process swap offsets for " << pid_
217 << " using invalid object";
218 return swap_offsets_;
219 }
220
221 if (maps_.empty() && !ReadMaps(get_wss_)) {
222 LOG(ERROR) << "Failed to get swap offsets for Process " << pid_;
223 }
224
225 return swap_offsets_;
226 }
227
PageMap(const Vma & vma,std::vector<uint64_t> * pagemap)228 bool ProcMemInfo::PageMap(const Vma& vma, std::vector<uint64_t>* pagemap) {
229 pagemap->clear();
230 std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid_);
231 ::android::base::unique_fd pagemap_fd(
232 TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC)));
233 if (pagemap_fd == -1) {
234 PLOG(ERROR) << "Failed to open " << pagemap_file;
235 return false;
236 }
237
238 uint64_t nr_pages = (vma.end - vma.start) / getpagesize();
239 pagemap->resize(nr_pages);
240
241 size_t bytes_to_read = sizeof(uint64_t) * nr_pages;
242 off64_t start_addr = (vma.start / getpagesize()) * sizeof(uint64_t);
243 ssize_t bytes_read = pread64(pagemap_fd, pagemap->data(), bytes_to_read, start_addr);
244 if (bytes_read == -1) {
245 PLOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_;
246 return false;
247 } else if (static_cast<size_t>(bytes_read) != bytes_to_read) {
248 LOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_
249 << ": read bytes " << bytes_read << " expected bytes " << bytes_to_read;
250 return false;
251 }
252
253 return true;
254 }
255
GetPagemapFd(pid_t pid)256 static int GetPagemapFd(pid_t pid) {
257 std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid);
258 int fd = TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC));
259 if (fd == -1) {
260 PLOG(ERROR) << "Failed to open " << pagemap_file;
261 }
262 return fd;
263 }
264
ReadMaps(bool get_wss,bool use_pageidle,bool get_usage_stats)265 bool ProcMemInfo::ReadMaps(bool get_wss, bool use_pageidle, bool get_usage_stats) {
266 // Each object reads /proc/<pid>/maps only once. This is done to make sure programs that are
267 // running for the lifetime of the system can recycle the objects and don't have to
268 // unnecessarily retain and update this object in memory (which can get significantly large).
269 // E.g. A program that only needs to reset the working set will never all ->Maps(), ->Usage().
270 // E.g. A program that is monitoring smaps_rollup, may never call ->maps(), Usage(), so it
271 // doesn't make sense for us to parse and retain unnecessary memory accounting stats by default.
272 if (!maps_.empty()) return true;
273
274 // parse and read /proc/<pid>/maps
275 std::string maps_file = ::android::base::StringPrintf("/proc/%d/maps", pid_);
276 if (!::android::procinfo::ReadMapFile(
277 maps_file, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t,
278 const char* name) {
279 if (std::find(g_blacklisted_vmas.begin(), g_blacklisted_vmas.end(), name) ==
280 g_blacklisted_vmas.end()) {
281 maps_.emplace_back(Vma(start, end, pgoff, flags, name));
282 }
283 })) {
284 LOG(ERROR) << "Failed to parse " << maps_file;
285 maps_.clear();
286 return false;
287 }
288
289 if (!get_usage_stats) {
290 return true;
291 }
292
293 ::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
294 if (pagemap_fd == -1) {
295 return false;
296 }
297
298 for (auto& vma : maps_) {
299 if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss, use_pageidle)) {
300 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
301 << vma.end << "]";
302 maps_.clear();
303 return false;
304 }
305 add_mem_usage(&usage_, vma.usage);
306 }
307
308 return true;
309 }
310
FillInVmaStats(Vma & vma)311 bool ProcMemInfo::FillInVmaStats(Vma& vma) {
312 ::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
313 if (pagemap_fd == -1) {
314 return false;
315 }
316
317 if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss_, false)) {
318 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
319 << vma.end << "]";
320 return false;
321 }
322 return true;
323 }
324
ReadVmaStats(int pagemap_fd,Vma & vma,bool get_wss,bool use_pageidle)325 bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle) {
326 PageAcct& pinfo = PageAcct::Instance();
327 if (get_wss && use_pageidle && !pinfo.InitPageAcct(true)) {
328 LOG(ERROR) << "Failed to init idle page accounting";
329 return false;
330 }
331
332 uint64_t pagesz = getpagesize();
333 size_t num_pages = (vma.end - vma.start) / pagesz;
334 size_t first_page = vma.start / pagesz;
335
336 std::vector<uint64_t> page_cache;
337 size_t cur_page_cache_index = 0;
338 size_t num_in_page_cache = 0;
339 size_t num_leftover_pages = num_pages;
340 for (size_t cur_page = first_page; cur_page < first_page + num_pages; ++cur_page) {
341 if (!get_wss) {
342 vma.usage.vss += pagesz;
343 }
344
345 // Cache page map data.
346 if (cur_page_cache_index == num_in_page_cache) {
347 static constexpr size_t kMaxPages = 2048;
348 num_leftover_pages -= num_in_page_cache;
349 if (num_leftover_pages > kMaxPages) {
350 num_in_page_cache = kMaxPages;
351 } else {
352 num_in_page_cache = num_leftover_pages;
353 }
354 page_cache.resize(num_in_page_cache);
355 size_t total_bytes = page_cache.size() * sizeof(uint64_t);
356 ssize_t bytes = pread64(pagemap_fd, page_cache.data(), total_bytes,
357 cur_page * sizeof(uint64_t));
358 if (bytes != total_bytes) {
359 if (bytes == -1) {
360 PLOG(ERROR) << "Failed to read page data at offset 0x" << std::hex
361 << cur_page * sizeof(uint64_t);
362 } else {
363 LOG(ERROR) << "Failed to read page data at offset 0x" << std::hex
364 << cur_page * sizeof(uint64_t) << std::dec << " read bytes " << bytes
365 << " expected bytes " << total_bytes;
366 }
367 return false;
368 }
369 cur_page_cache_index = 0;
370 }
371
372 uint64_t page_info = page_cache[cur_page_cache_index++];
373 if (!PAGE_PRESENT(page_info) && !PAGE_SWAPPED(page_info)) continue;
374
375 if (PAGE_SWAPPED(page_info)) {
376 vma.usage.swap += pagesz;
377 swap_offsets_.emplace_back(PAGE_SWAP_OFFSET(page_info));
378 continue;
379 }
380
381 uint64_t page_frame = PAGE_PFN(page_info);
382 uint64_t cur_page_flags;
383 if (!pinfo.PageFlags(page_frame, &cur_page_flags)) {
384 LOG(ERROR) << "Failed to get page flags for " << page_frame << " in process " << pid_;
385 swap_offsets_.clear();
386 return false;
387 }
388
389 // skip unwanted pages from the count
390 if ((cur_page_flags & pgflags_mask_) != pgflags_) continue;
391
392 uint64_t cur_page_counts;
393 if (!pinfo.PageMapCount(page_frame, &cur_page_counts)) {
394 LOG(ERROR) << "Failed to get page count for " << page_frame << " in process " << pid_;
395 swap_offsets_.clear();
396 return false;
397 }
398
399 // Page was unmapped between the presence check at the beginning of the loop and here.
400 if (cur_page_counts == 0) {
401 continue;
402 }
403
404 bool is_dirty = !!(cur_page_flags & (1 << KPF_DIRTY));
405 bool is_private = (cur_page_counts == 1);
406 // Working set
407 if (get_wss) {
408 bool is_referenced = use_pageidle ? (pinfo.IsPageIdle(page_frame) == 1)
409 : !!(cur_page_flags & (1 << KPF_REFERENCED));
410 if (!is_referenced) {
411 continue;
412 }
413 // This effectively makes vss = rss for the working set is requested.
414 // The libpagemap implementation returns vss > rss for
415 // working set, which doesn't make sense.
416 vma.usage.vss += pagesz;
417 }
418
419 vma.usage.rss += pagesz;
420 vma.usage.uss += is_private ? pagesz : 0;
421 vma.usage.pss += pagesz / cur_page_counts;
422 if (is_private) {
423 vma.usage.private_dirty += is_dirty ? pagesz : 0;
424 vma.usage.private_clean += is_dirty ? 0 : pagesz;
425 } else {
426 vma.usage.shared_dirty += is_dirty ? pagesz : 0;
427 vma.usage.shared_clean += is_dirty ? 0 : pagesz;
428 }
429 }
430 return true;
431 }
432
433 // Public APIs
ForEachVmaFromFile(const std::string & path,const VmaCallback & callback)434 bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback) {
435 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
436 if (fp == nullptr) {
437 return false;
438 }
439
440 char* line = nullptr;
441 bool parsing_vma = false;
442 ssize_t line_len;
443 size_t line_alloc = 0;
444 Vma vma;
445 while ((line_len = getline(&line, &line_alloc, fp.get())) > 0) {
446 // Make sure the line buffer terminates like a C string for ReadMapFile
447 line[line_len] = '\0';
448
449 if (parsing_vma) {
450 if (parse_smaps_field(line, &vma.usage)) {
451 // This was a stats field
452 continue;
453 }
454
455 // Done collecting stats, make the call back
456 callback(vma);
457 parsing_vma = false;
458 }
459
460 vma.clear();
461 // If it has, we are looking for the vma stats
462 // 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
463 if (!::android::procinfo::ReadMapFileContent(
464 line, [&](uint64_t start, uint64_t end, uint16_t flags, uint64_t pgoff, ino_t,
465 const char* name) {
466 vma.start = start;
467 vma.end = end;
468 vma.flags = flags;
469 vma.offset = pgoff;
470 vma.name = name;
471 })) {
472 LOG(ERROR) << "Failed to parse " << path;
473 return false;
474 }
475 parsing_vma = true;
476 }
477
478 // free getline() managed buffer
479 free(line);
480
481 if (parsing_vma) {
482 callback(vma);
483 }
484
485 return true;
486 }
487
488 enum smaps_rollup_support { UNTRIED, SUPPORTED, UNSUPPORTED };
489
490 static std::atomic<smaps_rollup_support> g_rollup_support = UNTRIED;
491
IsSmapsRollupSupported(pid_t pid)492 bool IsSmapsRollupSupported(pid_t pid) {
493 // Similar to OpenSmapsOrRollup checks from android_os_Debug.cpp, except
494 // the method only checks if rollup is supported and returns the status
495 // right away.
496 enum smaps_rollup_support rollup_support = g_rollup_support.load(std::memory_order_relaxed);
497 if (rollup_support != UNTRIED) {
498 return rollup_support == SUPPORTED;
499 }
500 std::string rollup_file = ::android::base::StringPrintf("/proc/%d/smaps_rollup", pid);
501 if (access(rollup_file.c_str(), F_OK | R_OK)) {
502 // No check for errno = ENOENT necessary here. The caller MUST fallback to
503 // using /proc/<pid>/smaps instead anyway.
504 g_rollup_support.store(UNSUPPORTED, std::memory_order_relaxed);
505 return false;
506 }
507
508 g_rollup_support.store(SUPPORTED, std::memory_order_relaxed);
509 LOG(INFO) << "Using smaps_rollup for pss collection";
510 return true;
511 }
512
SmapsOrRollupFromFile(const std::string & path,MemUsage * stats)513 bool SmapsOrRollupFromFile(const std::string& path, MemUsage* stats) {
514 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
515 if (fp == nullptr) {
516 return false;
517 }
518
519 char* line = nullptr;
520 size_t line_alloc = 0;
521 stats->clear();
522 while (getline(&line, &line_alloc, fp.get()) > 0) {
523 switch (line[0]) {
524 case 'P':
525 if (strncmp(line, "Pss:", 4) == 0) {
526 char* c = line + 4;
527 stats->pss += strtoull(c, nullptr, 10);
528 } else if (strncmp(line, "Private_Clean:", 14) == 0) {
529 char* c = line + 14;
530 uint64_t prcl = strtoull(c, nullptr, 10);
531 stats->private_clean += prcl;
532 stats->uss += prcl;
533 } else if (strncmp(line, "Private_Dirty:", 14) == 0) {
534 char* c = line + 14;
535 uint64_t prdi = strtoull(c, nullptr, 10);
536 stats->private_dirty += prdi;
537 stats->uss += prdi;
538 }
539 break;
540 case 'R':
541 if (strncmp(line, "Rss:", 4) == 0) {
542 char* c = line + 4;
543 stats->rss += strtoull(c, nullptr, 10);
544 }
545 break;
546 case 'S':
547 if (strncmp(line, "SwapPss:", 8) == 0) {
548 char* c = line + 8;
549 stats->swap_pss += strtoull(c, nullptr, 10);
550 }
551 break;
552 }
553 }
554
555 // free getline() managed buffer
556 free(line);
557 return true;
558 }
559
SmapsOrRollupPssFromFile(const std::string & path,uint64_t * pss)560 bool SmapsOrRollupPssFromFile(const std::string& path, uint64_t* pss) {
561 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
562 if (fp == nullptr) {
563 return false;
564 }
565 *pss = 0;
566 char* line = nullptr;
567 size_t line_alloc = 0;
568 while (getline(&line, &line_alloc, fp.get()) > 0) {
569 uint64_t v;
570 if (sscanf(line, "Pss: %" SCNu64 " kB", &v) == 1) {
571 *pss += v;
572 }
573 }
574
575 // free getline() managed buffer
576 free(line);
577 return true;
578 }
579
580 } // namespace meminfo
581 } // namespace android
582