Refined mallinfo() implementation (contributed by Eugene Toder).

git-svn-id: svn://svn.valgrind.org/valgrind/trunk@7901 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/coregrind/m_mallocfree.c b/coregrind/m_mallocfree.c
index c1f01f8..f78b18e 100644
--- a/coregrind/m_mallocfree.c
+++ b/coregrind/m_mallocfree.c
@@ -47,6 +47,10 @@
 // #define DEBUG_MALLOC      // turn on heavyweight debugging machinery
 // #define VERBOSE_MALLOC    // make verbose, esp. in debugging machinery
 
+/* Number and total size of blocks in free queue. Used by mallinfo(). */
+Long VG_(free_queue_volume) = 0;
+Long VG_(free_queue_length) = 0;
+
 /*------------------------------------------------------------*/
 /*--- Main types                                           ---*/
 /*------------------------------------------------------------*/
@@ -1468,9 +1472,34 @@
    return get_pszB(a, b);
 }
 
-// We cannot return the whole struct as the library function does,
-// because this is called by a client request.  So instead we use
-// a pointer to do call by reference.
+
+// Implementation of mallinfo(). There is no recent standard that defines
+// the behavior of mallinfo(). The meaning of the fields in struct mallinfo
+// is as follows:
+//
+//     struct mallinfo  {
+//                int arena;     /* total space in arena            */
+//                int ordblks;   /* number of ordinary blocks       */
+//                int smblks;    /* number of small blocks          */
+//                int hblks;     /* number of holding blocks        */
+//                int hblkhd;    /* space in holding block headers  */
+//                int usmblks;   /* space in small blocks in use    */
+//                int fsmblks;   /* space in free small blocks      */
+//                int uordblks;  /* space in ordinary blocks in use */
+//                int fordblks;  /* space in free ordinary blocks   */
+//                int keepcost;  /* space penalty if keep option    */
+//                               /* is used                         */
+//        };
+//
+// The glibc documentation about mallinfo (which is somewhat outdated) can
+// be found here:
+// http://www.gnu.org/software/libtool/manual/libc/Statistics-of-Malloc.html
+//
+// See also http://bugs.kde.org/show_bug.cgi?id=160956.
+//
+// Regarding the implementation of VG_(mallinfo)(): we cannot return the
+// whole struct as the library function does, because this is called by a
+// client request.  So instead we use a pointer to do call by reference.
 void VG_(mallinfo) ( ThreadId tid, struct vg_mallinfo* mi )
 {
    UInt   i, free_blocks, free_blocks_size;
@@ -1491,17 +1520,16 @@
    }
 
    // We don't have fastbins so smblks & fsmblks are always 0. Also we don't
-   // have a separate mmap allocator so set hblks & hblkhd to 0. See also
-   // http://www.gnu.org/software/libtool/manual/libc/Statistics-of-Malloc.html
+   // have a separate mmap allocator so set hblks & hblkhd to 0.
    mi->arena    = a->bytes_mmaped;
-   mi->ordblks  = free_blocks;
+   mi->ordblks  = free_blocks + VG_(free_queue_length);
    mi->smblks   = 0;
    mi->hblks    = 0;
    mi->hblkhd   = 0;
    mi->usmblks  = 0;
    mi->fsmblks  = 0;
-   mi->uordblks = a->bytes_on_loan;
-   mi->fordblks = free_blocks_size;
+   mi->uordblks = a->bytes_on_loan - VG_(free_queue_volume);
+   mi->fordblks = free_blocks_size + VG_(free_queue_volume);
    mi->keepcost = 0; // may want some value in here
 }
 
diff --git a/include/pub_tool_replacemalloc.h b/include/pub_tool_replacemalloc.h
index 9b54f20..dd8b23e 100644
--- a/include/pub_tool_replacemalloc.h
+++ b/include/pub_tool_replacemalloc.h
@@ -41,6 +41,13 @@
 extern void* VG_(cli_malloc) ( SizeT align, SizeT nbytes );
 extern void  VG_(cli_free)   ( void* p );
 
+/* If a tool uses deferred freeing (e.g. memcheck to catch accesses to
+   freed memory) it can maintain number and total size of queued blocks
+   in these variable to provide more accurate statistics about client
+   memory usage. Currently used by mallinfo(). */
+extern Long VG_(free_queue_volume);
+extern Long VG_(free_queue_length);
+
 /* Check if an address is within a range, allowing for redzones at edges */
 extern Bool VG_(addr_is_in_block)( Addr a, Addr start,
                                    SizeT size, SizeT rz_szB );
diff --git a/memcheck/mc_malloc_wrappers.c b/memcheck/mc_malloc_wrappers.c
index 06f568b..0c2eb72 100644
--- a/memcheck/mc_malloc_wrappers.c
+++ b/memcheck/mc_malloc_wrappers.c
@@ -71,7 +71,6 @@
 /* Records blocks after freeing. */
 static MC_Chunk* freed_list_start  = NULL;
 static MC_Chunk* freed_list_end    = NULL;
-static Long      freed_list_volume = 0;
 
 /* Put a shadow chunk on the freed blocks queue, possibly freeing up
    some of the oldest blocks in the queue at the same time. */
@@ -83,33 +82,35 @@
    if (freed_list_end == NULL) {
       tl_assert(freed_list_start == NULL);
       freed_list_end    = freed_list_start = mc;
-      freed_list_volume = (Long)mc->szB;
+      VG_(free_queue_volume) = (Long)mc->szB;
    } else {
       tl_assert(freed_list_end->next == NULL);
       freed_list_end->next = mc;
       freed_list_end       = mc;
-      freed_list_volume += (Long)mc->szB;
+      VG_(free_queue_volume) += (Long)mc->szB;
       if (show)
          VG_(printf)("mc_freelist: acquire: volume now %lld\n", 
-                     freed_list_volume);
+                     VG_(free_queue_volume));
    }
+   VG_(free_queue_length)++;
    mc->next = NULL;
 
    /* Release enough of the oldest blocks to bring the free queue
       volume below vg_clo_freelist_vol. */
 
-   while (freed_list_volume > MC_(clo_freelist_vol)) {
+   while (VG_(free_queue_volume) > MC_(clo_freelist_vol)) {
       MC_Chunk* mc1;
 
       tl_assert(freed_list_start != NULL);
       tl_assert(freed_list_end != NULL);
 
       mc1 = freed_list_start;
-      freed_list_volume -= (Long)mc1->szB;
+      VG_(free_queue_volume) -= (Long)mc1->szB;
+      VG_(free_queue_length)--;
       if (show)
          VG_(printf)("mc_freelist: discard: volume now %lld\n", 
-                     freed_list_volume);
-      tl_assert(freed_list_volume >= 0);
+                     VG_(free_queue_volume));
+      tl_assert(VG_(free_queue_volume) >= 0);
 
       if (freed_list_start == freed_list_end) {
          freed_list_start = freed_list_end = NULL;