/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws Exception {
String name = System.getProperty("java.vm.name");
if (!"Dalvik".equals(name)) {
System.out.println("This test is not supported on " + name);
return;
}
testRecentAllocationTracking();
}
private static ArrayList<Object> staticHolder = new ArrayList<>(100000);
private static void testRecentAllocationTracking() throws Exception {
System.out.println("Confirm empty");
Allocations empty = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("empty=" + empty);
System.out.println("Confirm enable");
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
DdmVmInternal.enableRecentAllocations(true);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
System.out.println("Capture some allocations (note just this causes allocations)");
Allocations before = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("before > 0=" + (before.numberOfEntries > 0));
System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248");
final int overflowAllocations = 64 * 1024; // Won't fit in unsigned 16-bit value.
for (int i = 0; i < overflowAllocations; i++) {
allocate(i, 0);
}
Allocations after = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations));
System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries));
System.out.println("after.numberOfEntries=" + after.numberOfEntries);
staticHolder.clear(); // Free the allocated objects.
System.out.println("Disable and confirm back to empty");
DdmVmInternal.enableRecentAllocations(false);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
Allocations reset = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("reset=" + reset);
System.out.println("Confirm we can disable twice in a row");
DdmVmInternal.enableRecentAllocations(false);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
DdmVmInternal.enableRecentAllocations(false);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
System.out.println("Confirm we can reenable twice in a row without losing allocations");
DdmVmInternal.enableRecentAllocations(true);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
for (int i = 0; i < 16 * 1024; i++) {
staticHolder.add(new String("fnord"));
}
Allocations first = new Allocations(DdmVmInternal.getRecentAllocations());
DdmVmInternal.enableRecentAllocations(true);
System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
Allocations second = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("second > first =" + (second.numberOfEntries > first.numberOfEntries));
System.out.println("Goodbye");
DdmVmInternal.enableRecentAllocations(false);
Allocations goodbye = new Allocations(DdmVmInternal.getRecentAllocations());
System.out.println("goodbye=" + goodbye);
}
// Allocate a simple object. Use depth for a reasonably deep stack.
private static final int ALLOCATE1_DEPTH = 50;
private static Object createProxy() {
try {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
// Don't expect to be invoked.
return null;
}
};
return Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[] { Runnable.class }, handler);
} catch (Exception e) {
// We don't really expect exceptions here.
throw new RuntimeException(e);
}
}
private static void allocate(int i, int depth) {
if (depth >= ALLOCATE1_DEPTH) {
// Mix proxies, int arrays and Objects to test the different descriptor paths.
switch (i) {
case 0:
staticHolder.add(createProxy());
break;
case 1:
staticHolder.add(new int[0]);
break;
case 2:
staticHolder.add(new Object[0]);
break;
default:
staticHolder.add(new Object());
break;
}
} else {
allocate(i, depth + 1);
}
}
private static class Allocations {
final int messageHeaderLen;
final int entryHeaderLen;
final int stackFrameLen;
final int numberOfEntries;
final int offsetToStringTableFromStartOfMessage;
final int numberOfClassNameStrings;
final int numberOfMethodNameStrings;
final int numberOfSourceFileNameStrings;
Allocations(byte[] allocations) {
ByteBuffer b = ByteBuffer.wrap(allocations);
messageHeaderLen = b.get() & 0xff;
if (messageHeaderLen != 15) {
throw new IllegalArgumentException("Unexpected messageHeaderLen " + messageHeaderLen);
}
entryHeaderLen = b.get() & 0xff;
if (entryHeaderLen != 9) {
throw new IllegalArgumentException("Unexpected entryHeaderLen " + entryHeaderLen);
}
stackFrameLen = b.get() & 0xff;
if (stackFrameLen != 8) {
throw new IllegalArgumentException("Unexpected messageHeaderLen " + stackFrameLen);
}
numberOfEntries = b.getShort() & 0xffff;
offsetToStringTableFromStartOfMessage = b.getInt();
numberOfClassNameStrings = b.getShort() & 0xffff;
numberOfMethodNameStrings = b.getShort() & 0xffff;
numberOfSourceFileNameStrings = b.getShort() & 0xffff;
}
public String toString() {
return ("Allocations[message header len: " + messageHeaderLen +
" entry header len: " + entryHeaderLen +
" stack frame len: " + stackFrameLen +
" number of entries: " + numberOfEntries +
" offset to string table from start of message: " + offsetToStringTableFromStartOfMessage +
" number of class name strings: " + numberOfClassNameStrings +
" number of method name strings: " + numberOfMethodNameStrings +
" number of source file name strings: " + numberOfSourceFileNameStrings +
"]");
}
}
private static class DdmVmInternal {
private static final Method enableRecentAllocationsMethod;
private static final Method getRecentAllocationStatusMethod;
private static final Method getRecentAllocationsMethod;
static {
try {
Class<?> c = Class.forName("org.apache.harmony.dalvik.ddmc.DdmVmInternal");
enableRecentAllocationsMethod = c.getDeclaredMethod("enableRecentAllocations",
Boolean.TYPE);
getRecentAllocationStatusMethod = c.getDeclaredMethod("getRecentAllocationStatus");
getRecentAllocationsMethod = c.getDeclaredMethod("getRecentAllocations");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void enableRecentAllocations(boolean enable) throws Exception {
enableRecentAllocationsMethod.invoke(null, enable);
}
public static boolean getRecentAllocationStatus() throws Exception {
return (boolean) getRecentAllocationStatusMethod.invoke(null);
}
public static byte[] getRecentAllocations() throws Exception {
return (byte[]) getRecentAllocationsMethod.invoke(null);
}
}
}