getRuntimeStat() support (ART).

Export some runtime stats (currently GC stats) via
VMDebug.getRuntimeStat().

Added several new GC stats such as blocking GC counts and GC count
histograms.

Bug: 19825248
Change-Id: I8ece9ed241dc3982dfd983d7159090ba82940dce
diff --git a/test/099-vmdebug/src/Main.java b/test/099-vmdebug/src/Main.java
index 4d781c3..a8db069 100644
--- a/test/099-vmdebug/src/Main.java
+++ b/test/099-vmdebug/src/Main.java
@@ -17,6 +17,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.util.Map;
 
 public class Main {
     public static void main(String[] args) throws Exception {
@@ -26,6 +27,7 @@
             return;
         }
         testMethodTracing();
+        testRuntimeStat();
     }
 
     private static File createTempFile() throws Exception {
@@ -109,10 +111,108 @@
         tempFile.delete();
     }
 
+    private static void checkNumber(String s) throws Exception {
+        if (s == null) {
+            System.out.println("Got null string");
+            return;
+        }
+        long n = Long.valueOf(s);
+        if (n < 0) {
+            System.out.println("Got negative number " + n);
+        }
+    }
+
+    private static void checkHistogram(String s) throws Exception {
+        if (s == null || s.length() == 0) {
+            System.out.println("Got null or empty string");
+            return;
+        }
+        String[] buckets = s.split(",");
+        long last_key = 0;
+        for (int i = 0; i < buckets.length; ++i) {
+            String bucket = buckets[i];
+            if (bucket.length() == 0) {
+                System.out.println("Got empty bucket");
+                continue;
+            }
+            String[] kv = bucket.split(":");
+            if (kv.length != 2 || kv[0].length() == 0 || kv[1].length() == 0) {
+                System.out.println("Got bad bucket " + bucket);
+                continue;
+            }
+            long key = Long.valueOf(kv[0]);
+            long value = Long.valueOf(kv[1]);
+            if (key < 0 || value < 0) {
+                System.out.println("Got negative key or value " + bucket);
+                continue;
+            }
+            if (key < last_key) {
+                System.out.println("Got decreasing key " + bucket);
+                continue;
+            }
+            last_key = key;
+        }
+    }
+
+    private static void testRuntimeStat() throws Exception {
+        // Invoke at least one GC and wait for 20 seconds or so so we get at
+        // least one bucket in the histograms.
+        for (int i = 0; i < 20; ++i) {
+          Runtime.getRuntime().gc();
+          Thread.sleep(1000L);
+        }
+        String gc_count = VMDebug.getRuntimeStat("art.gc.gc-count");
+        String gc_time = VMDebug.getRuntimeStat("art.gc.gc-time");
+        String bytes_allocated = VMDebug.getRuntimeStat("art.gc.bytes-allocated");
+        String bytes_freed = VMDebug.getRuntimeStat("art.gc.bytes-freed");
+        String blocking_gc_count = VMDebug.getRuntimeStat("art.gc.blocking-gc-count");
+        String blocking_gc_time = VMDebug.getRuntimeStat("art.gc.blocking-gc-time");
+        String gc_count_rate_histogram = VMDebug.getRuntimeStat("art.gc.gc-count-rate-histogram");
+        String blocking_gc_count_rate_histogram =
+            VMDebug.getRuntimeStat("art.gc.blocking-gc-count-rate-histogram");
+        checkNumber(gc_count);
+        checkNumber(gc_time);
+        checkNumber(bytes_allocated);
+        checkNumber(bytes_freed);
+        checkNumber(blocking_gc_count);
+        checkNumber(blocking_gc_time);
+        checkHistogram(gc_count_rate_histogram);
+        checkHistogram(blocking_gc_count_rate_histogram);
+    }
+
+    private static void testRuntimeStats() throws Exception {
+        // Invoke at least one GC and wait for 20 seconds or so so we get at
+        // least one bucket in the histograms.
+        for (int i = 0; i < 20; ++i) {
+          Runtime.getRuntime().gc();
+          Thread.sleep(1000L);
+        }
+        Map<String, String> map = VMDebug.getRuntimeStats();
+        String gc_count = map.get("art.gc.gc-count");
+        String gc_time = map.get("art.gc.gc-time");
+        String bytes_allocated = map.get("art.gc.bytes-allocated");
+        String bytes_freed = map.get("art.gc.bytes-freed");
+        String blocking_gc_count = map.get("art.gc.blocking-gc-count");
+        String blocking_gc_time = map.get("art.gc.blocking-gc-time");
+        String gc_count_rate_histogram = map.get("art.gc.gc-count-rate-histogram");
+        String blocking_gc_count_rate_histogram =
+            map.get("art.gc.blocking-gc-count-rate-histogram");
+        checkNumber(gc_count);
+        checkNumber(gc_time);
+        checkNumber(bytes_allocated);
+        checkNumber(bytes_freed);
+        checkNumber(blocking_gc_count);
+        checkNumber(blocking_gc_time);
+        checkHistogram(gc_count_rate_histogram);
+        checkHistogram(blocking_gc_count_rate_histogram);
+    }
+
     private static class VMDebug {
         private static final Method startMethodTracingMethod;
         private static final Method stopMethodTracingMethod;
         private static final Method getMethodTracingModeMethod;
+        private static final Method getRuntimeStatMethod;
+        private static final Method getRuntimeStatsMethod;
         static {
             try {
                 Class c = Class.forName("dalvik.system.VMDebug");
@@ -120,6 +220,8 @@
                         Integer.TYPE, Integer.TYPE, Boolean.TYPE, Integer.TYPE);
                 stopMethodTracingMethod = c.getDeclaredMethod("stopMethodTracing");
                 getMethodTracingModeMethod = c.getDeclaredMethod("getMethodTracingMode");
+                getRuntimeStatMethod = c.getDeclaredMethod("getRuntimeStat", String.class);
+                getRuntimeStatsMethod = c.getDeclaredMethod("getRuntimeStats");
             } catch (Exception e) {
                 throw new RuntimeException(e);
             }
@@ -136,5 +238,11 @@
         public static int getMethodTracingMode() throws Exception {
             return (int) getMethodTracingModeMethod.invoke(null);
         }
+        public static String getRuntimeStat(String statName) throws Exception {
+            return (String) getRuntimeStatMethod.invoke(null, statName);
+        }
+        public static Map<String, String> getRuntimeStats() throws Exception {
+            return (Map<String, String>) getRuntimeStatsMethod.invoke(null);
+        }
     }
 }