• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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