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 <meminfo/procmeminfo.h> 18 #include <meminfo/sysmeminfo.h> 19 20 #include <fcntl.h> 21 #include <inttypes.h> 22 #include <stdio.h> 23 #include <sys/stat.h> 24 #include <sys/types.h> 25 26 #include <string> 27 28 #include <android-base/file.h> 29 #include <android-base/logging.h> 30 #include <android-base/stringprintf.h> 31 #include <android-base/unique_fd.h> 32 33 #include <benchmark/benchmark.h> 34 35 using ::android::meminfo::MemUsage; 36 using ::android::meminfo::ProcMemInfo; 37 using ::android::meminfo::SmapsOrRollupFromFile; 38 using ::android::meminfo::SysMemInfo; 39 40 enum { 41 MEMINFO_TOTAL, 42 MEMINFO_FREE, 43 MEMINFO_BUFFERS, 44 MEMINFO_CACHED, 45 MEMINFO_SHMEM, 46 MEMINFO_SLAB, 47 MEMINFO_SLAB_RECLAIMABLE, 48 MEMINFO_SLAB_UNRECLAIMABLE, 49 MEMINFO_SWAP_TOTAL, 50 MEMINFO_SWAP_FREE, 51 MEMINFO_ZRAM_TOTAL, 52 MEMINFO_MAPPED, 53 MEMINFO_VMALLOC_USED, 54 MEMINFO_PAGE_TABLES, 55 MEMINFO_KERNEL_STACK, 56 MEMINFO_COUNT 57 }; 58 59 static void get_mem_info(uint64_t mem[], const char* file) { 60 char buffer[4096]; 61 unsigned int numFound = 0; 62 63 int fd = open(file, O_RDONLY); 64 65 if (fd < 0) { 66 printf("Unable to open %s: %s\n", file, strerror(errno)); 67 return; 68 } 69 70 const int len = read(fd, buffer, sizeof(buffer) - 1); 71 close(fd); 72 73 if (len < 0) { 74 printf("Empty %s\n", file); 75 return; 76 } 77 buffer[len] = 0; 78 79 static const char* const tags[] = { 80 "MemTotal:", "MemFree:", "Buffers:", "Cached:", "Shmem:", "Slab:", 81 "SReclaimable:", "SUnreclaim:", "SwapTotal:", "SwapFree:", "ZRam:", "Mapped:", 82 "VmallocUsed:", "PageTables:", "KernelStack:", NULL}; 83 84 static const int tagsLen[] = {9, 8, 8, 7, 6, 5, 13, 11, 10, 9, 5, 7, 12, 11, 12, 0}; 85 86 memset(mem, 0, sizeof(uint64_t) * 15); 87 char* p = buffer; 88 while (*p && (numFound < (sizeof(tagsLen) / sizeof(tagsLen[0])))) { 89 int i = 0; 90 while (tags[i]) { 91 // std::cout << "tag =" << tags[i] << " p = " << std::string(p, tagsLen[i]) << 92 // std::endl; 93 if (strncmp(p, tags[i], tagsLen[i]) == 0) { 94 p += tagsLen[i]; 95 while (*p == ' ') p++; 96 char* num = p; 97 while (*p >= '0' && *p <= '9') p++; 98 if (*p != 0) { 99 *p = 0; 100 p++; 101 } 102 mem[i] = atoll(num); 103 numFound++; 104 break; 105 } 106 i++; 107 } 108 while (*p && *p != '\n') { 109 p++; 110 } 111 if (*p) p++; 112 } 113 } 114 115 static void BM_ReadMemInfo_old(benchmark::State& state) { 116 std::string meminfo = R"meminfo(MemTotal: 3019740 kB 117 MemFree: 1809728 kB 118 MemAvailable: 2546560 kB 119 Buffers: 54736 kB 120 Cached: 776052 kB 121 SwapCached: 0 kB 122 Active: 445856 kB 123 Inactive: 459092 kB 124 Active(anon): 78492 kB 125 Inactive(anon): 2240 kB 126 Active(file): 367364 kB 127 Inactive(file): 456852 kB 128 Unevictable: 3096 kB 129 Mlocked: 3096 kB 130 SwapTotal: 0 kB 131 SwapFree: 0 kB 132 Dirty: 32 kB 133 Writeback: 0 kB 134 AnonPages: 74988 kB 135 Mapped: 62624 kB 136 Shmem: 4020 kB 137 Slab: 86464 kB 138 SReclaimable: 44432 kB 139 SUnreclaim: 42032 kB 140 KernelStack: 4880 kB 141 PageTables: 2900 kB 142 NFS_Unstable: 0 kB 143 Bounce: 0 kB 144 WritebackTmp: 0 kB 145 CommitLimit: 1509868 kB 146 Committed_AS: 80296 kB 147 VmallocTotal: 263061440 kB 148 VmallocUsed: 0 kB 149 VmallocChunk: 0 kB 150 AnonHugePages: 6144 kB 151 ShmemHugePages: 0 kB 152 ShmemPmdMapped: 0 kB 153 CmaTotal: 131072 kB 154 CmaFree: 130380 kB 155 HugePages_Total: 0 156 HugePages_Free: 0 157 HugePages_Rsvd: 0 158 HugePages_Surp: 0 159 Hugepagesize: 2048 kB)meminfo"; 160 161 TemporaryFile tf; 162 ::android::base::WriteStringToFd(meminfo, tf.fd); 163 164 uint64_t mem[MEMINFO_COUNT]; 165 for (auto _ : state) { 166 get_mem_info(mem, tf.path); 167 } 168 } 169 BENCHMARK(BM_ReadMemInfo_old); 170 171 static void BM_ReadMemInfo_new(benchmark::State& state) { 172 std::string meminfo = R"meminfo(MemTotal: 3019740 kB 173 MemFree: 1809728 kB 174 MemAvailable: 2546560 kB 175 Buffers: 54736 kB 176 Cached: 776052 kB 177 SwapCached: 0 kB 178 Active: 445856 kB 179 Inactive: 459092 kB 180 Active(anon): 78492 kB 181 Inactive(anon): 2240 kB 182 Active(file): 367364 kB 183 Inactive(file): 456852 kB 184 Unevictable: 3096 kB 185 Mlocked: 3096 kB 186 SwapTotal: 0 kB 187 SwapFree: 0 kB 188 Dirty: 32 kB 189 Writeback: 0 kB 190 AnonPages: 74988 kB 191 Mapped: 62624 kB 192 Shmem: 4020 kB 193 Slab: 86464 kB 194 SReclaimable: 44432 kB 195 SUnreclaim: 42032 kB 196 KernelStack: 4880 kB 197 PageTables: 2900 kB 198 NFS_Unstable: 0 kB 199 Bounce: 0 kB 200 WritebackTmp: 0 kB 201 CommitLimit: 1509868 kB 202 Committed_AS: 80296 kB 203 VmallocTotal: 263061440 kB 204 VmallocUsed: 0 kB 205 VmallocChunk: 0 kB 206 AnonHugePages: 6144 kB 207 ShmemHugePages: 0 kB 208 ShmemPmdMapped: 0 kB 209 CmaTotal: 131072 kB 210 CmaFree: 130380 kB 211 HugePages_Total: 0 212 HugePages_Free: 0 213 HugePages_Rsvd: 0 214 HugePages_Surp: 0 215 Hugepagesize: 2048 kB)meminfo"; 216 217 TemporaryFile tf; 218 android::base::WriteStringToFd(meminfo, tf.fd); 219 220 std::string file = std::string(tf.path); 221 std::vector<uint64_t> mem; 222 const std::vector<std::string_view> tags = { 223 SysMemInfo::kMemTotal, SysMemInfo::kMemFree, SysMemInfo::kMemBuffers, 224 SysMemInfo::kMemCached, SysMemInfo::kMemShmem, SysMemInfo::kMemSlab, 225 SysMemInfo::kMemSReclaim, SysMemInfo::kMemSUnreclaim, SysMemInfo::kMemSwapTotal, 226 SysMemInfo::kMemSwapFree, SysMemInfo::kMemMapped, SysMemInfo::kMemVmallocUsed, 227 SysMemInfo::kMemPageTables, SysMemInfo::kMemKernelStack, 228 }; 229 230 SysMemInfo smi; 231 for (auto _ : state) { 232 mem.resize(tags.size()); 233 smi.ReadMemInfo(tags.size(), tags.data(), mem.data(), file.c_str()); 234 } 235 } 236 BENCHMARK(BM_ReadMemInfo_new); 237 238 static uint64_t get_zram_mem_used(const std::string& zram_dir) { 239 FILE* f = fopen((zram_dir + "mm_stat").c_str(), "r"); 240 if (f) { 241 uint64_t mem_used_total = 0; 242 243 int matched = fscanf(f, "%*d %*d %" SCNu64 " %*d %*d %*d %*d", &mem_used_total); 244 if (matched != 1) 245 fprintf(stderr, "warning: failed to parse %s\n", (zram_dir + "mm_stat").c_str()); 246 247 fclose(f); 248 return mem_used_total; 249 } 250 251 f = fopen((zram_dir + "mem_used_total").c_str(), "r"); 252 if (f) { 253 uint64_t mem_used_total = 0; 254 255 int matched = fscanf(f, "%" SCNu64, &mem_used_total); 256 if (matched != 1) 257 fprintf(stderr, "warning: failed to parse %s\n", (zram_dir + "mem_used_total").c_str()); 258 259 fclose(f); 260 return mem_used_total; 261 } 262 263 return 0; 264 } 265 266 static void BM_ZramTotal_old(benchmark::State& state) { 267 std::string exec_dir = ::android::base::GetExecutableDirectory(); 268 std::string zram_mmstat_dir = exec_dir + "/testdata1/"; 269 for (auto _ : state) { 270 uint64_t zram_total __attribute__((unused)) = get_zram_mem_used(zram_mmstat_dir) / 1024; 271 } 272 } 273 BENCHMARK(BM_ZramTotal_old); 274 275 static void BM_ZramTotal_new(benchmark::State& state) { 276 std::string exec_dir = ::android::base::GetExecutableDirectory(); 277 std::string zram_mmstat_dir = exec_dir + "/testdata1/"; 278 SysMemInfo smi; 279 for (auto _ : state) { 280 uint64_t zram_total __attribute__((unused)) = smi.mem_zram_kb(zram_mmstat_dir.c_str()); 281 } 282 } 283 BENCHMARK(BM_ZramTotal_new); 284 285 static void BM_MemInfoWithZram_old(benchmark::State& state) { 286 std::string meminfo = R"meminfo(MemTotal: 3019740 kB 287 MemFree: 1809728 kB 288 MemAvailable: 2546560 kB 289 Buffers: 54736 kB 290 Cached: 776052 kB 291 SwapCached: 0 kB 292 Active: 445856 kB 293 Inactive: 459092 kB 294 Active(anon): 78492 kB 295 Inactive(anon): 2240 kB 296 Active(file): 367364 kB 297 Inactive(file): 456852 kB 298 Unevictable: 3096 kB 299 Mlocked: 3096 kB 300 SwapTotal: 0 kB 301 SwapFree: 0 kB 302 Dirty: 32 kB 303 Writeback: 0 kB 304 AnonPages: 74988 kB 305 Mapped: 62624 kB 306 Shmem: 4020 kB 307 Slab: 86464 kB 308 SReclaimable: 44432 kB 309 SUnreclaim: 42032 kB 310 KernelStack: 4880 kB 311 PageTables: 2900 kB 312 NFS_Unstable: 0 kB 313 Bounce: 0 kB 314 WritebackTmp: 0 kB 315 CommitLimit: 1509868 kB 316 Committed_AS: 80296 kB 317 VmallocTotal: 263061440 kB 318 VmallocUsed: 0 kB 319 VmallocChunk: 0 kB 320 AnonHugePages: 6144 kB 321 ShmemHugePages: 0 kB 322 ShmemPmdMapped: 0 kB 323 CmaTotal: 131072 kB 324 CmaFree: 130380 kB 325 HugePages_Total: 0 326 HugePages_Free: 0 327 HugePages_Rsvd: 0 328 HugePages_Surp: 0 329 Hugepagesize: 2048 kB)meminfo"; 330 331 TemporaryFile tf; 332 ::android::base::WriteStringToFd(meminfo, tf.fd); 333 std::string exec_dir = ::android::base::GetExecutableDirectory(); 334 std::string zram_mmstat_dir = exec_dir + "/testdata1/"; 335 uint64_t mem[MEMINFO_COUNT]; 336 for (auto _ : state) { 337 get_mem_info(mem, tf.path); 338 mem[MEMINFO_ZRAM_TOTAL] = get_zram_mem_used("/sys/block/zram0/") / 1024; 339 CHECK_EQ(mem[MEMINFO_KERNEL_STACK], 4880u); 340 } 341 } 342 BENCHMARK(BM_MemInfoWithZram_old); 343 344 static void BM_MemInfoWithZram_new(benchmark::State& state) { 345 std::string meminfo = R"meminfo(MemTotal: 3019740 kB 346 MemFree: 1809728 kB 347 MemAvailable: 2546560 kB 348 Buffers: 54736 kB 349 Cached: 776052 kB 350 SwapCached: 0 kB 351 Active: 445856 kB 352 Inactive: 459092 kB 353 Active(anon): 78492 kB 354 Inactive(anon): 2240 kB 355 Active(file): 367364 kB 356 Inactive(file): 456852 kB 357 Unevictable: 3096 kB 358 Mlocked: 3096 kB 359 SwapTotal: 0 kB 360 SwapFree: 0 kB 361 Dirty: 32 kB 362 Writeback: 0 kB 363 AnonPages: 74988 kB 364 Mapped: 62624 kB 365 Shmem: 4020 kB 366 Slab: 86464 kB 367 SReclaimable: 44432 kB 368 SUnreclaim: 42032 kB 369 KernelStack: 4880 kB 370 PageTables: 2900 kB 371 NFS_Unstable: 0 kB 372 Bounce: 0 kB 373 WritebackTmp: 0 kB 374 CommitLimit: 1509868 kB 375 Committed_AS: 80296 kB 376 VmallocTotal: 263061440 kB 377 VmallocUsed: 0 kB 378 VmallocChunk: 0 kB 379 AnonHugePages: 6144 kB 380 ShmemHugePages: 0 kB 381 ShmemPmdMapped: 0 kB 382 CmaTotal: 131072 kB 383 CmaFree: 130380 kB 384 HugePages_Total: 0 385 HugePages_Free: 0 386 HugePages_Rsvd: 0 387 HugePages_Surp: 0 388 Hugepagesize: 2048 kB)meminfo"; 389 390 TemporaryFile tf; 391 android::base::WriteStringToFd(meminfo, tf.fd); 392 393 std::string file = std::string(tf.path); 394 std::vector<uint64_t> mem; 395 std::vector<std::string_view> tags(SysMemInfo::kDefaultSysMemInfoTags.begin(), 396 SysMemInfo::kDefaultSysMemInfoTags.end()); 397 auto it = tags.begin(); 398 tags.insert(it + MEMINFO_ZRAM_TOTAL, "Zram:"); 399 SysMemInfo smi; 400 401 for (auto _ : state) { 402 mem.resize(tags.size()); 403 smi.ReadMemInfo(tags.size(), tags.data(), mem.data(), file.c_str()); 404 CHECK_EQ(mem[MEMINFO_KERNEL_STACK], 4880u); 405 } 406 } 407 BENCHMARK(BM_MemInfoWithZram_new); 408 409 // Current implementation is in frameworks/base/core/jni/android_os_Debug.cpp. 410 // That implementation is still buggy and it skips over vmalloc allocated memory by kernel modules. 411 // This is the *fixed* version of the same implementation intended for benchmarking against the new 412 // one. 413 static uint64_t get_allocated_vmalloc_memory(const std::string& vm_file) { 414 char line[1024]; 415 416 uint64_t vmalloc_allocated_size = 0; 417 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(vm_file.c_str(), "re"), fclose}; 418 if (fp == nullptr) { 419 return 0; 420 } 421 422 while (true) { 423 if (fgets(line, 1024, fp.get()) == NULL) { 424 break; 425 } 426 427 // check to see if there are pages mapped in vmalloc area 428 if (!strstr(line, "pages=")) { 429 continue; 430 } 431 432 long nr_pages; 433 if (sscanf(line, "%*x-%*x %*ld %*s pages=%ld", &nr_pages) == 1) { 434 vmalloc_allocated_size += (nr_pages * getpagesize()); 435 } else if (sscanf(line, "%*x-%*x %*ld %*s %*s pages=%ld", &nr_pages) == 1) { 436 // The second case is for kernel modules. If allocation comes from the module, 437 // kernel puts an extra string containing the module name before "pages=" in 438 // the line. 439 // See: https://elixir.bootlin.com/linux/latest/source/kernel/kallsyms.c#L373 440 vmalloc_allocated_size += (nr_pages * getpagesize()); 441 } 442 } 443 return vmalloc_allocated_size; 444 } 445 446 static void BM_VmallocInfo_old_fixed(benchmark::State& state) { 447 std::string exec_dir = ::android::base::GetExecutableDirectory(); 448 std::string vmallocinfo = 449 ::android::base::StringPrintf("%s/testdata1/vmallocinfo", exec_dir.c_str()); 450 for (auto _ : state) { 451 CHECK_EQ(get_allocated_vmalloc_memory(vmallocinfo), 29884416); 452 } 453 } 454 BENCHMARK(BM_VmallocInfo_old_fixed); 455 456 static void BM_VmallocInfo_new(benchmark::State& state) { 457 std::string exec_dir = ::android::base::GetExecutableDirectory(); 458 std::string vmallocinfo = 459 ::android::base::StringPrintf("%s/testdata1/vmallocinfo", exec_dir.c_str()); 460 for (auto _ : state) { 461 CHECK_EQ(::android::meminfo::ReadVmallocInfo(vmallocinfo.c_str()), 29884416); 462 } 463 } 464 BENCHMARK(BM_VmallocInfo_new); 465 466 // This implementation is picked up as-is from frameworks/base/core/jni/android_os_Debug.cpp 467 // and only slightly modified to use std:unique_ptr. 468 static bool get_smaps_rollup(const std::string path, MemUsage* rollup) { 469 char lineBuffer[1024]; 470 auto fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose}; 471 if (fp != nullptr) { 472 char* line; 473 while (true) { 474 if (fgets(lineBuffer, sizeof(lineBuffer), fp.get()) == NULL) { 475 break; 476 } 477 line = lineBuffer; 478 479 switch (line[0]) { 480 case 'P': 481 if (strncmp(line, "Pss:", 4) == 0) { 482 char* c = line + 4; 483 while (*c != 0 && (*c < '0' || *c > '9')) { 484 c++; 485 } 486 rollup->pss += atoi(c); 487 } else if (strncmp(line, "Private_Clean:", 14) == 0 || 488 strncmp(line, "Private_Dirty:", 14) == 0) { 489 char* c = line + 14; 490 while (*c != 0 && (*c < '0' || *c > '9')) { 491 c++; 492 } 493 rollup->uss += atoi(c); 494 } 495 break; 496 case 'R': 497 if (strncmp(line, "Rss:", 4) == 0) { 498 char* c = line + 4; 499 while (*c != 0 && (*c < '0' || *c > '9')) { 500 c++; 501 } 502 rollup->rss += atoi(c); 503 } 504 break; 505 case 'S': 506 if (strncmp(line, "SwapPss:", 8) == 0) { 507 char* c = line + 8; 508 long lSwapPss; 509 while (*c != 0 && (*c < '0' || *c > '9')) { 510 c++; 511 } 512 lSwapPss = atoi(c); 513 rollup->swap_pss += lSwapPss; 514 } 515 break; 516 } 517 } 518 } else { 519 return false; 520 } 521 522 return true; 523 } 524 525 static void BM_SmapsRollup_old(benchmark::State& state) { 526 std::string exec_dir = ::android::base::GetExecutableDirectory(); 527 std::string path = ::android::base::StringPrintf("%s/testdata1/smaps", exec_dir.c_str()); 528 for (auto _ : state) { 529 MemUsage stats; 530 CHECK_EQ(get_smaps_rollup(path, &stats), true); 531 CHECK_EQ(stats.pss, 108384); 532 } 533 } 534 BENCHMARK(BM_SmapsRollup_old); 535 536 static void BM_SmapsRollup_new(benchmark::State& state) { 537 std::string exec_dir = ::android::base::GetExecutableDirectory(); 538 std::string path = ::android::base::StringPrintf("%s/testdata1/smaps", exec_dir.c_str()); 539 for (auto _ : state) { 540 MemUsage stats; 541 CHECK_EQ(SmapsOrRollupFromFile(path, &stats), true); 542 CHECK_EQ(stats.pss, 108384); 543 } 544 } 545 BENCHMARK(BM_SmapsRollup_new); 546 547 BENCHMARK_MAIN(); 548