blob: 84f974244943b45a551fd561a1f01ce8d8e108ac [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é Fonseca6ea8dee2012-03-25 17:25:24 +0100286 * XXX: QJSON expects blocking IO.
José Fonseca5bba4772012-03-25 12:46:04 +0100287 *
288 * XXX: QJSON's scanner is inneficient as it abuses single
289 * character QIODevice::peek (not cheap), instead of maintaining a
290 * lookahead character on its own.
291 */
292
José Fonseca5bba4772012-03-25 12:46:04 +0100293 bool ok = false;
294 QJson::Parser jsonParser;
José Fonseca6ea8dee2012-03-25 17:25:24 +0100295 parsedJson = jsonParser.parse(&io, &ok).toMap();
José Fonseca5bba4772012-03-25 12:46:04 +0100296 if (!ok) {
297 msg = QLatin1String("failed to parse JSON");
298 }
299 } else if (m_captureThumbnails) {
300 /*
301 * Parse concatenated PNM images from output.
302 */
Dan McCabe66dfdda2012-03-05 17:20:39 -0800303
José Fonseca6ea8dee2012-03-25 17:25:24 +0100304 while (!io.atEnd()) {
José Fonseca5bba4772012-03-25 12:46:04 +0100305 unsigned channels = 0;
306 unsigned width = 0;
307 unsigned height = 0;
308
309 char header[512];
310 qint64 headerSize = 0;
311 int headerLines = 3; // assume no optional comment line
312
313 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
José Fonseca6ea8dee2012-03-25 17:25:24 +0100314 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
José Fonseca5bba4772012-03-25 12:46:04 +0100315
316 // if header actually contains optional comment line, ...
317 if (headerLine == 1 && header[headerSize] == '#') {
318 ++headerLines;
319 }
320
321 headerSize += headerRead;
322 }
323
324 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
325
326 // if invalid PNM header was encountered, ...
327 if (header == headerEnd) {
328 qDebug() << "error: invalid snapshot stream encountered";
329 break;
330 }
331
332 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
333
334 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
335
336 int rowBytes = channels * width;
337 for (int y = 0; y < height; ++y) {
338 unsigned char *scanLine = snapshot.scanLine(y);
José Fonseca6ea8dee2012-03-25 17:25:24 +0100339 qint64 readBytes = io.read((char *) scanLine, rowBytes);
340 Q_ASSERT(readBytes == rowBytes);
José Fonseca5bba4772012-03-25 12:46:04 +0100341 }
342
José Fonsecadc9e9c62012-03-26 10:29:32 +0100343 QImage thumb = thumbnail(snapshot);
344 thumbnails.append(thumb);
Dan McCabe66dfdda2012-03-05 17:20:39 -0800345 }
José Fonseca5bba4772012-03-25 12:46:04 +0100346
347 Q_ASSERT(process.state() != QProcess::Running);
348
José Fonseca56cd8ac2011-11-24 16:30:49 +0000349 } else {
José Fonseca676cd172012-03-24 09:46:24 +0000350 QByteArray output;
José Fonseca5bba4772012-03-25 12:46:04 +0100351 output = process.readAllStandardOutput();
José Fonseca126f64b2012-03-28 00:13:55 +0100352 if (output.length() < 80) {
353 msg = QString::fromUtf8(output);
354 }
José Fonseca56cd8ac2011-11-24 16:30:49 +0000355 }
Zack Rusinf389ae82011-04-10 19:27:28 -0400356 }
357
José Fonseca5bba4772012-03-25 12:46:04 +0100358 /*
359 * Wait for process termination
360 */
361
362 process.waitForFinished(-1);
363
364 if (process.exitStatus() != QProcess::NormalExit) {
365 msg = QLatin1String("Process crashed");
366 } else if (process.exitCode() != 0) {
367 msg = QLatin1String("Process exited with non zero exit code");
368 }
369
370 /*
371 * Parse errors.
372 */
373
Zack Rusin10fd4772011-09-14 01:45:12 -0400374 QList<ApiTraceError> errors;
José Fonseca5bba4772012-03-25 12:46:04 +0100375 process.setReadChannel(QProcess::StandardError);
José Fonseca8fdf56c2012-03-24 10:06:56 +0000376 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
José Fonseca5bba4772012-03-25 12:46:04 +0100377 while (!process.atEnd()) {
378 QString line = process.readLine();
Zack Rusinb39e1c62011-04-19 23:09:26 -0400379 if (regexp.indexIn(line) != -1) {
Zack Rusin10fd4772011-09-14 01:45:12 -0400380 ApiTraceError error;
Zack Rusinb39e1c62011-04-19 23:09:26 -0400381 error.callIndex = regexp.cap(1).toInt();
382 error.type = regexp.cap(2);
383 error.message = regexp.cap(3);
384 errors.append(error);
385 }
386 }
José Fonseca5bba4772012-03-25 12:46:04 +0100387
388 /*
389 * Emit signals
390 */
391
392 if (m_captureState) {
393 ApiTraceState *state = new ApiTraceState(parsedJson);
394 emit foundState(state);
395 msg = QLatin1String("State fetched.");
396 }
397
398 if (m_captureThumbnails && !thumbnails.isEmpty()) {
399 emit foundThumbnails(thumbnails);
400 }
401
Zack Rusinb39e1c62011-04-19 23:09:26 -0400402 if (!errors.isEmpty()) {
403 emit retraceErrors(errors);
404 }
José Fonseca5bba4772012-03-25 12:46:04 +0100405
Zack Rusinf389ae82011-04-10 19:27:28 -0400406 emit finished(msg);
Zack Rusin3acde362011-04-06 01:11:55 -0400407}
408
Zack Rusin3acde362011-04-06 01:11:55 -0400409#include "retracer.moc"