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