|
1 /* |
|
2 * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: Definitions for the class ParseAnalyzeData |
|
15 * |
|
16 */ |
|
17 package com.nokia.s60tools.analyzetool.engine; |
|
18 |
|
19 import java.util.ArrayList; |
|
20 import java.util.HashMap; |
|
21 import java.util.List; |
|
22 |
|
23 import com.nokia.s60tools.analyzetool.engine.statistic.AllocCallstack; |
|
24 import com.nokia.s60tools.analyzetool.engine.statistic.AllocInfo; |
|
25 import com.nokia.s60tools.analyzetool.engine.statistic.BaseInfo; |
|
26 import com.nokia.s60tools.analyzetool.engine.statistic.DllLoad; |
|
27 import com.nokia.s60tools.analyzetool.engine.statistic.FreeInfo; |
|
28 import com.nokia.s60tools.analyzetool.engine.statistic.ProcessInfo; |
|
29 import com.nokia.s60tools.analyzetool.global.Constants; |
|
30 import com.nokia.s60tools.analyzetool.global.Constants.Operation; |
|
31 |
|
32 /** |
|
33 * This class parses line of .dat file format and extracts |
|
34 * callstack data. For now this is a separate class but could |
|
35 * eventually be moved into ParseAnalyzeData after some refactoring. |
|
36 * |
|
37 */ |
|
38 public class CallstackDataParser { |
|
39 /** true if callstack reading for this BaseInfo is now complete*/ |
|
40 boolean complete = false; |
|
41 /** resulting callstack */ |
|
42 private List<AllocCallstack> callstack; |
|
43 /** BaseInfo for the current alloc or free */ |
|
44 private BaseInfo baseInfo; |
|
45 /** ProcessInfo for the current alloc or free */ |
|
46 private ProcessInfo process; |
|
47 /** number of remaining callstack elements expected to be processed with ALLOCF or FREEF */ |
|
48 private int remainingSize; |
|
49 |
|
50 /** Cache for callstack items. Used when allocation fragment is parsed in the wrong order. */ |
|
51 private HashMap<Integer, List<AllocCallstack>> callstackCache = null; |
|
52 |
|
53 /** |
|
54 * Constructor |
|
55 * @param baseInfo BaseInfo for the current alloc or free |
|
56 * @param p ProcessInfo for the current alloc or free |
|
57 */ |
|
58 public CallstackDataParser(BaseInfo baseInfo, ProcessInfo p) { |
|
59 if (p == null || baseInfo == null || p.getProcessID() != baseInfo.getProcessID()){ |
|
60 throw new IllegalArgumentException("BaseInfo and ProcessInfo are mandatory and must not be null; and the process id of both must match."); //$NON-NLS-1$ |
|
61 } |
|
62 this.baseInfo = baseInfo; |
|
63 this.process = p; |
|
64 callstack = new ArrayList<AllocCallstack>(); |
|
65 } |
|
66 |
|
67 /** |
|
68 * Parses one line of PCSS statement |
|
69 * @param aLine |
|
70 * @return true, if this callstack is now complete (all parts are available) |
|
71 */ |
|
72 public final boolean parseLine(final String aLine) { |
|
73 |
|
74 if (aLine.indexOf(Constants.PREFIX) == -1){ |
|
75 //not a PCSS statement |
|
76 return false; |
|
77 } |
|
78 |
|
79 String[] lineFragments = getLineFragments(aLine); |
|
80 if (lineFragments.length < 5){ |
|
81 //not a valid PCSS statment for callstack processing |
|
82 return false; |
|
83 } |
|
84 int processID = Integer.parseInt(lineFragments[1], 16); |
|
85 if (baseInfo.getProcessID() != processID || process.getProcessID() != processID){ |
|
86 //statement is not for current process id |
|
87 return false; |
|
88 } |
|
89 |
|
90 //the operation must match |
|
91 Constants.Operation op = Constants.Operation.toOperation(lineFragments[2]); |
|
92 if (!verifyOperation(op, baseInfo)){ |
|
93 return false; |
|
94 } |
|
95 |
|
96 //the memory address must match |
|
97 long memoryAddress = Long.parseLong(lineFragments[3],16); |
|
98 if (baseInfo.getMemoryAddress() != memoryAddress){ |
|
99 return false; |
|
100 } |
|
101 |
|
102 |
|
103 boolean ret = false; |
|
104 |
|
105 switch (op) { |
|
106 case ALLOC: |
|
107 ret = parseAlloc(lineFragments); |
|
108 break; |
|
109 case ALLOCH: |
|
110 ret = parseHeader(lineFragments, 6); |
|
111 break; |
|
112 case FREEH: |
|
113 int traceFileVersion = process.getTraceDataVersion(); |
|
114 ret = parseHeader(lineFragments, traceFileVersion > 1 ? 5 : 4); |
|
115 break; |
|
116 case ALLOCF: |
|
117 //fall through |
|
118 case FREEF: |
|
119 ret = parseFragment(lineFragments); |
|
120 break; |
|
121 default: |
|
122 // ignore this line |
|
123 break; |
|
124 } |
|
125 |
|
126 return ret; |
|
127 } |
|
128 |
|
129 private static boolean verifyOperation(Operation op, BaseInfo aBaseInfo) { |
|
130 return ((aBaseInfo instanceof AllocInfo && (op == Constants.Operation.ALLOC |
|
131 || op == Constants.Operation.ALLOCH || op == Constants.Operation.ALLOCF)) |
|
132 || (aBaseInfo instanceof FreeInfo && (op == Constants.Operation.FREEH |
|
133 || op == Constants.Operation.FREEF))); |
|
134 } |
|
135 |
|
136 /** |
|
137 * Returns the line fragments of the PCSS statement |
|
138 * @param aLine the PCSS statement |
|
139 * @return the line fragments separated by space |
|
140 */ |
|
141 private String[] getLineFragments(final String aLine){ |
|
142 int index = aLine.indexOf(Constants.PREFIX); // lines should be preceded |
|
143 |
|
144 if (index == -1){ |
|
145 return new String[0]; |
|
146 } |
|
147 |
|
148 String usedString = (index == 0) ? aLine : aLine.substring(index, aLine.length()); |
|
149 return usedString.split(" "); //$NON-NLS-1$ |
|
150 } |
|
151 |
|
152 /** |
|
153 * Parses an ALLOC statement. This statement contains a complete callstack. |
|
154 * @param fragments the line fragments of the statement to process |
|
155 * @return true if statement is complete (this method will always return true) |
|
156 */ |
|
157 private boolean parseAlloc(String[] fragments) { |
|
158 |
|
159 if (fragments.length > 5) { |
|
160 createCallstack(fragments, process, callstack, 6); |
|
161 } |
|
162 |
|
163 complete = true; |
|
164 return true; //we are done; there are no fragments for this alloc |
|
165 } |
|
166 |
|
167 /** |
|
168 * Parses an ALLOCH or FREEH statement |
|
169 * @param fragments the line fragments of the statement to process |
|
170 * @param startIndex index at which the callstack size is to be found |
|
171 * @return true if callstack is complete, false if callstack fragment is expected |
|
172 */ |
|
173 private boolean parseHeader(String[] fragments, int startIndex) { |
|
174 |
|
175 if (callstack.size() > 0 || remainingSize > 0) { |
|
176 throw new IllegalStateException( |
|
177 "Callstack list should still be empty when starting to process ALLOCH"); //$NON-NLS-1$ |
|
178 } |
|
179 |
|
180 if (fragments.length > startIndex) { |
|
181 |
|
182 int callstackSize = Integer.parseInt(fragments[startIndex], 16); |
|
183 startIndex ++; |
|
184 |
|
185 createCallstack(fragments, process, callstack, startIndex); |
|
186 |
|
187 callstackSize -= callstack.size(); |
|
188 if (callstackSize > 0) { |
|
189 // expect fragments |
|
190 remainingSize = callstackSize; |
|
191 } else { |
|
192 complete = true; |
|
193 } |
|
194 } else { |
|
195 complete = true; // this header doesn't have callstacks - a bit strange |
|
196 } |
|
197 return complete; |
|
198 } |
|
199 |
|
200 /** |
|
201 * Parses an ALLOCF or FREEF callstack fragment |
|
202 * @param fragments the line fragments of the statement to process |
|
203 * @return true if callstack is now complete; false otherwise |
|
204 */ |
|
205 private boolean parseFragment(String[] fragments) { |
|
206 long time = Long.parseLong(fragments[4],16); |
|
207 int packetNumber = Integer.parseInt(fragments[5], 16); |
|
208 |
|
209 if (baseInfo.getTime() == time) { |
|
210 List<AllocCallstack> tmpCallstack = new ArrayList<AllocCallstack>(); |
|
211 createCallstack(fragments, process, tmpCallstack, 6); |
|
212 updateFragment(tmpCallstack, packetNumber); |
|
213 |
|
214 remainingSize -= tmpCallstack.size(); |
|
215 if (remainingSize <= 0){ |
|
216 complete = true; |
|
217 finaliseCallstack(); |
|
218 } |
|
219 } |
|
220 return complete; |
|
221 } |
|
222 |
|
223 private DllLoad getDllForAddress(ProcessInfo p, Long memoryAddress, |
|
224 long time) { |
|
225 for (DllLoad oneLoad : p.getDllLoads()) { |
|
226 if (memoryAddress >= oneLoad.getStartAddress() |
|
227 && memoryAddress <= oneLoad.getEndAddress() |
|
228 && time >= oneLoad.getLoadTime() |
|
229 && time <= oneLoad.getUnloadTime()) { |
|
230 // dll load found |
|
231 return oneLoad; |
|
232 } |
|
233 } |
|
234 return null; |
|
235 } |
|
236 private void createCallstack(String[] fragments, ProcessInfo p, List<AllocCallstack> callstack, int startIndex) { |
|
237 for (int i = startIndex; i < fragments.length; i++) { |
|
238 AllocCallstack callstackElem = new AllocCallstack(fragments[i]); |
|
239 |
|
240 // find matching dll |
|
241 DllLoad dllLoad = getDllForAddress(process, callstackElem.getMemoryAddress(), baseInfo.getTime()); |
|
242 if (dllLoad != null) { |
|
243 callstackElem.setDllLoad(dllLoad); |
|
244 } |
|
245 callstack.add(callstackElem); |
|
246 } |
|
247 } |
|
248 /** |
|
249 * Updates allocation fragment. Means that given callstack is addition to |
|
250 * previous added alloc |
|
251 * |
|
252 * @param callstack |
|
253 * Addition tmpcallstack items |
|
254 * @param packetNumber |
|
255 * ordinal of callstack fragment |
|
256 */ |
|
257 private void updateFragment(List<AllocCallstack> tmpcallstack, |
|
258 int packetNumber) { |
|
259 if (packetNumber == 1){ |
|
260 //special case; this can be added to the end of the list straight away |
|
261 callstack.addAll(tmpcallstack); |
|
262 } else { |
|
263 //packages may come out of order; this is managed in the callstackCache |
|
264 if (callstackCache == null){ |
|
265 callstackCache = new HashMap<Integer, List<AllocCallstack>>(); |
|
266 } |
|
267 callstackCache.put(packetNumber, tmpcallstack); |
|
268 } |
|
269 } |
|
270 /** |
|
271 * Optimises internal callstack data structures. |
|
272 * Should only be called after all data for this memory operation |
|
273 * has been loaded (i.e. all fragments) |
|
274 */ |
|
275 public void finaliseCallstack(){ |
|
276 if (callstackCache == null){ |
|
277 //nothing to do |
|
278 return; |
|
279 } |
|
280 |
|
281 if (!complete){ |
|
282 throw new IllegalStateException("callstack processing is not yet complete."); //$NON-NLS-1$ |
|
283 } |
|
284 |
|
285 if (callstack == null && callstackCache != null){ |
|
286 throw new IllegalStateException(); //first set of callstacks should always be in callstacks |
|
287 } |
|
288 |
|
289 if (callstackCache != null){ |
|
290 int size = callstackCache.size(); |
|
291 int i = 2; |
|
292 while(size != 0){ |
|
293 List<AllocCallstack> nextCallStacks = callstackCache.get(i); |
|
294 if (nextCallStacks != null){ |
|
295 size --; |
|
296 callstack.addAll(nextCallStacks); |
|
297 } //TODO else: missing callstack: shall we report it or log it? |
|
298 i++; |
|
299 } |
|
300 callstackCache = null; |
|
301 } |
|
302 } |
|
303 |
|
304 /** |
|
305 * @return the completed callstack. |
|
306 */ |
|
307 public List<AllocCallstack> getCallstack(){ |
|
308 if (!complete){ |
|
309 throw new IllegalStateException("Callstack has not been completely processed."); //$NON-NLS-1$ |
|
310 } |
|
311 |
|
312 finaliseCallstack(); |
|
313 return callstack; |
|
314 } |
|
315 |
|
316 /** |
|
317 * Forces the callstack state to be set to complete. This should only be used when the end of file is encountered. |
|
318 */ |
|
319 public void forceComplete(){ |
|
320 complete = true; |
|
321 finaliseCallstack(); |
|
322 } |
|
323 |
|
324 } |