This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[RFC][PATCH 4/4] malloc_info: add tst-malloc_info-vs-mallinfo
- From: Ken Milmore <ken dot milmore at gmail dot com>
- To: libc-alpha at sourceware dot org, Carlos O'Donell <carlos at redhat dot com>
- Date: Thu, 24 May 2018 01:10:37 +0100
- Subject: [RFC][PATCH 4/4] malloc_info: add tst-malloc_info-vs-mallinfo
Add a unit test to verify consistency between the output of
malloc_info() and mallinfo().
The test is based on tst-malloc_info. It creates several threads, then
tries to exercise the allocator with small block allocations and frees
to produce some heap fragmentation. A few large blocks are also
allocated to force the use of mmap. At several points, the secondary
threads are stopped and malloc_info() and mallinfo() are both called
from the main thread. The malloc_info output is directed to a temporary
file and the "XML" is parsed back in using only standard C library
functions. The various byte totals and chunk counts reported by
mallinfo() are then compared against corresponding totals obtained from
the malloc_info() output. The test will fail should any of these totals
differ.
* malloc/Makefile: Add tst-malloc_info-vs-mallinfo.
* malloc/tst-malloc_info-vs-mallinfo.c: New file.
---
malloc/Makefile | 2 +
malloc/tst-malloc_info-vs-mallinfo.c | 258 +++++++++++++++++++++++++++++++++++
2 files changed, 260 insertions(+)
create mode 100644 malloc/tst-malloc_info-vs-mallinfo.c
diff --git a/malloc/Makefile b/malloc/Makefile
index 7d54bad866..abcb7f847d 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -36,6 +36,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
tst-alloc_buffer \
tst-malloc-tcache-leak \
tst-malloc_info \
+ tst-malloc_info-vs-mallinfo \
tst-malloc-too-large \
tst-malloc-stats-cancellation \
@@ -251,3 +252,4 @@ $(objpfx)tst-dynarray-fail-mem.out: $(objpfx)tst-dynarray-fail.out
$(objpfx)tst-malloc-tcache-leak: $(shared-thread-library)
$(objpfx)tst-malloc_info: $(shared-thread-library)
+$(objpfx)tst-malloc_info-vs-mallinfo: $(shared-thread-library)
diff --git a/malloc/tst-malloc_info-vs-mallinfo.c b/malloc/tst-malloc_info-vs-mallinfo.c
new file mode 100644
index 0000000000..9215cf2a97
--- /dev/null
+++ b/malloc/tst-malloc_info-vs-mallinfo.c
@@ -0,0 +1,258 @@
+/* Test for consistency of malloc_info with mallinfo.
+ Copyright (C) 2017-2018 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+/* This test checks for agreement between the amounts of free and
+ allocated space reported by the malloc_info and mallinfo functions.
+ It has been adapted from tst-malloc_info.c. */
+
+#include <array_length.h>
+#include <malloc.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/support.h>
+#include <support/check.h>
+#include <support/test-driver.h>
+#include <support/xthread.h>
+
+/* This barrier is used to have the main thread wait until the helper
+ threads have performed their allocations. */
+static pthread_barrier_t barrier;
+
+enum
+ {
+ /* Number of threads performing allocations. */
+ thread_count = 4,
+
+ /* Amount of small block memory allocation per thread.
+ We keep this modest so there can be no possiblity of the mallinfo
+ totals wrapping around. */
+ per_thread_allocations = 16 * 1024 * 1024,
+
+ /* Size of the largest small block to allocate.
+ We will allocate a range of sizes up to this maximum. */
+ max_alloc_size = 512,
+
+ /* Size of large block to allocate.
+ We allocate only one of these per thread. It should be large enough
+ to ensure use of mmap. */
+ large_block_size = 1024 * 1024,
+ };
+
+static void *
+allocation_thread_function (void *closure)
+{
+ struct list
+ {
+ struct list *next;
+ };
+
+ struct list *head = NULL;
+ size_t allocated = 0;
+ unsigned int seed = 1;
+
+ xpthread_barrier_wait (&barrier);
+ /* Before allocation */
+ xpthread_barrier_wait (&barrier);
+
+ /* Allocate a single large block and then a range of small block sizes: */
+ void *large_block = xmalloc (large_block_size);
+
+ while (allocated < per_thread_allocations)
+ {
+ size_t size = rand_r (&seed) % max_alloc_size;
+ struct list *new_head = xmalloc (size);
+ allocated += size;
+ new_head->next = head;
+ head = new_head;
+ }
+
+ xpthread_barrier_wait (&barrier);
+ /* After allocation */
+ xpthread_barrier_wait (&barrier);
+
+ /* Force some memory fragmentation by freeing every other block. */
+ struct list *pos = head;
+ while (pos != NULL && pos->next != NULL)
+ {
+ struct list *next_pos = pos->next->next;
+ free (pos->next);
+ pos->next = next_pos;
+ pos = next_pos;
+ }
+
+ xpthread_barrier_wait (&barrier);
+ /* After partial deallocation */
+ xpthread_barrier_wait (&barrier);
+
+ while (head != NULL)
+ {
+ struct list *next_head = head->next;
+ free (head);
+ head = next_head;
+ }
+
+ free (large_block);
+
+ return NULL;
+}
+
+static void
+check_malloc_info (const char *context)
+{
+ printf ("\ninfo: %s:\n", context);
+
+ FILE *f = tmpfile ();
+ if (f == NULL)
+ FAIL_EXIT1 ("tmpfile failed: %m");
+
+ /* Perform a dummy seek, to allocate the stream buffer. */
+ if(fseek(f, 0L, SEEK_SET) != 0)
+ FAIL_EXIT1 ("fseek failed: %m");
+
+ /* We call mallinfo twice, i.e. before and after malloc_info, so we can
+ check that nothing has changed in the meantime which could lead to
+ false comparisons. */
+ struct mallinfo mib4 = mallinfo ();
+ malloc_info (0, f);
+ struct mallinfo mi = mallinfo ();
+
+ TEST_VERIFY_EXIT (memcmp (&mi, &mib4, sizeof (mi)) == 0);
+
+ if (fseek (f, 0L, SEEK_SET) != 0)
+ FAIL_EXIT1 ("fseek failed: %m");
+
+ size_t total_fast_count = 0;
+ size_t total_fast_size = 0;
+ size_t total_rest_count = 0;
+ size_t total_rest_size = 0;
+ size_t total_top_count = 0;
+ size_t total_top_size = 0;
+ size_t total_mmap_count = 0;
+ size_t total_mmap_size = 0;
+ size_t system_current_size = 0;
+ size_t heap0_top_size = 0;
+ int heap_nr = -1;
+
+ for (;;)
+ {
+ char tag[100];
+ if (fscanf (f, " <%99[^>]> ", tag) != 1)
+ FAIL_EXIT1 ("fscanf failed (or EOF)");
+
+ if (test_verbose > 0)
+ printf ("<%s>\n", tag);
+
+ if (strcmp (tag, "/malloc") == 0)
+ {
+ TEST_VERIFY_EXIT (heap_nr == -1);
+ break;
+ }
+ else if (strcmp (tag, "/heap") == 0)
+ {
+ TEST_VERIFY_EXIT (heap_nr >= 0);
+ heap_nr = -1;
+ }
+ else if (heap_nr == 0)
+ {
+ sscanf (tag, "total type=\"top\" count=\"1\" size=\"%zu\"",
+ &heap0_top_size);
+ }
+ else if (heap_nr == -1)
+ {
+ sscanf (tag, "heap nr=\"%d\"", &heap_nr);
+ sscanf (tag, "total type=\"fast\" count=\"%zu\" size=\"%zu\"",
+ &total_fast_count, &total_fast_size);
+ sscanf (tag, "total type=\"rest\" count=\"%zu\" size=\"%zu\"",
+ &total_rest_count, &total_rest_size);
+ sscanf (tag, "total type=\"top\" count=\"%zu\" size=\"%zu\"",
+ &total_top_count, &total_top_size);
+ sscanf (tag, "total type=\"mmap\" count=\"%zu\" size=\"%zu\"",
+ &total_mmap_count, &total_mmap_size);
+ sscanf (tag, "system type=\"current\" size=\"%zu\"",
+ &system_current_size);
+ }
+ }
+
+ fclose (f);
+
+ size_t total_nonfast_count = total_rest_count + total_top_count;
+ size_t total_free_size = total_fast_size + total_rest_size + total_top_size;
+ size_t total_alloc_size = system_current_size - total_free_size;
+
+ printf ("arena=%d/%zu\n", mi.arena, system_current_size);
+ TEST_COMPARE (mi.arena, system_current_size);
+
+ printf ("ordblks=%d/%zu\n", mi.ordblks, total_nonfast_count);
+ TEST_COMPARE (mi.ordblks, total_nonfast_count);
+
+ printf ("smblks=%d/%zu\n", mi.smblks, total_fast_count);
+ TEST_COMPARE (mi.smblks, total_fast_count);
+
+ printf ("hblks=%d/%zu\n", mi.hblks, total_mmap_count);
+ TEST_COMPARE (mi.hblks, total_mmap_count);
+
+ printf ("hblkhd=%d/%zu\n", mi.hblkhd, total_mmap_size);
+ TEST_COMPARE (mi.hblkhd, total_mmap_size);
+
+ printf ("fsmblks=%d/%zu\n", mi.fsmblks, total_fast_size);
+ TEST_COMPARE (mi.fsmblks, total_fast_size);
+
+ printf ("uordblks=%d/%zu\n", mi.uordblks, total_alloc_size);
+ TEST_COMPARE (mi.uordblks, total_alloc_size);
+
+ printf ("fordblks=%d/%zu\n", mi.fordblks, total_free_size);
+ TEST_COMPARE (mi.fordblks, total_free_size);
+
+ printf ("keepcost=%d/%zu\n", mi.keepcost, heap0_top_size);
+ TEST_COMPARE (mi.keepcost, heap0_top_size);
+}
+
+static int
+do_test (void)
+{
+ xpthread_barrier_init (&barrier, NULL, thread_count + 1);
+
+ pthread_t threads[thread_count];
+ for (size_t i = 0; i < array_length (threads); ++i)
+ threads[i] = xpthread_create (NULL, allocation_thread_function, NULL);
+
+ xpthread_barrier_wait (&barrier);
+ check_malloc_info ("Before allocation");
+ xpthread_barrier_wait (&barrier);
+
+ xpthread_barrier_wait (&barrier);
+ check_malloc_info ("After allocation");
+ xpthread_barrier_wait (&barrier);
+
+ xpthread_barrier_wait (&barrier);
+ check_malloc_info ("After partial deallocation");
+ xpthread_barrier_wait (&barrier);
+
+ for (size_t i = 0; i < array_length (threads); ++i)
+ xpthread_join (threads[i]);
+
+ check_malloc_info ("After full deallocation");
+
+ malloc_trim (0);
+
+ check_malloc_info ("After malloc_trim");
+
+ return 0;
+}
+
+#include <support/test-driver.c>
--
2.11.0