3 #include "apitracecall.h"
6 #include "image/image.hpp"
8 #include "trace_profiler.hpp"
15 #include <qjson/parser.h>
18 * Wrapper around a QProcess which enforces IO to block .
20 * Several QIODevice users (notably QJSON) expect blocking semantics, e.g.,
21 * they expect that QIODevice::read() will blocked until the requested ammount
22 * of bytes is read or end of file is reached. But by default QProcess, does
23 * not block. And passing QIODevice::Unbuffered mitigates but does not fully
24 * address the problem either.
26 * This class wraps around QProcess, providing QIODevice interface, while
27 * ensuring that all reads block.
29 * This class also works around a bug in QProcess::atEnd() implementation.
32 * - http://qt-project.org/wiki/Simple_Crypt_IO_Device
33 * - http://qt-project.org/wiki/Custom_IO_Device
35 class BlockingIODevice : public QIODevice
37 /* We don't use the Q_OBJECT in this class given we don't declare any
38 * signals and slots or use any other services provided by Qt's meta-object
41 BlockingIODevice(QProcess * io);
42 bool isSequential() const;
44 bool waitForReadyRead(int msecs = -1);
47 qint64 readData(char * data, qint64 maxSize);
48 qint64 writeData(const char * data, qint64 maxSize);
54 BlockingIODevice::BlockingIODevice(QProcess * io) :
58 * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do
59 * its own buffering on top of the overridden readData() method.
61 * The only buffering used will be to satisfy QIODevice::peek() and
62 * QIODevice::ungetChar().
64 setOpenMode(ReadOnly | Unbuffered);
67 bool BlockingIODevice::isSequential() const
72 bool BlockingIODevice::atEnd() const
75 * XXX: QProcess::atEnd() documentation is wrong -- it will return true
76 * even when the process is running --, so we try to workaround that here.
78 if (m_device->atEnd()) {
79 if (m_device->state() == QProcess::Running) {
80 if (!m_device->waitForReadyRead(-1)) {
88 bool BlockingIODevice::waitForReadyRead(int msecs)
94 qint64 BlockingIODevice::readData(char * data, qint64 maxSize)
96 qint64 bytesToRead = maxSize;
99 qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead);
107 Q_ASSERT(chunkSize <= bytesToRead);
108 bytesToRead -= chunkSize;
109 readSoFar += chunkSize;
111 if (!m_device->waitForReadyRead(-1)) {
112 qDebug() << "waitForReadyRead failed\n";
116 } while(bytesToRead);
121 qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize)
127 Q_DECLARE_METATYPE(QList<ApiTraceError>);
129 Retracer::Retracer(QObject *parent)
131 m_benchmarking(false),
132 m_doubleBuffered(true),
133 m_singlethread(false),
134 m_captureState(false),
138 m_profilePixels(false)
140 qRegisterMetaType<QList<ApiTraceError> >();
143 QString Retracer::fileName() const
148 void Retracer::setFileName(const QString &name)
153 QString Retracer::remoteTarget() const
155 return m_remoteTarget;
158 void Retracer::setRemoteTarget(const QString &host)
160 m_remoteTarget = host;
163 void Retracer::setAPI(trace::API api)
168 bool Retracer::isBenchmarking() const
170 return m_benchmarking;
173 void Retracer::setBenchmarking(bool bench)
175 m_benchmarking = bench;
178 bool Retracer::isDoubleBuffered() const
180 return m_doubleBuffered;
183 void Retracer::setDoubleBuffered(bool db)
185 m_doubleBuffered = db;
188 bool Retracer::isSinglethread() const
190 return m_singlethread;
193 void Retracer::setSinglethread(bool singlethread)
195 m_singlethread = singlethread;
198 bool Retracer::isProfilingGpu() const
203 bool Retracer::isProfilingCpu() const
208 bool Retracer::isProfilingPixels() const
210 return m_profilePixels;
213 bool Retracer::isProfiling() const
215 return m_profileGpu || m_profileCpu || m_profilePixels;
218 void Retracer::setProfiling(bool gpu, bool cpu, bool pixels)
222 m_profilePixels = pixels;
225 void Retracer::setCaptureAtCallNumber(qlonglong num)
230 qlonglong Retracer::captureAtCallNumber() const
232 return m_captureCall;
235 bool Retracer::captureState() const
237 return m_captureState;
240 void Retracer::setCaptureState(bool enable)
242 m_captureState = enable;
245 bool Retracer::captureThumbnails() const
247 return m_captureThumbnails;
250 void Retracer::setCaptureThumbnails(bool enable)
252 m_captureThumbnails = enable;
256 * Starting point for the retracing thread.
258 * Overrides QThread::run().
262 QString msg = QLatin1String("Replay finished!");
265 * Construct command line
269 QStringList arguments;
273 prog = QLatin1String("glretrace");
276 prog = QLatin1String("eglretrace");
279 case trace::API_D3D7:
280 case trace::API_D3D8:
281 case trace::API_D3D9:
282 case trace::API_DXGI:
284 prog = QLatin1String("d3dretrace");
286 prog = QLatin1String("wine");
287 arguments << QLatin1String("d3dretrace.exe");
291 emit finished(QLatin1String("Unsupported API"));
295 if (m_singlethread) {
296 arguments << QLatin1String("--singlethread");
299 if (m_captureState) {
300 arguments << QLatin1String("-D");
301 arguments << QString::number(m_captureCall);
302 } else if (m_captureThumbnails) {
303 arguments << QLatin1String("-s"); // emit snapshots
304 arguments << QLatin1String("-"); // emit to stdout
305 } else if (isProfiling()) {
307 arguments << QLatin1String("--pgpu");
311 arguments << QLatin1String("--pcpu");
314 if (m_profilePixels) {
315 arguments << QLatin1String("--ppd");
318 if (m_doubleBuffered) {
319 arguments << QLatin1String("--db");
321 arguments << QLatin1String("--sb");
324 if (m_benchmarking) {
325 arguments << QLatin1String("-b");
329 arguments << m_fileName;
332 * Support remote execution on a separate target.
335 if (m_remoteTarget.length() != 0) {
336 arguments.prepend(prog);
337 arguments.prepend(m_remoteTarget);
338 prog = QLatin1String("ssh");
347 process.start(prog, arguments, QIODevice::ReadOnly);
348 if (!process.waitForStarted(-1)) {
349 emit finished(QLatin1String("Could not start process"));
354 * Process standard output
357 QList<QImage> thumbnails;
358 QVariantMap parsedJson;
359 trace::Profile* profile = NULL;
361 process.setReadChannel(QProcess::StandardOutput);
362 if (process.waitForReadyRead(-1)) {
363 BlockingIODevice io(&process);
365 if (m_captureState) {
367 * Parse JSON from the output.
369 * XXX: QJSON's scanner is inneficient as it abuses single
370 * character QIODevice::peek (not cheap), instead of maintaining a
371 * lookahead character on its own.
375 QJson::Parser jsonParser;
377 // Allow Nan/Infinity
378 jsonParser.allowSpecialNumbers(true);
380 parsedJson = jsonParser.parse(&io, &ok).toMap();
383 * XXX: QJSON expects blocking IO, and it looks like
384 * BlockingIODevice does not work reliably in all cases.
386 process.waitForFinished(-1);
387 parsedJson = jsonParser.parse(&process, &ok).toMap();
390 msg = QLatin1String("failed to parse JSON");
392 } else if (m_captureThumbnails) {
394 * Parse concatenated PNM images from output.
397 while (!io.atEnd()) {
401 qint64 headerSize = 0;
402 int headerLines = 3; // assume no optional comment line
404 for (int headerLine = 0; headerLine < headerLines; ++headerLine) {
405 qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize);
407 // if header actually contains optional comment line, ...
408 if (headerLine == 1 && header[headerSize] == '#') {
412 headerSize += headerRead;
415 const char *headerEnd = image::readPNMHeader(header, headerSize, info);
417 // if invalid PNM header was encountered, ...
418 if (headerEnd == NULL ||
419 info.channelType != image::TYPE_UNORM8) {
420 qDebug() << "error: invalid snapshot stream encountered";
424 unsigned channels = info.channels;
425 unsigned width = info.width;
426 unsigned height = info.height;
428 // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height";
430 QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888);
432 int rowBytes = channels * width;
433 for (int y = 0; y < height; ++y) {
434 unsigned char *scanLine = snapshot.scanLine(y);
435 qint64 readBytes = io.read((char *) scanLine, rowBytes);
436 Q_ASSERT(readBytes == rowBytes);
440 QImage thumb = thumbnail(snapshot);
441 thumbnails.append(thumb);
444 Q_ASSERT(process.state() != QProcess::Running);
445 } else if (isProfiling()) {
446 profile = new trace::Profile();
448 while (!io.atEnd()) {
452 lineLength = io.readLine(line, 256);
454 if (lineLength == -1)
457 trace::Profiler::parseLine(line, profile);
461 output = process.readAllStandardOutput();
462 if (output.length() < 80) {
463 msg = QString::fromUtf8(output);
469 * Wait for process termination
472 process.waitForFinished(-1);
474 if (process.exitStatus() != QProcess::NormalExit) {
475 msg = QLatin1String("Process crashed");
476 } else if (process.exitCode() != 0) {
477 msg = QLatin1String("Process exited with non zero exit code");
484 QList<ApiTraceError> errors;
485 process.setReadChannel(QProcess::StandardError);
486 QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$");
487 while (!process.atEnd()) {
488 QString line = process.readLine();
489 if (regexp.indexIn(line) != -1) {
491 error.callIndex = regexp.cap(1).toInt();
492 error.type = regexp.cap(2);
493 error.message = regexp.cap(3);
494 errors.append(error);
495 } else if (!errors.isEmpty()) {
496 // Probably a multiligne message
497 ApiTraceError &previous = errors.last();
498 if (line.endsWith("\n")) {
501 previous.message.append('\n');
502 previous.message.append(line);
510 if (m_captureState) {
511 ApiTraceState *state = new ApiTraceState(parsedJson);
512 emit foundState(state);
515 if (m_captureThumbnails && !thumbnails.isEmpty()) {
516 emit foundThumbnails(thumbnails);
519 if (isProfiling() && profile) {
520 emit foundProfile(profile);
523 if (!errors.isEmpty()) {
524 emit retraceErrors(errors);
530 #include "retracer.moc"