1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import java.io.File;
18import java.io.IOException;
19import java.lang.reflect.Method;
20import java.util.Arrays;
21import java.util.ArrayList;
22import java.util.Map;
23
24public class Main {
25    private static final String TEMP_FILE_NAME_PREFIX = "test";
26    private static final String TEMP_FILE_NAME_SUFFIX = ".trace";
27
28    public static void main(String[] args) throws Exception {
29        String name = System.getProperty("java.vm.name");
30        if (!"Dalvik".equals(name)) {
31            System.out.println("This test is not supported on " + name);
32            return;
33        }
34        testMethodTracing();
35        testCountInstances();
36        testRuntimeStat();
37        testRuntimeStats();
38    }
39
40    private static File createTempFile() throws Exception {
41        try {
42            return  File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
43        } catch (IOException e) {
44            System.setProperty("java.io.tmpdir", "/data/local/tmp");
45            try {
46                return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
47            } catch (IOException e2) {
48                System.setProperty("java.io.tmpdir", "/sdcard");
49                return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
50            }
51        }
52    }
53
54    private static void testMethodTracing() throws Exception {
55        File tempFile = null;
56        try {
57            tempFile = createTempFile();
58            testMethodTracingToFile(tempFile);
59        } finally {
60            if (tempFile != null) {
61                tempFile.delete();
62            }
63        }
64    }
65
66    private static void testMethodTracingToFile(File tempFile) throws Exception {
67        String tempFileName = tempFile.getPath();
68
69        if (VMDebug.getMethodTracingMode() != 0) {
70            VMDebug.stopMethodTracing();
71        }
72
73        System.out.println("Confirm enable/disable");
74        System.out.println("status=" + VMDebug.getMethodTracingMode());
75        VMDebug.startMethodTracing(tempFileName, 0, 0, false, 0);
76        System.out.println("status=" + VMDebug.getMethodTracingMode());
77        VMDebug.stopMethodTracing();
78        System.out.println("status=" + VMDebug.getMethodTracingMode());
79        if (tempFile.length() == 0) {
80            System.out.println("ERROR: tracing output file is empty");
81        }
82
83        System.out.println("Confirm sampling");
84        VMDebug.startMethodTracing(tempFileName, 0, 0, true, 1000);
85        System.out.println("status=" + VMDebug.getMethodTracingMode());
86        VMDebug.stopMethodTracing();
87        System.out.println("status=" + VMDebug.getMethodTracingMode());
88        if (tempFile.length() == 0) {
89            System.out.println("ERROR: sample tracing output file is empty");
90        }
91
92        System.out.println("Test starting when already started");
93        VMDebug.startMethodTracing(tempFileName, 0, 0, false, 0);
94        System.out.println("status=" + VMDebug.getMethodTracingMode());
95        VMDebug.startMethodTracing(tempFileName, 0, 0, false, 0);
96        System.out.println("status=" + VMDebug.getMethodTracingMode());
97
98        System.out.println("Test stopping when already stopped");
99        VMDebug.stopMethodTracing();
100        System.out.println("status=" + VMDebug.getMethodTracingMode());
101        VMDebug.stopMethodTracing();
102        System.out.println("status=" + VMDebug.getMethodTracingMode());
103
104        System.out.println("Test tracing with empty filename");
105        try {
106            VMDebug.startMethodTracing("", 0, 0, false, 0);
107            System.out.println("Should have thrown an exception");
108        } catch (Exception e) {
109            System.out.println("Got expected exception");
110        }
111
112        System.out.println("Test tracing with bogus (< 1024 && != 0) filesize");
113        try {
114            VMDebug.startMethodTracing(tempFileName, 1000, 0, false, 0);
115            System.out.println("Should have thrown an exception");
116        } catch (Exception e) {
117            System.out.println("Got expected exception");
118        }
119
120        System.out.println("Test sampling with bogus (<= 0) interval");
121        try {
122            VMDebug.startMethodTracing(tempFileName, 0, 0, true, 0);
123            System.out.println("Should have thrown an exception");
124        } catch (Exception e) {
125            System.out.println("Got expected exception");
126        }
127
128        tempFile.delete();
129    }
130
131    private static void checkNumber(String s) throws Exception {
132        if (s == null) {
133            System.out.println("Got null string");
134            return;
135        }
136        long n = Long.valueOf(s);
137        if (n < 0) {
138            System.out.println("Got negative number " + n);
139        }
140    }
141
142    private static void checkHistogram(String s) throws Exception {
143        if (s == null || s.length() == 0) {
144            System.out.println("Got null or empty string");
145            return;
146        }
147        String[] buckets = s.split(",");
148        long last_key = 0;
149        for (int i = 0; i < buckets.length; ++i) {
150            String bucket = buckets[i];
151            if (bucket.length() == 0) {
152                System.out.println("Got empty bucket");
153                continue;
154            }
155            String[] kv = bucket.split(":");
156            if (kv.length != 2 || kv[0].length() == 0 || kv[1].length() == 0) {
157                System.out.println("Got bad bucket " + bucket);
158                continue;
159            }
160            long key = Long.valueOf(kv[0]);
161            long value = Long.valueOf(kv[1]);
162            if (key < 0 || value < 0) {
163                System.out.println("Got negative key or value " + bucket);
164                continue;
165            }
166            if (key < last_key) {
167                System.out.println("Got decreasing key " + bucket);
168                continue;
169            }
170            last_key = key;
171        }
172    }
173
174    private static void testRuntimeStat() throws Exception {
175        // Invoke at least one GC and wait for 20 seconds or so so we get at
176        // least one bucket in the histograms.
177        for (int i = 0; i < 20; ++i) {
178          Runtime.getRuntime().gc();
179          Thread.sleep(1000L);
180        }
181        String gc_count = VMDebug.getRuntimeStat("art.gc.gc-count");
182        String gc_time = VMDebug.getRuntimeStat("art.gc.gc-time");
183        String bytes_allocated = VMDebug.getRuntimeStat("art.gc.bytes-allocated");
184        String bytes_freed = VMDebug.getRuntimeStat("art.gc.bytes-freed");
185        String blocking_gc_count = VMDebug.getRuntimeStat("art.gc.blocking-gc-count");
186        String blocking_gc_time = VMDebug.getRuntimeStat("art.gc.blocking-gc-time");
187        String gc_count_rate_histogram = VMDebug.getRuntimeStat("art.gc.gc-count-rate-histogram");
188        String blocking_gc_count_rate_histogram =
189            VMDebug.getRuntimeStat("art.gc.blocking-gc-count-rate-histogram");
190        checkNumber(gc_count);
191        checkNumber(gc_time);
192        checkNumber(bytes_allocated);
193        checkNumber(bytes_freed);
194        checkNumber(blocking_gc_count);
195        checkNumber(blocking_gc_time);
196        checkHistogram(gc_count_rate_histogram);
197        checkHistogram(blocking_gc_count_rate_histogram);
198    }
199
200    private static void testRuntimeStats() throws Exception {
201        // Invoke at least one GC and wait for 20 seconds or so so we get at
202        // least one bucket in the histograms.
203        for (int i = 0; i < 20; ++i) {
204          Runtime.getRuntime().gc();
205          Thread.sleep(1000L);
206        }
207        Map<String, String> map = VMDebug.getRuntimeStats();
208        String gc_count = map.get("art.gc.gc-count");
209        String gc_time = map.get("art.gc.gc-time");
210        String bytes_allocated = map.get("art.gc.bytes-allocated");
211        String bytes_freed = map.get("art.gc.bytes-freed");
212        String blocking_gc_count = map.get("art.gc.blocking-gc-count");
213        String blocking_gc_time = map.get("art.gc.blocking-gc-time");
214        String gc_count_rate_histogram = map.get("art.gc.gc-count-rate-histogram");
215        String blocking_gc_count_rate_histogram =
216            map.get("art.gc.blocking-gc-count-rate-histogram");
217        checkNumber(gc_count);
218        checkNumber(gc_time);
219        checkNumber(bytes_allocated);
220        checkNumber(bytes_freed);
221        checkNumber(blocking_gc_count);
222        checkNumber(blocking_gc_time);
223        checkHistogram(gc_count_rate_histogram);
224        checkHistogram(blocking_gc_count_rate_histogram);
225    }
226
227    static class ClassA { }
228    static class ClassB { }
229    static class ClassC extends ClassA { }
230
231    private static void testCountInstances() throws Exception {
232        ArrayList<Object> l = new ArrayList<Object>();
233        l.add(new ClassA());
234        l.add(new ClassB());
235        l.add(new ClassA());
236        l.add(new ClassC());
237        Runtime.getRuntime().gc();
238        System.out.println("Instances of ClassA " +
239                VMDebug.countInstancesofClass(ClassA.class, false));
240        System.out.println("Instances of ClassB " +
241                VMDebug.countInstancesofClass(ClassB.class, false));
242        System.out.println("Instances of null " + VMDebug.countInstancesofClass(null, false));
243        System.out.println("Instances of ClassA assignable " +
244                VMDebug.countInstancesofClass(ClassA.class, true));
245        Class[] classes = new Class[]{ClassA.class, ClassB.class, null};
246        long[] counts = VMDebug.countInstancesofClasses(classes, false);
247        System.out.println("Array counts " + Arrays.toString(counts));
248        counts = VMDebug.countInstancesofClasses(classes, true);
249        System.out.println("Array counts assignable " + Arrays.toString(counts));
250    }
251
252    private static class VMDebug {
253        private static final Method startMethodTracingMethod;
254        private static final Method stopMethodTracingMethod;
255        private static final Method getMethodTracingModeMethod;
256        private static final Method getRuntimeStatMethod;
257        private static final Method getRuntimeStatsMethod;
258        private static final Method countInstancesOfClassMethod;
259        private static final Method countInstancesOfClassesMethod;
260        static {
261            try {
262                Class c = Class.forName("dalvik.system.VMDebug");
263                startMethodTracingMethod = c.getDeclaredMethod("startMethodTracing", String.class,
264                        Integer.TYPE, Integer.TYPE, Boolean.TYPE, Integer.TYPE);
265                stopMethodTracingMethod = c.getDeclaredMethod("stopMethodTracing");
266                getMethodTracingModeMethod = c.getDeclaredMethod("getMethodTracingMode");
267                getRuntimeStatMethod = c.getDeclaredMethod("getRuntimeStat", String.class);
268                getRuntimeStatsMethod = c.getDeclaredMethod("getRuntimeStats");
269                countInstancesOfClassMethod = c.getDeclaredMethod("countInstancesOfClass",
270                        Class.class, Boolean.TYPE);
271                countInstancesOfClassesMethod = c.getDeclaredMethod("countInstancesOfClasses",
272                        Class[].class, Boolean.TYPE);
273            } catch (Exception e) {
274                throw new RuntimeException(e);
275            }
276        }
277
278        public static void startMethodTracing(String filename, int bufferSize, int flags,
279                boolean samplingEnabled, int intervalUs) throws Exception {
280            startMethodTracingMethod.invoke(null, filename, bufferSize, flags, samplingEnabled,
281                    intervalUs);
282        }
283        public static void stopMethodTracing() throws Exception {
284            stopMethodTracingMethod.invoke(null);
285        }
286        public static int getMethodTracingMode() throws Exception {
287            return (int) getMethodTracingModeMethod.invoke(null);
288        }
289        public static String getRuntimeStat(String statName) throws Exception {
290            return (String) getRuntimeStatMethod.invoke(null, statName);
291        }
292        public static Map<String, String> getRuntimeStats() throws Exception {
293            return (Map<String, String>) getRuntimeStatsMethod.invoke(null);
294        }
295        public static long countInstancesofClass(Class c, boolean assignable) throws Exception {
296            return (long) countInstancesOfClassMethod.invoke(null, new Object[]{c, assignable});
297        }
298        public static long[] countInstancesofClasses(Class[] classes, boolean assignable)
299                throws Exception {
300            return (long[]) countInstancesOfClassesMethod.invoke(
301                    null, new Object[]{classes, assignable});
302        }
303    }
304}
305