1/*
2 * Copyright (C) 2010 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
17package dalvik.system.profiler;
18
19import dalvik.system.profiler.AsciiHprofWriter;
20import dalvik.system.profiler.BinaryHprofReader;
21import dalvik.system.profiler.BinaryHprofWriter;
22import dalvik.system.profiler.HprofData.Sample;
23import dalvik.system.profiler.HprofData.StackTrace;
24import dalvik.system.profiler.HprofData.ThreadEvent;
25import dalvik.system.profiler.HprofData;
26import dalvik.system.profiler.SamplingProfiler.ThreadSet;
27import java.io.ByteArrayInputStream;
28import java.io.ByteArrayOutputStream;
29import java.io.File;
30import java.io.FileOutputStream;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.math.BigInteger;
34import java.security.KeyPairGenerator;
35import java.security.SecureRandom;
36import java.util.Arrays;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Map;
41import java.util.Set;
42import javax.crypto.spec.DHParameterSpec;
43import junit.framework.TestCase;
44
45public class SamplingProfilerTest extends TestCase {
46
47    /**
48     * Run the SamplingProfiler to gather some data on an actual
49     * computation, then assert that it looks correct with test_HprofData.
50     */
51    public void test_SamplingProfiler() throws Exception {
52        ThreadSet threadSet = SamplingProfiler.newArrayThreadSet(Thread.currentThread());
53        SamplingProfiler profiler = new SamplingProfiler(12, threadSet);
54        profiler.start(100);
55        toBeMeasured();
56        profiler.stop();
57        profiler.shutdown();
58        test_HprofData(profiler.getHprofData(), true);
59    }
60
61    private static final String P_STR =
62            "9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e3"
63            + "1db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b";
64    private static final String G_STR =
65            "98ab7c5c431479d8645e33aa09758e0907c78747798d0968576f9877421a9089"
66            + "756f7876e76590b76765645c987976d764dd4564698a87585e64554984bb4445"
67            + "76e5764786f875b4456c";
68
69    private static final byte[] P = new BigInteger(P_STR,16).toByteArray();
70    private static final byte[] G = new BigInteger(G_STR,16).toByteArray();
71
72    private static void toBeMeasured () throws Exception {
73        long start = System.currentTimeMillis();
74        for (int i = 0; i < 10000; i++) {
75            BigInteger p = new BigInteger(P);
76            BigInteger g = new BigInteger(G);
77            KeyPairGenerator gen = KeyPairGenerator.getInstance("DH");
78            gen.initialize(new DHParameterSpec(p, g), new SecureRandom());
79        }
80        long end = System.currentTimeMillis();
81    }
82
83    public void test_HprofData_null() throws Exception {
84        try {
85            new HprofData(null);
86            fail();
87        } catch (NullPointerException expected) {
88        }
89    }
90
91    public void test_HprofData_empty() throws Exception {
92        Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
93        HprofData hprofData = new HprofData(stackTraces);
94        test_HprofData(hprofData, true);
95    }
96
97    public void test_HprofData_timeMillis() throws Exception {
98        Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
99        HprofData hprofData = new HprofData(stackTraces);
100        long now = System.currentTimeMillis();
101        hprofData.setStartMillis(now);
102        assertEquals(now, hprofData.getStartMillis());
103        test_HprofData(hprofData, true);
104    }
105
106    public void test_HprofData_addThreadEvent_null() throws Exception {
107        Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
108        HprofData hprofData = new HprofData(stackTraces);
109        try {
110            hprofData.addThreadEvent(null);
111            fail();
112        } catch (NullPointerException expected) {
113        }
114        test_HprofData(hprofData, true);
115    }
116
117    public void test_HprofData_addThreadEvent() throws Exception {
118        Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
119        HprofData hprofData = new HprofData(stackTraces);
120
121        // should have nothing in the thread history to start
122        assertEquals(0, hprofData.getThreadHistory().size());
123
124        // add thread 1
125        final int threadId = 1;
126        final int objectId = 2;
127        ThreadEvent start1 = ThreadEvent.start(objectId, threadId,
128                                               "thread-name", "thread-group", "parent-group");
129        hprofData.addThreadEvent(start1);
130        assertEquals(Arrays.asList(start1), hprofData.getThreadHistory());
131        test_HprofData(hprofData, true);
132
133        // remove thread 2, which should not exist (but that's okay on the RI)
134        ThreadEvent end2 = ThreadEvent.end(threadId+1);
135        hprofData.addThreadEvent(end2);
136        assertEquals(Arrays.asList(start1, end2), hprofData.getThreadHistory());
137        test_HprofData(hprofData, false); // non-strict from here down because of this RI data
138
139        // remove thread 1, which should exist
140        ThreadEvent end1 = ThreadEvent.end(threadId);
141        hprofData.addThreadEvent(end1);
142        assertEquals(Arrays.asList(start1, end2, end1), hprofData.getThreadHistory());
143        test_HprofData(hprofData, false);
144
145        // remove thread 1 again, which should not exist (its not okay to have end followed by end)
146        try {
147            hprofData.addThreadEvent(ThreadEvent.end(threadId));
148            fail();
149        } catch (IllegalArgumentException expected) {
150        }
151        assertEquals(Arrays.asList(start1, end2, end1), hprofData.getThreadHistory());
152        test_HprofData(hprofData, false);
153    }
154
155    public void test_HprofData_addStackTrace() throws Exception {
156        Map<StackTrace, int[]> stackTraces = new HashMap<StackTrace, int[]>();
157        HprofData hprofData = new HprofData(stackTraces);
158
159        // should have no samples to start
160        assertEquals(0, hprofData.getSamples().size());
161
162        // attempt to add a stack for a non-existent thread, should fail
163        final int stackTraceId = 1;
164        final int threadId = 2;
165        final int objectId = 3;
166        final int sampleCount = 4;
167        StackTraceElement[] stackFrames = new Throwable().getStackTrace();
168        final int[] countCell = new int[] { 4 };
169        StackTrace stackTrace = new StackTrace(stackTraceId, threadId, stackFrames);
170        try {
171            hprofData.addStackTrace(stackTrace, countCell);
172            fail();
173        } catch (IllegalArgumentException expected) {
174        }
175
176        // add the thread and add the event
177        ThreadEvent start = ThreadEvent.start(objectId, threadId,
178                                              "thread-name", "thread-group", "parent-group");
179        hprofData.addThreadEvent(start);
180        hprofData.addStackTrace(stackTrace, countCell);
181        Set<Sample> samples = hprofData.getSamples();
182        assertNotNull(samples);
183        assertNotSame(samples, hprofData.getSamples());
184        assertEquals(1, samples.size());
185        Sample sample = samples.iterator().next();
186        assertNotNull(sample);
187        assertEquals(stackTrace, sample.stackTrace);
188        assertEquals(sampleCount, sample.count);
189        test_HprofData(hprofData, true);
190
191        // confirm we can mutate the sample count, but that its not
192        // visible in the current sample, but it will be visible in a
193        // new one.
194        countCell[0] += 42;
195        assertEquals(sampleCount, sample.count);
196        Sample sample2 = hprofData.getSamples().iterator().next();
197        assertEquals(sampleCount + 42, sample2.count);
198        test_HprofData(hprofData, true);
199
200        // try to reuse the stackTraceId, should fail
201        try {
202            hprofData.addStackTrace(stackTrace, countCell);
203            fail();
204        } catch (IllegalArgumentException expected) {
205        }
206        assertEquals(1, hprofData.getSamples().size());
207        test_HprofData(hprofData, true);
208
209    }
210
211    private void test_HprofData(HprofData hprofData, boolean strict) throws Exception {
212        assertHprofData(hprofData, strict);
213        test_HprofData_ascii(hprofData);
214        test_HprofData_binary(hprofData, strict);
215    }
216
217    /**
218     * Assert general properities of HprofData hold true.
219     */
220    private void assertHprofData(HprofData hprofData, boolean strict) throws Exception {
221        List<ThreadEvent> threadHistory = hprofData.getThreadHistory();
222        assertNotNull(threadHistory);
223        Set<Integer> threadsSeen = new HashSet<Integer>();
224        Set<Integer> threadsActive = new HashSet<Integer>();
225        for (ThreadEvent event : threadHistory) {
226            assertNotNull(event);
227            assertNotNull(event.type);
228            switch (event.type) {
229                case START:
230                    assertNotNull(event.threadName);
231                    assertTrue(threadsActive.add(event.threadId));
232                    assertTrue(threadsSeen.add(event.threadId));
233                    break;
234                case END:
235                    assertEquals(-1, event.objectId);
236                    assertNull(event.threadName);
237                    assertNull(event.groupName);
238                    assertNull(event.parentGroupName);
239                    if (strict) {
240                        assertTrue(threadsActive.remove(event.threadId));
241                    }
242                    break;
243            }
244        }
245
246        Set<Sample> samples = hprofData.getSamples();
247        assertNotNull(samples);
248        for (Sample sample : samples) {
249            assertNotNull(sample);
250            assertTrue(sample.count > 0);
251            assertNotNull(sample.stackTrace);
252            assertTrue(sample.stackTrace.stackTraceId != -1);
253            assertTrue(threadsSeen.contains(sample.stackTrace.getThreadId()));
254            assertNotNull(sample.stackTrace.getStackFrames());
255        }
256    }
257
258    /**
259     * Convert to HprofData to ASCII to see if it triggers any exceptions
260     */
261    private void test_HprofData_ascii(HprofData hprofData) throws Exception {
262        ByteArrayOutputStream out = new ByteArrayOutputStream();
263        AsciiHprofWriter.write(hprofData, out);
264        assertFalse(out.toByteArray().length == 0);
265    }
266
267    /**
268     * Convert to HprofData to binary and then reparse as to
269     * HprofData. Make sure the accessible data is equivalent.
270     */
271    private void test_HprofData_binary(HprofData hprofData, boolean strict) throws Exception {
272
273        ByteArrayOutputStream out = new ByteArrayOutputStream();
274        BinaryHprofWriter.write(hprofData, out);
275        out.close();
276
277        byte[] bytes = out.toByteArray();
278        assertFalse(bytes.length == 0);
279        if (false) {
280            File file = new File("/sdcard/java.hprof");
281            OutputStream debug = new FileOutputStream(file);
282            debug.write(bytes);
283            debug.close();
284            System.out.println("Wrote binary hprof data to " + file);
285        }
286
287        InputStream in = new ByteArrayInputStream(bytes);
288        BinaryHprofReader reader = new BinaryHprofReader(in);
289        assertTrue(reader.getStrict());
290        reader.read();
291        in.close();
292        assertEquals("JAVA PROFILE 1.0.2", reader.getVersion());
293        assertNotNull(reader.getHprofData());
294
295        HprofData parsed = reader.getHprofData();
296        assertHprofData(hprofData, strict);
297
298        assertEquals(Long.toHexString(hprofData.getStartMillis()),
299                     Long.toHexString(parsed.getStartMillis()));
300        assertEquals(Long.toHexString(hprofData.getFlags()),
301                     Long.toHexString(parsed.getFlags()));
302        assertEquals(Long.toHexString(hprofData.getDepth()),
303                     Long.toHexString(parsed.getDepth()));
304        assertEquals(hprofData.getThreadHistory(),
305                     parsed.getThreadHistory());
306        assertEquals(hprofData.getSamples(),
307                     parsed.getSamples());
308    }
309}
310