]> git.cworth.org Git - apitrace/blob - gui/retracer.cpp
Fix includes.
[apitrace] / gui / retracer.cpp
1 #include "retracer.h"
2
3 #include "apitracecall.h"
4
5 #include "image.hpp"
6
7 #include <QDebug>
8 #include <QVariant>
9 #include <QList>
10 #include <QImage>
11
12 #include <qjson/parser.h>
13
14 /**
15  * Wrapper around a QProcess which enforces IO to block .
16  *
17  * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
18  * they expect that QIODevice::read() will blocked until the requested ammount
19  * of bytes is read or end of file is reached. But by default QProcess, does
20  * not block.  And passing QIODevice::Unbuffered mitigates but does not fully
21  * address the problem either.
22  *
23  * This class wraps around QProcess, providing QIODevice interface, while
24  * ensuring that all reads block.
25  *
26  * This class also works around a bug in QProcess::atEnd() implementation.
27  *
28  * See also:
29  * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
30  * - http://qt-project.org/wiki/Custom_IO_Device
31  */
32 class BlockingIODevice : public QIODevice
33 {
34     /* We don't use the Q_OBJECT in this class given we don't declare any
35      * signals and slots or use any other services provided by Qt's meta-object
36      * system. */
37 public:
38     BlockingIODevice(QProcess * io);
39     bool isSequential() const;
40     bool atEnd() const;
41     bool waitForReadyRead(int msecs = -1);
42
43 protected:
44     qint64 readData(char * data, qint64 maxSize);
45     qint64 writeData(const char * data, qint64 maxSize);
46
47 private:
48     QProcess *m_device;
49 };
50
51 BlockingIODevice::BlockingIODevice(QProcess * io) :
52     m_device(io)
53 {
54     /*
55      * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
56      * its own buffering on top of the overridden readData() method.
57      *
58      * The only buffering used will be to satisfy QIODevice::peek() and
59      * QIODevice::ungetChar().
60      */
61     setOpenMode(ReadOnly | Unbuffered);
62 }
63
64 bool BlockingIODevice::isSequential() const
65 {
66     return true;
67 }
68
69 bool BlockingIODevice::atEnd() const
70 {
71     /*
72      * XXX: QProcess::atEnd() documentation is wrong -- it will return true
73      * even when the process is running --, so we try to workaround that here.
74      */
75     if (m_device->atEnd()) {
76         if (m_device->state() == QProcess::Running) {
77             if (!m_device->waitForReadyRead(-1)) {
78                 return true;
79             }
80         }
81     }
82     return false;
83 }
84
85 bool BlockingIODevice::waitForReadyRead(int msecs)
86 {
87     Q_UNUSED(msecs);
88     return true;
89 }
90
91 qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
92 {
93     qint64 bytesToRead = maxSize;
94     qint64 readSoFar = 0;
95     do {
96         qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
97         if (chunkSize < 0) {
98             if (readSoFar) {
99                 return readSoFar;
100             } else {
101                 return chunkSize;
102             }
103         }
104         Q_ASSERT(chunkSize <= bytesToRead);
105         bytesToRead -= chunkSize;
106         readSoFar += chunkSize;
107         if (bytesToRead) {
108             if (!m_device->waitForReadyRead(-1)) {
109                 qDebug() << "waitForReadyRead failed\n";
110                 break;
111             }
112         }
113     } while(bytesToRead);
114
115     return readSoFar;
116 }
117
118 qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
119 {
120     Q_ASSERT(false);
121     return -1;
122 }
123
124 Q_DECLARE_METATYPE(QList<ApiTraceError>);
125
126 Retracer::Retracer(QObject *parent)
127     : QThread(parent),
128       m_benchmarking(false),
129       m_doubleBuffered(true),
130       m_captureState(false),
131       m_captureCall(0)
132 {
133     qRegisterMetaType<QList<ApiTraceError> >();
134
135 #ifdef Q_OS_WIN
136     QString format = QLatin1String("%1;");
137 #else
138     QString format = QLatin1String("%1:");
139 #endif
140     QString buildPath = format.arg(APITRACE_BINARY_DIR);
141     m_processEnvironment = QProcessEnvironment::systemEnvironment();
142     m_processEnvironment.insert("PATH", buildPath +
143                                 m_processEnvironment.value("PATH"));
144
145     qputenv("PATH",
146             m_processEnvironment.value("PATH").toLatin1());
147 }
148
149 QString Retracer::fileName() const
150 {
151     return m_fileName;
152 }
153
154 void Retracer::setFileName(const QString &name)
155 {
156     m_fileName = name;
157 }
158
159 void Retracer::setAPI(trace::API api)
160 {
161     m_api = api;
162 }
163
164 bool Retracer::isBenchmarking() const
165 {
166     return m_benchmarking;
167 }
168
169 void Retracer::setBenchmarking(bool bench)
170 {
171     m_benchmarking = bench;
172 }
173
174 bool Retracer::isDoubleBuffered() const
175 {
176     return m_doubleBuffered;
177 }
178
179 void Retracer::setDoubleBuffered(bool db)
180 {
181     m_doubleBuffered = db;
182 }
183
184 void Retracer::setCaptureAtCallNumber(qlonglong num)
185 {
186     m_captureCall = num;
187 }
188
189 qlonglong Retracer::captureAtCallNumber() const
190 {
191     return m_captureCall;
192 }
193
194 bool Retracer::captureState() const
195 {
196     return m_captureState;
197 }
198
199 void Retracer::setCaptureState(bool enable)
200 {
201     m_captureState = enable;
202 }
203
204 bool Retracer::captureThumbnails() const
205 {
206     return m_captureThumbnails;
207 }
208
209 void Retracer::setCaptureThumbnails(bool enable)
210 {
211     m_captureThumbnails = enable;
212 }
213
214
215 /**
216  * Starting point for the retracing thread.
217  *
218  * Overrides QThread::run().
219  */
220 void Retracer::run()
221 {
222     QString msg;
223
224     /*
225      * Construct command line
226      */
227
228     QString prog;
229     QStringList arguments;
230
231     if (m_api == trace::API_GL) {
232         prog = QLatin1String("glretrace");
233     } else if (m_api == trace::API_EGL) {
234         prog = QLatin1String("eglretrace");
235     } else {
236         Q_ASSERT(0);
237         return;
238     }
239
240     if (m_doubleBuffered) {
241         arguments << QLatin1String("-db");
242     } else {
243         arguments << QLatin1String("-sb");
244     }
245
246     if (m_captureState) {
247         arguments << QLatin1String("-D");
248         arguments << QString::number(m_captureCall);
249     } else if (m_captureThumbnails) {
250         arguments << QLatin1String("-s"); // emit snapshots
251         arguments << QLatin1String("-"); // emit to stdout
252     } else if (m_benchmarking) {
253         arguments << QLatin1String("-b");
254     }
255
256     arguments << m_fileName;
257
258     /*
259      * Start the process.
260      */
261
262     QProcess process;
263
264     process.start(prog, arguments, QIODevice::ReadOnly);
265     if (!process.waitForStarted(-1)) {
266         emit finished(QLatin1String("Could not start process"));
267         return;
268     }
269
270     /*
271      * Process standard output
272      */
273
274     QList<QImage> thumbnails;
275     QVariantMap parsedJson;
276
277     process.setReadChannel(QProcess::StandardOutput);
278     if (process.waitForReadyRead(-1)) {
279         BlockingIODevice io(&process);
280
281         if (m_captureState) {
282             /*
283              * Parse JSON from the output.
284              *
285              * XXX: QJSON expects blocking IO.
286              *
287              * XXX: QJSON's scanner is inneficient as it abuses single
288              * character QIODevice::peek (not cheap), instead of maintaining a
289              * lookahead character on its own.
290              */
291
292             bool ok = false;
293             QJson::Parser jsonParser;
294             parsedJson = jsonParser.parse(&io, &ok).toMap();
295             if (!ok) {
296                 msg = QLatin1String("failed to parse JSON");
297             }
298         } else if (m_captureThumbnails) {
299             /*
300              * Parse concatenated PNM images from output.
301              */
302
303             while (!io.atEnd()) {
304                 unsigned channels = 0;
305                 unsigned width = 0;
306                 unsigned height = 0;
307
308                 char header[512];
309                 qint64 headerSize = 0;
310                 int headerLines = 3; // assume no optional comment line
311
312                 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
313                     qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
314
315                     // if header actually contains optional comment line, ...
316                     if (headerLine == 1 && header[headerSize] == '#') {
317                         ++headerLines;
318                     }
319
320                     headerSize += headerRead;
321                 }
322
323                 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
324
325                 // if invalid PNM header was encountered, ...
326                 if (header == headerEnd) {
327                     qDebug() << "error: invalid snapshot stream encountered";
328                     break;
329                 }
330
331                 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
332
333                 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
334
335                 int rowBytes = channels * width;
336                 for (int y = 0; y < height; ++y) {
337                     unsigned char *scanLine = snapshot.scanLine(y);
338                     qint64 readBytes = io.read((char *) scanLine, rowBytes);
339                     Q_ASSERT(readBytes == rowBytes);
340                 }
341
342                 QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
343                 thumbnails.append(thumbnail);
344             }
345
346             Q_ASSERT(process.state() != QProcess::Running);
347
348         } else {
349             QByteArray output;
350             output = process.readAllStandardOutput();
351             msg = QString::fromUtf8(output);
352         }
353     }
354
355     /*
356      * Wait for process termination
357      */
358
359     process.waitForFinished(-1);
360
361     if (process.exitStatus() != QProcess::NormalExit) {
362         msg = QLatin1String("Process crashed");
363     } else if (process.exitCode() != 0) {
364         msg = QLatin1String("Process exited with non zero exit code");
365     }
366
367     /*
368      * Parse errors.
369      */
370
371     QList<ApiTraceError> errors;
372     process.setReadChannel(QProcess::StandardError);
373     QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
374     while (!process.atEnd()) {
375         QString line = process.readLine();
376         if (regexp.indexIn(line) != -1) {
377             ApiTraceError error;
378             error.callIndex = regexp.cap(1).toInt();
379             error.type = regexp.cap(2);
380             error.message = regexp.cap(3);
381             errors.append(error);
382         }
383     }
384
385     /*
386      * Emit signals
387      */
388
389     if (m_captureState) {
390         ApiTraceState *state = new ApiTraceState(parsedJson);
391         emit foundState(state);
392         msg = QLatin1String("State fetched.");
393     }
394
395     if (m_captureThumbnails && !thumbnails.isEmpty()) {
396         emit foundThumbnails(thumbnails);
397     }
398
399     if (!errors.isEmpty()) {
400         emit retraceErrors(errors);
401     }
402
403     emit finished(msg);
404 }
405
406 #include "retracer.moc"