]> git.cworth.org Git - apitrace/blob - gui/retracer.cpp
Don't buffer all stdout from retrace when generating thumbnails.
[apitrace] / gui / retracer.cpp
1 #include "retracer.h"
2 #include <iostream>
3
4 #include "apitracecall.h"
5
6 #include "image.hpp"
7
8 #include <QDebug>
9 #include <QVariant>
10 #include <QList>
11 #include <QImage>
12
13 #include <qjson/parser.h>
14
15 Q_DECLARE_METATYPE(QList<ApiTraceError>);
16
17 Retracer::Retracer(QObject *parent)
18     : QThread(parent),
19       m_benchmarking(false),
20       m_doubleBuffered(true),
21       m_captureState(false),
22       m_captureCall(0)
23 {
24     qRegisterMetaType<QList<ApiTraceError> >();
25
26 #ifdef Q_OS_WIN
27     QString format = QLatin1String("%1;");
28 #else
29     QString format = QLatin1String("%1:");
30 #endif
31     QString buildPath = format.arg(APITRACE_BINARY_DIR);
32     m_processEnvironment = QProcessEnvironment::systemEnvironment();
33     m_processEnvironment.insert("PATH", buildPath +
34                                 m_processEnvironment.value("PATH"));
35
36     qputenv("PATH",
37             m_processEnvironment.value("PATH").toLatin1());
38 }
39
40 QString Retracer::fileName() const
41 {
42     return m_fileName;
43 }
44
45 void Retracer::setFileName(const QString &name)
46 {
47     m_fileName = name;
48 }
49
50 void Retracer::setAPI(trace::API api)
51 {
52     m_api = api;
53 }
54
55 bool Retracer::isBenchmarking() const
56 {
57     return m_benchmarking;
58 }
59
60 void Retracer::setBenchmarking(bool bench)
61 {
62     m_benchmarking = bench;
63 }
64
65 bool Retracer::isDoubleBuffered() const
66 {
67     return m_doubleBuffered;
68 }
69
70 void Retracer::setDoubleBuffered(bool db)
71 {
72     m_doubleBuffered = db;
73 }
74
75 void Retracer::setCaptureAtCallNumber(qlonglong num)
76 {
77     m_captureCall = num;
78 }
79
80 qlonglong Retracer::captureAtCallNumber() const
81 {
82     return m_captureCall;
83 }
84
85 bool Retracer::captureState() const
86 {
87     return m_captureState;
88 }
89
90 void Retracer::setCaptureState(bool enable)
91 {
92     m_captureState = enable;
93 }
94
95 bool Retracer::captureThumbnails() const
96 {
97     return m_captureThumbnails;
98 }
99
100 void Retracer::setCaptureThumbnails(bool enable)
101 {
102     m_captureThumbnails = enable;
103 }
104
105
106 /**
107  * Starting point for the retracing thread.
108  *
109  * Overrides QThread::run().
110  */
111 void Retracer::run()
112 {
113     QString msg;
114
115     /*
116      * Construct command line
117      */
118
119     QString prog;
120     QStringList arguments;
121
122     if (m_api == trace::API_GL) {
123         prog = QLatin1String("glretrace");
124     } else if (m_api == trace::API_EGL) {
125         prog = QLatin1String("eglretrace");
126     } else {
127         Q_ASSERT(0);
128         return;
129     }
130
131     if (m_doubleBuffered) {
132         arguments << QLatin1String("-db");
133     } else {
134         arguments << QLatin1String("-sb");
135     }
136
137     if (m_captureState) {
138         arguments << QLatin1String("-D");
139         arguments << QString::number(m_captureCall);
140     } else if (m_captureThumbnails) {
141         arguments << QLatin1String("-s"); // emit snapshots
142         arguments << QLatin1String("-"); // emit to stdout
143     } else if (m_benchmarking) {
144         arguments << QLatin1String("-b");
145     }
146
147     arguments << m_fileName;
148
149     /*
150      * Start the process.
151      */
152
153     QProcess process;
154
155     process.start(prog, arguments);
156     if (!process.waitForStarted(-1)) {
157         emit finished(QLatin1String("Could not start process"));
158         return;
159     }
160
161     /*
162      * Process standard output
163      */
164
165     QList<QImage> thumbnails;
166     QVariantMap parsedJson;
167
168     process.setReadChannel(QProcess::StandardOutput);
169     if (process.waitForReadyRead(-1)) {
170         if (m_captureState) {
171             /*
172              * Parse JSON from the output.
173              *
174              * XXX: QJSON does not wait for QIODevice::waitForReadyRead so we
175              * need to buffer all stdout.
176              *
177              * XXX: QJSON's scanner is inneficient as it abuses single
178              * character QIODevice::peek (not cheap), instead of maintaining a
179              * lookahead character on its own.
180              */
181
182             if (!process.waitForFinished(-1)) {
183                 return;
184             }
185
186             bool ok = false;
187             QJson::Parser jsonParser;
188             parsedJson = jsonParser.parse(&process, &ok).toMap();
189             if (!ok) {
190                 msg = QLatin1String("failed to parse JSON");
191             }
192         } else if (m_captureThumbnails) {
193             /*
194              * Parse concatenated PNM images from output.
195              */
196
197             while (true) {
198                 /*
199                  * QProcess::atEnd() documentation is wrong -- it will return
200                  * true even when the process is running --, so try to handle
201                  * that here.
202                  */
203                 if (process.atEnd()) {
204                     if (process.state() == QProcess::Running) {
205                         if (!process.waitForReadyRead(-1)) {
206                             break;
207                         }
208                     }
209                 }
210
211                 unsigned channels = 0;
212                 unsigned width = 0;
213                 unsigned height = 0;
214
215                 char header[512];
216                 qint64 headerSize = 0;
217                 int headerLines = 3; // assume no optional comment line
218
219                 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
220                     while (!process.canReadLine()) {
221                         if (!process.waitForReadyRead(-1)) {
222                             qDebug() << "QProcess::waitForReadyRead failed";
223                             break;
224                         }
225                     }
226
227                     qint64 headerRead = process.readLine(&header[headerSize], sizeof(header) - headerSize);
228
229                     // if header actually contains optional comment line, ...
230                     if (headerLine == 1 && header[headerSize] == '#') {
231                         ++headerLines;
232                     }
233
234                     headerSize += headerRead;
235                 }
236
237                 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
238
239                 // if invalid PNM header was encountered, ...
240                 if (header == headerEnd) {
241                     qDebug() << "error: invalid snapshot stream encountered";
242                     break;
243                 }
244
245                 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
246
247                 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
248
249                 int rowBytes = channels * width;
250                 for (int y = 0; y < height; ++y) {
251                     unsigned char *scanLine = snapshot.scanLine(y);
252
253                     while (process.bytesAvailable() < rowBytes) {
254                         if (!process.waitForReadyRead(-1)) {
255                             qDebug() << "QProcess::waitForReadyRead failed";
256                             break;
257                         }
258                     }
259
260                     qint64 read = process.read((char *) scanLine, rowBytes);
261                     Q_ASSERT(read == rowBytes);
262                 }
263
264                 QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
265                 thumbnails.append(thumbnail);
266             }
267
268             Q_ASSERT(process.state() != QProcess::Running);
269
270         } else {
271             QByteArray output;
272             output = process.readAllStandardOutput();
273             msg = QString::fromUtf8(output);
274         }
275     }
276
277     /*
278      * Wait for process termination
279      */
280
281     process.waitForFinished(-1);
282
283     if (process.exitStatus() != QProcess::NormalExit) {
284         msg = QLatin1String("Process crashed");
285     } else if (process.exitCode() != 0) {
286         msg = QLatin1String("Process exited with non zero exit code");
287     }
288
289     /*
290      * Parse errors.
291      */
292
293     QList<ApiTraceError> errors;
294     process.setReadChannel(QProcess::StandardError);
295     QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
296     while (!process.atEnd()) {
297         QString line = process.readLine();
298         if (regexp.indexIn(line) != -1) {
299             ApiTraceError error;
300             error.callIndex = regexp.cap(1).toInt();
301             error.type = regexp.cap(2);
302             error.message = regexp.cap(3);
303             errors.append(error);
304         }
305     }
306
307     /*
308      * Emit signals
309      */
310
311     if (m_captureState) {
312         ApiTraceState *state = new ApiTraceState(parsedJson);
313         emit foundState(state);
314         msg = QLatin1String("State fetched.");
315     }
316
317     if (m_captureThumbnails && !thumbnails.isEmpty()) {
318         emit foundThumbnails(thumbnails);
319     }
320
321     if (!errors.isEmpty()) {
322         emit retraceErrors(errors);
323     }
324
325     emit finished(msg);
326 }
327
328 #include "retracer.moc"