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 
17 import java.lang.reflect.InvocationHandler;
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Proxy;
20 import java.nio.ByteBuffer;
21 import java.util.ArrayList;
22 
23 public class Main {
main(String[] args)24     public static void main(String[] args) throws Exception {
25         String name = System.getProperty("java.vm.name");
26         if (!"Dalvik".equals(name)) {
27             System.out.println("This test is not supported on " + name);
28             return;
29         }
30         testRecentAllocationTracking();
31     }
32 
33     private static ArrayList<Object> staticHolder = new ArrayList<>(100000);
34 
testRecentAllocationTracking()35     private static void testRecentAllocationTracking() throws Exception {
36         System.out.println("Confirm empty");
37         Allocations empty = new Allocations(DdmVmInternal.getRecentAllocations());
38         System.out.println("empty=" + empty);
39 
40         System.out.println("Confirm enable");
41         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
42         DdmVmInternal.enableRecentAllocations(true);
43         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
44 
45         System.out.println("Capture some allocations (note just this causes allocations)");
46         Allocations before = new Allocations(DdmVmInternal.getRecentAllocations());
47         System.out.println("before > 0=" + (before.numberOfEntries > 0));
48 
49         System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248");
50         final int overflowAllocations = 64 * 1024;  // Won't fit in unsigned 16-bit value.
51         for (int i = 0; i < overflowAllocations; i++) {
52             allocate(i, 0);
53         }
54         Allocations after = new Allocations(DdmVmInternal.getRecentAllocations());
55         System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations));
56         System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries));
57         System.out.println("after.numberOfEntries=" + after.numberOfEntries);
58 
59         staticHolder.clear();  // Free the allocated objects.
60 
61         System.out.println("Disable and confirm back to empty");
62         DdmVmInternal.enableRecentAllocations(false);
63         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
64         Allocations reset = new Allocations(DdmVmInternal.getRecentAllocations());
65         System.out.println("reset=" + reset);
66 
67         System.out.println("Confirm we can disable twice in a row");
68         DdmVmInternal.enableRecentAllocations(false);
69         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
70         DdmVmInternal.enableRecentAllocations(false);
71         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
72 
73         System.out.println("Confirm we can reenable twice in a row without losing allocations");
74         DdmVmInternal.enableRecentAllocations(true);
75         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
76         for (int i = 0; i < 16 * 1024; i++) {
77             staticHolder.add(new String("fnord"));
78         }
79         Allocations first = new Allocations(DdmVmInternal.getRecentAllocations());
80         DdmVmInternal.enableRecentAllocations(true);
81         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
82         Allocations second = new Allocations(DdmVmInternal.getRecentAllocations());
83         System.out.println("second > first =" + (second.numberOfEntries > first.numberOfEntries));
84 
85         System.out.println("Goodbye");
86         DdmVmInternal.enableRecentAllocations(false);
87         Allocations goodbye = new Allocations(DdmVmInternal.getRecentAllocations());
88         System.out.println("goodbye=" + goodbye);
89     }
90 
91     // Allocate a simple object. Use depth for a reasonably deep stack.
92     private static final int ALLOCATE1_DEPTH = 50;
93 
createProxy()94     private static Object createProxy() {
95         try {
96             InvocationHandler handler = new InvocationHandler() {
97                 public Object invoke(Object proxy, Method method, Object[] args) {
98                     // Don't expect to be invoked.
99                     return null;
100                 }
101             };
102             return Proxy.newProxyInstance(Main.class.getClassLoader(),
103                     new Class[] { Runnable.class }, handler);
104         } catch (Exception e) {
105             // We don't really expect exceptions here.
106             throw new RuntimeException(e);
107         }
108     }
109 
allocate(int i, int depth)110     private static void allocate(int i, int depth) {
111         if (depth >= ALLOCATE1_DEPTH) {
112             // Mix proxies, int arrays and Objects to test the different descriptor paths.
113             switch (i) {
114                 case 0:
115                     staticHolder.add(createProxy());
116                     break;
117 
118                 case 1:
119                     staticHolder.add(new int[0]);
120                     break;
121 
122                 case 2:
123                     staticHolder.add(new Object[0]);
124                     break;
125 
126                 default:
127                     staticHolder.add(new Object());
128                     break;
129             }
130         } else {
131             allocate(i, depth + 1);
132         }
133     }
134 
135     private static class Allocations {
136         final int messageHeaderLen;
137         final int entryHeaderLen;
138         final int stackFrameLen;
139         final int numberOfEntries;
140         final int offsetToStringTableFromStartOfMessage;
141         final int numberOfClassNameStrings;
142         final int numberOfMethodNameStrings;
143         final int numberOfSourceFileNameStrings;
144 
Allocations(byte[] allocations)145         Allocations(byte[] allocations) {
146             ByteBuffer b = ByteBuffer.wrap(allocations);
147             messageHeaderLen = b.get() & 0xff;
148             if (messageHeaderLen != 15) {
149                 throw new IllegalArgumentException("Unexpected messageHeaderLen " + messageHeaderLen);
150             }
151             entryHeaderLen = b.get() & 0xff;
152             if (entryHeaderLen != 9) {
153                 throw new IllegalArgumentException("Unexpected entryHeaderLen " + entryHeaderLen);
154             }
155             stackFrameLen = b.get() & 0xff;
156             if (stackFrameLen != 8) {
157                 throw new IllegalArgumentException("Unexpected messageHeaderLen " + stackFrameLen);
158             }
159             numberOfEntries = b.getShort() & 0xffff;
160             offsetToStringTableFromStartOfMessage = b.getInt();
161             numberOfClassNameStrings = b.getShort() & 0xffff;
162             numberOfMethodNameStrings = b.getShort() & 0xffff;
163             numberOfSourceFileNameStrings = b.getShort() & 0xffff;
164         }
165 
toString()166         public String toString() {
167             return ("Allocations[message header len: " + messageHeaderLen +
168                     " entry header len: " + entryHeaderLen +
169                     " stack frame len: " + stackFrameLen +
170                     " number of entries: " + numberOfEntries +
171                     " offset to string table from start of message: " + offsetToStringTableFromStartOfMessage +
172                     " number of class name strings: " + numberOfClassNameStrings +
173                     " number of method name strings: " + numberOfMethodNameStrings +
174                     " number of source file name strings: " + numberOfSourceFileNameStrings +
175                     "]");
176         }
177     }
178 
179     private static class DdmVmInternal {
180         private static final Method enableRecentAllocationsMethod;
181         private static final Method getRecentAllocationStatusMethod;
182         private static final Method getRecentAllocationsMethod;
183         static {
184             try {
185                 Class<?> c = Class.forName("org.apache.harmony.dalvik.ddmc.DdmVmInternal");
186                 enableRecentAllocationsMethod = c.getDeclaredMethod("enableRecentAllocations",
187                                                                     Boolean.TYPE);
188                 getRecentAllocationStatusMethod = c.getDeclaredMethod("getRecentAllocationStatus");
189                 getRecentAllocationsMethod = c.getDeclaredMethod("getRecentAllocations");
190             } catch (Exception e) {
191                 throw new RuntimeException(e);
192             }
193         }
194 
enableRecentAllocations(boolean enable)195         public static void enableRecentAllocations(boolean enable) throws Exception {
196             enableRecentAllocationsMethod.invoke(null, enable);
197         }
getRecentAllocationStatus()198         public static boolean getRecentAllocationStatus() throws Exception {
199             return (boolean) getRecentAllocationStatusMethod.invoke(null);
200         }
getRecentAllocations()201         public static byte[] getRecentAllocations() throws Exception {
202             return (byte[]) getRecentAllocationsMethod.invoke(null);
203         }
204     }
205 }
206