X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=gui%2Fretracer.cpp;h=2de9d23ee849de85f12c6cbf51461750ba7edf68;hb=7257dfcc57b3a1af576a8a2ca69992ae8d77dea4;hp=b9d74b627ac429f7d5bfcfc6ba82fe0bbb9ae154;hpb=3acde366b677d3d68e85116f795c7a569ee3d18a;p=apitrace diff --git a/gui/retracer.cpp b/gui/retracer.cpp index b9d74b6..2de9d23 100644 --- a/gui/retracer.cpp +++ b/gui/retracer.cpp @@ -1,15 +1,142 @@ #include "retracer.h" +#include "apitracecall.h" +#include "thumbnail.h" + +#include "image/image.hpp" + +#include "trace_profiler.hpp" + #include +#include +#include +#include + +#include + +/** + * Wrapper around a QProcess which enforces IO to block . + * + * Several QIODevice users (notably QJSON) expect blocking semantics, e.g., + * they expect that QIODevice::read() will blocked until the requested ammount + * of bytes is read or end of file is reached. But by default QProcess, does + * not block. And passing QIODevice::Unbuffered mitigates but does not fully + * address the problem either. + * + * This class wraps around QProcess, providing QIODevice interface, while + * ensuring that all reads block. + * + * This class also works around a bug in QProcess::atEnd() implementation. + * + * See also: + * - http://qt-project.org/wiki/Simple_Crypt_IO_Device + * - http://qt-project.org/wiki/Custom_IO_Device + */ +class BlockingIODevice : public QIODevice +{ + /* We don't use the Q_OBJECT in this class given we don't declare any + * signals and slots or use any other services provided by Qt's meta-object + * system. */ +public: + BlockingIODevice(QProcess * io); + bool isSequential() const; + bool atEnd() const; + bool waitForReadyRead(int msecs = -1); + +protected: + qint64 readData(char * data, qint64 maxSize); + qint64 writeData(const char * data, qint64 maxSize); + +private: + QProcess *m_device; +}; + +BlockingIODevice::BlockingIODevice(QProcess * io) : + m_device(io) +{ + /* + * We pass QIODevice::Unbuffered to prevent the base QIODevice class to do + * its own buffering on top of the overridden readData() method. + * + * The only buffering used will be to satisfy QIODevice::peek() and + * QIODevice::ungetChar(). + */ + setOpenMode(ReadOnly | Unbuffered); +} + +bool BlockingIODevice::isSequential() const +{ + return true; +} + +bool BlockingIODevice::atEnd() const +{ + /* + * XXX: QProcess::atEnd() documentation is wrong -- it will return true + * even when the process is running --, so we try to workaround that here. + */ + if (m_device->atEnd()) { + if (m_device->state() == QProcess::Running) { + if (!m_device->waitForReadyRead(-1)) { + return true; + } + } + } + return false; +} + +bool BlockingIODevice::waitForReadyRead(int msecs) +{ + Q_UNUSED(msecs); + return true; +} + +qint64 BlockingIODevice::readData(char * data, qint64 maxSize) +{ + qint64 bytesToRead = maxSize; + qint64 readSoFar = 0; + do { + qint64 chunkSize = m_device->read(data + readSoFar, bytesToRead); + if (chunkSize < 0) { + if (readSoFar) { + return readSoFar; + } else { + return chunkSize; + } + } + Q_ASSERT(chunkSize <= bytesToRead); + bytesToRead -= chunkSize; + readSoFar += chunkSize; + if (bytesToRead) { + if (!m_device->waitForReadyRead(-1)) { + qDebug() << "waitForReadyRead failed\n"; + break; + } + } + } while(bytesToRead); + + return readSoFar; +} + +qint64 BlockingIODevice::writeData(const char * data, qint64 maxSize) +{ + Q_ASSERT(false); + return -1; +} + +Q_DECLARE_METATYPE(QList); Retracer::Retracer(QObject *parent) - : QObject(parent), - m_benchmarking(true), + : QThread(parent), + m_benchmarking(false), m_doubleBuffered(true), m_captureState(false), m_captureCall(0), - m_process(0) + m_profileGpu(false), + m_profileCpu(false), + m_profilePixels(false) { + qRegisterMetaType >(); } QString Retracer::fileName() const @@ -22,6 +149,21 @@ void Retracer::setFileName(const QString &name) m_fileName = name; } +QString Retracer::remoteTarget() const +{ + return m_remoteTarget; +} + +void Retracer::setRemoteTarget(const QString &host) +{ + m_remoteTarget = host; +} + +void Retracer::setAPI(trace::API api) +{ + m_api = api; +} + bool Retracer::isBenchmarking() const { return m_benchmarking; @@ -42,53 +184,31 @@ void Retracer::setDoubleBuffered(bool db) m_doubleBuffered = db; } -void Retracer::start() +bool Retracer::isProfilingGpu() const { - if (!m_process) { -#ifdef Q_OS_WIN - QString format = QLatin1String("%1;"); -#else - QString format = QLatin1String("%1:"); -#endif - QString buildPath = format.arg(BUILD_DIR); - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("PATH", buildPath + env.value("PATH")); - - qputenv("PATH", env.value("PATH").toLatin1()); - - m_process = new QProcess(this); - m_process->setProcessEnvironment(env); - - connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(replayFinished())); - connect(m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(replayError(QProcess::ProcessError))); - } - - QStringList arguments; + return m_profileGpu; +} - if (m_captureState) { - arguments << QLatin1String("-D"); - arguments << QString::number(m_captureCall); - } else { - if (m_benchmarking) { - arguments << QLatin1String("-b"); - } - if (m_doubleBuffered) { - arguments << QLatin1String("-db"); - } - } +bool Retracer::isProfilingCpu() const +{ + return m_profileCpu; +} - arguments << m_fileName; +bool Retracer::isProfilingPixels() const +{ + return m_profilePixels; +} - m_process->start(QLatin1String("glretrace"), arguments); +bool Retracer::isProfiling() const +{ + return m_profileGpu || m_profileCpu || m_profilePixels; } -void Retracer::terminate() +void Retracer::setProfiling(bool gpu, bool cpu, bool pixels) { - if (m_process) { - m_process->terminate(); - } + m_profileGpu = gpu; + m_profileCpu = cpu; + m_profilePixels = pixels; } void Retracer::setCaptureAtCallNumber(qlonglong num) @@ -111,26 +231,282 @@ void Retracer::setCaptureState(bool enable) m_captureState = enable; } -void Retracer::replayFinished() +bool Retracer::captureThumbnails() const { - QByteArray output = m_process->readAllStandardOutput(); + return m_captureThumbnails; +} -#if 0 - qDebug()<<"Process finished = "; - qDebug()<<"\terr = "<readAllStandardError(); - qDebug()<<"\tout = "< thumbnails; + QVariantMap parsedJson; + trace::Profile* profile = NULL; + + process.setReadChannel(QProcess::StandardOutput); + if (process.waitForReadyRead(-1)) { + BlockingIODevice io(&process); + + if (m_captureState) { + /* + * Parse JSON from the output. + * + * XXX: QJSON's scanner is inneficient as it abuses single + * character QIODevice::peek (not cheap), instead of maintaining a + * lookahead character on its own. + */ + + bool ok = false; + QJson::Parser jsonParser; + + // Allow Nan/Infinity + jsonParser.allowSpecialNumbers(true); +#if 0 + parsedJson = jsonParser.parse(&io, &ok).toMap(); +#else + /* + * XXX: QJSON expects blocking IO, and it looks like + * BlockingIODevice does not work reliably in all cases. + */ + process.waitForFinished(-1); + parsedJson = jsonParser.parse(&process, &ok).toMap(); +#endif + if (!ok) { + msg = QLatin1String("failed to parse JSON"); + } + } else if (m_captureThumbnails) { + /* + * Parse concatenated PNM images from output. + */ + + while (!io.atEnd()) { + unsigned channels = 0; + unsigned width = 0; + unsigned height = 0; + + char header[512]; + qint64 headerSize = 0; + int headerLines = 3; // assume no optional comment line + + for (int headerLine = 0; headerLine < headerLines; ++headerLine) { + qint64 headerRead = io.readLine(&header[headerSize], sizeof(header) - headerSize); + + // if header actually contains optional comment line, ... + if (headerLine == 1 && header[headerSize] == '#') { + ++headerLines; + } + + headerSize += headerRead; + } + + const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height); + + // if invalid PNM header was encountered, ... + if (header == headerEnd) { + qDebug() << "error: invalid snapshot stream encountered"; + break; + } + + // qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height"; + + QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888); + + int rowBytes = channels * width; + for (int y = 0; y < height; ++y) { + unsigned char *scanLine = snapshot.scanLine(y); + qint64 readBytes = io.read((char *) scanLine, rowBytes); + Q_ASSERT(readBytes == rowBytes); + (void)readBytes; + } + + QImage thumb = thumbnail(snapshot); + thumbnails.append(thumb); + } + + Q_ASSERT(process.state() != QProcess::Running); + } else if (isProfiling()) { + profile = new trace::Profile(); + + while (!io.atEnd()) { + char line[256]; + qint64 lineLength; + + lineLength = io.readLine(line, 256); + + if (lineLength == -1) + break; + + trace::Profiler::parseLine(line, profile); + } + } else { + QByteArray output; + output = process.readAllStandardOutput(); + if (output.length() < 80) { + msg = QString::fromUtf8(output); + } + } + } + + /* + * Wait for process termination + */ + + process.waitForFinished(-1); + + if (process.exitStatus() != QProcess::NormalExit) { + msg = QLatin1String("Process crashed"); + } else if (process.exitCode() != 0) { + msg = QLatin1String("Process exited with non zero exit code"); + } + + /* + * Parse errors. + */ + + QList errors; + process.setReadChannel(QProcess::StandardError); + QRegExp regexp("(^\\d+): +(\\b\\w+\\b): ([^\\r\\n]+)[\\r\\n]*$"); + while (!process.atEnd()) { + QString line = process.readLine(); + if (regexp.indexIn(line) != -1) { + ApiTraceError error; + error.callIndex = regexp.cap(1).toInt(); + error.type = regexp.cap(2); + error.message = regexp.cap(3); + errors.append(error); + } else if (!errors.isEmpty()) { + // Probably a multiligne message + ApiTraceError &previous = errors.last(); + if (line.endsWith("\n")) { + line.chop(1); + } + previous.message.append('\n'); + previous.message.append(line); + } + } + + /* + * Emit signals + */ + + if (m_captureState) { + ApiTraceState *state = new ApiTraceState(parsedJson); + emit foundState(state); + } + + if (m_captureThumbnails && !thumbnails.isEmpty()) { + emit foundThumbnails(thumbnails); + } + + if (isProfiling() && profile) { + emit foundProfile(profile); + } + + if (!errors.isEmpty()) { + emit retraceErrors(errors); + } - emit error( - tr("Couldn't execute the replay file '%1'").arg(m_fileName)); + emit finished(msg); } #include "retracer.moc"