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 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. 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 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 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 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 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 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 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 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 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 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 202 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 208 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 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 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 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 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 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 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 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 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 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 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