FML
MemoryLogging.h
Go to the documentation of this file.
1#ifndef MEMORYLOG_HEADER
2#define MEMORYLOG_HEADER
3
4#include <chrono>
5#include <iomanip>
6#include <iostream>
7#include <map>
8#include <mutex>
9#include <typeinfo>
10#include <vector>
11
12#ifdef USE_MPI
13#include <mpi.h>
14#endif
15
16namespace FML {
17
18 extern int ThisTask;
19 extern int NTasks;
20 extern std::pair<double, double> get_system_memory_use();
21
22 //=======================================================
23 // Do not log allocations smaller than a minimum size
24 // The standard size is 0
25 // Set a limit to how many items we can have in the log
26 // at the same time to avoid it taking up too much memory
27 // If this limit is reached then we clear the memory associated
28 // with the logger and stop logging any more allocations
29 //=======================================================
30#ifndef MIN_BYTES_TO_LOG
31#define MIN_BYTES_TO_LOG 0
32#endif
33#ifndef MAX_ALLOCATIONS_IN_MEMORY
34#define MAX_ALLOCATIONS_IN_MEMORY 100000
35#endif
36
37 class MemoryLog;
38 using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;
39
40 //=======================================================
41 /// Logs all heap allocations in the code that are larger
42 /// than min_bytes_to_log. Implemented as a singleton so only
43 /// one instance of this object exists. Thread safe.
44 //=======================================================
45 class MemoryLog {
46 private:
47 static MemoryLog * instance;
48
49 // Lock
50 std::mutex mymutex{};
51
52 // The first allocation
53 TimePoint time_start{};
54
55 // Containing all current allocations
56 std::map<void *, size_t> allocations{};
57
58 // For more info, after allocating we can (externally)
59 // call add with the pointer and store a label
60 std::map<void *, std::string> labels{};
61
62 // The total memory as function of time
63 std::map<TimePoint, size_t> memory_vs_time{};
64
65 // Total memory in use at any given time
66 size_t memory_in_use{0};
67 size_t peak_memory_use{0};
68
69 // The minimum allocation size in bytes to log
70 size_t min_bytes_to_log = MIN_BYTES_TO_LOG;
71 size_t max_allocations_to_log = MAX_ALLOCATIONS_IN_MEMORY;
72 size_t nallocation{0};
73 bool stop_logging{false};
74
75 // Private constructor and destructor
76 MemoryLog() {
77 time_start = std::chrono::steady_clock::now();
78 memory_in_use = 0;
79 memory_vs_time[time_start] = 0;
80 nallocation = 0;
81 stop_logging = false;
82 }
83 ~MemoryLog() = default;
84
85 public:
86 static MemoryLog * get() {
87 if (not instance)
88 instance = new MemoryLog;
89 return instance;
90 }
91
92 MemoryLog(const MemoryLog & arg) = delete;
93 MemoryLog(const MemoryLog && arg) = delete;
94 MemoryLog & operator=(const MemoryLog & arg) = delete;
95 MemoryLog & operator=(const MemoryLog && arg) = delete;
96
97 // Add a new allocation label
98 void add_label(void * ptr, std::string name) {
99 if (stop_logging)
100 return;
101 if (allocations.find(ptr) == allocations.end()) {
102 std::cout << "[MemoryLogging] Warning could not find pointer in allocation list to add label " + name +
103 " to\n";
104 return;
105 }
106 std::lock_guard<std::mutex> guard(mymutex);
107 labels[ptr] = name;
108 }
109 void add_label(void * ptr, size_t size, std::string name) {
110 if (stop_logging or size < min_bytes_to_log)
111 return;
112 add_label(ptr, name);
113 }
114
115 void add(void * ptr, size_t size) {
116 if (stop_logging or size < min_bytes_to_log)
117 return;
118 std::lock_guard<std::mutex> guard(mymutex);
119 allocations[ptr] = size;
120
121 memory_in_use += size;
122 if (memory_in_use > peak_memory_use)
123 peak_memory_use = memory_in_use;
124
125 auto time = std::chrono::steady_clock::now();
126 memory_vs_time[time] = memory_in_use;
127 nallocation++;
128
129 // If we have saturated then stop logging
130 stop_logging = nallocation >= max_allocations_to_log;
131 }
132
133 void clear() {
134 allocations.clear();
135 memory_vs_time.clear();
136 }
137
138 // Remove an allocation and log info
139 void remove(void * ptr, size_t size) {
140 if (stop_logging or size < min_bytes_to_log)
141 return;
142
143#ifdef DEBUG_MEMORYLOG
144 std::string name = labels[ptr];
145 if (ThisTask == 0)
146 std::cout << "===> MemoryLog::remove Freeing[" << name << "]" << std::endl;
147#endif
148 std::lock_guard<std::mutex> guard(mymutex);
149 allocations.erase(ptr);
150 labels.erase(ptr);
151
152 memory_in_use -= size;
153
154 auto time = std::chrono::steady_clock::now();
155 memory_vs_time[time] = memory_in_use;
156 }
157
158 // Print the total memory in use
159 void print() {
160 // Check if any task has saturated the allocation limit
161 char ok = stop_logging ? 0 : 1;
162#ifdef USE_MPI
163 MPI_Allreduce(MPI_IN_PLACE, &ok, 1, MPI_CHAR, MPI_MIN, MPI_COMM_WORLD);
164#endif
165 if (ok == 0) {
166 clear();
167 stop_logging = true;
168
169 if (ThisTask == 0)
170 std::cout << "Warning: The memory log has stopped working "
171 << " due to max number of allocation having been reached\n";
172 return;
173 }
174
175 // Fetch system memory usage
176 auto sysmem = get_system_memory_use();
177 double min_sys_vmemory = sysmem.first;
178 double max_sys_vmemory = sysmem.first;
179 double mean_sys_vmemory = sysmem.first;
180 double min_sys_rssmemory = sysmem.second;
181 double max_sys_rssmemory = sysmem.second;
182 double mean_sys_rssmemory = sysmem.second;
183
184#ifdef USE_MPI
185 MPI_Allreduce(MPI_IN_PLACE, &min_sys_vmemory, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
186 MPI_Allreduce(MPI_IN_PLACE, &max_sys_vmemory, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
187 MPI_Allreduce(MPI_IN_PLACE, &mean_sys_vmemory, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
188 MPI_Allreduce(MPI_IN_PLACE, &min_sys_rssmemory, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
189 MPI_Allreduce(MPI_IN_PLACE, &max_sys_rssmemory, 1, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
190 MPI_Allreduce(MPI_IN_PLACE, &mean_sys_rssmemory, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
191#endif
192 mean_sys_vmemory /= NTasks;
193 mean_sys_rssmemory /= NTasks;
194 if (ThisTask == 0) {
195 std::cout << "\n#=====================================================\n";
196 std::cout << "# MemoryLogging\n";
197 std::cout << "#=====================================================\n\n";
198 std::cout << "Info about resident set size:\n";
199 std::cout << "[Current resident set size]:\n";
200 std::cout << "Min over tasks: " << std::setw(15) << min_sys_vmemory / 1.0e6 << " MB\n";
201 std::cout << "Mean over tasks: " << std::setw(15) << mean_sys_vmemory / 1.0e6 << " MB\n";
202 std::cout << "Max over tasks: " << std::setw(15) << max_sys_vmemory / 1.0e6 << " MB\n";
203 std::cout << "[Peak resident set size]:\n";
204 std::cout << "Min over tasks: " << std::setw(15) << min_sys_rssmemory / 1.0e6 << " MB\n";
205 std::cout << "Mean over tasks: " << std::setw(15) << mean_sys_rssmemory / 1.0e6 << " MB\n";
206 std::cout << "Max over tasks: " << std::setw(15) << max_sys_rssmemory / 1.0e6 << " MB\n";
207 }
208
209 if (ThisTask == 0) {
210 std::cout << "\n";
211 std::cout << "For info below we are only tracking allocation of standard\n";
212 std::cout << "container (Vector) and only allocations larger than " << min_bytes_to_log << " bytes\n\n";
213 }
214
215 long long int min_memory = memory_in_use;
216 long long int max_memory = memory_in_use;
217 long long int mean_memory = memory_in_use;
218 long long int peak_memory = peak_memory_use;
219#ifdef USE_MPI
220 MPI_Allreduce(MPI_IN_PLACE, &min_memory, 1, MPI_LONG_LONG, MPI_MIN, MPI_COMM_WORLD);
221 MPI_Allreduce(MPI_IN_PLACE, &max_memory, 1, MPI_LONG_LONG, MPI_MAX, MPI_COMM_WORLD);
222 MPI_Allreduce(MPI_IN_PLACE, &mean_memory, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD);
223 MPI_Allreduce(MPI_IN_PLACE, &peak_memory, 1, MPI_LONG_LONG, MPI_MAX, MPI_COMM_WORLD);
224#endif
225 mean_memory /= NTasks;
226 if (ThisTask == 0) {
227 std::cout << "Memory in use (Task 0): " << std::setw(15) << double(memory_in_use) / 1.0e6 << " MB\n";
228 std::cout << "Min over tasks: " << std::setw(15) << double(min_memory) / 1.0e6 << " MB\n";
229 std::cout << "Mean over tasks: " << std::setw(15) << double(mean_memory) / 1.0e6 << " MB\n";
230 std::cout << "Max over tasks: " << std::setw(15) << double(max_memory) / 1.0e6 << " MB\n";
231 std::cout << "\n";
232 std::cout << "Peak memory use (Task 0): " << std::setw(15) << double(peak_memory_use) / 1.0e6
233 << " MB\n";
234 std::cout << "Max over tasks: " << std::setw(15) << double(peak_memory) / 1.0e6 << " MB\n";
235 std::cout << "\n";
236 }
237 if (not allocations.empty()) {
238 if (ThisTask == 0) {
239 std::cout << "\nWe have the following things allocated on task 0: \n";
240 }
241 for (auto && a : allocations) {
242 if (ThisTask == 0) {
243 std::string name = "";
244 if (labels.find(a.first) != labels.end())
245 name = labels[a.first];
246 std::string bytelabel = " (MB)";
247 double factor = 1e6;
248 std::cout << "Address: " << a.first << " Size: " << double(a.second) / factor << bytelabel
249 << " Label: " << name << "\n";
250 }
251 }
252 if (ThisTask == 0)
253 std::cout << "\n";
254 }
255 if (not memory_vs_time.empty()) {
256 if (ThisTask == 0)
257 std::cout << "Total memory as function of time:\n";
258 for (auto && m : memory_vs_time) {
259 double time_in_sec =
260 std::chrono::duration_cast<std::chrono::duration<double>>(m.first - time_start).count();
261 if (ThisTask == 0) {
262 std::cout << " Time (sec): " << std::setw(13) << time_in_sec;
263 std::string bytelabel = " (MB)";
264 double factor = 1e6;
265 std::cout << " Memory: " << std::setw(13) << double(m.second) / factor << bytelabel << "\n";
266 }
267 }
268 }
269 if (ThisTask == 0) {
270 std::cout << "\n#=====================================================\n";
271 std::cout << std::flush;
272 }
273 }
274 };
275
276 /// Custom allocator that logs allocations
277 template <typename T>
279 using value_type = T;
280
281 LogAllocator() = default;
282 template <class U>
284
285 T * allocate(std::size_t size) {
286 if (size <= std::numeric_limits<std::size_t>::max() / sizeof(T)) {
287 if (auto ptr = std::malloc(size * sizeof(T))) {
288 MemoryLog::get()->add(ptr, size * sizeof(T));
289 return static_cast<T *>(ptr);
290 }
291 }
292 std::cout << "[LogAllocator] Allocation of " << size << " elements of type " << typeid(T).name()
293 << " of size " << sizeof(T) << " failed" << std::endl;
294 throw std::bad_alloc();
295 }
296 void deallocate(T * ptr, std::size_t size) {
297 MemoryLog::get()->remove(ptr, size * sizeof(T));
298 std::free(ptr);
299 }
300 };
301
302 template <typename T, typename U>
303 inline bool operator==(const LogAllocator<T> &, const LogAllocator<U> &) {
304 return true;
305 }
306
307 template <typename T, typename U>
308 inline bool operator!=(const LogAllocator<T> & a, const LogAllocator<U> & b) {
309 return not(a == b);
310 }
311} // namespace FML
312
313#endif
void free(HaloModel **hm)
Definition: Wrapper.cpp:34
Logs all heap allocations in the code that are larger than min_bytes_to_log.
Definition: MemoryLogging.h:45
static MemoryLog * get()
Definition: MemoryLogging.h:86
void add(void *ptr, size_t size)
MemoryLog & operator=(const MemoryLog &arg)=delete
MemoryLog & operator=(const MemoryLog &&arg)=delete
MemoryLog(const MemoryLog &arg)=delete
void add_label(void *ptr, std::string name)
Definition: MemoryLogging.h:98
void remove(void *ptr, size_t size)
MemoryLog(const MemoryLog &&arg)=delete
void add_label(void *ptr, size_t size, std::string name)
Definition: CAMBReader.h:10
std::pair< double, double > get_system_memory_use()
Fetch the resident set size currently and the peak value so far of it.
Definition: Global.cpp:223
bool operator==(const LogAllocator< T > &, const LogAllocator< U > &)
int ThisTask
The MPI task number.
Definition: Global.cpp:33
std::chrono::time_point< std::chrono::steady_clock > TimePoint
Definition: MemoryLogging.h:38
bool operator!=(const LogAllocator< T > &a, const LogAllocator< U > &b)
int NTasks
Total number of MPI tasks.
Definition: Global.cpp:34
Custom allocator that logs allocations.
LogAllocator(const LogAllocator< U > &)
void deallocate(T *ptr, std::size_t size)
T * allocate(std::size_t size)
LogAllocator()=default