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 // - On x86, [vsyscall] is a kernel memory that is outside of pagemap range.
47 static const std::vector<std::string> g_excluded_vmas = {
48 "[vectors]",
49 #ifdef __x86_64__
50 "[vsyscall]"
51 #endif
52 };
53
add_mem_usage(MemUsage * to,const MemUsage & from)54 static void add_mem_usage(MemUsage* to, const MemUsage& from) {
55 to->vss += from.vss;
56 to->rss += from.rss;
57 to->pss += from.pss;
58 to->uss += from.uss;
59
60 to->swap += from.swap;
61
62 to->private_clean += from.private_clean;
63 to->private_dirty += from.private_dirty;
64
65 to->shared_clean += from.shared_clean;
66 to->shared_dirty += from.shared_dirty;
67 }
68
69 // Converts MemUsage stats from KB to B in case usage is expected in bytes.
convert_usage_kb_to_b(MemUsage & usage)70 static void convert_usage_kb_to_b(MemUsage& usage) {
71 // These stats are only populated if /proc/<pid>/smaps is read, so they are excluded:
72 // swap_pss, anon_huge_pages, shmem_pmdmapped, file_pmd_mapped, shared_hugetlb, private_hugetlb.
73 constexpr int conversion_factor = 1024;
74 usage.vss *= conversion_factor;
75 usage.rss *= conversion_factor;
76 usage.pss *= conversion_factor;
77 usage.uss *= conversion_factor;
78
79 usage.swap *= conversion_factor;
80
81 usage.private_clean *= conversion_factor;
82 usage.private_dirty *= conversion_factor;
83
84 usage.shared_clean *= conversion_factor;
85 usage.shared_dirty *= conversion_factor;
86
87 usage.thp *= conversion_factor;
88 }
89
90 // Returns true if the line was valid smaps stats line false otherwise.
parse_smaps_field(const char * line,MemUsage * stats)91 static bool parse_smaps_field(const char* line, MemUsage* stats) {
92 const char *end = line;
93
94 // https://lore.kernel.org/patchwork/patch/1088579/ introduced tabs. Handle this case as well.
95 while (*end && !isspace(*end)) end++;
96 if (*end && end > line && *(end - 1) == ':') {
97 const char* c = end;
98 while (isspace(*c)) c++;
99 switch (line[0]) {
100 case 'P':
101 if (strncmp(line, "Pss:", 4) == 0) {
102 stats->pss = strtoull(c, nullptr, 10);
103 } else if (strncmp(line, "Private_Clean:", 14) == 0) {
104 uint64_t prcl = strtoull(c, nullptr, 10);
105 stats->private_clean = prcl;
106 stats->uss += prcl;
107 } else if (strncmp(line, "Private_Dirty:", 14) == 0) {
108 uint64_t prdi = strtoull(c, nullptr, 10);
109 stats->private_dirty = prdi;
110 stats->uss += prdi;
111 } else if (strncmp(line, "Private_Hugetlb:", 16) == 0) {
112 stats->private_hugetlb = strtoull(c, nullptr, 10);
113 }
114 break;
115 case 'S':
116 if (strncmp(line, "Size:", 5) == 0) {
117 stats->vss = strtoull(c, nullptr, 10);
118 } else if (strncmp(line, "Shared_Clean:", 13) == 0) {
119 stats->shared_clean = strtoull(c, nullptr, 10);
120 } else if (strncmp(line, "Shared_Dirty:", 13) == 0) {
121 stats->shared_dirty = strtoull(c, nullptr, 10);
122 } else if (strncmp(line, "Swap:", 5) == 0) {
123 stats->swap = strtoull(c, nullptr, 10);
124 } else if (strncmp(line, "SwapPss:", 8) == 0) {
125 stats->swap_pss = strtoull(c, nullptr, 10);
126 } else if (strncmp(line, "ShmemPmdMapped:", 15) == 0) {
127 stats->shmem_pmd_mapped = strtoull(c, nullptr, 10);
128 } else if (strncmp(line, "Shared_Hugetlb:", 15) == 0) {
129 stats->shared_hugetlb = strtoull(c, nullptr, 10);
130 }
131 break;
132 case 'R':
133 if (strncmp(line, "Rss:", 4) == 0) {
134 stats->rss = strtoull(c, nullptr, 10);
135 }
136 break;
137 case 'A':
138 if (strncmp(line, "AnonHugePages:", 14) == 0) {
139 stats->anon_huge_pages = strtoull(c, nullptr, 10);
140 }
141 break;
142 case 'F':
143 if (strncmp(line, "FilePmdMapped:", 14) == 0) {
144 stats->file_pmd_mapped = strtoull(c, nullptr, 10);
145 }
146 break;
147 case 'L':
148 if (strncmp(line, "Locked:", 7) == 0) {
149 stats->locked = strtoull(c, nullptr, 10);
150 }
151 break;
152 }
153 return true;
154 }
155
156 return false;
157 }
158
ResetWorkingSet(pid_t pid)159 bool ProcMemInfo::ResetWorkingSet(pid_t pid) {
160 std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid);
161 if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) {
162 PLOG(ERROR) << "Failed to write to " << clear_refs_path;
163 return false;
164 }
165
166 return true;
167 }
168
ProcMemInfo(pid_t pid,bool get_wss,uint64_t pgflags,uint64_t pgflags_mask)169 ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask)
170 : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) {}
171
Maps()172 const std::vector<Vma>& ProcMemInfo::Maps() {
173 if (maps_.empty() && !ReadMaps(get_wss_)) {
174 LOG(ERROR) << "Failed to read maps for Process " << pid_;
175 }
176
177 return maps_;
178 }
179
MapsWithPageIdle()180 const std::vector<Vma>& ProcMemInfo::MapsWithPageIdle() {
181 if (maps_.empty() && !ReadMaps(get_wss_, true)) {
182 LOG(ERROR) << "Failed to read maps with page idle for Process " << pid_;
183 }
184
185 return maps_;
186 }
187
MapsWithoutUsageStats()188 const std::vector<Vma>& ProcMemInfo::MapsWithoutUsageStats() {
189 if (maps_.empty() && !ReadMaps(get_wss_, false, false)) {
190 LOG(ERROR) << "Failed to read maps for Process " << pid_;
191 }
192
193 return maps_;
194 }
195
GetPagemapFd(pid_t pid)196 static int GetPagemapFd(pid_t pid) {
197 std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid);
198 int fd = TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC));
199 if (fd == -1) {
200 PLOG(ERROR) << "Failed to open " << pagemap_file;
201 }
202 return fd;
203 }
204
Smaps(const std::string & path,bool collect_usage,bool collect_swap_offsets)205 const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path, bool collect_usage,
206 bool collect_swap_offsets) {
207 if (!maps_.empty()) {
208 return maps_;
209 }
210
211 ::android::base::unique_fd pagemap_fd;
212 if (collect_swap_offsets) {
213 pagemap_fd = ::android::base::unique_fd(GetPagemapFd(pid_));
214 if (pagemap_fd == -1) {
215 LOG(ERROR) << "Failed to open pagemap for pid " << pid_ << " during Smaps()";
216 return maps_;
217 }
218 }
219
220 auto collect_vmas = [&](Vma& vma) {
221 if (std::find(g_excluded_vmas.begin(), g_excluded_vmas.end(), vma.name) ==
222 g_excluded_vmas.end()) {
223 maps_.emplace_back(vma);
224 if (collect_usage) {
225 add_mem_usage(&usage_, vma.usage);
226 }
227 if (collect_swap_offsets &&
228 !ReadVmaStats(pagemap_fd.get(), vma, false, false, false, false)) {
229 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start
230 << "-" << vma.end << "]";
231 return false;
232 }
233 }
234 return true;
235 };
236
237 if (path.empty() && !ForEachVma(collect_vmas)) {
238 LOG(ERROR) << "Failed to read smaps for Process " << pid_;
239 maps_.clear();
240 }
241
242 if (!path.empty() && !ForEachVmaFromFile(path, collect_vmas)) {
243 LOG(ERROR) << "Failed to read smaps from file " << path;
244 maps_.clear();
245 }
246
247 return maps_;
248 }
249
Usage()250 const MemUsage& ProcMemInfo::Usage() {
251 if (get_wss_) {
252 LOG(WARNING) << "Trying to read process memory usage for " << pid_
253 << " using invalid object";
254 return usage_;
255 }
256
257 if (maps_.empty() && !ReadMaps(get_wss_)) {
258 LOG(ERROR) << "Failed to get memory usage for Process " << pid_;
259 }
260
261 return usage_;
262 }
263
Wss()264 const MemUsage& ProcMemInfo::Wss() {
265 if (!get_wss_) {
266 LOG(WARNING) << "Trying to read process working set for " << pid_
267 << " using invalid object";
268 return usage_;
269 }
270
271 if (maps_.empty() && !ReadMaps(get_wss_)) {
272 LOG(ERROR) << "Failed to get working set for Process " << pid_;
273 }
274
275 return usage_;
276 }
277
ForEachVma(const VmaCallback & callback,bool use_smaps)278 bool ProcMemInfo::ForEachVma(const VmaCallback& callback, bool use_smaps) {
279 std::string path =
280 ::android::base::StringPrintf("/proc/%d/%s", pid_, use_smaps ? "smaps" : "maps");
281 return ForEachVmaFromFile(path, callback, use_smaps);
282 }
283
ForEachExistingVma(const VmaCallback & callback)284 bool ProcMemInfo::ForEachExistingVma(const VmaCallback& callback) {
285 if (maps_.empty()) {
286 return false;
287 }
288 for (auto& vma : maps_) {
289 if (!callback(vma)) {
290 return false;
291 }
292 }
293 return true;
294 }
295
ForEachVmaFromMaps(const VmaCallback & callback)296 bool ProcMemInfo::ForEachVmaFromMaps(const VmaCallback& callback) {
297 Vma vma;
298 auto vmaCollect = [&callback,&vma](const uint64_t start, uint64_t end, uint16_t flags,
299 uint64_t pgoff, ino_t inode, const char* name, bool shared) {
300 vma.start = start;
301 vma.end = end;
302 vma.flags = flags;
303 vma.offset = pgoff;
304 vma.name = name;
305 vma.inode = inode;
306 vma.is_shared = shared;
307 callback(vma);
308 };
309
310 bool success = ::android::procinfo::ReadProcessMaps(pid_, vmaCollect);
311
312 return success;
313 }
314
ForEachVmaFromMaps(const VmaCallback & callback,std::string & mapsBuffer)315 bool ProcMemInfo::ForEachVmaFromMaps(const VmaCallback& callback, std::string& mapsBuffer) {
316 Vma vma;
317 vma.name.reserve(256);
318 auto vmaCollect = [&callback,&vma](const uint64_t start, uint64_t end, uint16_t flags,
319 uint64_t pgoff, ino_t inode, const char* name, bool shared) {
320 vma.start = start;
321 vma.end = end;
322 vma.flags = flags;
323 vma.offset = pgoff;
324 vma.name = name;
325 vma.inode = inode;
326 vma.is_shared = shared;
327 callback(vma);
328 };
329
330 bool success = ::android::procinfo::ReadProcessMaps(pid_, vmaCollect, mapsBuffer);
331
332 return success;
333 }
334
SmapsOrRollup(MemUsage * stats) const335 bool ProcMemInfo::SmapsOrRollup(MemUsage* stats) const {
336 std::string path = ::android::base::StringPrintf(
337 "/proc/%d/%s", pid_, IsSmapsRollupSupported() ? "smaps_rollup" : "smaps");
338 return SmapsOrRollupFromFile(path, stats);
339 }
340
SmapsOrRollupPss(uint64_t * pss) const341 bool ProcMemInfo::SmapsOrRollupPss(uint64_t* pss) const {
342 std::string path = ::android::base::StringPrintf(
343 "/proc/%d/%s", pid_, IsSmapsRollupSupported() ? "smaps_rollup" : "smaps");
344 return SmapsOrRollupPssFromFile(path, pss);
345 }
346
StatusVmRSS(uint64_t * rss) const347 bool ProcMemInfo::StatusVmRSS(uint64_t* rss) const {
348 std::string path = ::android::base::StringPrintf("/proc/%d/status", pid_);
349 return StatusVmRSSFromFile(path, rss);
350 }
351
SwapOffsets()352 const std::vector<uint64_t>& ProcMemInfo::SwapOffsets() {
353 if (get_wss_) {
354 LOG(WARNING) << "Trying to read process swap offsets for " << pid_
355 << " using invalid object";
356 return swap_offsets_;
357 }
358
359 if (maps_.empty() && !ReadMaps(get_wss_, false, true, false)) {
360 LOG(ERROR) << "Failed to get swap offsets for Process " << pid_;
361 }
362
363 return swap_offsets_;
364 }
365
PageMap(const Vma & vma,std::vector<uint64_t> * pagemap)366 bool ProcMemInfo::PageMap(const Vma& vma, std::vector<uint64_t>* pagemap) {
367 pagemap->clear();
368 std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid_);
369 ::android::base::unique_fd pagemap_fd(
370 TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC)));
371 if (pagemap_fd == -1) {
372 PLOG(ERROR) << "Failed to open " << pagemap_file;
373 return false;
374 }
375
376 uint64_t nr_pages = (vma.end - vma.start) / getpagesize();
377 pagemap->resize(nr_pages);
378
379 size_t bytes_to_read = sizeof(uint64_t) * nr_pages;
380 off64_t start_addr = (vma.start / getpagesize()) * sizeof(uint64_t);
381 ssize_t bytes_read = pread64(pagemap_fd, pagemap->data(), bytes_to_read, start_addr);
382 if (bytes_read == -1) {
383 PLOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_;
384 return false;
385 } else if (static_cast<size_t>(bytes_read) != bytes_to_read) {
386 LOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_
387 << ": read bytes " << bytes_read << " expected bytes " << bytes_to_read;
388 return false;
389 }
390
391 return true;
392 }
393
ReadMaps(bool get_wss,bool use_pageidle,bool get_usage_stats,bool update_mem_usage)394 bool ProcMemInfo::ReadMaps(bool get_wss, bool use_pageidle, bool get_usage_stats,
395 bool update_mem_usage) {
396 // Each object reads /proc/<pid>/maps only once. This is done to make sure programs that are
397 // running for the lifetime of the system can recycle the objects and don't have to
398 // unnecessarily retain and update this object in memory (which can get significantly large).
399 // E.g. A program that only needs to reset the working set will never all ->Maps(), ->Usage().
400 // E.g. A program that is monitoring smaps_rollup, may never call ->maps(), Usage(), so it
401 // doesn't make sense for us to parse and retain unnecessary memory accounting stats by default.
402 if (!maps_.empty()) return true;
403
404 // parse and read /proc/<pid>/maps
405 std::string maps_file = ::android::base::StringPrintf("/proc/%d/maps", pid_);
406 if (!::android::procinfo::ReadMapFile(
407 maps_file, [&](const android::procinfo::MapInfo& mapinfo) {
408 if (std::find(g_excluded_vmas.begin(), g_excluded_vmas.end(), mapinfo.name) ==
409 g_excluded_vmas.end()) {
410 maps_.emplace_back(Vma(mapinfo.start, mapinfo.end,
411 mapinfo.pgoff, mapinfo.flags,
412 mapinfo.name,
413 mapinfo.inode, mapinfo.shared));
414 }
415 })) {
416 LOG(ERROR) << "Failed to parse " << maps_file;
417 maps_.clear();
418 return false;
419 }
420
421 if (!get_usage_stats) {
422 return true;
423 }
424
425 if (!GetUsageStats(get_wss, use_pageidle, update_mem_usage)) {
426 maps_.clear();
427 return false;
428 }
429 return true;
430 }
431
GetUsageStats(bool get_wss,bool use_pageidle,bool update_mem_usage)432 bool ProcMemInfo::GetUsageStats(bool get_wss, bool use_pageidle, bool update_mem_usage) {
433 ::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
434 if (pagemap_fd == -1) {
435 return false;
436 }
437
438 for (auto& vma : maps_) {
439 if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss, use_pageidle, update_mem_usage, true)) {
440 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
441 << vma.end << "]";
442 return false;
443 }
444 add_mem_usage(&usage_, vma.usage);
445 }
446
447 return true;
448 }
449
FillInVmaStats(Vma & vma,bool use_kb)450 bool ProcMemInfo::FillInVmaStats(Vma& vma, bool use_kb) {
451 ::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
452 if (pagemap_fd == -1) {
453 return false;
454 }
455
456 if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss_, false, true, true)) {
457 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
458 << vma.end << "]";
459 return false;
460 }
461 if (!use_kb) {
462 convert_usage_kb_to_b(vma.usage);
463 }
464 return true;
465 }
466
ReadVmaStats(int pagemap_fd,Vma & vma,bool get_wss,bool use_pageidle,bool update_mem_usage,bool update_swap_usage)467 bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle,
468 bool update_mem_usage, bool update_swap_usage) {
469 PageAcct& pinfo = PageAcct::Instance();
470 if (get_wss && use_pageidle && !pinfo.InitPageAcct(true)) {
471 LOG(ERROR) << "Failed to init idle page accounting";
472 return false;
473 }
474
475 uint64_t pagesz_kb = getpagesize() / 1024;
476 size_t num_pages = (vma.end - vma.start) / getpagesize();
477 size_t first_page = vma.start / getpagesize();
478
479 std::vector<uint64_t> page_cache;
480 size_t cur_page_cache_index = 0;
481 size_t num_in_page_cache = 0;
482 size_t num_leftover_pages = num_pages;
483 for (size_t cur_page = first_page; cur_page < first_page + num_pages; ++cur_page) {
484 // Cache page map data.
485 if (cur_page_cache_index == num_in_page_cache) {
486 static constexpr size_t kMaxPages = 2048;
487 num_leftover_pages -= num_in_page_cache;
488 if (num_leftover_pages > kMaxPages) {
489 num_in_page_cache = kMaxPages;
490 } else {
491 num_in_page_cache = num_leftover_pages;
492 }
493 page_cache.resize(num_in_page_cache);
494 size_t total_bytes = page_cache.size() * sizeof(uint64_t);
495 ssize_t bytes = pread64(pagemap_fd, page_cache.data(), total_bytes,
496 cur_page * sizeof(uint64_t));
497 if (bytes != total_bytes) {
498 if (bytes == -1) {
499 PLOG(ERROR) << "Failed to read page data at offset 0x" << std::hex
500 << cur_page * sizeof(uint64_t);
501 } else {
502 LOG(ERROR) << "Failed to read page data at offset 0x" << std::hex
503 << cur_page * sizeof(uint64_t) << std::dec << " read bytes " << bytes
504 << " expected bytes " << total_bytes;
505 }
506 return false;
507 }
508 cur_page_cache_index = 0;
509 }
510
511 uint64_t page_info = page_cache[cur_page_cache_index++];
512 if (!PAGE_PRESENT(page_info) && !PAGE_SWAPPED(page_info)) continue;
513
514 if (PAGE_SWAPPED(page_info)) {
515 if (update_swap_usage) {
516 vma.usage.swap += pagesz_kb;
517 }
518 swap_offsets_.emplace_back(PAGE_SWAP_OFFSET(page_info));
519 continue;
520 }
521
522 if (!update_mem_usage) continue;
523
524 uint64_t page_frame = PAGE_PFN(page_info);
525 uint64_t cur_page_flags;
526 if (!pinfo.PageFlags(page_frame, &cur_page_flags)) {
527 LOG(ERROR) << "Failed to get page flags for " << page_frame << " in process " << pid_;
528 swap_offsets_.clear();
529 return false;
530 }
531
532 if (KPAGEFLAG_THP(cur_page_flags)) {
533 vma.usage.thp += pagesz_kb;
534 }
535
536 // skip unwanted pages from the count
537 if ((cur_page_flags & pgflags_mask_) != pgflags_) continue;
538
539 uint64_t cur_page_counts;
540 if (!pinfo.PageMapCount(page_frame, &cur_page_counts)) {
541 LOG(ERROR) << "Failed to get page count for " << page_frame << " in process " << pid_;
542 swap_offsets_.clear();
543 return false;
544 }
545
546 // Page was unmapped between the presence check at the beginning of the loop and here.
547 if (cur_page_counts == 0) {
548 continue;
549 }
550
551 bool is_dirty = !!(cur_page_flags & (1 << KPF_DIRTY));
552 bool is_private = (cur_page_counts == 1);
553 // Working set
554 if (get_wss) {
555 bool is_referenced = use_pageidle ? (pinfo.IsPageIdle(page_frame) == 1)
556 : !!(cur_page_flags & (1 << KPF_REFERENCED));
557 if (!is_referenced) {
558 continue;
559 }
560 // This effectively makes vss = rss for the working set is requested.
561 // The libpagemap implementation returns vss > rss for
562 // working set, which doesn't make sense.
563 vma.usage.vss += pagesz_kb;
564 }
565
566 vma.usage.rss += pagesz_kb;
567 vma.usage.uss += is_private ? pagesz_kb : 0;
568 vma.usage.pss += pagesz_kb / cur_page_counts;
569 if (is_private) {
570 vma.usage.private_dirty += is_dirty ? pagesz_kb : 0;
571 vma.usage.private_clean += is_dirty ? 0 : pagesz_kb;
572 } else {
573 vma.usage.shared_dirty += is_dirty ? pagesz_kb : 0;
574 vma.usage.shared_clean += is_dirty ? 0 : pagesz_kb;
575 }
576 }
577 if (!get_wss) {
578 vma.usage.vss += pagesz_kb * num_pages;
579 }
580 return true;
581 }
582
583 // Public APIs
ForEachVmaFromFile(const std::string & path,const VmaCallback & callback,bool read_smaps_fields)584 bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback,
585 bool read_smaps_fields) {
586 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
587 if (fp == nullptr) {
588 return false;
589 }
590
591 char* line = nullptr;
592 bool parsing_vma = false;
593 ssize_t line_len;
594 size_t line_alloc = 0;
595 Vma vma;
596 while ((line_len = getline(&line, &line_alloc, fp.get())) > 0) {
597 // Make sure the line buffer terminates like a C string for ReadMapFile
598 line[line_len] = '\0';
599
600 if (parsing_vma) {
601 if (parse_smaps_field(line, &vma.usage)) {
602 // This was a stats field
603 continue;
604 }
605
606 // Done collecting stats, make the call back
607 if (!callback(vma)) {
608 free(line);
609 return false;
610 }
611 parsing_vma = false;
612 }
613
614 vma.clear();
615 // If it has, we are looking for the vma stats
616 // 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
617 if (!::android::procinfo::ReadMapFileContent(
618 line, [&](const android::procinfo::MapInfo& mapinfo) {
619 vma.start = mapinfo.start;
620 vma.end = mapinfo.end;
621 vma.flags = mapinfo.flags;
622 vma.offset = mapinfo.pgoff;
623 vma.name = mapinfo.name;
624 vma.inode = mapinfo.inode;
625 vma.is_shared = mapinfo.shared;
626 })) {
627 // free getline() managed buffer
628 free(line);
629 LOG(ERROR) << "Failed to parse " << path;
630 return false;
631 }
632 if (read_smaps_fields) {
633 parsing_vma = true;
634 } else {
635 // Done collecting stats, make the call back
636 if (!callback(vma)) {
637 free(line);
638 return false;
639 }
640 }
641 }
642
643 // free getline() managed buffer
644 free(line);
645
646 if (parsing_vma) {
647 if (!callback(vma)) {
648 return false;
649 }
650 }
651
652 return true;
653 }
654
655 enum smaps_rollup_support { UNTRIED, SUPPORTED, UNSUPPORTED };
656
657 static std::atomic<smaps_rollup_support> g_rollup_support = UNTRIED;
658
IsSmapsRollupSupported()659 bool IsSmapsRollupSupported() {
660 // Similar to OpenSmapsOrRollup checks from android_os_Debug.cpp, except
661 // the method only checks if rollup is supported and returns the status
662 // right away.
663 enum smaps_rollup_support rollup_support = g_rollup_support.load(std::memory_order_relaxed);
664 if (rollup_support != UNTRIED) {
665 return rollup_support == SUPPORTED;
666 }
667
668 // Check the calling process for smaps_rollup since it is guaranteed to be alive
669 if (access("/proc/self/smaps_rollup", F_OK | R_OK)) {
670 g_rollup_support.store(UNSUPPORTED, std::memory_order_relaxed);
671 return false;
672 }
673
674 g_rollup_support.store(SUPPORTED, std::memory_order_relaxed);
675 LOG(INFO) << "Using smaps_rollup for pss collection";
676 return true;
677 }
678
SmapsOrRollupFromFile(const std::string & path,MemUsage * stats)679 bool SmapsOrRollupFromFile(const std::string& path, MemUsage* stats) {
680 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
681 if (fp == nullptr) {
682 return false;
683 }
684
685 char* line = nullptr;
686 size_t line_alloc = 0;
687 stats->clear();
688 while (getline(&line, &line_alloc, fp.get()) > 0) {
689 switch (line[0]) {
690 case 'P':
691 if (strncmp(line, "Pss:", 4) == 0) {
692 char* c = line + 4;
693 stats->pss += strtoull(c, nullptr, 10);
694 } else if (strncmp(line, "Private_Clean:", 14) == 0) {
695 char* c = line + 14;
696 uint64_t prcl = strtoull(c, nullptr, 10);
697 stats->private_clean += prcl;
698 stats->uss += prcl;
699 } else if (strncmp(line, "Private_Dirty:", 14) == 0) {
700 char* c = line + 14;
701 uint64_t prdi = strtoull(c, nullptr, 10);
702 stats->private_dirty += prdi;
703 stats->uss += prdi;
704 }
705 break;
706 case 'R':
707 if (strncmp(line, "Rss:", 4) == 0) {
708 char* c = line + 4;
709 stats->rss += strtoull(c, nullptr, 10);
710 }
711 break;
712 case 'S':
713 if (strncmp(line, "SwapPss:", 8) == 0) {
714 char* c = line + 8;
715 stats->swap_pss += strtoull(c, nullptr, 10);
716 }
717 break;
718 }
719 }
720
721 // free getline() managed buffer
722 free(line);
723 return true;
724 }
725
SmapsOrRollupPssFromFile(const std::string & path,uint64_t * pss)726 bool SmapsOrRollupPssFromFile(const std::string& path, uint64_t* pss) {
727 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
728 if (fp == nullptr) {
729 return false;
730 }
731 *pss = 0;
732 char* line = nullptr;
733 size_t line_alloc = 0;
734 while (getline(&line, &line_alloc, fp.get()) > 0) {
735 uint64_t v;
736 if (sscanf(line, "Pss: %" SCNu64 " kB", &v) == 1) {
737 *pss += v;
738 }
739 }
740
741 // free getline() managed buffer
742 free(line);
743 return true;
744 }
745
StatusVmRSSFromFile(const std::string & path,uint64_t * rss)746 bool StatusVmRSSFromFile(const std::string& path, uint64_t* rss) {
747 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
748 if (fp == nullptr) {
749 return false;
750 }
751
752 // We use this bool because -1 as an "invalid" value for RSS will wrap
753 // around to a positive number.
754 bool success = false;
755
756 *rss = 0;
757 char* line = nullptr;
758 size_t line_alloc = 0;
759 while (getline(&line, &line_alloc, fp.get()) > 0) {
760 uint64_t v;
761 if (sscanf(line, "VmRSS: %" SCNu64 " kB", &v) == 1) {
762 *rss = v;
763 success = true;
764 // Break because there is only one VmRSS field in status.
765 break;
766 }
767 }
768
769 // free getline() managed buffer
770 free(line);
771 return success;
772 }
773
GetFormat(std::string_view arg)774 Format GetFormat(std::string_view arg) {
775 if (arg == "json") {
776 return Format::JSON;
777 }
778 if (arg == "csv") {
779 return Format::CSV;
780 }
781 if (arg == "raw") {
782 return Format::RAW;
783 }
784 return Format::INVALID;
785 }
786
EscapeCsvString(const std::string & raw)787 std::string EscapeCsvString(const std::string& raw) {
788 std::string ret;
789 for (auto it = raw.cbegin(); it != raw.cend(); it++) {
790 switch (*it) {
791 case '"':
792 ret += "\"";
793 break;
794 default:
795 ret += *it;
796 break;
797 }
798 }
799 return '"' + ret + '"';
800 }
801
EscapeJsonString(const std::string & raw)802 std::string EscapeJsonString(const std::string& raw) {
803 std::string ret;
804 for (auto it = raw.cbegin(); it != raw.cend(); it++) {
805 switch (*it) {
806 case '\\':
807 ret += "\\\\";
808 break;
809 case '"':
810 ret += "\\\"";
811 break;
812 case '/':
813 ret += "\\/";
814 break;
815 case '\b':
816 ret += "\\b";
817 break;
818 case '\f':
819 ret += "\\f";
820 break;
821 case '\n':
822 ret += "\\n";
823 break;
824 case '\r':
825 ret += "\\r";
826 break;
827 case '\t':
828 ret += "\\t";
829 break;
830 default:
831 ret += *it;
832 break;
833 }
834 }
835 return '"' + ret + '"';
836 }
837
838 } // namespace meminfo
839 } // namespace android
840