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