3 #include "apitracecall.h"
13 #include <qjson/parser.h>
16 * Wrapper around a QProcess which enforces IO to block .
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.
24 * This class wraps around QProcess, providing QIODevice interface, while
25 * ensuring that all reads block.
27 * This class also works around a bug in QProcess::atEnd() implementation.
30 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
31 * - http://qt-project.org/wiki/Custom_IO_Device
33 class BlockingIODevice : public QIODevice
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
39 BlockingIODevice(QProcess * io);
40 bool isSequential() const;
42 bool waitForReadyRead(int msecs = -1);
45 qint64 readData(char * data, qint64 maxSize);
46 qint64 writeData(const char * data, qint64 maxSize);
52 BlockingIODevice::BlockingIODevice(QProcess * io) :
56 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
57 * its own buffering on top of the overridden readData() method.
59 * The only buffering used will be to satisfy QIODevice::peek() and
60 * QIODevice::ungetChar().
62 setOpenMode(ReadOnly | Unbuffered);
65 bool BlockingIODevice::isSequential() const
70 bool BlockingIODevice::atEnd() const
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.
76 if (m_device->atEnd()) {
77 if (m_device->state() == QProcess::Running) {
78 if (!m_device->waitForReadyRead(-1)) {
86 bool BlockingIODevice::waitForReadyRead(int msecs)
92 qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
94 qint64 bytesToRead = maxSize;
97 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
105 Q_ASSERT(chunkSize <= bytesToRead);
106 bytesToRead -= chunkSize;
107 readSoFar += chunkSize;
109 if (!m_device->waitForReadyRead(-1)) {
110 qDebug() << "waitForReadyRead failed\n";
114 } while(bytesToRead);
119 qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
125 Q_DECLARE_METATYPE(QList<ApiTraceError>);
127 Retracer::Retracer(QObject *parent)
129 m_benchmarking(false),
130 m_doubleBuffered(true),
131 m_captureState(false),
134 qRegisterMetaType<QList<ApiTraceError> >();
137 QString format = QLatin1String("%1;");
139 QString format = QLatin1String("%1:");
141 QString buildPath = format.arg(APITRACE_BINARY_DIR);
142 m_processEnvironment = QProcessEnvironment::systemEnvironment();
143 m_processEnvironment.insert("PATH", buildPath +
144 m_processEnvironment.value("PATH"));
147 m_processEnvironment.value("PATH").toLatin1());
150 QString Retracer::fileName() const
155 void Retracer::setFileName(const QString &name)
160 void Retracer::setAPI(trace::API api)
165 bool Retracer::isBenchmarking() const
167 return m_benchmarking;
170 void Retracer::setBenchmarking(bool bench)
172 m_benchmarking = bench;
175 bool Retracer::isDoubleBuffered() const
177 return m_doubleBuffered;
180 void Retracer::setDoubleBuffered(bool db)
182 m_doubleBuffered = db;
185 void Retracer::setCaptureAtCallNumber(qlonglong num)
190 qlonglong Retracer::captureAtCallNumber() const
192 return m_captureCall;
195 bool Retracer::captureState() const
197 return m_captureState;
200 void Retracer::setCaptureState(bool enable)
202 m_captureState = enable;
205 bool Retracer::captureThumbnails() const
207 return m_captureThumbnails;
210 void Retracer::setCaptureThumbnails(bool enable)
212 m_captureThumbnails = enable;
217 * Starting point for the retracing thread.
219 * Overrides QThread::run().
223 QString msg = QLatin1String("Replay finished!");
226 * Construct command line
230 QStringList arguments;
234 prog = QLatin1String("glretrace");
237 prog = QLatin1String("eglretrace");
240 case trace::API_D3D7:
241 case trace::API_D3D8:
242 case trace::API_D3D9:
243 case trace::API_D3D10:
244 case trace::API_D3D10_1:
245 case trace::API_D3D11:
247 prog = QLatin1String("d3dretrace");
249 prog = QLatin1String("wine");
250 arguments << QLatin1String("d3dretrace.exe");
254 emit finished(QLatin1String("Unsupported API"));
258 if (m_doubleBuffered) {
259 arguments << QLatin1String("-db");
261 arguments << QLatin1String("-sb");
264 if (m_captureState) {
265 arguments << QLatin1String("-D");
266 arguments << QString::number(m_captureCall);
267 } else if (m_captureThumbnails) {
268 arguments << QLatin1String("-s"); // emit snapshots
269 arguments << QLatin1String("-"); // emit to stdout
270 } else if (m_benchmarking) {
271 arguments << QLatin1String("-b");
274 arguments << m_fileName;
282 process.start(prog, arguments, QIODevice::ReadOnly);
283 if (!process.waitForStarted(-1)) {
284 emit finished(QLatin1String("Could not start process"));
289 * Process standard output
292 QList<QImage> thumbnails;
293 QVariantMap parsedJson;
295 process.setReadChannel(QProcess::StandardOutput);
296 if (process.waitForReadyRead(-1)) {
297 BlockingIODevice io(&process);
299 if (m_captureState) {
301 * Parse JSON from the output.
303 * XXX: QJSON's scanner is inneficient as it abuses single
304 * character QIODevice::peek (not cheap), instead of maintaining a
305 * lookahead character on its own.
309 QJson::Parser jsonParser;
311 parsedJson = jsonParser.parse(&io, &ok).toMap();
314 * XXX: QJSON expects blocking IO, and it looks like
315 * BlockingIODevice does not work reliably in all cases.
317 process.waitForFinished(-1);
318 parsedJson = jsonParser.parse(&process, &ok).toMap();
321 msg = QLatin1String("failed to parse JSON");
323 } else if (m_captureThumbnails) {
325 * Parse concatenated PNM images from output.
328 while (!io.atEnd()) {
329 unsigned channels = 0;
334 qint64 headerSize = 0;
335 int headerLines = 3; // assume no optional comment line
337 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
338 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
340 // if header actually contains optional comment line, ...
341 if (headerLine == 1 && header[headerSize] == '#') {
345 headerSize += headerRead;
348 const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height);
350 // if invalid PNM header was encountered, ...
351 if (header == headerEnd) {
352 qDebug() << "error: invalid snapshot stream encountered";
356 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
358 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
360 int rowBytes = channels * width;
361 for (int y = 0; y < height; ++y) {
362 unsigned char *scanLine = snapshot.scanLine(y);
363 qint64 readBytes = io.read((char *) scanLine, rowBytes);
364 Q_ASSERT(readBytes == rowBytes);
367 QImage thumb = thumbnail(snapshot);
368 thumbnails.append(thumb);
371 Q_ASSERT(process.state() != QProcess::Running);
375 output = process.readAllStandardOutput();
376 if (output.length() < 80) {
377 msg = QString::fromUtf8(output);
383 * Wait for process termination
386 process.waitForFinished(-1);
388 if (process.exitStatus() != QProcess::NormalExit) {
389 msg = QLatin1String("Process crashed");
390 } else if (process.exitCode() != 0) {
391 msg = QLatin1String("Process exited with non zero exit code");
398 QList<ApiTraceError> errors;
399 process.setReadChannel(QProcess::StandardError);
400 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
401 while (!process.atEnd()) {
402 QString line = process.readLine();
403 if (regexp.indexIn(line) != -1) {
405 error.callIndex = regexp.cap(1).toInt();
406 error.type = regexp.cap(2);
407 error.message = regexp.cap(3);
408 errors.append(error);
409 } else if (!errors.isEmpty()) {
410 // Probably a multiligne message
411 ApiTraceError &previous = errors.last();
412 if (line.endsWith("\n")) {
415 previous.message.append('\n');
416 previous.message.append(line);
424 if (m_captureState) {
425 ApiTraceState *state = new ApiTraceState(parsedJson);
426 emit foundState(state);
427 msg = QLatin1String("State fetched.");
430 if (m_captureThumbnails && !thumbnails.isEmpty()) {
431 emit foundThumbnails(thumbnails);
434 if (!errors.isEmpty()) {
435 emit retraceErrors(errors);
441 #include "retracer.moc"