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