/* * Copyright (C) 2011 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. */ package com.android.ddmuilib.heap; import com.android.ddmlib.NativeAllocationInfo; import com.android.ddmlib.NativeStackCallInfo; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.operation.IRunnableWithProgress; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.InputMismatchException; import java.util.List; import java.util.Scanner; import java.util.regex.Pattern; public class NativeHeapDataImporter implements IRunnableWithProgress { private LineNumberReader mReader; private int mStartLineNumber; private int mEndLineNumber; private NativeHeapSnapshot mSnapshot; public NativeHeapDataImporter(Reader stream) { mReader = new LineNumberReader(stream); mReader.setLineNumber(1); // start numbering at 1 } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask("Importing Heap Data", IProgressMonitor.UNKNOWN); List<NativeAllocationInfo> allocations = new ArrayList<NativeAllocationInfo>(); try { while (true) { String line; StringBuilder sb = new StringBuilder(); // read in a sequence of lines corresponding to a single NativeAllocationInfo mStartLineNumber = mReader.getLineNumber(); while ((line = mReader.readLine()) != null) { if (line.trim().length() == 0) { // each block of allocations end with an empty line break; } sb.append(line); sb.append('\n'); } mEndLineNumber = mReader.getLineNumber(); // parse those lines into a NativeAllocationInfo object String allocationBlock = sb.toString(); if (allocationBlock.trim().length() > 0) { allocations.add(getNativeAllocation(allocationBlock)); } if (line == null) { // EOF break; } } } catch (Exception e) { if (e.getMessage() == null) { e = new RuntimeException(genericErrorMessage("Unexpected Parse error")); } throw new InvocationTargetException(e); } finally { try { mReader.close(); } catch (IOException e) { // we can ignore this exception } monitor.done(); } mSnapshot = new NativeHeapSnapshot(allocations); } /** Parse a single native allocation dump. This is the complement of * {@link NativeAllocationInfo#toString()}. * * An allocation is of the following form: * Allocations: 1 * Size: 344748 * Total Size: 344748 * BeginStackTrace: * 40069bd8 /lib/libc_malloc_leak.so --- get_backtrace --- /libc/bionic/malloc_leak.c:258 * 40069dd8 /lib/libc_malloc_leak.so --- leak_calloc --- /libc/bionic/malloc_leak.c:576 * 40069bd8 /lib/libc_malloc_leak.so --- 40069bd8 --- * 40069dd8 /lib/libc_malloc_leak.so --- 40069dd8 --- * EndStackTrace * Note that in the above stack trace, the last two lines are examples where the address * was not resolved. * * @param block a string of lines corresponding to a single {@code NativeAllocationInfo} * @return parse the input and return the corresponding {@link NativeAllocationInfo} * @throws InputMismatchException if there are any parse errors */ private NativeAllocationInfo getNativeAllocation(String block) { Scanner sc = new Scanner(block); String kw = sc.next(); if (!NativeAllocationInfo.ALLOCATIONS_KW.equals(kw)) { throw new InputMismatchException( expectedKeywordErrorMessage(NativeAllocationInfo.ALLOCATIONS_KW, kw)); } int allocations = sc.nextInt(); kw = sc.next(); if (!NativeAllocationInfo.SIZE_KW.equals(kw)) { throw new InputMismatchException( expectedKeywordErrorMessage(NativeAllocationInfo.SIZE_KW, kw)); } int size = sc.nextInt(); kw = sc.next(); if (!NativeAllocationInfo.TOTAL_SIZE_KW.equals(kw)) { throw new InputMismatchException( expectedKeywordErrorMessage(NativeAllocationInfo.TOTAL_SIZE_KW, kw)); } int totalSize = sc.nextInt(); if (totalSize != size * allocations) { throw new InputMismatchException( genericErrorMessage("Total Size does not match size * # of allocations")); } NativeAllocationInfo info = new NativeAllocationInfo(size, allocations); kw = sc.next(); if (!NativeAllocationInfo.BEGIN_STACKTRACE_KW.equals(kw)) { throw new InputMismatchException( expectedKeywordErrorMessage(NativeAllocationInfo.BEGIN_STACKTRACE_KW, kw)); } List<NativeStackCallInfo> stackInfo = new ArrayList<NativeStackCallInfo>(); Pattern endTracePattern = Pattern.compile(NativeAllocationInfo.END_STACKTRACE_KW); while (true) { long address = sc.nextLong(16); info.addStackCallAddress(address); String library = sc.next(); sc.next(); // ignore "---" String method = scanTillSeparator(sc, "---"); String filename = ""; if (!isUnresolved(method, address)) { filename = sc.next(); } stackInfo.add(new NativeStackCallInfo(address, library, method, filename)); if (sc.hasNext(endTracePattern)) { break; } } info.setResolvedStackCall(stackInfo); return info; } private String scanTillSeparator(Scanner sc, String separator) { StringBuilder sb = new StringBuilder(); while (true) { String token = sc.next(); if (token.equals(separator)) { break; } sb.append(token); // We do not know the exact delimiter that was skipped over, but we know // that there was atleast 1 whitespace. Add a single whitespace character // to account for this. sb.append(' '); } return sb.toString().trim(); } private boolean isUnresolved(String method, long address) { // a method is unresolved if it is just the hex representation of the address return Long.toString(address, 16).equals(method); } private String genericErrorMessage(String message) { return String.format("%1$s between lines %2$d and %3$d", message, mStartLineNumber, mEndLineNumber); } private String expectedKeywordErrorMessage(String expected, String actual) { return String.format("Expected keyword '%1$s', saw '%2$s' between lines %3$d to %4$d.", expected, actual, mStartLineNumber, mEndLineNumber); } public NativeHeapSnapshot getImportedSnapshot() { return mSnapshot; } }