vppinfra: numa vector placement support
[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)
247 {
248   void *heap;
249   unsigned long this_numa;
250
251   heap =
252     clib_mem_init_internal (memory, memory_size,
253                             0 /* do NOT clib_mem_set_heap */ );
254
255   ASSERT (heap);
256
257   this_numa = os_get_numa_index ();
258
259 #if HAVE_NUMA_LIBRARY > 0
260   unsigned long nodemask = 1 << this_numa;
261   void *page_base;
262   unsigned long page_mask;
263   long rv;
264
265   /*
266    * Bind the heap to the current thread's NUMA node.
267    * heap is not naturally page-aligned, so fix it.
268    */
269
270   page_mask = ~(clib_mem_get_page_size () - 1);
271   page_base = (void *) (((unsigned long) heap) & page_mask);
272
273   clib_warning ("Bind heap at %llx size %llx to NUMA numa %d",
274                 page_base, memory_size, this_numa);
275
276   rv = mbind (page_base, memory_size, MPOL_BIND /* mode */ ,
277               &nodemask /* nodemask */ ,
278               BITS (nodemask) /* max node number */ ,
279               MPOL_MF_MOVE /* flags */ );
280
281   if (rv < 0)
282     clib_unix_warning ("mbind");
283 #else
284   clib_warning ("mbind unavailable, can't bind to numa %d", this_numa);
285 #endif
286
287   return heap;
288 }
289
290 u8 *
291 format_clib_mem_usage (u8 * s, va_list * va)
292 {
293   int verbose = va_arg (*va, int);
294   return format (s, "$$$$ heap at %llx verbose %d", clib_mem_get_heap (),
295                  verbose);
296 }
297
298 /*
299  * Magic decoder ring for mallinfo stats (ala dlmalloc):
300  *
301  * size_t arena;     / * Non-mmapped space allocated (bytes) * /
302  * size_t ordblks;   / * Number of free chunks * /
303  * size_t smblks;    / * Number of free fastbin blocks * /
304  * size_t hblks;     / * Number of mmapped regions * /
305  * size_t hblkhd;    / * Space allocated in mmapped regions (bytes) * /
306  * size_t usmblks;   / * Maximum total allocated space (bytes) * /
307  * size_t fsmblks;   / * Space in freed fastbin blocks (bytes) * /
308  * size_t uordblks;  / * Total allocated space (bytes) * /
309  * size_t fordblks;  / * Total free space (bytes) * /
310  * size_t keepcost;  / * Top-most, releasable space (bytes) * /
311  *
312  */
313
314 u8 *
315 format_msize (u8 * s, va_list * va)
316 {
317   uword a = va_arg (*va, uword);
318
319   if (a >= 1ULL << 30)
320     s = format (s, "%.2fG", (((f64) a) / ((f64) (1ULL << 30))));
321   else if (a >= 1ULL << 20)
322     s = format (s, "%.2fM", (((f64) a) / ((f64) (1ULL << 20))));
323   else if (a >= 1ULL << 10)
324     s = format (s, "%.2fK", (((f64) a) / ((f64) (1ULL << 10))));
325   else
326     s = format (s, "%lld", a);
327   return s;
328 }
329
330 static int
331 mheap_trace_sort (const void *_t1, const void *_t2)
332 {
333   const mheap_trace_t *t1 = _t1;
334   const mheap_trace_t *t2 = _t2;
335   word cmp;
336
337   cmp = (word) t2->n_bytes - (word) t1->n_bytes;
338   if (!cmp)
339     cmp = (word) t2->n_allocations - (word) t1->n_allocations;
340   return cmp;
341 }
342
343 u8 *
344 format_mheap_trace (u8 * s, va_list * va)
345 {
346   mheap_trace_main_t *tm = va_arg (*va, mheap_trace_main_t *);
347   int verbose = va_arg (*va, int);
348   int have_traces = 0;
349   int i;
350
351   clib_spinlock_lock (&tm->lock);
352   if (vec_len (tm->traces) > 0 &&
353       clib_mem_get_heap () == tm->current_traced_mheap)
354     {
355       have_traces = 1;
356
357       /* Make a copy of traces since we'll be sorting them. */
358       mheap_trace_t *t, *traces_copy;
359       u32 indent, total_objects_traced;
360
361       traces_copy = vec_dup (tm->traces);
362
363       qsort (traces_copy, vec_len (traces_copy), sizeof (traces_copy[0]),
364              mheap_trace_sort);
365
366       total_objects_traced = 0;
367       s = format (s, "\n");
368       vec_foreach (t, traces_copy)
369       {
370         /* Skip over free elements. */
371         if (t->n_allocations == 0)
372           continue;
373
374         total_objects_traced += t->n_allocations;
375
376         /* When not verbose only report allocations of more than 1k. */
377         if (!verbose && t->n_bytes < 1024)
378           continue;
379
380         if (t == traces_copy)
381           s = format (s, "%=9s%=9s %=10s Traceback\n", "Bytes", "Count",
382                       "Sample");
383         s = format (s, "%9d%9d %p", t->n_bytes, t->n_allocations, t->offset);
384         indent = format_get_indent (s);
385         for (i = 0; i < ARRAY_LEN (t->callers) && t->callers[i]; i++)
386           {
387             if (i > 0)
388               s = format (s, "%U", format_white_space, indent);
389 #if defined(CLIB_UNIX) && !defined(__APPLE__)
390             /* $$$$ does this actually work? */
391             s =
392               format (s, " %U\n", format_clib_elf_symbol_with_address,
393                       t->callers[i]);
394 #else
395             s = format (s, " %p\n", t->callers[i]);
396 #endif
397           }
398       }
399
400       s = format (s, "%d total traced objects\n", total_objects_traced);
401
402       vec_free (traces_copy);
403     }
404   clib_spinlock_unlock (&tm->lock);
405   if (have_traces == 0)
406     s = format (s, "no traced allocations\n");
407
408   return s;
409 }
410
411
412 u8 *
413 format_mheap (u8 * s, va_list * va)
414 {
415   void *heap = va_arg (*va, u8 *);
416   int verbose = va_arg (*va, int);
417   struct dlmallinfo mi;
418   mheap_trace_main_t *tm = &mheap_trace_main;
419
420   mi = mspace_mallinfo (heap);
421
422   s = format (s, "total: %U, used: %U, free: %U, trimmable: %U",
423               format_msize, mi.arena,
424               format_msize, mi.uordblks,
425               format_msize, mi.fordblks, format_msize, mi.keepcost);
426   if (verbose > 0)
427     {
428       s = format (s, "\n    free chunks %llu free fastbin blks %llu",
429                   mi.ordblks, mi.smblks);
430       s =
431         format (s, "\n    max total allocated %U", format_msize, mi.usmblks);
432     }
433
434   if (mspace_is_traced (heap))
435     s = format (s, "\n%U", format_mheap_trace, tm, verbose);
436   return s;
437 }
438
439 void
440 clib_mem_usage (clib_mem_usage_t * u)
441 {
442   clib_warning ("unimp");
443 }
444
445 void
446 mheap_usage (void *heap, clib_mem_usage_t * usage)
447 {
448   struct dlmallinfo mi = mspace_mallinfo (heap);
449
450   /* TODO: Fill in some more values */
451   usage->object_count = 0;
452   usage->bytes_total = mi.arena;
453   usage->bytes_overhead = 0;
454   usage->bytes_max = 0;
455   usage->bytes_used = mi.uordblks;
456   usage->bytes_free = mi.fordblks;
457   usage->bytes_free_reclaimed = 0;
458 }
459
460 /* Call serial number for debugger breakpoints. */
461 uword clib_mem_validate_serial = 0;
462
463 void
464 clib_mem_validate (void)
465 {
466   clib_warning ("unimp");
467 }
468
469 void
470 mheap_trace (void *v, int enable)
471 {
472   (void) mspace_enable_disable_trace (v, enable);
473
474   if (enable == 0)
475     mheap_trace_main_free (&mheap_trace_main);
476 }
477
478 void
479 clib_mem_trace (int enable)
480 {
481   mheap_trace_main_t *tm = &mheap_trace_main;
482   void *current_heap = clib_mem_get_heap ();
483
484   tm->enabled = enable;
485   mheap_trace (current_heap, enable);
486
487   if (enable)
488     tm->current_traced_mheap = current_heap;
489   else
490     tm->current_traced_mheap = 0;
491 }
492
493 int
494 clib_mem_is_traced (void)
495 {
496   return mspace_is_traced (clib_mem_get_heap ());
497 }
498
499 uword
500 clib_mem_trace_enable_disable (uword enable)
501 {
502   uword rv;
503   mheap_trace_main_t *tm = &mheap_trace_main;
504
505   rv = tm->enabled;
506   tm->enabled = enable;
507   return rv;
508 }
509
510 /*
511  * These API functions seem like layering violations, but
512  * by introducing them we greatly reduce the number
513  * of code changes required to use dlmalloc spaces
514  */
515 void *
516 mheap_alloc_with_lock (void *memory, uword size, int locked)
517 {
518   void *rv;
519   if (memory == 0)
520     return create_mspace (size, locked);
521   else
522     {
523       rv = create_mspace_with_base (memory, size, locked);
524       if (rv)
525         mspace_disable_expand (rv);
526       return rv;
527     }
528 }
529
530 /*
531  * fd.io coding-style-patch-verification: ON
532  *
533  * Local Variables:
534  * eval: (c-set-style "gnu")
535  * End:
536  */