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