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 // Returns true if the line was valid smaps stats line false otherwise.
parse_smaps_field(const char * line,MemUsage * stats)70 static bool parse_smaps_field(const char* line, MemUsage* stats) {
71 const char *end = line;
72
73 // https://lore.kernel.org/patchwork/patch/1088579/ introduced tabs. Handle this case as well.
74 while (*end && !isspace(*end)) end++;
75 if (*end && end > line && *(end - 1) == ':') {
76 const char* c = end;
77 while (isspace(*c)) c++;
78 switch (line[0]) {
79 case 'P':
80 if (strncmp(line, "Pss:", 4) == 0) {
81 stats->pss = strtoull(c, nullptr, 10);
82 } else if (strncmp(line, "Private_Clean:", 14) == 0) {
83 uint64_t prcl = strtoull(c, nullptr, 10);
84 stats->private_clean = prcl;
85 stats->uss += prcl;
86 } else if (strncmp(line, "Private_Dirty:", 14) == 0) {
87 uint64_t prdi = strtoull(c, nullptr, 10);
88 stats->private_dirty = prdi;
89 stats->uss += prdi;
90 } else if (strncmp(line, "Private_Hugetlb:", 16) == 0) {
91 stats->private_hugetlb = strtoull(c, nullptr, 10);
92 }
93 break;
94 case 'S':
95 if (strncmp(line, "Size:", 5) == 0) {
96 stats->vss = strtoull(c, nullptr, 10);
97 } else if (strncmp(line, "Shared_Clean:", 13) == 0) {
98 stats->shared_clean = strtoull(c, nullptr, 10);
99 } else if (strncmp(line, "Shared_Dirty:", 13) == 0) {
100 stats->shared_dirty = strtoull(c, nullptr, 10);
101 } else if (strncmp(line, "Swap:", 5) == 0) {
102 stats->swap = strtoull(c, nullptr, 10);
103 } else if (strncmp(line, "SwapPss:", 8) == 0) {
104 stats->swap_pss = strtoull(c, nullptr, 10);
105 } else if (strncmp(line, "ShmemPmdMapped:", 15) == 0) {
106 stats->shmem_pmd_mapped = strtoull(c, nullptr, 10);
107 } else if (strncmp(line, "Shared_Hugetlb:", 15) == 0) {
108 stats->shared_hugetlb = strtoull(c, nullptr, 10);
109 }
110 break;
111 case 'R':
112 if (strncmp(line, "Rss:", 4) == 0) {
113 stats->rss = strtoull(c, nullptr, 10);
114 }
115 break;
116 case 'A':
117 if (strncmp(line, "AnonHugePages:", 14) == 0) {
118 stats->anon_huge_pages = strtoull(c, nullptr, 10);
119 }
120 break;
121 case 'F':
122 if (strncmp(line, "FilePmdMapped:", 14) == 0) {
123 stats->file_pmd_mapped = strtoull(c, nullptr, 10);
124 }
125 break;
126 }
127 return true;
128 }
129
130 return false;
131 }
132
ResetWorkingSet(pid_t pid)133 bool ProcMemInfo::ResetWorkingSet(pid_t pid) {
134 std::string clear_refs_path = ::android::base::StringPrintf("/proc/%d/clear_refs", pid);
135 if (!::android::base::WriteStringToFile("1\n", clear_refs_path)) {
136 PLOG(ERROR) << "Failed to write to " << clear_refs_path;
137 return false;
138 }
139
140 return true;
141 }
142
ProcMemInfo(pid_t pid,bool get_wss,uint64_t pgflags,uint64_t pgflags_mask)143 ProcMemInfo::ProcMemInfo(pid_t pid, bool get_wss, uint64_t pgflags, uint64_t pgflags_mask)
144 : pid_(pid), get_wss_(get_wss), pgflags_(pgflags), pgflags_mask_(pgflags_mask) {}
145
Maps()146 const std::vector<Vma>& ProcMemInfo::Maps() {
147 if (maps_.empty() && !ReadMaps(get_wss_)) {
148 LOG(ERROR) << "Failed to read maps for Process " << pid_;
149 }
150
151 return maps_;
152 }
153
MapsWithPageIdle()154 const std::vector<Vma>& ProcMemInfo::MapsWithPageIdle() {
155 if (maps_.empty() && !ReadMaps(get_wss_, true)) {
156 LOG(ERROR) << "Failed to read maps with page idle for Process " << pid_;
157 }
158
159 return maps_;
160 }
161
MapsWithoutUsageStats()162 const std::vector<Vma>& ProcMemInfo::MapsWithoutUsageStats() {
163 if (maps_.empty() && !ReadMaps(get_wss_, false, false)) {
164 LOG(ERROR) << "Failed to read maps for Process " << pid_;
165 }
166
167 return maps_;
168 }
169
Smaps(const std::string & path)170 const std::vector<Vma>& ProcMemInfo::Smaps(const std::string& path) {
171 if (!maps_.empty()) {
172 return maps_;
173 }
174
175 auto collect_vmas = [&](const Vma& vma) {
176 if (std::find(g_excluded_vmas.begin(), g_excluded_vmas.end(), vma.name) ==
177 g_excluded_vmas.end()) {
178 maps_.emplace_back(vma);
179 }
180 };
181 if (path.empty() && !ForEachVma(collect_vmas)) {
182 LOG(ERROR) << "Failed to read smaps for Process " << pid_;
183 maps_.clear();
184 }
185
186 if (!path.empty() && !ForEachVmaFromFile(path, collect_vmas)) {
187 LOG(ERROR) << "Failed to read smaps from file " << path;
188 maps_.clear();
189 }
190
191 return maps_;
192 }
193
Usage()194 const MemUsage& ProcMemInfo::Usage() {
195 if (get_wss_) {
196 LOG(WARNING) << "Trying to read process memory usage for " << pid_
197 << " using invalid object";
198 return usage_;
199 }
200
201 if (maps_.empty() && !ReadMaps(get_wss_)) {
202 LOG(ERROR) << "Failed to get memory usage for Process " << pid_;
203 }
204
205 return usage_;
206 }
207
Wss()208 const MemUsage& ProcMemInfo::Wss() {
209 if (!get_wss_) {
210 LOG(WARNING) << "Trying to read process working set for " << pid_
211 << " using invalid object";
212 return usage_;
213 }
214
215 if (maps_.empty() && !ReadMaps(get_wss_)) {
216 LOG(ERROR) << "Failed to get working set for Process " << pid_;
217 }
218
219 return usage_;
220 }
221
ForEachVma(const VmaCallback & callback,bool use_smaps)222 bool ProcMemInfo::ForEachVma(const VmaCallback& callback, bool use_smaps) {
223 std::string path =
224 ::android::base::StringPrintf("/proc/%d/%s", pid_, use_smaps ? "smaps" : "maps");
225 return ForEachVmaFromFile(path, callback, use_smaps);
226 }
227
ForEachVmaFromMaps(const VmaCallback & callback)228 bool ProcMemInfo::ForEachVmaFromMaps(const VmaCallback& callback) {
229 Vma vma;
230 auto vmaCollect = [&callback,&vma](const uint64_t start, uint64_t end, uint16_t flags,
231 uint64_t pgoff, ino_t inode, const char* name, bool shared) {
232 vma.start = start;
233 vma.end = end;
234 vma.flags = flags;
235 vma.offset = pgoff;
236 vma.name = name;
237 vma.inode = inode;
238 vma.is_shared = shared;
239 callback(vma);
240 };
241
242 bool success = ::android::procinfo::ReadProcessMaps(pid_, vmaCollect);
243
244 return success;
245 }
246
SmapsOrRollup(MemUsage * stats) const247 bool ProcMemInfo::SmapsOrRollup(MemUsage* stats) const {
248 std::string path = ::android::base::StringPrintf(
249 "/proc/%d/%s", pid_, IsSmapsRollupSupported() ? "smaps_rollup" : "smaps");
250 return SmapsOrRollupFromFile(path, stats);
251 }
252
SmapsOrRollupPss(uint64_t * pss) const253 bool ProcMemInfo::SmapsOrRollupPss(uint64_t* pss) const {
254 std::string path = ::android::base::StringPrintf(
255 "/proc/%d/%s", pid_, IsSmapsRollupSupported() ? "smaps_rollup" : "smaps");
256 return SmapsOrRollupPssFromFile(path, pss);
257 }
258
SwapOffsets()259 const std::vector<uint64_t>& ProcMemInfo::SwapOffsets() {
260 if (get_wss_) {
261 LOG(WARNING) << "Trying to read process swap offsets for " << pid_
262 << " using invalid object";
263 return swap_offsets_;
264 }
265
266 if (maps_.empty() && !ReadMaps(get_wss_, false, true, true)) {
267 LOG(ERROR) << "Failed to get swap offsets for Process " << pid_;
268 }
269
270 return swap_offsets_;
271 }
272
PageMap(const Vma & vma,std::vector<uint64_t> * pagemap)273 bool ProcMemInfo::PageMap(const Vma& vma, std::vector<uint64_t>* pagemap) {
274 pagemap->clear();
275 std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid_);
276 ::android::base::unique_fd pagemap_fd(
277 TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC)));
278 if (pagemap_fd == -1) {
279 PLOG(ERROR) << "Failed to open " << pagemap_file;
280 return false;
281 }
282
283 uint64_t nr_pages = (vma.end - vma.start) / getpagesize();
284 pagemap->resize(nr_pages);
285
286 size_t bytes_to_read = sizeof(uint64_t) * nr_pages;
287 off64_t start_addr = (vma.start / getpagesize()) * sizeof(uint64_t);
288 ssize_t bytes_read = pread64(pagemap_fd, pagemap->data(), bytes_to_read, start_addr);
289 if (bytes_read == -1) {
290 PLOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_;
291 return false;
292 } else if (static_cast<size_t>(bytes_read) != bytes_to_read) {
293 LOG(ERROR) << "Failed to read page frames from page map for pid: " << pid_
294 << ": read bytes " << bytes_read << " expected bytes " << bytes_to_read;
295 return false;
296 }
297
298 return true;
299 }
300
GetPagemapFd(pid_t pid)301 static int GetPagemapFd(pid_t pid) {
302 std::string pagemap_file = ::android::base::StringPrintf("/proc/%d/pagemap", pid);
303 int fd = TEMP_FAILURE_RETRY(open(pagemap_file.c_str(), O_RDONLY | O_CLOEXEC));
304 if (fd == -1) {
305 PLOG(ERROR) << "Failed to open " << pagemap_file;
306 }
307 return fd;
308 }
309
ReadMaps(bool get_wss,bool use_pageidle,bool get_usage_stats,bool swap_only)310 bool ProcMemInfo::ReadMaps(bool get_wss, bool use_pageidle, bool get_usage_stats, bool swap_only) {
311 // Each object reads /proc/<pid>/maps only once. This is done to make sure programs that are
312 // running for the lifetime of the system can recycle the objects and don't have to
313 // unnecessarily retain and update this object in memory (which can get significantly large).
314 // E.g. A program that only needs to reset the working set will never all ->Maps(), ->Usage().
315 // E.g. A program that is monitoring smaps_rollup, may never call ->maps(), Usage(), so it
316 // doesn't make sense for us to parse and retain unnecessary memory accounting stats by default.
317 if (!maps_.empty()) return true;
318
319 // parse and read /proc/<pid>/maps
320 std::string maps_file = ::android::base::StringPrintf("/proc/%d/maps", pid_);
321 if (!::android::procinfo::ReadMapFile(
322 maps_file, [&](const android::procinfo::MapInfo& mapinfo) {
323 if (std::find(g_excluded_vmas.begin(), g_excluded_vmas.end(), mapinfo.name) ==
324 g_excluded_vmas.end()) {
325 maps_.emplace_back(Vma(mapinfo.start, mapinfo.end,
326 mapinfo.pgoff, mapinfo.flags,
327 mapinfo.name,
328 mapinfo.inode, mapinfo.shared));
329 }
330 })) {
331 LOG(ERROR) << "Failed to parse " << maps_file;
332 maps_.clear();
333 return false;
334 }
335
336 if (!get_usage_stats) {
337 return true;
338 }
339
340 ::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
341 if (pagemap_fd == -1) {
342 return false;
343 }
344
345 for (auto& vma : maps_) {
346 if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss, use_pageidle, swap_only)) {
347 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
348 << vma.end << "]";
349 maps_.clear();
350 return false;
351 }
352 add_mem_usage(&usage_, vma.usage);
353 }
354
355 return true;
356 }
357
FillInVmaStats(Vma & vma)358 bool ProcMemInfo::FillInVmaStats(Vma& vma) {
359 ::android::base::unique_fd pagemap_fd(GetPagemapFd(pid_));
360 if (pagemap_fd == -1) {
361 return false;
362 }
363
364 if (!ReadVmaStats(pagemap_fd.get(), vma, get_wss_, false, false)) {
365 LOG(ERROR) << "Failed to read page map for vma " << vma.name << "[" << vma.start << "-"
366 << vma.end << "]";
367 return false;
368 }
369 return true;
370 }
371
ReadVmaStats(int pagemap_fd,Vma & vma,bool get_wss,bool use_pageidle,bool swap_only)372 bool ProcMemInfo::ReadVmaStats(int pagemap_fd, Vma& vma, bool get_wss, bool use_pageidle,
373 bool swap_only) {
374 PageAcct& pinfo = PageAcct::Instance();
375 if (get_wss && use_pageidle && !pinfo.InitPageAcct(true)) {
376 LOG(ERROR) << "Failed to init idle page accounting";
377 return false;
378 }
379
380 uint64_t pagesz = getpagesize();
381 size_t num_pages = (vma.end - vma.start) / pagesz;
382 size_t first_page = vma.start / pagesz;
383
384 std::vector<uint64_t> page_cache;
385 size_t cur_page_cache_index = 0;
386 size_t num_in_page_cache = 0;
387 size_t num_leftover_pages = num_pages;
388 for (size_t cur_page = first_page; cur_page < first_page + num_pages; ++cur_page) {
389 // Cache page map data.
390 if (cur_page_cache_index == num_in_page_cache) {
391 static constexpr size_t kMaxPages = 2048;
392 num_leftover_pages -= num_in_page_cache;
393 if (num_leftover_pages > kMaxPages) {
394 num_in_page_cache = kMaxPages;
395 } else {
396 num_in_page_cache = num_leftover_pages;
397 }
398 page_cache.resize(num_in_page_cache);
399 size_t total_bytes = page_cache.size() * sizeof(uint64_t);
400 ssize_t bytes = pread64(pagemap_fd, page_cache.data(), total_bytes,
401 cur_page * sizeof(uint64_t));
402 if (bytes != total_bytes) {
403 if (bytes == -1) {
404 PLOG(ERROR) << "Failed to read page data at offset 0x" << std::hex
405 << cur_page * sizeof(uint64_t);
406 } else {
407 LOG(ERROR) << "Failed to read page data at offset 0x" << std::hex
408 << cur_page * sizeof(uint64_t) << std::dec << " read bytes " << bytes
409 << " expected bytes " << total_bytes;
410 }
411 return false;
412 }
413 cur_page_cache_index = 0;
414 }
415
416 uint64_t page_info = page_cache[cur_page_cache_index++];
417 if (!PAGE_PRESENT(page_info) && !PAGE_SWAPPED(page_info)) continue;
418
419 if (PAGE_SWAPPED(page_info)) {
420 vma.usage.swap += pagesz;
421 swap_offsets_.emplace_back(PAGE_SWAP_OFFSET(page_info));
422 continue;
423 }
424
425 if (swap_only)
426 continue;
427
428 uint64_t page_frame = PAGE_PFN(page_info);
429 uint64_t cur_page_flags;
430 if (!pinfo.PageFlags(page_frame, &cur_page_flags)) {
431 LOG(ERROR) << "Failed to get page flags for " << page_frame << " in process " << pid_;
432 swap_offsets_.clear();
433 return false;
434 }
435
436 if (KPAGEFLAG_THP(cur_page_flags)) {
437 vma.usage.thp += pagesz;
438 }
439
440 // skip unwanted pages from the count
441 if ((cur_page_flags & pgflags_mask_) != pgflags_) continue;
442
443 uint64_t cur_page_counts;
444 if (!pinfo.PageMapCount(page_frame, &cur_page_counts)) {
445 LOG(ERROR) << "Failed to get page count for " << page_frame << " in process " << pid_;
446 swap_offsets_.clear();
447 return false;
448 }
449
450 // Page was unmapped between the presence check at the beginning of the loop and here.
451 if (cur_page_counts == 0) {
452 continue;
453 }
454
455 bool is_dirty = !!(cur_page_flags & (1 << KPF_DIRTY));
456 bool is_private = (cur_page_counts == 1);
457 // Working set
458 if (get_wss) {
459 bool is_referenced = use_pageidle ? (pinfo.IsPageIdle(page_frame) == 1)
460 : !!(cur_page_flags & (1 << KPF_REFERENCED));
461 if (!is_referenced) {
462 continue;
463 }
464 // This effectively makes vss = rss for the working set is requested.
465 // The libpagemap implementation returns vss > rss for
466 // working set, which doesn't make sense.
467 vma.usage.vss += pagesz;
468 }
469
470 vma.usage.rss += pagesz;
471 vma.usage.uss += is_private ? pagesz : 0;
472 vma.usage.pss += pagesz / cur_page_counts;
473 if (is_private) {
474 vma.usage.private_dirty += is_dirty ? pagesz : 0;
475 vma.usage.private_clean += is_dirty ? 0 : pagesz;
476 } else {
477 vma.usage.shared_dirty += is_dirty ? pagesz : 0;
478 vma.usage.shared_clean += is_dirty ? 0 : pagesz;
479 }
480 }
481 if (!get_wss) {
482 vma.usage.vss += pagesz * num_pages;
483 }
484 return true;
485 }
486
487 // Public APIs
ForEachVmaFromFile(const std::string & path,const VmaCallback & callback,bool read_smaps_fields)488 bool ForEachVmaFromFile(const std::string& path, const VmaCallback& callback,
489 bool read_smaps_fields) {
490 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
491 if (fp == nullptr) {
492 return false;
493 }
494
495 char* line = nullptr;
496 bool parsing_vma = false;
497 ssize_t line_len;
498 size_t line_alloc = 0;
499 Vma vma;
500 while ((line_len = getline(&line, &line_alloc, fp.get())) > 0) {
501 // Make sure the line buffer terminates like a C string for ReadMapFile
502 line[line_len] = '\0';
503
504 if (parsing_vma) {
505 if (parse_smaps_field(line, &vma.usage)) {
506 // This was a stats field
507 continue;
508 }
509
510 // Done collecting stats, make the call back
511 callback(vma);
512 parsing_vma = false;
513 }
514
515 vma.clear();
516 // If it has, we are looking for the vma stats
517 // 00400000-00409000 r-xp 00000000 fc:00 426998 /usr/lib/gvfs/gvfsd-http
518 if (!::android::procinfo::ReadMapFileContent(
519 line, [&](const android::procinfo::MapInfo& mapinfo) {
520 vma.start = mapinfo.start;
521 vma.end = mapinfo.end;
522 vma.flags = mapinfo.flags;
523 vma.offset = mapinfo.pgoff;
524 vma.name = mapinfo.name;
525 vma.inode = mapinfo.inode;
526 vma.is_shared = mapinfo.shared;
527 })) {
528 // free getline() managed buffer
529 free(line);
530 LOG(ERROR) << "Failed to parse " << path;
531 return false;
532 }
533 if (read_smaps_fields) {
534 parsing_vma = true;
535 } else {
536 // Done collecting stats, make the call back
537 callback(vma);
538 }
539 }
540
541 // free getline() managed buffer
542 free(line);
543
544 if (parsing_vma) {
545 callback(vma);
546 }
547
548 return true;
549 }
550
551 enum smaps_rollup_support { UNTRIED, SUPPORTED, UNSUPPORTED };
552
553 static std::atomic<smaps_rollup_support> g_rollup_support = UNTRIED;
554
IsSmapsRollupSupported()555 bool IsSmapsRollupSupported() {
556 // Similar to OpenSmapsOrRollup checks from android_os_Debug.cpp, except
557 // the method only checks if rollup is supported and returns the status
558 // right away.
559 enum smaps_rollup_support rollup_support = g_rollup_support.load(std::memory_order_relaxed);
560 if (rollup_support != UNTRIED) {
561 return rollup_support == SUPPORTED;
562 }
563
564 // Check the calling process for smaps_rollup since it is guaranteed to be alive
565 if (access("/proc/self/smaps_rollup", F_OK | R_OK)) {
566 g_rollup_support.store(UNSUPPORTED, std::memory_order_relaxed);
567 return false;
568 }
569
570 g_rollup_support.store(SUPPORTED, std::memory_order_relaxed);
571 LOG(INFO) << "Using smaps_rollup for pss collection";
572 return true;
573 }
574
SmapsOrRollupFromFile(const std::string & path,MemUsage * stats)575 bool SmapsOrRollupFromFile(const std::string& path, MemUsage* stats) {
576 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
577 if (fp == nullptr) {
578 return false;
579 }
580
581 char* line = nullptr;
582 size_t line_alloc = 0;
583 stats->clear();
584 while (getline(&line, &line_alloc, fp.get()) > 0) {
585 switch (line[0]) {
586 case 'P':
587 if (strncmp(line, "Pss:", 4) == 0) {
588 char* c = line + 4;
589 stats->pss += strtoull(c, nullptr, 10);
590 } else if (strncmp(line, "Private_Clean:", 14) == 0) {
591 char* c = line + 14;
592 uint64_t prcl = strtoull(c, nullptr, 10);
593 stats->private_clean += prcl;
594 stats->uss += prcl;
595 } else if (strncmp(line, "Private_Dirty:", 14) == 0) {
596 char* c = line + 14;
597 uint64_t prdi = strtoull(c, nullptr, 10);
598 stats->private_dirty += prdi;
599 stats->uss += prdi;
600 }
601 break;
602 case 'R':
603 if (strncmp(line, "Rss:", 4) == 0) {
604 char* c = line + 4;
605 stats->rss += strtoull(c, nullptr, 10);
606 }
607 break;
608 case 'S':
609 if (strncmp(line, "SwapPss:", 8) == 0) {
610 char* c = line + 8;
611 stats->swap_pss += strtoull(c, nullptr, 10);
612 }
613 break;
614 }
615 }
616
617 // free getline() managed buffer
618 free(line);
619 return true;
620 }
621
SmapsOrRollupPssFromFile(const std::string & path,uint64_t * pss)622 bool SmapsOrRollupPssFromFile(const std::string& path, uint64_t* pss) {
623 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
624 if (fp == nullptr) {
625 return false;
626 }
627 *pss = 0;
628 char* line = nullptr;
629 size_t line_alloc = 0;
630 while (getline(&line, &line_alloc, fp.get()) > 0) {
631 uint64_t v;
632 if (sscanf(line, "Pss: %" SCNu64 " kB", &v) == 1) {
633 *pss += v;
634 }
635 }
636
637 // free getline() managed buffer
638 free(line);
639 return true;
640 }
641
642 } // namespace meminfo
643 } // namespace android
644