/*
* Copyright (C) 2008 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.io.Serializable;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Memory usage information.
*/
class MemoryUsage implements Serializable {
private static final long serialVersionUID = 0;
static final MemoryUsage NOT_AVAILABLE = new MemoryUsage();
static int errorCount = 0;
// These values are in 1kB increments (not 4kB like you'd expect).
final int nativeSharedPages;
final int javaSharedPages;
final int otherSharedPages;
final int nativePrivatePages;
final int javaPrivatePages;
final int otherPrivatePages;
final int allocCount;
final int allocSize;
final int freedCount;
final int freedSize;
final long nativeHeapSize;
public MemoryUsage(String line) {
String[] parsed = line.split(",");
nativeSharedPages = Integer.parseInt(parsed[1]);
javaSharedPages = Integer.parseInt(parsed[2]);
otherSharedPages = Integer.parseInt(parsed[3]);
nativePrivatePages = Integer.parseInt(parsed[4]);
javaPrivatePages = Integer.parseInt(parsed[5]);
otherPrivatePages = Integer.parseInt(parsed[6]);
allocCount = Integer.parseInt(parsed[7]);
allocSize = Integer.parseInt(parsed[8]);
freedCount = Integer.parseInt(parsed[9]);
freedSize = Integer.parseInt(parsed[10]);
nativeHeapSize = Long.parseLong(parsed[11]);
}
MemoryUsage() {
nativeSharedPages = -1;
javaSharedPages = -1;
otherSharedPages = -1;
nativePrivatePages = -1;
javaPrivatePages = -1;
otherPrivatePages = -1;
allocCount = -1;
allocSize = -1;
freedCount = -1;
freedSize = -1;
nativeHeapSize = -1;
}
MemoryUsage(int nativeSharedPages,
int javaSharedPages,
int otherSharedPages,
int nativePrivatePages,
int javaPrivatePages,
int otherPrivatePages,
int allocCount,
int allocSize,
int freedCount,
int freedSize,
long nativeHeapSize) {
this.nativeSharedPages = nativeSharedPages;
this.javaSharedPages = javaSharedPages;
this.otherSharedPages = otherSharedPages;
this.nativePrivatePages = nativePrivatePages;
this.javaPrivatePages = javaPrivatePages;
this.otherPrivatePages = otherPrivatePages;
this.allocCount = allocCount;
this.allocSize = allocSize;
this.freedCount = freedCount;
this.freedSize = freedSize;
this.nativeHeapSize = nativeHeapSize;
}
MemoryUsage subtract(MemoryUsage baseline) {
return new MemoryUsage(
nativeSharedPages - baseline.nativeSharedPages,
javaSharedPages - baseline.javaSharedPages,
otherSharedPages - baseline.otherSharedPages,
nativePrivatePages - baseline.nativePrivatePages,
javaPrivatePages - baseline.javaPrivatePages,
otherPrivatePages - baseline.otherPrivatePages,
allocCount - baseline.allocCount,
allocSize - baseline.allocSize,
freedCount - baseline.freedCount,
freedSize - baseline.freedSize,
nativeHeapSize - baseline.nativeHeapSize);
}
int javaHeapSize() {
return allocSize - freedSize;
}
int totalHeap() {
return javaHeapSize() + (int) nativeHeapSize;
}
int javaPagesInK() {
return javaSharedPages + javaPrivatePages;
}
int nativePagesInK() {
return nativeSharedPages + nativePrivatePages;
}
int otherPagesInK() {
return otherSharedPages + otherPrivatePages;
}
int totalPages() {
return javaSharedPages + javaPrivatePages + nativeSharedPages +
nativePrivatePages + otherSharedPages + otherPrivatePages;
}
/**
* Was this information available?
*/
boolean isAvailable() {
return nativeSharedPages != -1;
}
/**
* Measures baseline memory usage.
*/
static MemoryUsage baseline() {
return forClass(null);
}
private static final String CLASS_PATH = "-Xbootclasspath"
+ ":/system/framework/core.jar"
+ ":/system/framework/ext.jar"
+ ":/system/framework/framework.jar"
+ ":/system/framework/framework-tests.jar"
+ ":/system/framework/services.jar"
+ ":/system/framework/loadclass.jar";
private static final String[] GET_DIRTY_PAGES = {
"adb", "-e", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
/**
* Measures memory usage for the given class.
*/
static MemoryUsage forClass(String className) {
MeasureWithTimeout measurer = new MeasureWithTimeout(className);
new Thread(measurer).start();
synchronized (measurer) {
if (measurer.memoryUsage == null) {
// Wait up to 10s.
try {
measurer.wait(30000);
} catch (InterruptedException e) {
System.err.println("Interrupted waiting for measurement.");
e.printStackTrace();
return NOT_AVAILABLE;
}
// If it's still null.
if (measurer.memoryUsage == null) {
System.err.println("Timed out while measuring "
+ className + ".");
return NOT_AVAILABLE;
}
}
System.err.println("Got memory usage for " + className + ".");
return measurer.memoryUsage;
}
}
static class MeasureWithTimeout implements Runnable {
final String className;
MemoryUsage memoryUsage = null;
MeasureWithTimeout(String className) {
this.className = className;
}
public void run() {
MemoryUsage measured = measure();
synchronized (this) {
memoryUsage = measured;
notifyAll();
}
}
private MemoryUsage measure() {
String[] commands = GET_DIRTY_PAGES;
if (className != null) {
List<String> commandList = new ArrayList<String>(
GET_DIRTY_PAGES.length + 1);
commandList.addAll(Arrays.asList(commands));
commandList.add(className);
commands = commandList.toArray(new String[commandList.size()]);
}
try {
final Process process = Runtime.getRuntime().exec(commands);
final InputStream err = process.getErrorStream();
// Send error output to stderr.
Thread errThread = new Thread() {
@Override
public void run() {
copy(err, System.err);
}
};
errThread.setDaemon(true);
errThread.start();
BufferedReader in = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line = in.readLine();
if (line == null || !line.startsWith("DECAFBAD,")) {
System.err.println("Got bad response for " + className
+ ": " + line);
errorCount += 1;
return NOT_AVAILABLE;
}
in.close();
err.close();
process.destroy();
return new MemoryUsage(line);
} catch (IOException e) {
System.err.println("Error getting stats for "
+ className + ".");
e.printStackTrace();
return NOT_AVAILABLE;
}
}
}
/**
* Copies from one stream to another.
*/
private static void copy(InputStream in, OutputStream out) {
byte[] buffer = new byte[1024];
int read;
try {
while ((read = in.read(buffer)) > -1) {
out.write(buffer, 0, read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/** Measures memory usage information and stores it in the model. */
public static void main(String[] args) throws IOException,
ClassNotFoundException {
Root root = Root.fromFile(args[0]);
root.baseline = baseline();
for (LoadedClass loadedClass : root.loadedClasses.values()) {
if (loadedClass.systemClass) {
loadedClass.measureMemoryUsage();
}
}
root.toFile(args[0]);
}
}