1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <malloc.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include <algorithm>
34 #include <chrono>
35 #include <iostream>
36 #include <memory>
37 #include <random>
38 #include <thread>
39 #include <vector>
40
41 #include <android-base/strings.h>
42 #if defined(__BIONIC__)
43 #include <malloc.h>
44 #include <meminfo/procmeminfo.h>
45 #include <procinfo/process_map.h>
46 #endif
47
48 constexpr size_t kMaxThreads = 8;
49 // The max number of bytes that can be allocated by a thread. Note that each
50 // allocator may have its own limitation on each size allocation. For example,
51 // Scudo has a 256 MB limit for each size-class in the primary allocator. The
52 // amount of memory allocated should not exceed the limit in each allocator.
53 constexpr size_t kMaxBytes = 1 << 24;
54 constexpr size_t kMaxLen = kMaxBytes;
55 void* MemPool[kMaxThreads][kMaxLen];
56
dirtyMem(void * ptr,size_t bytes)57 void dirtyMem(void* ptr, size_t bytes) {
58 memset(ptr, 1U, bytes);
59 }
60
ThreadTask(int id,size_t allocSize)61 void ThreadTask(int id, size_t allocSize) {
62 // In the following, we will first allocate blocks with kMaxBytes of memory
63 // and release all of them in random order. In the end, we will do another
64 // round of allocations until it reaches 1/10 kMaxBytes.
65
66 // Total number of blocks
67 const size_t maxCounts = kMaxBytes / allocSize;
68 // The number of blocks in the end
69 const size_t finalCounts = maxCounts / 10;
70
71 for (size_t i = 0; i < maxCounts; ++i) {
72 MemPool[id][i] = malloc(allocSize);
73 if (MemPool[id][i] == 0) {
74 std::cout << "Allocation failure."
75 "Please consider reducing the number of threads"
76 << std::endl;
77 exit(1);
78 }
79 dirtyMem(MemPool[id][i], allocSize);
80 }
81
82 // Each allocator may apply different strategies to manage the free blocks and
83 // each strategy may have different impacts on future memory usage. For
84 // example, managing free blocks in simple FIFO list may have its memory usage
85 // highly correlated with the blocks releasing pattern. Therefore, release the
86 // blocks in random order to observe the impact of free blocks handling.
87 unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
88 std::shuffle(MemPool[id], MemPool[id] + maxCounts, std::default_random_engine(seed));
89 for (size_t i = 0; i < maxCounts; ++i) {
90 free(MemPool[id][i]);
91 MemPool[id][i] = nullptr;
92 }
93
94 for (size_t i = 0; i < finalCounts; ++i) {
95 MemPool[id][i] = malloc(allocSize);
96 dirtyMem(MemPool[id][i], allocSize);
97 }
98 }
99
StressSizeClass(size_t numThreads,size_t allocSize)100 void StressSizeClass(size_t numThreads, size_t allocSize) {
101 // We would like to see the minimum memory usage under aggressive page
102 // releasing.
103 mallopt(M_DECAY_TIME, 0);
104
105 std::thread* threads[kMaxThreads];
106 for (size_t i = 0; i < numThreads; ++i) threads[i] = new std::thread(ThreadTask, i, allocSize);
107
108 for (size_t i = 0; i < numThreads; ++i) {
109 threads[i]->join();
110 delete threads[i];
111 }
112
113 // Do an explicit purge to ensure we will be more likely to get the actual
114 // in-use memory.
115 mallopt(M_PURGE_ALL, 0);
116
117 android::meminfo::ProcMemInfo proc_mem(getpid());
118 const std::vector<android::meminfo::Vma>& maps = proc_mem.MapsWithoutUsageStats();
119 uint64_t rss_bytes = 0;
120 uint64_t vss_bytes = 0;
121
122 for (auto& vma : maps) {
123 if (vma.name == "[anon:libc_malloc]" || android::base::StartsWith(vma.name, "[anon:scudo:") ||
124 android::base::StartsWith(vma.name, "[anon:GWP-ASan")) {
125 android::meminfo::Vma update_vma(vma);
126 if (!proc_mem.FillInVmaStats(update_vma)) {
127 std::cout << "Failed to parse VMA" << std::endl;
128 exit(1);
129 }
130 rss_bytes += update_vma.usage.rss;
131 vss_bytes += update_vma.usage.vss;
132 }
133 }
134
135 std::cout << "RSS: " << rss_bytes / (1024.0 * 1024.0) << " MB" << std::endl;
136 std::cout << "VSS: " << vss_bytes / (1024.0 * 1024.0) << " MB" << std::endl;
137
138 for (size_t i = 0; i < numThreads; ++i) {
139 for (size_t j = 0; j < kMaxLen; ++j) free(MemPool[i][j]);
140 }
141 }
142
main(int argc,char * argv[])143 int main(int argc, char* argv[]) {
144 if (argc != 3) {
145 std::cerr << "usage: " << argv[0] << " $NUM_THREADS $ALLOC_SIZE" << std::endl;
146 return 1;
147 }
148
149 size_t numThreads = atoi(argv[1]);
150 size_t allocSize = atoi(argv[2]);
151
152 if (numThreads == 0 || allocSize == 0) {
153 std::cerr << "Please provide valid $NUM_THREADS and $ALLOC_SIZE" << std::endl;
154 return 1;
155 }
156
157 if (numThreads > kMaxThreads) {
158 std::cerr << "The max number of threads is " << kMaxThreads << std::endl;
159 return 1;
160 }
161
162 StressSizeClass(numThreads, allocSize);
163
164 return 0;
165 }
166