1 /* 2 * Copyright (C) 2011 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 package com.android.ddmuilib.heap; 18 19 import com.android.ddmlib.NativeAllocationInfo; 20 import com.android.ddmlib.NativeStackCallInfo; 21 22 import org.eclipse.core.runtime.IProgressMonitor; 23 import org.eclipse.jface.operation.IRunnableWithProgress; 24 25 import java.io.IOException; 26 import java.io.LineNumberReader; 27 import java.io.Reader; 28 import java.lang.reflect.InvocationTargetException; 29 import java.util.ArrayList; 30 import java.util.InputMismatchException; 31 import java.util.List; 32 import java.util.Scanner; 33 import java.util.regex.Pattern; 34 35 public class NativeHeapDataImporter implements IRunnableWithProgress { 36 private LineNumberReader mReader; 37 private int mStartLineNumber; 38 private int mEndLineNumber; 39 40 private NativeHeapSnapshot mSnapshot; 41 NativeHeapDataImporter(Reader stream)42 public NativeHeapDataImporter(Reader stream) { 43 mReader = new LineNumberReader(stream); 44 mReader.setLineNumber(1); // start numbering at 1 45 } 46 47 @Override run(IProgressMonitor monitor)48 public void run(IProgressMonitor monitor) 49 throws InvocationTargetException, InterruptedException { 50 monitor.beginTask("Importing Heap Data", IProgressMonitor.UNKNOWN); 51 52 List<NativeAllocationInfo> allocations = new ArrayList<NativeAllocationInfo>(); 53 try { 54 while (true) { 55 String line; 56 StringBuilder sb = new StringBuilder(); 57 58 // read in a sequence of lines corresponding to a single NativeAllocationInfo 59 mStartLineNumber = mReader.getLineNumber(); 60 while ((line = mReader.readLine()) != null) { 61 if (line.trim().length() == 0) { 62 // each block of allocations end with an empty line 63 break; 64 } 65 66 sb.append(line); 67 sb.append('\n'); 68 } 69 mEndLineNumber = mReader.getLineNumber(); 70 71 // parse those lines into a NativeAllocationInfo object 72 String allocationBlock = sb.toString(); 73 if (allocationBlock.trim().length() > 0) { 74 allocations.add(getNativeAllocation(allocationBlock)); 75 } 76 77 if (line == null) { // EOF 78 break; 79 } 80 } 81 } catch (Exception e) { 82 if (e.getMessage() == null) { 83 e = new RuntimeException(genericErrorMessage("Unexpected Parse error")); 84 } 85 throw new InvocationTargetException(e); 86 } finally { 87 try { 88 mReader.close(); 89 } catch (IOException e) { 90 // we can ignore this exception 91 } 92 monitor.done(); 93 } 94 95 mSnapshot = new NativeHeapSnapshot(allocations); 96 } 97 98 /** Parse a single native allocation dump. This is the complement of 99 * {@link NativeAllocationInfo#toString()}. 100 * 101 * An allocation is of the following form: 102 * Allocations: 1 103 * Size: 344748 104 * Total Size: 344748 105 * BeginStackTrace: 106 * 40069bd8 /lib/libc_malloc_leak.so --- get_backtrace --- /libc/bionic/malloc_leak.c:258 107 * 40069dd8 /lib/libc_malloc_leak.so --- leak_calloc --- /libc/bionic/malloc_leak.c:576 108 * 40069bd8 /lib/libc_malloc_leak.so --- 40069bd8 --- 109 * 40069dd8 /lib/libc_malloc_leak.so --- 40069dd8 --- 110 * EndStackTrace 111 * Note that in the above stack trace, the last two lines are examples where the address 112 * was not resolved. 113 * 114 * @param block a string of lines corresponding to a single {@code NativeAllocationInfo} 115 * @return parse the input and return the corresponding {@link NativeAllocationInfo} 116 * @throws InputMismatchException if there are any parse errors 117 */ getNativeAllocation(String block)118 private NativeAllocationInfo getNativeAllocation(String block) { 119 Scanner sc = new Scanner(block); 120 121 String kw = sc.next(); 122 if (!NativeAllocationInfo.ALLOCATIONS_KW.equals(kw)) { 123 throw new InputMismatchException( 124 expectedKeywordErrorMessage(NativeAllocationInfo.ALLOCATIONS_KW, kw)); 125 } 126 127 int allocations = sc.nextInt(); 128 129 kw = sc.next(); 130 if (!NativeAllocationInfo.SIZE_KW.equals(kw)) { 131 throw new InputMismatchException( 132 expectedKeywordErrorMessage(NativeAllocationInfo.SIZE_KW, kw)); 133 } 134 135 int size = sc.nextInt(); 136 137 kw = sc.next(); 138 if (!NativeAllocationInfo.TOTAL_SIZE_KW.equals(kw)) { 139 throw new InputMismatchException( 140 expectedKeywordErrorMessage(NativeAllocationInfo.TOTAL_SIZE_KW, kw)); 141 } 142 143 int totalSize = sc.nextInt(); 144 if (totalSize != size * allocations) { 145 throw new InputMismatchException( 146 genericErrorMessage("Total Size does not match size * # of allocations")); 147 } 148 149 NativeAllocationInfo info = new NativeAllocationInfo(size, allocations); 150 151 kw = sc.next(); 152 if (!NativeAllocationInfo.BEGIN_STACKTRACE_KW.equals(kw)) { 153 throw new InputMismatchException( 154 expectedKeywordErrorMessage(NativeAllocationInfo.BEGIN_STACKTRACE_KW, kw)); 155 } 156 157 List<NativeStackCallInfo> stackInfo = new ArrayList<NativeStackCallInfo>(); 158 Pattern endTracePattern = Pattern.compile(NativeAllocationInfo.END_STACKTRACE_KW); 159 160 while (true) { 161 long address = sc.nextLong(16); 162 info.addStackCallAddress(address); 163 164 String library = sc.next(); 165 sc.next(); // ignore "---" 166 String method = scanTillSeparator(sc, "---"); 167 168 String filename = ""; 169 if (!isUnresolved(method, address)) { 170 filename = sc.next(); 171 } 172 173 stackInfo.add(new NativeStackCallInfo(address, library, method, filename)); 174 175 if (sc.hasNext(endTracePattern)) { 176 break; 177 } 178 } 179 180 info.setResolvedStackCall(stackInfo); 181 return info; 182 } 183 scanTillSeparator(Scanner sc, String separator)184 private String scanTillSeparator(Scanner sc, String separator) { 185 StringBuilder sb = new StringBuilder(); 186 187 while (true) { 188 String token = sc.next(); 189 if (token.equals(separator)) { 190 break; 191 } 192 193 sb.append(token); 194 195 // We do not know the exact delimiter that was skipped over, but we know 196 // that there was atleast 1 whitespace. Add a single whitespace character 197 // to account for this. 198 sb.append(' '); 199 } 200 201 return sb.toString().trim(); 202 } 203 isUnresolved(String method, long address)204 private boolean isUnresolved(String method, long address) { 205 // a method is unresolved if it is just the hex representation of the address 206 return Long.toString(address, 16).equals(method); 207 } 208 genericErrorMessage(String message)209 private String genericErrorMessage(String message) { 210 return String.format("%1$s between lines %2$d and %3$d", 211 message, mStartLineNumber, mEndLineNumber); 212 } 213 expectedKeywordErrorMessage(String expected, String actual)214 private String expectedKeywordErrorMessage(String expected, String actual) { 215 return String.format("Expected keyword '%1$s', saw '%2$s' between lines %3$d to %4$d.", 216 expected, actual, mStartLineNumber, mEndLineNumber); 217 } 218 getImportedSnapshot()219 public NativeHeapSnapshot getImportedSnapshot() { 220 return mSnapshot; 221 } 222 } 223