3 #include "apitracecall.h"
12 #include <qjson/parser.h>
15 * Wrapper around a QProcess which enforces IO to block .
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.
23 * This class wraps around QProcess, providing QIODevice interface, while
24 * ensuring that all reads block.
26 * This class also works around a bug in QProcess::atEnd() implementation.
29 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
30 * - http://qt-project.org/wiki/Custom_IO_Device
32 class BlockingIODevice : public QIODevice
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
38 BlockingIODevice(QProcess * io);
39 bool isSequential() const;
41 bool waitForReadyRead(int msecs = -1);
44 qint64 readData(char * data, qint64 maxSize);
45 qint64 writeData(const char * data, qint64 maxSize);
51 BlockingIODevice::BlockingIODevice(QProcess * io) :
55 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
56 * its own buffering on top of the overridden readData() method.
58 * The only buffering used will be to satisfy QIODevice::peek() and
59 * QIODevice::ungetChar().
61 setOpenMode(ReadOnly | Unbuffered);
64 bool BlockingIODevice::isSequential() const
69 bool BlockingIODevice::atEnd() const
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.
75 if (m_device->atEnd()) {
76 if (m_device->state() == QProcess::Running) {
77 if (!m_device->waitForReadyRead(-1)) {
85 bool BlockingIODevice::waitForReadyRead(int msecs)
91 qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
93 qint64 bytesToRead = maxSize;
96 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
104 Q_ASSERT(chunkSize <= bytesToRead);
105 bytesToRead -= chunkSize;
106 readSoFar += chunkSize;
108 if (!m_device->waitForReadyRead(-1)) {
109 qDebug() << "waitForReadyRead failed\n";
113 } while(bytesToRead);
118 qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
124 Q_DECLARE_METATYPE(QList<ApiTraceError>);
126 Retracer::Retracer(QObject *parent)
128 m_benchmarking(false),
129 m_doubleBuffered(true),
130 m_captureState(false),
133 qRegisterMetaType<QList<ApiTraceError> >();
136 QString format = QLatin1String("%1;");
138 QString format = QLatin1String("%1:");
140 QString buildPath = format.arg(APITRACE_BINARY_DIR);
141 m_processEnvironment = QProcessEnvironment::systemEnvironment();
142 m_processEnvironment.insert("PATH", buildPath +
143 m_processEnvironment.value("PATH"));
146 m_processEnvironment.value("PATH").toLatin1());
149 QString Retracer::fileName() const
154 void Retracer::setFileName(const QString &name)
159 void Retracer::setAPI(trace::API api)
164 bool Retracer::isBenchmarking() const
166 return m_benchmarking;
169 void Retracer::setBenchmarking(bool bench)
171 m_benchmarking = bench;
174 bool Retracer::isDoubleBuffered() const
176 return m_doubleBuffered;
179 void Retracer::setDoubleBuffered(bool db)
181 m_doubleBuffered = db;
184 void Retracer::setCaptureAtCallNumber(qlonglong num)
189 qlonglong Retracer::captureAtCallNumber() const
191 return m_captureCall;
194 bool Retracer::captureState() const
196 return m_captureState;
199 void Retracer::setCaptureState(bool enable)
201 m_captureState = enable;
204 bool Retracer::captureThumbnails() const
206 return m_captureThumbnails;
209 void Retracer::setCaptureThumbnails(bool enable)
211 m_captureThumbnails = enable;
216 * Starting point for the retracing thread.
218 * Overrides QThread::run().
225 * Construct command line
229 QStringList arguments;
231 if (m_api == trace::API_GL) {
232 prog = QLatin1String("glretrace");
233 } else if (m_api == trace::API_EGL) {
234 prog = QLatin1String("eglretrace");
240 if (m_doubleBuffered) {
241 arguments << QLatin1String("-db");
243 arguments << QLatin1String("-sb");
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");
256 arguments << m_fileName;
264 process.start(prog, arguments, QIODevice::ReadOnly);
265 if (!process.waitForStarted(-1)) {
266 emit finished(QLatin1String("Could not start process"));
271 * Process standard output
274 QList<QImage> thumbnails;
275 QVariantMap parsedJson;
277 process.setReadChannel(QProcess::StandardOutput);
278 if (process.waitForReadyRead(-1)) {
279 BlockingIODevice io(&process);
281 if (m_captureState) {
283 * Parse JSON from the output.
285 * XXX: QJSON expects blocking IO.
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.
293 QJson::Parser jsonParser;
294 parsedJson = jsonParser.parse(&io, &ok).toMap();
296 msg = QLatin1String("failed to parse JSON");
298 } else if (m_captureThumbnails) {
300 * Parse concatenated PNM images from output.
303 while (!io.atEnd()) {
304 unsigned channels = 0;
309 qint64 headerSize = 0;
310 int headerLines = 3; // assume no optional comment line
312 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
313 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
315 // if header actually contains optional comment line, ...
316 if (headerLine == 1 && header[headerSize] == '#') {
320 headerSize += headerRead;
323 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
325 // if invalid PNM header was encountered, ...
326 if (header == headerEnd) {
327 qDebug() << "error: invalid snapshot stream encountered";
331 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
333 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
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);
342 QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation);
343 thumbnails.append(thumbnail);
346 Q_ASSERT(process.state() != QProcess::Running);
350 output = process.readAllStandardOutput();
351 msg = QString::fromUtf8(output);
356 * Wait for process termination
359 process.waitForFinished(-1);
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");
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) {
378 error.callIndex = regexp.cap(1).toInt();
379 error.type = regexp.cap(2);
380 error.message = regexp.cap(3);
381 errors.append(error);
389 if (m_captureState) {
390 ApiTraceState *state = new ApiTraceState(parsedJson);
391 emit foundState(state);
392 msg = QLatin1String("State fetched.");
395 if (m_captureThumbnails && !thumbnails.isEmpty()) {
396 emit foundThumbnails(thumbnails);
399 if (!errors.isEmpty()) {
400 emit retraceErrors(errors);
406 #include "retracer.moc"