vppinfra: use vm memory allocator for numa mapping
[vpp.git] / src / vppinfra / mem_dlmalloc.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <vppinfra/format.h>
17 #include <vppinfra/dlmalloc.h>
18 #include <vppinfra/os.h>
19 #include <vppinfra/lock.h>
20 #include <vppinfra/hash.h>
21 #include <vppinfra/elf_clib.h>
22 #include <vppinfra/sanitizer.h>
23 #include <numaif.h>
24
25 void *clib_per_cpu_mheaps[CLIB_MAX_MHEAPS];
26 void *clib_per_numa_mheaps[CLIB_MAX_NUMAS];
27
28 typedef struct
29 {
30   /* Address of callers: outer first, inner last. */
31   uword callers[12];
32
33   /* Count of allocations with this traceback. */
34 #if CLIB_VEC64 > 0
35   u64 n_allocations;
36 #else
37   u32 n_allocations;
38 #endif
39
40   /* Count of bytes allocated with this traceback. */
41   u32 n_bytes;
42
43   /* Offset of this item */
44   uword offset;
45 } mheap_trace_t;
46
47 typedef struct
48 {
49   clib_spinlock_t lock;
50   uword enabled;
51
52   mheap_trace_t *traces;
53
54   /* Indices of free traces. */
55   u32 *trace_free_list;
56
57   /* Hash table mapping callers to trace index. */
58   uword *trace_by_callers;
59
60   /* Hash table mapping mheap offset to trace index. */
61   uword *trace_index_by_offset;
62
63   /* So we can easily shut off current segment trace, if any */
64   void *current_traced_mheap;
65
66 } mheap_trace_main_t;
67
68 mheap_trace_main_t mheap_trace_main;
69
70 void
71 mheap_get_trace (uword offset, uword size)
72 {
73   mheap_trace_main_t *tm = &mheap_trace_main;
74   mheap_trace_t *t;
75   uword i, n_callers, trace_index, *p;
76   mheap_trace_t trace;
77   uword save_enabled;
78
79   if (tm->enabled == 0 || (clib_mem_get_heap () != tm->current_traced_mheap))
80     return;
81
82   /* Spurious Coverity warnings be gone. */
83   clib_memset (&trace, 0, sizeof (trace));
84
85   /* Skip our frame and mspace_get_aligned's frame */
86   n_callers = clib_backtrace (trace.callers, ARRAY_LEN (trace.callers), 2);
87   if (n_callers == 0)
88     return;
89
90   clib_spinlock_lock (&tm->lock);
91
92   /* Turn off tracing to avoid embarrassment... */
93   save_enabled = tm->enabled;
94   tm->enabled = 0;
95
96   if (!tm->trace_by_callers)
97     tm->trace_by_callers =
98       hash_create_shmem (0, sizeof (trace.callers), sizeof (uword));
99
100   p = hash_get_mem (tm->trace_by_callers, &trace.callers);
101   if (p)
102     {
103       trace_index = p[0];
104       t = tm->traces + trace_index;
105     }
106   else
107     {
108       i = vec_len (tm->trace_free_list);
109       if (i > 0)
110         {
111           trace_index = tm->trace_free_list[i - 1];
112           _vec_len (tm->trace_free_list) = i - 1;
113         }
114       else
115         {
116           mheap_trace_t *old_start = tm->traces;
117           mheap_trace_t *old_end = vec_end (tm->traces);
118
119           vec_add2 (tm->traces, t, 1);
120
121           if (tm->traces != old_start)
122             {
123               hash_pair_t *p;
124               mheap_trace_t *q;
125             /* *INDENT-OFF* */
126             hash_foreach_pair (p, tm->trace_by_callers,
127             ({
128               q = uword_to_pointer (p->key, mheap_trace_t *);
129               ASSERT (q >= old_start && q < old_end);
130               p->key = pointer_to_uword (tm->traces + (q - old_start));
131             }));
132             /* *INDENT-ON* */
133             }
134           trace_index = t - tm->traces;
135         }
136
137       t = tm->traces + trace_index;
138       t[0] = trace;
139       t->n_allocations = 0;
140       t->n_bytes = 0;
141       hash_set_mem (tm->trace_by_callers, t->callers, trace_index);
142     }
143
144   t->n_allocations += 1;
145   t->n_bytes += size;
146   t->offset = offset;           /* keep a sample to autopsy */
147   hash_set (tm->trace_index_by_offset, offset, t - tm->traces);
148   tm->enabled = save_enabled;
149   clib_spinlock_unlock (&tm->lock);
150 }
151
152 void
153 mheap_put_trace (uword offset, uword size)
154 {
155   mheap_trace_t *t;
156   uword trace_index, *p;
157   mheap_trace_main_t *tm = &mheap_trace_main;
158   uword save_enabled;
159
160   if (tm->enabled == 0)
161     return;
162
163   clib_spinlock_lock (&tm->lock);
164
165   /* Turn off tracing for a moment */
166   save_enabled = tm->enabled;
167   tm->enabled = 0;
168
169   p = hash_get (tm->trace_index_by_offset, offset);
170   if (!p)
171     {
172       tm->enabled = save_enabled;
173       clib_spinlock_unlock (&tm->lock);
174       return;
175     }
176
177   trace_index = p[0];
178   hash_unset (tm->trace_index_by_offset, offset);
179   ASSERT (trace_index < vec_len (tm->traces));
180
181   t = tm->traces + trace_index;
182   ASSERT (t->n_allocations > 0);
183   ASSERT (t->n_bytes >= size);
184   t->n_allocations -= 1;
185   t->n_bytes -= size;
186   if (t->n_allocations == 0)
187     {
188       hash_unset_mem (tm->trace_by_callers, t->callers);
189       vec_add1 (tm->trace_free_list, trace_index);
190       clib_memset (t, 0, sizeof (t[0]));
191     }
192   tm->enabled = save_enabled;
193   clib_spinlock_unlock (&tm->lock);
194 }
195
196 always_inline void
197 mheap_trace_main_free (mheap_trace_main_t * tm)
198 {
199   vec_free (tm->traces);
200   vec_free (tm->trace_free_list);
201   hash_free (tm->trace_by_callers);
202   hash_free (tm->trace_index_by_offset);
203 }
204
205 /* Initialize CLIB heap based on memory/size given by user.
206    Set memory to 0 and CLIB will try to allocate its own heap. */
207 static void *
208 clib_mem_init_internal (void *memory, uword memory_size, int set_heap)
209 {
210   u8 *heap;
211
212   if (memory)
213     {
214       heap = create_mspace_with_base (memory, memory_size, 1 /* locked */ );
215       mspace_disable_expand (heap);
216     }
217   else
218     heap = create_mspace (memory_size, 1 /* locked */ );
219
220   CLIB_MEM_POISON (mspace_least_addr (heap), mspace_footprint (heap));
221
222   if (set_heap)
223     clib_mem_set_heap (heap);
224
225   if (mheap_trace_main.lock == 0)
226     clib_spinlock_init (&mheap_trace_main.lock);
227
228   return heap;
229 }
230
231 void *
232 clib_mem_init (void *memory, uword memory_size)
233 {
234   return clib_mem_init_internal (memory, memory_size,
235                                  1 /* do clib_mem_set_heap */ );
236 }
237
238 void *
239 clib_mem_init_thread_safe (void *memory, uword memory_size)
240 {
241   return clib_mem_init_internal (memory, memory_size,
242                                  1 /* do clib_mem_set_heap */ );
243 }
244
245 void *
246 clib_mem_init_thread_safe_numa (void *memory, uword memory_size, u8 numa)
247 {
248   clib_mem_vm_alloc_t alloc = { 0 };
249   clib_error_t *err;
250   void *heap;
251
252   alloc.size = memory_size;
253   alloc.flags = CLIB_MEM_VM_F_NUMA_FORCE;
254   alloc.numa_node = numa;
255   if ((err = clib_mem_vm_ext_alloc (&alloc)))
256     {
257       clib_error_report (err);
258       return 0;
259     }
260
261   heap = clib_mem_init_internal (memory, memory_size,
262                                  0 /* do NOT clib_mem_set_heap */ );
263
264   ASSERT (heap);
265
266   return heap;
267 }
268
269 u8 *
270 format_clib_mem_usage (u8 * s, va_list * va)
271 {
272   int verbose = va_arg (*va, int);
273   return format (s, "$$$$ heap at %llx verbose %d", clib_mem_get_heap (),
274                  verbose);
275 }
276
277 /*
278  * Magic decoder ring for mallinfo stats (ala dlmalloc):
279  *
280  * size_t arena;     / * Non-mmapped space allocated (bytes) * /
281  * size_t ordblks;   / * Number of free chunks * /
282  * size_t smblks;    / * Number of free fastbin blocks * /
283  * size_t hblks;     / * Number of mmapped regions * /
284  * size_t hblkhd;    / * Space allocated in mmapped regions (bytes) * /
285  * size_t usmblks;   / * Maximum total allocated space (bytes) * /
286  * size_t fsmblks;   / * Space in freed fastbin blocks (bytes) * /
287  * size_t uordblks;  / * Total allocated space (bytes) * /
288  * size_t fordblks;  / * Total free space (bytes) * /
289  * size_t keepcost;  / * Top-most, releasable space (bytes) * /
290  *
291  */
292
293 u8 *
294 format_msize (u8 * s, va_list * va)
295 {
296   uword a = va_arg (*va, uword);
297
298   if (a >= 1ULL << 30)
299     s = format (s, "%.2fG", (((f64) a) / ((f64) (1ULL << 30))));
300   else if (a >= 1ULL << 20)
301     s = format (s, "%.2fM", (((f64) a) / ((f64) (1ULL << 20))));
302   else if (a >= 1ULL << 10)
303     s = format (s, "%.2fK", (((f64) a) / ((f64) (1ULL << 10))));
304   else
305     s = format (s, "%lld", a);
306   return s;
307 }
308
309 static int
310 mheap_trace_sort (const void *_t1, const void *_t2)
311 {
312   const mheap_trace_t *t1 = _t1;
313   const mheap_trace_t *t2 = _t2;
314   word cmp;
315
316   cmp = (word) t2->n_bytes - (word) t1->n_bytes;
317   if (!cmp)
318     cmp = (word) t2->n_allocations - (word) t1->n_allocations;
319   return cmp;
320 }
321
322 u8 *
323 format_mheap_trace (u8 * s, va_list * va)
324 {
325   mheap_trace_main_t *tm = va_arg (*va, mheap_trace_main_t *);
326   int verbose = va_arg (*va, int);
327   int have_traces = 0;
328   int i;
329
330   clib_spinlock_lock (&tm->lock);
331   if (vec_len (tm->traces) > 0 &&
332       clib_mem_get_heap () == tm->current_traced_mheap)
333     {
334       have_traces = 1;
335
336       /* Make a copy of traces since we'll be sorting them. */
337       mheap_trace_t *t, *traces_copy;
338       u32 indent, total_objects_traced;
339
340       traces_copy = vec_dup (tm->traces);
341
342       qsort (traces_copy, vec_len (traces_copy), sizeof (traces_copy[0]),
343              mheap_trace_sort);
344
345       total_objects_traced = 0;
346       s = format (s, "\n");
347       vec_foreach (t, traces_copy)
348       {
349         /* Skip over free elements. */
350         if (t->n_allocations == 0)
351           continue;
352
353         total_objects_traced += t->n_allocations;
354
355         /* When not verbose only report allocations of more than 1k. */
356         if (!verbose && t->n_bytes < 1024)
357           continue;
358
359         if (t == traces_copy)
360           s = format (s, "%=9s%=9s %=10s Traceback\n", "Bytes", "Count",
361                       "Sample");
362         s = format (s, "%9d%9d %p", t->n_bytes, t->n_allocations, t->offset);
363         indent = format_get_indent (s);
364         for (i = 0; i < ARRAY_LEN (t->callers) && t->callers[i]; i++)
365           {
366             if (i > 0)
367               s = format (s, "%U", format_white_space, indent);
368 #if defined(CLIB_UNIX) && !defined(__APPLE__)
369             /* $$$$ does this actually work? */
370             s =
371               format (s, " %U\n", format_clib_elf_symbol_with_address,
372                       t->callers[i]);
373 #else
374             s = format (s, " %p\n", t->callers[i]);
375 #endif
376           }
377       }
378
379       s = format (s, "%d total traced objects\n", total_objects_traced);
380
381       vec_free (traces_copy);
382     }
383   clib_spinlock_unlock (&tm->lock);
384   if (have_traces == 0)
385     s = format (s, "no traced allocations\n");
386
387   return s;
388 }
389
390
391 u8 *
392 format_mheap (u8 * s, va_list * va)
393 {
394   void *heap = va_arg (*va, u8 *);
395   int verbose = va_arg (*va, int);
396   struct dlmallinfo mi;
397   mheap_trace_main_t *tm = &mheap_trace_main;
398
399   mi = mspace_mallinfo (heap);
400
401   s = format (s, "total: %U, used: %U, free: %U, trimmable: %U",
402               format_msize, mi.arena,
403               format_msize, mi.uordblks,
404               format_msize, mi.fordblks, format_msize, mi.keepcost);
405   if (verbose > 0)
406     {
407       s = format (s, "\n    free chunks %llu free fastbin blks %llu",
408                   mi.ordblks, mi.smblks);
409       s =
410         format (s, "\n    max total allocated %U", format_msize, mi.usmblks);
411     }
412
413   if (mspace_is_traced (heap))
414     s = format (s, "\n%U", format_mheap_trace, tm, verbose);
415   return s;
416 }
417
418 void
419 clib_mem_usage (clib_mem_usage_t * u)
420 {
421   clib_warning ("unimp");
422 }
423
424 void
425 mheap_usage (void *heap, clib_mem_usage_t * usage)
426 {
427   struct dlmallinfo mi = mspace_mallinfo (heap);
428
429   /* TODO: Fill in some more values */
430   usage->object_count = 0;
431   usage->bytes_total = mi.arena;
432   usage->bytes_overhead = 0;
433   usage->bytes_max = 0;
434   usage->bytes_used = mi.uordblks;
435   usage->bytes_free = mi.fordblks;
436   usage->bytes_free_reclaimed = 0;
437 }
438
439 /* Call serial number for debugger breakpoints. */
440 uword clib_mem_validate_serial = 0;
441
442 void
443 clib_mem_validate (void)
444 {
445   clib_warning ("unimp");
446 }
447
448 void
449 mheap_trace (void *v, int enable)
450 {
451   (void) mspace_enable_disable_trace (v, enable);
452
453   if (enable == 0)
454     mheap_trace_main_free (&mheap_trace_main);
455 }
456
457 void
458 clib_mem_trace (int enable)
459 {
460   mheap_trace_main_t *tm = &mheap_trace_main;
461   void *current_heap = clib_mem_get_heap ();
462
463   tm->enabled = enable;
464   mheap_trace (current_heap, enable);
465
466   if (enable)
467     tm->current_traced_mheap = current_heap;
468   else
469     tm->current_traced_mheap = 0;
470 }
471
472 int
473 clib_mem_is_traced (void)
474 {
475   return mspace_is_traced (clib_mem_get_heap ());
476 }
477
478 uword
479 clib_mem_trace_enable_disable (uword enable)
480 {
481   uword rv;
482   mheap_trace_main_t *tm = &mheap_trace_main;
483
484   rv = tm->enabled;
485   tm->enabled = enable;
486   return rv;
487 }
488
489 /*
490  * These API functions seem like layering violations, but
491  * by introducing them we greatly reduce the number
492  * of code changes required to use dlmalloc spaces
493  */
494 void *
495 mheap_alloc_with_lock (void *memory, uword size, int locked)
496 {
497   void *rv;
498   if (memory == 0)
499     return create_mspace (size, locked);
500   else
501     {
502       rv = create_mspace_with_base (memory, size, locked);
503       if (rv)
504         mspace_disable_expand (rv);
505       return rv;
506     }
507 }
508
509 /*
510  * fd.io coding-style-patch-verification: ON
511  *
512  * Local Variables:
513  * eval: (c-set-style "gnu")
514  * End:
515  */