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