• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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