blob: 983cd1a99f21f4f8f70c2c18ac49233afb737d53 [file] [log] [blame]
Zack Rusin3acde362011-04-06 01:11:55 -04001#include "retracer.h"
2
Zack Rusinf389ae82011-04-10 19:27:28 -04003#include "apitracecall.h"
José Fonseca3f456402012-03-25 20:59:24 +01004#include "thumbnail.h"
Zack Rusinf389ae82011-04-10 19:27:28 -04005
Dan McCabe66dfdda2012-03-05 17:20:39 -08006#include "image.hpp"
7
Zack Rusin3acde362011-04-06 01:11:55 -04008#include <QDebug>
Zack Rusinf389ae82011-04-10 19:27:28 -04009#include <QVariant>
Dan McCabe66dfdda2012-03-05 17:20:39 -080010#include <QList>
11#include <QImage>
Zack Rusinf389ae82011-04-10 19:27:28 -040012
13#include <qjson/parser.h>
Zack Rusin3acde362011-04-06 01:11:55 -040014
José Fonseca6ea8dee2012-03-25 17:25:24 +010015/**
16 * Wrapper around a QProcess which enforces IO to block .
17 *
18 * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
19 * they expect that QIODevice::read() will blocked until the requested ammount
20 * of bytes is read or end of file is reached. But by default QProcess, does
21 * not block. And passing QIODevice::Unbuffered mitigates but does not fully
22 * address the problem either.
23 *
24 * This class wraps around QProcess, providing QIODevice interface, while
25 * ensuring that all reads block.
26 *
27 * This class also works around a bug in QProcess::atEnd() implementation.
28 *
29 * See also:
30 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
31 * - http://qt-project.org/wiki/Custom_IO_Device
32 */
33class BlockingIODevice : public QIODevice
34{
35 /* We don't use the Q_OBJECT in this class given we don't declare any
36 * signals and slots or use any other services provided by Qt's meta-object
37 * system. */
38public:
39 BlockingIODevice(QProcess * io);
40 bool isSequential() const;
41 bool atEnd() const;
42 bool waitForReadyRead(int msecs = -1);
43
44protected:
45 qint64 readData(char * data, qint64 maxSize);
46 qint64 writeData(const char * data, qint64 maxSize);
47
48private:
49 QProcess *m_device;
50};
51
52BlockingIODevice::BlockingIODevice(QProcess * io) :
53 m_device(io)
54{
55 /*
56 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
57 * its own buffering on top of the overridden readData() method.
58 *
59 * The only buffering used will be to satisfy QIODevice::peek() and
60 * QIODevice::ungetChar().
61 */
62 setOpenMode(ReadOnly | Unbuffered);
63}
64
65bool BlockingIODevice::isSequential() const
66{
67 return true;
68}
69
70bool BlockingIODevice::atEnd() const
71{
72 /*
73 * XXX: QProcess::atEnd() documentation is wrong -- it will return true
74 * even when the process is running --, so we try to workaround that here.
75 */
76 if (m_device->atEnd()) {
77 if (m_device->state() == QProcess::Running) {
78 if (!m_device->waitForReadyRead(-1)) {
79 return true;
80 }
81 }
82 }
83 return false;
84}
85
86bool BlockingIODevice::waitForReadyRead(int msecs)
87{
88 Q_UNUSED(msecs);
89 return true;
90}
91
92qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
93{
94 qint64 bytesToRead = maxSize;
95 qint64 readSoFar = 0;
96 do {
97 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
98 if (chunkSize < 0) {
99 if (readSoFar) {
100 return readSoFar;
101 } else {
102 return chunkSize;
103 }
104 }
105 Q_ASSERT(chunkSize <= bytesToRead);
106 bytesToRead -= chunkSize;
107 readSoFar += chunkSize;
108 if (bytesToRead) {
109 if (!m_device->waitForReadyRead(-1)) {
110 qDebug() << "waitForReadyRead failed\n";
111 break;
112 }
113 }
114 } while(bytesToRead);
115
116 return readSoFar;
117}
118
119qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
120{
121 Q_ASSERT(false);
122 return -1;
123}
124
José Fonseca5bba4772012-03-25 12:46:04 +0100125Q_DECLARE_METATYPE(QList<ApiTraceError>);
126
Zack Rusin3acde362011-04-06 01:11:55 -0400127Retracer::Retracer(QObject *parent)
Zack Rusinf389ae82011-04-10 19:27:28 -0400128 : QThread(parent),
Zack Rusin404a1ef2011-04-19 23:49:56 -0400129 m_benchmarking(false),
Zack Rusin3acde362011-04-06 01:11:55 -0400130 m_doubleBuffered(true),
131 m_captureState(false),
Zack Rusinf389ae82011-04-10 19:27:28 -0400132 m_captureCall(0)
Zack Rusin3acde362011-04-06 01:11:55 -0400133{
José Fonseca5bba4772012-03-25 12:46:04 +0100134 qRegisterMetaType<QList<ApiTraceError> >();
135
Zack Rusinf389ae82011-04-10 19:27:28 -0400136#ifdef Q_OS_WIN
137 QString format = QLatin1String("%1;");
138#else
139 QString format = QLatin1String("%1:");
140#endif
José Fonseca27440922011-11-01 08:27:12 +0000141 QString buildPath = format.arg(APITRACE_BINARY_DIR);
Zack Rusinf389ae82011-04-10 19:27:28 -0400142 m_processEnvironment = QProcessEnvironment::systemEnvironment();
143 m_processEnvironment.insert("PATH", buildPath +
144 m_processEnvironment.value("PATH"));
145
146 qputenv("PATH",
147 m_processEnvironment.value("PATH").toLatin1());
Zack Rusin3acde362011-04-06 01:11:55 -0400148}
149
150QString Retracer::fileName() const
151{
152 return m_fileName;
153}
154
155void Retracer::setFileName(const QString &name)
156{
157 m_fileName = name;
158}
159
José Fonseca62997b42011-11-27 15:16:34 +0000160void Retracer::setAPI(trace::API api)
161{
162 m_api = api;
163}
164
Zack Rusin3acde362011-04-06 01:11:55 -0400165bool Retracer::isBenchmarking() const
166{
167 return m_benchmarking;
168}
169
170void Retracer::setBenchmarking(bool bench)
171{
172 m_benchmarking = bench;
173}
174
175bool Retracer::isDoubleBuffered() const
176{
177 return m_doubleBuffered;
178}
179
180void Retracer::setDoubleBuffered(bool db)
181{
182 m_doubleBuffered = db;
183}
184
Zack Rusin3acde362011-04-06 01:11:55 -0400185void Retracer::setCaptureAtCallNumber(qlonglong num)
186{
187 m_captureCall = num;
188}
189
190qlonglong Retracer::captureAtCallNumber() const
191{
192 return m_captureCall;
193}
194
195bool Retracer::captureState() const
196{
197 return m_captureState;
198}
199
200void Retracer::setCaptureState(bool enable)
201{
202 m_captureState = enable;
203}
204
Dan McCabe66dfdda2012-03-05 17:20:39 -0800205bool Retracer::captureThumbnails() const
206{
207 return m_captureThumbnails;
208}
209
210void Retracer::setCaptureThumbnails(bool enable)
211{
212 m_captureThumbnails = enable;
213}
214
Zack Rusinf389ae82011-04-10 19:27:28 -0400215
José Fonseca5bba4772012-03-25 12:46:04 +0100216/**
217 * Starting point for the retracing thread.
218 *
219 * Overrides QThread::run().
220 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400221void Retracer::run()
222{
José Fonseca126f64b2012-03-28 00:13:55 +0100223 QString msg = QLatin1String("Replay finished!");
Zack Rusinf389ae82011-04-10 19:27:28 -0400224
José Fonseca5bba4772012-03-25 12:46:04 +0100225 /*
226 * Construct command line
227 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400228
José Fonseca62997b42011-11-27 15:16:34 +0000229 QString prog;
Zack Rusinf389ae82011-04-10 19:27:28 -0400230 QStringList arguments;
Zack Rusin16ae0362011-04-11 21:30:04 -0400231
José Fonseca62997b42011-11-27 15:16:34 +0000232 if (m_api == trace::API_GL) {
233 prog = QLatin1String("glretrace");
234 } else if (m_api == trace::API_EGL) {
235 prog = QLatin1String("eglretrace");
236 } else {
José Fonseca67964382012-03-27 23:54:30 +0100237 emit finished(QLatin1String("Unsupported API"));
José Fonseca62997b42011-11-27 15:16:34 +0000238 return;
239 }
240
Zack Rusin16ae0362011-04-11 21:30:04 -0400241 if (m_doubleBuffered) {
242 arguments << QLatin1String("-db");
José Fonseca1872d962011-06-01 19:49:13 +0100243 } else {
244 arguments << QLatin1String("-sb");
Zack Rusin16ae0362011-04-11 21:30:04 -0400245 }
246
José Fonseca5bba4772012-03-25 12:46:04 +0100247 if (m_captureState) {
248 arguments << QLatin1String("-D");
249 arguments << QString::number(m_captureCall);
250 } else if (m_captureThumbnails) {
251 arguments << QLatin1String("-s"); // emit snapshots
252 arguments << QLatin1String("-"); // emit to stdout
253 } else if (m_benchmarking) {
254 arguments << QLatin1String("-b");
Zack Rusinf389ae82011-04-10 19:27:28 -0400255 }
256
257 arguments << m_fileName;
258
José Fonseca5bba4772012-03-25 12:46:04 +0100259 /*
260 * Start the process.
261 */
Zack Rusinf389ae82011-04-10 19:27:28 -0400262
José Fonseca5bba4772012-03-25 12:46:04 +0100263 QProcess process;
Zack Rusinf389ae82011-04-10 19:27:28 -0400264
José Fonseca6ea8dee2012-03-25 17:25:24 +0100265 process.start(prog, arguments, QIODevice::ReadOnly);
José Fonseca5bba4772012-03-25 12:46:04 +0100266 if (!process.waitForStarted(-1)) {
267 emit finished(QLatin1String("Could not start process"));
268 return;
269 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000270
José Fonseca5bba4772012-03-25 12:46:04 +0100271 /*
272 * Process standard output
273 */
274
275 QList<QImage> thumbnails;
276 QVariantMap parsedJson;
277
278 process.setReadChannel(QProcess::StandardOutput);
279 if (process.waitForReadyRead(-1)) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100280 BlockingIODevice io(&process);
281
José Fonseca5bba4772012-03-25 12:46:04 +0100282 if (m_captureState) {
283 /*
284 * Parse JSON from the output.
285 *
José Fonseca5bba4772012-03-25 12:46:04 +0100286 * XXX: QJSON's scanner is inneficient as it abuses single
287 * character QIODevice::peek (not cheap), instead of maintaining a
288 * lookahead character on its own.
289 */
290
José Fonseca5bba4772012-03-25 12:46:04 +0100291 bool ok = false;
292 QJson::Parser jsonParser;
José Fonseca95b40562012-04-05 20:06:42 +0100293#if 0
José Fonseca6ea8dee2012-03-25 17:25:24 +0100294 parsedJson = jsonParser.parse(&io, &ok).toMap();
José Fonseca95b40562012-04-05 20:06:42 +0100295#else
296 /*
297 * XXX: QJSON expects blocking IO, and it looks like
298 * BlockingIODevice does not work reliably in all cases.
299 */
300 process.waitForFinished(-1);
301 parsedJson = jsonParser.parse(&process, &ok).toMap();
302#endif
José Fonseca5bba4772012-03-25 12:46:04 +0100303 if (!ok) {
304 msg = QLatin1String("failed to parse JSON");
305 }
306 } else if (m_captureThumbnails) {
307 /*
308 * Parse concatenated PNM images from output.
309 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800310
José Fonseca6ea8dee2012-03-25 17:25:24 +0100311 while (!io.atEnd()) {
José Fonseca5bba4772012-03-25 12:46:04 +0100312 unsigned channels = 0;
313 unsigned width = 0;
314 unsigned height = 0;
315
316 char header[512];
317 qint64 headerSize = 0;
318 int headerLines = 3; // assume no optional comment line
319
320 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100321 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100322
323 // if header actually contains optional comment line, ...
324 if (headerLine == 1 && header[headerSize] == '#') {
325 ++headerLines;
326 }
327
328 headerSize += headerRead;
329 }
330
331 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
332
333 // if invalid PNM header was encountered, ...
334 if (header == headerEnd) {
335 qDebug() << "error: invalid snapshot stream encountered";
336 break;
337 }
338
339 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
340
341 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
342
343 int rowBytes = channels * width;
344 for (int y = 0; y < height; ++y) {
345 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100346 qint64 readBytes = io.read((char *) scanLine, rowBytes);
347 Q_ASSERT(readBytes == rowBytes);
José Fonseca5bba4772012-03-25 12:46:04 +0100348 }
349
José Fonsecadc9e9c62012-03-26 10:29:32 +0100350 QImage thumb = thumbnail(snapshot);
351 thumbnails.append(thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800352 }
José Fonseca5bba4772012-03-25 12:46:04 +0100353
354 Q_ASSERT(process.state() != QProcess::Running);
355
José Fonseca56cd8ac2011-11-24 16:30:49 +0000356 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000357 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100358 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100359 if (output.length() < 80) {
360 msg = QString::fromUtf8(output);
361 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000362 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400363 }
364
José Fonseca5bba4772012-03-25 12:46:04 +0100365 /*
366 * Wait for process termination
367 */
368
369 process.waitForFinished(-1);
370
371 if (process.exitStatus() != QProcess::NormalExit) {
372 msg = QLatin1String("Process crashed");
373 } else if (process.exitCode() != 0) {
374 msg = QLatin1String("Process exited with non zero exit code");
375 }
376
377 /*
378 * Parse errors.
379 */
380
Zack Rusin10fd4772011-09-14 01:45:12 -0400381 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100382 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000383 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100384 while (!process.atEnd()) {
385 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400386 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400387 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400388 error.callIndex = regexp.cap(1).toInt();
389 error.type = regexp.cap(2);
390 error.message = regexp.cap(3);
391 errors.append(error);
392 }
393 }
José Fonseca5bba4772012-03-25 12:46:04 +0100394
395 /*
396 * Emit signals
397 */
398
399 if (m_captureState) {
400 ApiTraceState *state = new ApiTraceState(parsedJson);
401 emit foundState(state);
402 msg = QLatin1String("State fetched.");
403 }
404
405 if (m_captureThumbnails && !thumbnails.isEmpty()) {
406 emit foundThumbnails(thumbnails);
407 }
408
Zack Rusinb39e1c62011-04-19 23:09:26 -0400409 if (!errors.isEmpty()) {
410 emit retraceErrors(errors);
411 }
José Fonseca5bba4772012-03-25 12:46:04 +0100412
Zack Rusinf389ae82011-04-10 19:27:28 -0400413 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400414}
415
Zack Rusin3acde362011-04-06 01:11:55 -0400416#include "retracer.moc"