]> git.ipfire.org Git - thirdparty/valgrind.git/commitdiff
Implement gdbsrv "v.info stats" command giving statistics for valgrind core + tools
authorPhilippe Waroquiers <philippe.waroquiers@skynet.be>
Sun, 15 Dec 2013 20:24:43 +0000 (20:24 +0000)
committerPhilippe Waroquiers <philippe.waroquiers@skynet.be>
Sun, 15 Dec 2013 20:24:43 +0000 (20:24 +0000)
* addition of VG_(needs_print_stats) in pub_tool_tooliface.h
* use the above in memcheck and helgrind
* output valgrind stats and calls print_stats in server.c

git-svn-id: svn://svn.valgrind.org/valgrind/trunk@13760

NEWS
coregrind/m_gdbserver/server.c
coregrind/m_tooliface.c
coregrind/pub_core_tooliface.h
docs/xml/manual-core-adv.xml
gdbserver_tests/mchelp.stdoutB.exp
helgrind/hg_main.c
include/pub_tool_tooliface.h
memcheck/mc_main.c

diff --git a/NEWS b/NEWS
index 7b0f94942df1bbdf32bcd673cf3ec71bb30ca648..6e484ef8f878636ccff9129b3f3b8072b94709f2 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,11 @@ Release 3.10.0 (?? ?????? 201?)
 
 * ==================== OTHER CHANGES ====================
 
+* New and modified GDB server monitor features:
+
+  - A new monitor command "v.info stats" that shows various valgrind core and tool
+    statistics.
+
 * ==================== FIXED BUGS ====================
 
 The following bugs have been fixed or resolved.  Note that "n-i-bz"
index 9feb0f61a53a49f155c413b279e14661cb549468..6f45ba8def74cca45f9d26ca83f1a861d396d506 100644 (file)
@@ -30,6 +30,7 @@
 #include "pub_core_execontext.h"
 #include "pub_core_syswrap.h"      // VG_(show_open_fds)
 #include "pub_core_scheduler.h"
+#include "pub_core_transtab.h"
 
 unsigned long cont_thread;
 unsigned long general_thread;
@@ -216,6 +217,7 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
 "     (with aspacemgr arg, also shows valgrind segments on log ouput)\n"
 "  v.info exectxt          : show stacktraces and stats of all execontexts\n"
 "  v.info scheduler        : show valgrind thread state and stacktrace\n"
+"  v.info stats            : show various valgrind and tool stats\n"
 "  v.set debuglog <level>  : set valgrind debug log level to <level>\n"
 "  v.translate <addr> [<traceflags>]  : debug translation of <addr> with <traceflags>\n"
 "    (default traceflags 0b00100000 : show after instrumentation)\n"
@@ -288,7 +290,7 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
       wcmd = strtok_r (NULL, " ", &ssaveptr);
       switch (kwdid = VG_(keyword_id) 
               ("all_errors n_errs_found last_error gdbserver_status memory"
-               " scheduler open_fds exectxt",
+               " scheduler stats open_fds exectxt",
                wcmd, kwd_report_all)) {
       case -2:
       case -1: 
@@ -311,6 +313,8 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
          VG_(gdbserver_status_output)();
          break;
       case  4: /* memory */
+         VG_(printf) ("%llu bytes have already been allocated.\n",
+                      VG_(am_get_anonsize_total)());
          VG_(print_all_arena_stats) ();
          if (VG_(clo_profile_heap))
             VG_(print_arena_cc_analysis) ();
@@ -332,7 +336,18 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
          VG_(show_sched_status) ();
          ret = 1;
          break;
-      case  6: /* open_fds */
+      case  6: /* stats */
+         VG_(print_translation_stats)();
+         VG_(print_tt_tc_stats)();
+         VG_(print_scheduler_stats)();
+         VG_(print_ExeContext_stats)( False /* with_stacktraces */ );
+         VG_(print_errormgr_stats)();
+         if (VG_(needs).print_stats) {
+            VG_TDICT_CALL(tool_print_stats);
+         }
+         ret = 1;
+         break;
+      case  7: /* open_fds */
          if (VG_(clo_track_fds))
             VG_(show_open_fds) ("");
          else
@@ -341,7 +356,7 @@ int handle_gdb_valgrind_command (char *mon, OutputSink *sink_wanted_at_return)
                 " to show open fds\n");
          ret = 1;
          break;
-      case  7: /* exectxt */
+      case  8: /* exectxt */
          VG_(print_ExeContext_stats) (True /* with_stacktraces */);
          ret = 1;
          break;
index f2cfcbe7fb3906f71f7f302acbba5ac9e21d7346..fc0350700df5957a9783938917e1d238eb7adf8b 100644 (file)
@@ -92,6 +92,7 @@ VgNeeds VG_(needs) = {
    .client_requests      = False,
    .syscall_wrapper      = False,
    .sanity_checks        = False,
+   .print_stats          = False,
    .var_info            = False,
    .malloc_replacement   = False,
    .xml_output           = False,
@@ -294,6 +295,14 @@ void VG_(needs_sanity_checks)(
    VG_(tdict).tool_expensive_sanity_check = expen;
 }
 
+void VG_(needs_print_stats) (
+   void (*print_stats)(void)
+)
+{
+   VG_(needs).print_stats = True;
+   VG_(tdict).tool_print_stats = print_stats;
+}
+
 void VG_(needs_malloc_replacement)(
    void* (*malloc)               ( ThreadId, SizeT ),
    void* (*__builtin_new)        ( ThreadId, SizeT ),
index 39c67f051ed6cd6e49e0bd7e1cbbe2be9b75d625..fd84ef13a2b3b99ded487a909d905a545f04ad5a 100644 (file)
@@ -88,6 +88,7 @@ typedef
       Bool client_requests;
       Bool syscall_wrapper;
       Bool sanity_checks;
+      Bool print_stats;
       Bool var_info;
       Bool malloc_replacement;
       Bool xml_output;
@@ -149,6 +150,9 @@ typedef struct {
    Bool (*tool_cheap_sanity_check)(void);
    Bool (*tool_expensive_sanity_check)(void);
 
+   // VG_(needs).print_stats
+   void (*tool_print_stats)(void);
+
    // VG_(needs).malloc_replacement
    void* (*tool_malloc)              (ThreadId, SizeT);
    void* (*tool___builtin_new)       (ThreadId, SizeT);
index 025ea70cad30163a5b3bbf2fc18f6efef7a4e423..382b86a85ee65a734ab776e81a9d9d1eb10d5e67 100644 (file)
@@ -1435,6 +1435,13 @@ problems or bugs.</para>
     </para>
   </listitem>
 
+  <listitem>
+    <para><varname>v.info stats</varname> shows various valgrind core and
+    tool statistics. With this, Valgrind and tool statistics can
+    be examined while running, even without option <option>--stats=yes</option>.
+    </para>
+  </listitem>
+
   <listitem>
     <para><varname>v.set debuglog &lt;intvalue&gt;</varname> sets the
     Valgrind debug log level to &lt;intvalue&gt;.  This allows to
index c9b17299e89807ac9133b71c486b7dc13b7cbedf..7dfd0c326b740570973e250e7bdf5c4e88f15778 100644 (file)
@@ -62,6 +62,7 @@ debugging valgrind internals monitor commands:
      (with aspacemgr arg, also shows valgrind segments on log ouput)
   v.info exectxt          : show stacktraces and stats of all execontexts
   v.info scheduler        : show valgrind thread state and stacktrace
+  v.info stats            : show various valgrind and tool stats
   v.set debuglog <level>  : set valgrind debug log level to <level>
   v.translate <addr> [<traceflags>]  : debug translation of <addr> with <traceflags>
     (default traceflags 0b00100000 : show after instrumentation)
index dca958320e0e5aa1094128745564156c9f12f32b..532bdca9384f939c0f1b3ee92d7f3d3708840a1e 100644 (file)
@@ -5133,6 +5133,67 @@ static void hg_print_debug_usage ( void )
     );
 }
 
+static void hg_print_stats (void)
+{
+
+   if (1) {
+      VG_(printf)("\n");
+      HG_(ppWSUstats)( univ_lsets, "univ_lsets" );
+      if (HG_(clo_track_lockorders)) {
+         VG_(printf)("\n");
+         HG_(ppWSUstats)( univ_laog,  "univ_laog" );
+      }
+   }
+
+   //zz       VG_(printf)("\n");
+   //zz       VG_(printf)(" hbefore: %'10lu queries\n",        stats__hbefore_queries);
+   //zz       VG_(printf)(" hbefore: %'10lu cache 0 hits\n",   stats__hbefore_cache0s);
+   //zz       VG_(printf)(" hbefore: %'10lu cache > 0 hits\n", stats__hbefore_cacheNs);
+   //zz       VG_(printf)(" hbefore: %'10lu graph searches\n", stats__hbefore_gsearches);
+   //zz       VG_(printf)(" hbefore: %'10lu   of which slow\n",
+   //zz                   stats__hbefore_gsearches - stats__hbefore_gsearchFs);
+   //zz       VG_(printf)(" hbefore: %'10lu stack high water mark\n",
+   //zz                   stats__hbefore_stk_hwm);
+   //zz       VG_(printf)(" hbefore: %'10lu cache invals\n",   stats__hbefore_invals);
+   //zz       VG_(printf)(" hbefore: %'10lu probes\n",         stats__hbefore_probes);
+
+   VG_(printf)("\n");
+   VG_(printf)("        locksets: %'8d unique lock sets\n",
+               (Int)HG_(cardinalityWSU)( univ_lsets ));
+   if (HG_(clo_track_lockorders)) {
+      VG_(printf)("       univ_laog: %'8d unique lock sets\n",
+                  (Int)HG_(cardinalityWSU)( univ_laog ));
+   }
+
+   //VG_(printf)("L(ast)L(ock) map: %'8lu inserts (%d map size)\n",
+   //            stats__ga_LL_adds,
+   //            (Int)(ga_to_lastlock ? VG_(sizeFM)( ga_to_lastlock ) : 0) );
+
+   VG_(printf)("  LockN-to-P map: %'8llu queries (%llu map size)\n",
+               HG_(stats__LockN_to_P_queries),
+               HG_(stats__LockN_to_P_get_map_size)() );
+
+   VG_(printf)("string table map: %'8llu queries (%llu map size)\n",
+               HG_(stats__string_table_queries),
+               HG_(stats__string_table_get_map_size)() );
+   if (HG_(clo_track_lockorders)) {
+      VG_(printf)("            LAOG: %'8d map size\n",
+                  (Int)(laog ? VG_(sizeFM)( laog ) : 0));
+      VG_(printf)(" LAOG exposition: %'8d map size\n",
+                  (Int)(laog_exposition ? VG_(sizeFM)( laog_exposition ) : 0));
+   }
+
+   VG_(printf)("           locks: %'8lu acquires, "
+               "%'lu releases\n",
+               stats__lockN_acquires,
+               stats__lockN_releases
+              );
+   VG_(printf)("   sanity checks: %'8lu\n", stats__sanity_checks);
+
+   VG_(printf)("\n");
+   libhb_shutdown(True); // This in fact only print stats.
+}
+
 static void hg_fini ( Int exitcode )
 {
    if (VG_(clo_verbosity) == 1 && !VG_(clo_xml)) {
@@ -5154,65 +5215,8 @@ static void hg_fini ( Int exitcode )
    if (HG_(clo_sanity_flags))
       all__sanity_check("SK_(fini)");
 
-   if (VG_(clo_stats)) {
-
-      if (1) {
-         VG_(printf)("\n");
-         HG_(ppWSUstats)( univ_lsets, "univ_lsets" );
-         if (HG_(clo_track_lockorders)) {
-            VG_(printf)("\n");
-            HG_(ppWSUstats)( univ_laog,  "univ_laog" );
-         }
-      }
-
-      //zz       VG_(printf)("\n");
-      //zz       VG_(printf)(" hbefore: %'10lu queries\n",        stats__hbefore_queries);
-      //zz       VG_(printf)(" hbefore: %'10lu cache 0 hits\n",   stats__hbefore_cache0s);
-      //zz       VG_(printf)(" hbefore: %'10lu cache > 0 hits\n", stats__hbefore_cacheNs);
-      //zz       VG_(printf)(" hbefore: %'10lu graph searches\n", stats__hbefore_gsearches);
-      //zz       VG_(printf)(" hbefore: %'10lu   of which slow\n",
-      //zz                   stats__hbefore_gsearches - stats__hbefore_gsearchFs);
-      //zz       VG_(printf)(" hbefore: %'10lu stack high water mark\n",
-      //zz                   stats__hbefore_stk_hwm);
-      //zz       VG_(printf)(" hbefore: %'10lu cache invals\n",   stats__hbefore_invals);
-      //zz       VG_(printf)(" hbefore: %'10lu probes\n",         stats__hbefore_probes);
-
-      VG_(printf)("\n");
-      VG_(printf)("        locksets: %'8d unique lock sets\n",
-                  (Int)HG_(cardinalityWSU)( univ_lsets ));
-      if (HG_(clo_track_lockorders)) {
-         VG_(printf)("       univ_laog: %'8d unique lock sets\n",
-                     (Int)HG_(cardinalityWSU)( univ_laog ));
-      }
-
-      //VG_(printf)("L(ast)L(ock) map: %'8lu inserts (%d map size)\n",
-      //            stats__ga_LL_adds,
-      //            (Int)(ga_to_lastlock ? VG_(sizeFM)( ga_to_lastlock ) : 0) );
-
-      VG_(printf)("  LockN-to-P map: %'8llu queries (%llu map size)\n",
-                  HG_(stats__LockN_to_P_queries),
-                  HG_(stats__LockN_to_P_get_map_size)() );
-
-      VG_(printf)("string table map: %'8llu queries (%llu map size)\n",
-                  HG_(stats__string_table_queries),
-                  HG_(stats__string_table_get_map_size)() );
-      if (HG_(clo_track_lockorders)) {
-         VG_(printf)("            LAOG: %'8d map size\n",
-                     (Int)(laog ? VG_(sizeFM)( laog ) : 0));
-         VG_(printf)(" LAOG exposition: %'8d map size\n",
-                     (Int)(laog_exposition ? VG_(sizeFM)( laog_exposition ) : 0));
-      }
-         
-      VG_(printf)("           locks: %'8lu acquires, "
-                  "%'lu releases\n",
-                  stats__lockN_acquires,
-                  stats__lockN_releases
-                 );
-      VG_(printf)("   sanity checks: %'8lu\n", stats__sanity_checks);
-
-      VG_(printf)("\n");
-      libhb_shutdown(True);
-   }
+   if (VG_(clo_stats))
+      hg_print_stats();
 }
 
 /* FIXME: move these somewhere sane */
@@ -5305,6 +5309,8 @@ static void hg_pre_clo_init ( void )
    //VG_(needs_sanity_checks)       (hg_cheap_sanity_check,
    //                                hg_expensive_sanity_check);
 
+   VG_(needs_print_stats) (hg_print_stats);
+
    VG_(needs_malloc_replacement)  (hg_cli__malloc,
                                    hg_cli____builtin_new,
                                    hg_cli____builtin_vec_new,
index a346196ac9a2a9052945a425881c2a34cd7bf616..0ccf767c51c7df818b09d50b5300f3ce7fe29121 100644 (file)
@@ -451,6 +451,13 @@ extern void VG_(needs_sanity_checks) (
    Bool(*expensive_sanity_check)(void)
 );
 
+/* Can the tool produce stats during execution? */
+extern void VG_(needs_print_stats) (
+   // Print out tool status. Note that the stats at end of execution
+   // should be output by the VG_(basic_tool_funcs) "fini" function.
+   void (*print_stats)(void)
+);
+
 /* Do we need to see variable type and location information? */
 extern void VG_(needs_var_info) ( void );
 
index 9bab8f36a4449febc206c5f6f45fa3cf9a25cf58..f7755245947f710aa951da5cbb266061887a3310 100644 (file)
@@ -6539,6 +6539,98 @@ static void print_SM_info(const HChar* type, Int n_SMs)
       n_SMs * sizeof(SecMap) / (1024 * 1024UL) );
 }
 
+static void mc_print_stats (void)
+{
+   SizeT max_secVBit_szB, max_SMs_szB, max_shmem_szB;
+
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: sanity checks: %d cheap, %d expensive\n",
+      n_sanity_cheap, n_sanity_expensive );
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: auxmaps: %lld auxmap entries (%lldk, %lldM) in use\n",
+      n_auxmap_L2_nodes, 
+      n_auxmap_L2_nodes * 64, 
+      n_auxmap_L2_nodes / 16 );
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: auxmaps_L1: %lld searches, %lld cmps, ratio %lld:10\n",
+      n_auxmap_L1_searches, n_auxmap_L1_cmps,
+      (10ULL * n_auxmap_L1_cmps) 
+         / (n_auxmap_L1_searches ? n_auxmap_L1_searches : 1) 
+   );   
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: auxmaps_L2: %lld searches, %lld nodes\n",
+      n_auxmap_L2_searches, n_auxmap_L2_nodes
+   );   
+
+   print_SM_info("n_issued     ", n_issued_SMs);
+   print_SM_info("n_deissued   ", n_deissued_SMs);
+   print_SM_info("max_noaccess ", max_noaccess_SMs);
+   print_SM_info("max_undefined", max_undefined_SMs);
+   print_SM_info("max_defined  ", max_defined_SMs);
+   print_SM_info("max_non_DSM  ", max_non_DSM_SMs);
+
+   // Three DSMs, plus the non-DSM ones
+   max_SMs_szB = (3 + max_non_DSM_SMs) * sizeof(SecMap);
+   // The 3*sizeof(Word) bytes is the AVL node metadata size.
+   // The VG_ROUNDUP is because the OSet pool allocator will/must align
+   // the elements on pointer size.
+   // Note that the pool allocator has some additional small overhead
+   // which is not counted in the below.
+   // Hardwiring this logic sucks, but I don't see how else to do it.
+   max_secVBit_szB = max_secVBit_nodes * 
+         (3*sizeof(Word) + VG_ROUNDUP(sizeof(SecVBitNode), sizeof(void*)));
+   max_shmem_szB   = sizeof(primary_map) + max_SMs_szB + max_secVBit_szB;
+
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: max sec V bit nodes:    %d (%ldk, %ldM)\n",
+      max_secVBit_nodes, max_secVBit_szB / 1024,
+                         max_secVBit_szB / (1024 * 1024));
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: set_sec_vbits8 calls: %llu (new: %llu, updates: %llu)\n",
+      sec_vbits_new_nodes + sec_vbits_updates,
+      sec_vbits_new_nodes, sec_vbits_updates );
+   VG_(message)(Vg_DebugMsg,
+      " memcheck: max shadow mem size:   %ldk, %ldM\n",
+      max_shmem_szB / 1024, max_shmem_szB / (1024 * 1024));
+
+   if (MC_(clo_mc_level) >= 3) {
+      VG_(message)(Vg_DebugMsg,
+                   " ocacheL1: %'12lu refs   %'12lu misses (%'lu lossage)\n",
+                   stats_ocacheL1_find, 
+                   stats_ocacheL1_misses,
+                   stats_ocacheL1_lossage );
+      VG_(message)(Vg_DebugMsg,
+                   " ocacheL1: %'12lu at 0   %'12lu at 1\n",
+                   stats_ocacheL1_find - stats_ocacheL1_misses 
+                      - stats_ocacheL1_found_at_1 
+                      - stats_ocacheL1_found_at_N,
+                   stats_ocacheL1_found_at_1 );
+      VG_(message)(Vg_DebugMsg,
+                   " ocacheL1: %'12lu at 2+  %'12lu move-fwds\n",
+                   stats_ocacheL1_found_at_N,
+                   stats_ocacheL1_movefwds );
+      VG_(message)(Vg_DebugMsg,
+                   " ocacheL1: %'12lu sizeB  %'12u useful\n",
+                   (UWord)sizeof(OCache),
+                   4 * OC_W32S_PER_LINE * OC_LINES_PER_SET * OC_N_SETS );
+      VG_(message)(Vg_DebugMsg,
+                   " ocacheL2: %'12lu refs   %'12lu misses\n",
+                   stats__ocacheL2_refs, 
+                   stats__ocacheL2_misses );
+      VG_(message)(Vg_DebugMsg,
+                   " ocacheL2:    %'9lu max nodes %'9lu curr nodes\n",
+                   stats__ocacheL2_n_nodes_max,
+                   stats__ocacheL2_n_nodes );
+      VG_(message)(Vg_DebugMsg,
+                   " niacache: %'12lu refs   %'12lu misses\n",
+                   stats__nia_cache_queries, stats__nia_cache_misses);
+   } else {
+      tl_assert(ocacheL1 == NULL);
+      tl_assert(ocacheL2 == NULL);
+   }
+}
+
+
 static void mc_fini ( Int exitcode )
 {
    MC_(print_malloc_stats)();
@@ -6576,95 +6668,8 @@ static void mc_fini ( Int exitcode )
 
    done_prof_mem();
 
-   if (VG_(clo_stats)) {
-      SizeT max_secVBit_szB, max_SMs_szB, max_shmem_szB;
-      
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: sanity checks: %d cheap, %d expensive\n",
-         n_sanity_cheap, n_sanity_expensive );
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: auxmaps: %lld auxmap entries (%lldk, %lldM) in use\n",
-         n_auxmap_L2_nodes, 
-         n_auxmap_L2_nodes * 64, 
-         n_auxmap_L2_nodes / 16 );
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: auxmaps_L1: %lld searches, %lld cmps, ratio %lld:10\n",
-         n_auxmap_L1_searches, n_auxmap_L1_cmps,
-         (10ULL * n_auxmap_L1_cmps) 
-            / (n_auxmap_L1_searches ? n_auxmap_L1_searches : 1) 
-      );   
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: auxmaps_L2: %lld searches, %lld nodes\n",
-         n_auxmap_L2_searches, n_auxmap_L2_nodes
-      );   
-
-      print_SM_info("n_issued     ", n_issued_SMs);
-      print_SM_info("n_deissued   ", n_deissued_SMs);
-      print_SM_info("max_noaccess ", max_noaccess_SMs);
-      print_SM_info("max_undefined", max_undefined_SMs);
-      print_SM_info("max_defined  ", max_defined_SMs);
-      print_SM_info("max_non_DSM  ", max_non_DSM_SMs);
-
-      // Three DSMs, plus the non-DSM ones
-      max_SMs_szB = (3 + max_non_DSM_SMs) * sizeof(SecMap);
-      // The 3*sizeof(Word) bytes is the AVL node metadata size.
-      // The VG_ROUNDUP is because the OSet pool allocator will/must align
-      // the elements on pointer size.
-      // Note that the pool allocator has some additional small overhead
-      // which is not counted in the below.
-      // Hardwiring this logic sucks, but I don't see how else to do it.
-      max_secVBit_szB = max_secVBit_nodes * 
-            (3*sizeof(Word) + VG_ROUNDUP(sizeof(SecVBitNode), sizeof(void*)));
-      max_shmem_szB   = sizeof(primary_map) + max_SMs_szB + max_secVBit_szB;
-
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: max sec V bit nodes:    %d (%ldk, %ldM)\n",
-         max_secVBit_nodes, max_secVBit_szB / 1024,
-                            max_secVBit_szB / (1024 * 1024));
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: set_sec_vbits8 calls: %llu (new: %llu, updates: %llu)\n",
-         sec_vbits_new_nodes + sec_vbits_updates,
-         sec_vbits_new_nodes, sec_vbits_updates );
-      VG_(message)(Vg_DebugMsg,
-         " memcheck: max shadow mem size:   %ldk, %ldM\n",
-         max_shmem_szB / 1024, max_shmem_szB / (1024 * 1024));
-
-      if (MC_(clo_mc_level) >= 3) {
-         VG_(message)(Vg_DebugMsg,
-                      " ocacheL1: %'12lu refs   %'12lu misses (%'lu lossage)\n",
-                      stats_ocacheL1_find, 
-                      stats_ocacheL1_misses,
-                      stats_ocacheL1_lossage );
-         VG_(message)(Vg_DebugMsg,
-                      " ocacheL1: %'12lu at 0   %'12lu at 1\n",
-                      stats_ocacheL1_find - stats_ocacheL1_misses 
-                         - stats_ocacheL1_found_at_1 
-                         - stats_ocacheL1_found_at_N,
-                      stats_ocacheL1_found_at_1 );
-         VG_(message)(Vg_DebugMsg,
-                      " ocacheL1: %'12lu at 2+  %'12lu move-fwds\n",
-                      stats_ocacheL1_found_at_N,
-                      stats_ocacheL1_movefwds );
-         VG_(message)(Vg_DebugMsg,
-                      " ocacheL1: %'12lu sizeB  %'12u useful\n",
-                      (UWord)sizeof(OCache),
-                      4 * OC_W32S_PER_LINE * OC_LINES_PER_SET * OC_N_SETS );
-         VG_(message)(Vg_DebugMsg,
-                      " ocacheL2: %'12lu refs   %'12lu misses\n",
-                      stats__ocacheL2_refs, 
-                      stats__ocacheL2_misses );
-         VG_(message)(Vg_DebugMsg,
-                      " ocacheL2:    %'9lu max nodes %'9lu curr nodes\n",
-                      stats__ocacheL2_n_nodes_max,
-                      stats__ocacheL2_n_nodes );
-         VG_(message)(Vg_DebugMsg,
-                      " niacache: %'12lu refs   %'12lu misses\n",
-                      stats__nia_cache_queries, stats__nia_cache_misses);
-      } else {
-         tl_assert(ocacheL1 == NULL);
-         tl_assert(ocacheL2 == NULL);
-      }
-   }
+   if (VG_(clo_stats))
+      mc_print_stats();
 
    if (0) {
       VG_(message)(Vg_DebugMsg, 
@@ -6726,6 +6731,7 @@ static void mc_pre_clo_init(void)
    VG_(needs_client_requests)     (mc_handle_client_request);
    VG_(needs_sanity_checks)       (mc_cheap_sanity_check,
                                    mc_expensive_sanity_check);
+   VG_(needs_print_stats)         (mc_print_stats);
    VG_(needs_malloc_replacement)  (MC_(malloc),
                                    MC_(__builtin_new),
                                    MC_(__builtin_vec_new),