• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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