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.ide.eclipse.gltrace;
18 
19 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage;
20 import com.android.ide.eclipse.gltrace.GLProtoBuf.GLMessage.Function;
21 import com.android.ide.eclipse.gltrace.format.GLAPISpec;
22 import com.android.ide.eclipse.gltrace.format.GLMessageFormatter;
23 import com.android.ide.eclipse.gltrace.model.GLCall;
24 import com.android.ide.eclipse.gltrace.model.GLFrame;
25 import com.android.ide.eclipse.gltrace.model.GLTrace;
26 import com.android.ide.eclipse.gltrace.state.transforms.StateTransformFactory;
27 
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.jface.operation.IRunnableWithProgress;
30 
31 import java.io.File;
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.RandomAccessFile;
35 import java.lang.reflect.InvocationTargetException;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Comparator;
39 import java.util.List;
40 import java.util.Set;
41 import java.util.TreeSet;
42 
43 public class TraceFileParserTask implements IRunnableWithProgress {
44     private static final TraceFileReader sReader = new TraceFileReader();
45 
46     private static final GLMessageFormatter sGLMessageFormatter =
47             new GLMessageFormatter(GLAPISpec.getSpecs());
48 
49     private String mTraceFilePath;
50     private RandomAccessFile mFile;
51 
52     private List<GLCall> mGLCalls;
53     private Set<Integer> mGLContextIds;
54 
55     private GLTrace mTrace;
56 
57     /**
58      * Construct a GL Trace file parser.
59      * @param path path to trace file
60      */
TraceFileParserTask(String path)61     public TraceFileParserTask(String path) {
62         try {
63             mFile = new RandomAccessFile(path, "r"); //$NON-NLS-1$
64         } catch (FileNotFoundException e) {
65             throw new IllegalArgumentException(e);
66         }
67 
68         mTraceFilePath = path;
69         mGLCalls = new ArrayList<GLCall>();
70         mGLContextIds = new TreeSet<Integer>();
71     }
72 
addMessage(int index, long traceFileOffset, GLMessage msg, long startTime)73     private void addMessage(int index, long traceFileOffset, GLMessage msg, long startTime) {
74         String formattedMsg;
75         try {
76             formattedMsg = sGLMessageFormatter.formatGLMessage(msg);
77         } catch (Exception e) {
78             formattedMsg = String.format("%s()", msg.getFunction().toString()); //$NON-NLS-1$
79         }
80 
81         GLCall c = new GLCall(index,
82                                 startTime,
83                                 traceFileOffset,
84                                 formattedMsg,
85                                 msg.getFunction(),
86                                 msg.hasFb(),
87                                 msg.getContextId(),
88                                 msg.getDuration(),
89                                 msg.getThreadtime());
90 
91         addProperties(c, msg);
92 
93         try {
94             c.setStateTransformations(StateTransformFactory.getTransformsFor(msg));
95         } catch (Exception e) {
96             c.setStateTransformationCreationError(e.getMessage());
97             GlTracePlugin.getDefault().logMessage("Error while creating transformations for "
98                                                         + c.toString() + ":");
99             GlTracePlugin.getDefault().logMessage(e.getMessage());
100         }
101 
102         mGLCalls.add(c);
103         mGLContextIds.add(Integer.valueOf(c.getContextId()));
104     }
105 
106     /** Save important values from the {@link GLMessage} in the {@link GLCall} as properties. */
addProperties(GLCall c, GLMessage msg)107     private void addProperties(GLCall c, GLMessage msg) {
108         switch (msg.getFunction()) {
109         case glPushGroupMarkerEXT:
110             // void PushGroupMarkerEXT(sizei length, const char *marker);
111             // save the marker name
112             c.addProperty(GLCall.PROPERTY_MARKERNAME,
113                     msg.getArgs(1).getCharValue(0).toStringUtf8());
114             break;
115         case glVertexAttribPointerData:
116             // void glVertexAttribPointerData(GLuint indx, GLint size, GLenum type,
117             //         GLboolean normalized, GLsizei stride, const GLvoid* ptr,
118             //         int minIndex, int maxIndex)
119             c.addProperty(GLCall.PROPERTY_VERTEX_ATTRIB_POINTER_SIZE,
120                     Integer.valueOf(msg.getArgs(1).getIntValue(0)));
121             c.addProperty(GLCall.PROPERTY_VERTEX_ATTRIB_POINTER_TYPE,
122                     GLEnum.valueOf(msg.getArgs(2).getIntValue(0)));
123             c.addProperty(GLCall.PROPERTY_VERTEX_ATTRIB_POINTER_DATA,
124                     msg.getArgs(5).getRawBytes(0).toByteArray());
125             break;
126         default:
127             break;
128         }
129     }
130 
131     /**
132      * Parse the entire file and create a {@link GLTrace} object that can be retrieved
133      * using {@link #getTrace()}.
134      */
135     @Override
run(IProgressMonitor monitor)136     public void run(IProgressMonitor monitor) throws InvocationTargetException,
137             InterruptedException {
138         long fileLength;
139         try {
140             fileLength = mFile.length();
141         } catch (IOException e1) {
142             fileLength = 0;
143         }
144 
145         monitor.beginTask("Parsing OpenGL Trace File",
146                 fileLength > 0 ? 100 : IProgressMonitor.UNKNOWN);
147 
148         List<GLFrame> glFrames = null;
149 
150         try {
151             GLMessage msg = null;
152             int msgCount = 0;
153             long filePointer = mFile.getFilePointer();
154             int percentParsed = 0;
155 
156             // counters that maintain some statistics about the trace messages
157             long minTraceStartTime = Long.MAX_VALUE;
158 
159             while ((msg = sReader.getMessageAtOffset(mFile, -1)) != null) {
160                 if (minTraceStartTime > msg.getStartTime()) {
161                     minTraceStartTime = msg.getStartTime();
162                 }
163 
164                 addMessage(msgCount, filePointer, msg, msg.getStartTime() - minTraceStartTime);
165 
166                 filePointer = mFile.getFilePointer();
167                 msgCount++;
168 
169                 if (monitor.isCanceled()) {
170                     throw new InterruptedException();
171                 }
172 
173                 if (fileLength > 0) {
174                     int percentParsedNow = (int)((filePointer * 100) / fileLength);
175                     monitor.worked(percentParsedNow - percentParsed);
176                     percentParsed = percentParsedNow;
177                 }
178             }
179 
180             if (mGLContextIds.size() > 1) {
181                 // if there are multiple contexts, then the calls may arrive at the
182                 // host out of order. So we perform a sort based on the invocation time.
183                 Collections.sort(mGLCalls, new Comparator<GLCall>() {
184                     @Override
185                     public int compare(GLCall c1, GLCall c2) {
186                         long diff = (c1.getStartTime() - c2.getStartTime());
187 
188                         // We could return diff casted to an int. But in Java, casting
189                         // from a long to an int truncates the bits and will not preserve
190                         // the sign. So we resort to comparing the diff to 0 and returning
191                         // the sign.
192                         if (diff == 0) {
193                             return 0;
194                         } else if (diff > 0) {
195                             return 1;
196                         } else {
197                             return -1;
198                         }
199                     }
200                 });
201 
202                 // reassign indices after sorting
203                 for (int i = 0; i < mGLCalls.size(); i++) {
204                     mGLCalls.get(i).setIndex(i);
205                 }
206             }
207 
208             glFrames = createFrames(mGLCalls);
209         } catch (Exception e) {
210             throw new InvocationTargetException(e);
211         } finally {
212             try {
213                 mFile.close();
214             } catch (IOException e) {
215                 // ignore exception while closing file
216             }
217             monitor.done();
218         }
219 
220         File f = new File(mTraceFilePath);
221         TraceFileInfo fileInfo = new TraceFileInfo(mTraceFilePath, f.length(), f.lastModified());
222         mTrace = new GLTrace(fileInfo, glFrames, mGLCalls, new ArrayList<Integer>(mGLContextIds));
223     }
224 
225     /** Assign GL calls to GL Frames. */
createFrames(List<GLCall> calls)226     private List<GLFrame> createFrames(List<GLCall> calls) {
227         List<GLFrame> glFrames = new ArrayList<GLFrame>();
228         int startCallIndex = 0;
229         int frameIndex = 0;
230 
231         for (int i = 0; i < calls.size(); i++) {
232             GLCall c = calls.get(i);
233             if (c.getFunction() == Function.eglSwapBuffers) {
234                 glFrames.add(new GLFrame(frameIndex, startCallIndex, i + 1));
235                 startCallIndex = i + 1;
236                 frameIndex++;
237             }
238         }
239 
240         // assign left over calls at the end to the last frame
241         if (startCallIndex != mGLCalls.size()) {
242             glFrames.add(new GLFrame(frameIndex, startCallIndex, mGLCalls.size()));
243         }
244 
245         return glFrames;
246     }
247 
248     /**
249      * Retrieve the trace object constructed from messages in the trace file.
250      */
getTrace()251     public GLTrace getTrace() {
252         return mTrace;
253     }
254 }
255