From 5bba477616c20710142165f6c67eb9b6571aa086 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Fonseca?= Date: Sun, 25 Mar 2012 12:46:04 +0100 Subject: [PATCH] Don't buffer all stdout from retrace when generating thumbnails. --- gui/retracer.cpp | 379 +++++++++++++++++++++++-------------------------------- gui/retracer.h | 64 ---------- 2 files changed, 155 insertions(+), 288 deletions(-) diff --git a/gui/retracer.cpp b/gui/retracer.cpp index fa22011..ca9da9a 100644 --- a/gui/retracer.cpp +++ b/gui/retracer.cpp @@ -1,4 +1,5 @@ #include "retracer.h" +#include #include "apitracecall.h" @@ -11,6 +12,8 @@ #include +Q_DECLARE_METATYPE(QList); + Retracer::Retracer(QObject *parent) : QThread(parent), m_benchmarking(false), @@ -18,6 +21,8 @@ Retracer::Retracer(QObject *parent) m_captureState(false), m_captureCall(0) { + qRegisterMetaType >(); + #ifdef Q_OS_WIN QString format = QLatin1String("%1;"); #else @@ -98,49 +103,19 @@ void Retracer::setCaptureThumbnails(bool enable) } +/** + * Starting point for the retracing thread. + * + * Overrides QThread::run(). + */ void Retracer::run() { - RetraceProcess *retrace = new RetraceProcess(); - retrace->process()->setProcessEnvironment(m_processEnvironment); - - retrace->setFileName(m_fileName); - retrace->setAPI(m_api); - retrace->setBenchmarking(m_benchmarking); - retrace->setDoubleBuffered(m_doubleBuffered); - retrace->setCaptureState(m_captureState); - retrace->setCaptureThumbnails(m_captureThumbnails); - retrace->setCaptureAtCallNumber(m_captureCall); - - connect(retrace, SIGNAL(finished(const QString&)), - this, SLOT(cleanup())); - connect(retrace, SIGNAL(error(const QString&)), - this, SLOT(cleanup())); - connect(retrace, SIGNAL(finished(const QString&)), - this, SIGNAL(finished(const QString&))); - connect(retrace, SIGNAL(error(const QString&)), - this, SIGNAL(error(const QString&))); - connect(retrace, SIGNAL(foundState(ApiTraceState*)), - this, SIGNAL(foundState(ApiTraceState*))); - connect(retrace, SIGNAL(foundThumbnails(const QList&)), - this, SIGNAL(foundThumbnails(const QList&))); - connect(retrace, SIGNAL(retraceErrors(const QList&)), - this, SIGNAL(retraceErrors(const QList&))); - - retrace->start(); - - exec(); - - /* means we need to kill the process */ - if (retrace->process()->state() != QProcess::NotRunning) { - retrace->terminate(); - } - - delete retrace; -} + QString msg; + /* + * Construct command line + */ -void RetraceProcess::start() -{ QString prog; QStringList arguments; @@ -149,7 +124,7 @@ void RetraceProcess::start() } else if (m_api == trace::API_EGL) { prog = QLatin1String("eglretrace"); } else { - assert(0); + Q_ASSERT(0); return; } @@ -159,107 +134,167 @@ void RetraceProcess::start() arguments << QLatin1String("-sb"); } - if (m_captureState || m_captureThumbnails) { - if (m_captureState) { - arguments << QLatin1String("-D"); - arguments << QString::number(m_captureCall); - } - if (m_captureThumbnails) { - arguments << QLatin1String("-s"); // emit snapshots - arguments << QLatin1String("-"); // emit to stdout - } - } else { - if (m_benchmarking) { - arguments << QLatin1String("-b"); - } + if (m_captureState) { + arguments << QLatin1String("-D"); + arguments << QString::number(m_captureCall); + } else if (m_captureThumbnails) { + arguments << QLatin1String("-s"); // emit snapshots + arguments << QLatin1String("-"); // emit to stdout + } else if (m_benchmarking) { + arguments << QLatin1String("-b"); } arguments << m_fileName; - m_process->start(prog, arguments); -} + /* + * Start the process. + */ + QProcess process; -void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - QString msg; + process.start(prog, arguments); + if (!process.waitForStarted(-1)) { + emit finished(QLatin1String("Could not start process")); + return; + } - if (exitStatus != QProcess::NormalExit) { - msg = QLatin1String("Process crashed"); - } else if (exitCode != 0) { - msg = QLatin1String("Process exited with non zero exit code"); - } else { - if (m_captureState || m_captureThumbnails) { - if (m_captureState) { - bool ok = false; - m_process->setReadChannel(QProcess::StandardOutput); - QVariantMap parsedJson = m_jsonParser->parse(m_process, &ok).toMap(); - ApiTraceState *state = new ApiTraceState(parsedJson); - emit foundState(state); - msg = tr("State fetched."); - } - if (m_captureThumbnails) { - m_process->setReadChannel(QProcess::StandardOutput); + /* + * Process standard output + */ - QList thumbnails; + QList thumbnails; + QVariantMap parsedJson; - while (!m_process->atEnd()) { - unsigned channels = 0; - unsigned width = 0; - unsigned height = 0; + process.setReadChannel(QProcess::StandardOutput); + if (process.waitForReadyRead(-1)) { + if (m_captureState) { + /* + * Parse JSON from the output. + * + * XXX: QJSON does not wait for QIODevice::waitForReadyRead so we + * need to buffer all stdout. + * + * XXX: QJSON's scanner is inneficient as it abuses single + * character QIODevice::peek (not cheap), instead of maintaining a + * lookahead character on its own. + */ + + if (!process.waitForFinished(-1)) { + return; + } - char header[512]; - qint64 headerSize = 0; - int headerLines = 3; // assume no optional comment line + bool ok = false; + QJson::Parser jsonParser; + parsedJson = jsonParser.parse(&process, &ok).toMap(); + if (!ok) { + msg = QLatin1String("failed to parse JSON"); + } + } else if (m_captureThumbnails) { + /* + * Parse concatenated PNM images from output. + */ + + while (true) { + /* + * QProcess::atEnd() documentation is wrong -- it will return + * true even when the process is running --, so try to handle + * that here. + */ + if (process.atEnd()) { + if (process.state() == QProcess::Running) { + if (!process.waitForReadyRead(-1)) { + break; + } + } + } - for (int headerLine = 0; headerLine < headerLines; ++headerLine) { - qint64 headerRead = m_process->readLine(&header[headerSize], sizeof(header) - headerSize); + unsigned channels = 0; + unsigned width = 0; + unsigned height = 0; - // if header actually contains optional comment line, ... - if (headerLine == 1 && header[headerSize] == '#') { - ++headerLines; - } + char header[512]; + qint64 headerSize = 0; + int headerLines = 3; // assume no optional comment line - headerSize += headerRead; + for (int headerLine = 0; headerLine < headerLines; ++headerLine) { + while (!process.canReadLine()) { + if (!process.waitForReadyRead(-1)) { + qDebug() << "QProcess::waitForReadyRead failed"; + break; + } } - const char *headerEnd = image::readPNMHeader(header, headerSize, &channels, &width, &height); + qint64 headerRead = process.readLine(&header[headerSize], sizeof(header) - headerSize); - // if invalid PNM header was encountered, ... - if (header == headerEnd) { - qDebug() << "error: invalid snapshot stream encountered\n"; - break; + // if header actually contains optional comment line, ... + if (headerLine == 1 && header[headerSize] == '#') { + ++headerLines; } - //qDebug() << "channels: " << channels << ", width: " << width << ", height: " << height << "\n"; + headerSize += headerRead; + } - QImage snapshot = QImage(width, height, channels == 1 ? QImage::Format_Mono : QImage::Format_RGB888); + 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; + } - int rowBytes = channels * width; - for (int y = 0; y < height; ++y) { - unsigned char *scanLine = snapshot.scanLine(y); - m_process->read((char *) scanLine, rowBytes); + // 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); + + while (process.bytesAvailable() < rowBytes) { + if (!process.waitForReadyRead(-1)) { + qDebug() << "QProcess::waitForReadyRead failed"; + break; + } } - QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation); - thumbnails.append(thumbnail); + qint64 read = process.read((char *) scanLine, rowBytes); + Q_ASSERT(read == rowBytes); } - emit foundThumbnails(thumbnails); - msg = tr("Thumbnails fetched."); + QImage thumbnail = snapshot.scaled(16, 16, Qt::KeepAspectRatio, Qt::FastTransformation); + thumbnails.append(thumbnail); } + + Q_ASSERT(process.state() != QProcess::Running); + } else { QByteArray output; - output = m_process->readAllStandardOutput(); + output = process.readAllStandardOutput(); msg = QString::fromUtf8(output); } } - m_process->setReadChannel(QProcess::StandardError); + /* + * 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 (!m_process->atEnd()) { - QString line = m_process->readLine(); + while (!process.atEnd()) { + QString line = process.readLine(); if (regexp.indexIn(line) != -1) { ApiTraceError error; error.callIndex = regexp.cap(1).toInt(); @@ -268,130 +303,26 @@ void RetraceProcess::replayFinished(int exitCode, QProcess::ExitStatus exitStatu errors.append(error); } } - if (!errors.isEmpty()) { - emit retraceErrors(errors); - } - emit finished(msg); -} -void RetraceProcess::replayError(QProcess::ProcessError err) -{ /* - * XXX: this function is likely unnecessary and should be eliminated given - * that replayFinished is always called, even on errors. + * Emit signals */ -#if 0 - qDebug()<<"Process error = "<); -RetraceProcess::RetraceProcess(QObject *parent) - : QObject(parent) -{ - m_process = new QProcess(this); - m_jsonParser = new QJson::Parser(); - - qRegisterMetaType >(); - - connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(replayFinished(int, QProcess::ExitStatus))); - connect(m_process, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(replayError(QProcess::ProcessError))); -} - -QProcess * RetraceProcess::process() const -{ - return m_process; -} - -QString RetraceProcess::fileName() const -{ - return m_fileName; -} - -void RetraceProcess::setFileName(const QString &name) -{ - m_fileName = name; -} - -void RetraceProcess::setAPI(trace::API api) -{ - m_api = api; -} - -bool RetraceProcess::isBenchmarking() const -{ - return m_benchmarking; -} - -void RetraceProcess::setBenchmarking(bool bench) -{ - m_benchmarking = bench; -} - -bool RetraceProcess::isDoubleBuffered() const -{ - return m_doubleBuffered; -} - -void RetraceProcess::setDoubleBuffered(bool db) -{ - m_doubleBuffered = db; -} - -void RetraceProcess::setCaptureAtCallNumber(qlonglong num) -{ - m_captureCall = num; -} - -qlonglong RetraceProcess::captureAtCallNumber() const -{ - return m_captureCall; -} - -bool RetraceProcess::captureState() const -{ - return m_captureState; -} - -void RetraceProcess::setCaptureState(bool enable) -{ - m_captureState = enable; -} - -bool RetraceProcess::captureThumbnails() const -{ - return m_captureThumbnails; -} - -void RetraceProcess::setCaptureThumbnails(bool enable) -{ - m_captureThumbnails = enable; -} + if (m_captureState) { + ApiTraceState *state = new ApiTraceState(parsedJson); + emit foundState(state); + msg = QLatin1String("State fetched."); + } -void RetraceProcess::terminate() -{ - if (m_process) { - m_process->terminate(); - emit finished(tr("Process terminated.")); + if (m_captureThumbnails && !thumbnails.isEmpty()) { + emit foundThumbnails(thumbnails); } -} -void Retracer::cleanup() -{ - quit(); -} + if (!errors.isEmpty()) { + emit retraceErrors(errors); + } -RetraceProcess::~RetraceProcess() -{ - delete m_jsonParser; + emit finished(msg); } #include "retracer.moc" diff --git a/gui/retracer.h b/gui/retracer.h index 1a78841..d6da7ac 100644 --- a/gui/retracer.h +++ b/gui/retracer.h @@ -8,68 +8,6 @@ #include class ApiTraceState; -namespace QJson { - class Parser; -} - -/* internal class used by the retracer to run - * in the thread */ -class RetraceProcess : public QObject -{ - Q_OBJECT -public: - RetraceProcess(QObject *parent=0); - ~RetraceProcess(); - - QProcess *process() const; - - QString fileName() const; - void setFileName(const QString &name); - - void setAPI(trace::API api); - - bool isBenchmarking() const; - void setBenchmarking(bool bench); - - bool isDoubleBuffered() const; - void setDoubleBuffered(bool db); - - void setCaptureAtCallNumber(qlonglong num); - qlonglong captureAtCallNumber() const; - - bool captureState() const; - void setCaptureState(bool enable); - - bool captureThumbnails() const; - void setCaptureThumbnails(bool enable); - -public slots: - void start(); - void terminate(); - -signals: - void finished(const QString &output); - void error(const QString &msg); - void foundState(ApiTraceState *state); - void foundThumbnails(const QList &thumbnails); - void retraceErrors(const QList &errors); - -private slots: - void replayFinished(int exitCode, QProcess::ExitStatus exitStatus); - void replayError(QProcess::ProcessError err); - -private: - QString m_fileName; - trace::API m_api; - bool m_benchmarking; - bool m_doubleBuffered; - bool m_captureState; - bool m_captureThumbnails; - qlonglong m_captureCall; - - QProcess *m_process; - QJson::Parser *m_jsonParser; -}; class Retracer : public QThread { @@ -107,8 +45,6 @@ signals: protected: virtual void run(); -private slots: - void cleanup(); private: QString m_fileName; trace::API m_api; -- 2.7.4