diff --git a/src/openstudio_lib/MainWindow.cpp b/src/openstudio_lib/MainWindow.cpp index 762e7b592..aede76df6 100644 --- a/src/openstudio_lib/MainWindow.cpp +++ b/src/openstudio_lib/MainWindow.cpp @@ -261,6 +261,7 @@ void MainWindow::readSettings() { restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("state").toByteArray()); m_displayIP = settings.value("displayIP").toBool(); + m_verboseOutput = settings.value("verboseOutput").toBool(); } void MainWindow::writeSettings() { @@ -272,6 +273,7 @@ void MainWindow::writeSettings() { settings.setValue("geometry", saveGeometry()); settings.setValue("state", saveState()); settings.setValue("displayIP", m_displayIP); + settings.setValue("verboseOutput", m_verboseOutput); } QString MainWindow::lastPath() const { @@ -282,6 +284,14 @@ void MainWindow::toggleUnits(bool displayIP) { m_displayIP = displayIP; } +bool MainWindow::verboseOutput() const { + return m_verboseOutput; +} + +void MainWindow::toggleVerboseOutput(bool verboseOutput) { + m_verboseOutput = verboseOutput; +} + void MainWindow::configureProxyClicked() { QString organizationName = QCoreApplication::organizationName(); QString applicationName = QCoreApplication::applicationName(); diff --git a/src/openstudio_lib/MainWindow.hpp b/src/openstudio_lib/MainWindow.hpp index 3a32e78a5..8d1cf3270 100644 --- a/src/openstudio_lib/MainWindow.hpp +++ b/src/openstudio_lib/MainWindow.hpp @@ -75,6 +75,8 @@ class MainWindow : public QMainWindow bool displayIP(); + bool verboseOutput() const; + void enableRevertToSavedAction(bool enable); void enableFileImportActions(bool enable); @@ -165,6 +167,10 @@ class MainWindow : public QMainWindow void enableComponentsMeasures(bool enable); + public slots: + + void toggleVerboseOutput(bool verboseOutput); + protected: void closeEvent(QCloseEvent* event) override; @@ -191,6 +197,8 @@ class MainWindow : public QMainWindow bool m_displayIP; + bool m_verboseOutput = false; + QString m_lastPath; private slots: diff --git a/src/openstudio_lib/RunTabView.cpp b/src/openstudio_lib/RunTabView.cpp index 16529f90e..66262c11b 100644 --- a/src/openstudio_lib/RunTabView.cpp +++ b/src/openstudio_lib/RunTabView.cpp @@ -66,6 +66,7 @@ #include "../model_editor/Application.hpp" #include "../model_editor/Utilities.hpp" +#include "MainWindow.hpp" #include #include @@ -92,6 +93,7 @@ #include #include #include +#include namespace openstudio { @@ -131,19 +133,30 @@ RunView::RunView() : QWidget(), m_runSocket(nullptr) { progressbarlayout->addWidget(m_progressBar); mainLayout->addLayout(progressbarlayout, 0, 1); + auto* mainWindow = OSAppBase::instance()->currentDocument()->mainWindow(); + bool verboseOutput = mainWindow->verboseOutput(); + m_verboseOutputBox = new QCheckBox(); + m_verboseOutputBox->setText("Verbose Output"); + m_verboseOutputBox->setChecked(verboseOutput); + connect(m_verboseOutputBox, &QCheckBox::clicked, mainWindow, &MainWindow::toggleVerboseOutput); + mainLayout->addWidget(m_verboseOutputBox, 0, 2); + m_openSimDirButton = new QPushButton(); m_openSimDirButton->setText("Show Simulation"); m_openSimDirButton->setFlat(true); m_openSimDirButton->setObjectName("StandardGrayButton"); connect(m_openSimDirButton, &QPushButton::clicked, this, &RunView::onOpenSimDirClicked); - mainLayout->addWidget(m_openSimDirButton, 0, 2); + mainLayout->addWidget(m_openSimDirButton, 0, 3); m_textInfo = new QTextEdit(); m_textInfo->setReadOnly(true); - mainLayout->addWidget(m_textInfo, 1, 0, 1, 3); + mainLayout->addWidget(m_textInfo, 1, 0, 1, 4); m_runProcess = new QProcess(this); - connect(m_runProcess, static_cast(&QProcess::finished), this, &RunView::onRunProcessFinished); + connect(m_runProcess, &QProcess::finished, this, &RunView::onRunProcessFinished); + connect(m_runProcess, &QProcess::errorOccurred, this, &RunView::onRunProcessErrored); + connect(m_runProcess, &QProcess::readyReadStandardError, this, &RunView::readyReadStandardError); + connect(m_runProcess, &QProcess::readyReadStandardOutput, this, &RunView::readyReadStandardOutput); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); @@ -178,8 +191,37 @@ void RunView::onOpenSimDirClicked() { } } +void RunView::onRunProcessErrored(QProcess::ProcessError error) { + m_textInfo->setTextColor(Qt::red); + m_textInfo->setFontPointSize(18); + QString text = tr("onRunProcessErrored: Simulation failed to run, QProcess::ProcessError: ") + QString::number(error); + m_textInfo->append(text); +} + void RunView::onRunProcessFinished(int exitCode, QProcess::ExitStatus status) { - LOG(Debug, "run finished"); + if (status == QProcess::NormalExit) { + LOG(Debug, "run finished, exit code = " << exitCode); + } + + if (exitCode != 0 || status == QProcess::CrashExit) { + m_textInfo->setTextColor(Qt::red); + m_textInfo->setFontPointSize(18); + m_textInfo->append(tr("Simulation failed to run, with exit code ") + QString::number(exitCode)); + + //m_textInfo->setTextColor(Qt::black); + //m_textInfo->setFontPointSize(15); + //m_textInfo->append("Stderr:"); + //m_textInfo->setFontPointSize(12); + //QString errorString = QString(m_runProcess->readAllStandardError()); + //m_textInfo->append(errorString); + + //m_textInfo->setFontPointSize(15); + //m_textInfo->append("Stdout:"); + //m_textInfo->setFontPointSize(12); + //QString outString = QString(m_runProcess->readAllStandardOutput()); + //m_textInfo->append(outString); + } + m_playButton->setChecked(false); m_state = State::stopped; m_progressBar->setMaximum(State::complete); @@ -220,30 +262,44 @@ void RunView::playButtonClicked(bool t_checked) { //auto basePath = getCompanionFolder( toPath(osdocument->savePath()) ); // run in temp dir - auto basePath = toPath(osdocument->modelTempDir()) / toPath("resources"); + m_basePath = toPath(osdocument->modelTempDir()) / toPath("resources"); - auto workflowPath = basePath / "workflow.osw"; - auto stdoutPath = basePath / "stdout"; - auto stderrPath = basePath / "stderr"; + auto workflowPath = m_basePath / "workflow.osw"; + auto stdoutPath = m_basePath / "stdout"; + auto stderrPath = m_basePath / "stderr"; OS_ASSERT(exists(workflowPath)); auto workflowJSONPath = QString::fromStdString(workflowPath.string()); unsigned port = m_runTcpServer->serverPort(); - bool haveConnection = (port != 0); + m_hasSocketConnexion = (port != 0); + // NOTE: temp test, uncomment to see fallback to stdout + // m_hasSocketConnexion = false; QStringList arguments; - if (haveConnection) { + LOG(Debug, "Checkbox is checked? " << std::boolalpha << m_verboseOutputBox->isChecked()); + if (m_verboseOutputBox->isChecked()) { + arguments << "--verbose"; + } else { + // If not verbose, we save the stdout/stderr to a file, like historical + // Actually, we don't, we just read it + // m_runProcess->setStandardOutputFile(toQString(stdoutPath)); + // m_runProcess->setStandardErrorFile(toQString(stderrPath)); + } + + if (m_hasSocketConnexion) { arguments << "run" << "-s" << QString::number(port) << "-w" << workflowJSONPath; } else { arguments << "run" + << "--show-stdout" + // << "--style-stdout" + // << "--add-timings" << "-w" << workflowJSONPath; } - LOG(Debug, "openstudioExePath='" << toString(openstudioExePath) << "'"); - LOG(Debug, "run arguments" << arguments.join(";").toStdString()); + LOG(Debug, "run arguments = " << arguments.join(";").toStdString()); osdocument->disableTabsDuringRun(); m_openSimDirButton->setEnabled(false); @@ -254,33 +310,35 @@ void RunView::playButtonClicked(bool t_checked) { if (exists(stderrPath)) { remove(stderrPath); } + // touch + openstudio::filesystem::ofstream{stdoutPath}; + openstudio::filesystem::ofstream{stderrPath}; + m_state = State::stopped; m_textInfo->clear(); - if (haveConnection) { - m_progressBar->setMinimum(0); - m_progressBar->setMaximum(State::complete); - m_progressBar->setValue(0); - } else { - m_progressBar->setMinimum(0); - m_progressBar->setMaximum(0); - m_progressBar->setValue(0); + m_progressBar->setMinimum(0); + m_progressBar->setMaximum(State::complete); + m_progressBar->setValue(0); - m_textInfo->setTextColor(Qt::black); - m_textInfo->setFontPointSize(18); - m_textInfo->append("Could not open socket connection to OpenStudio CLI."); + if (!m_hasSocketConnexion) { + m_textInfo->setTextColor(Qt::red); m_textInfo->setFontPointSize(15); - m_textInfo->append("Live simulation feedback during run not available."); - m_textInfo->append("View simulation directory when run is complete."); + m_textInfo->append("Could not open socket connection to OpenStudio CLI."); + m_textInfo->setFontPointSize(12); + m_textInfo->append("Falling back to stdout/stderr parsing, live updates might be slower."); } - m_runProcess->setStandardOutputFile(toQString(stdoutPath)); - m_runProcess->setStandardErrorFile(toQString(stderrPath)); m_runProcess->start(openstudioExePath, arguments); } else { // stop running LOG(Debug, "Kill Simulation"); + m_textInfo->setTextColor(Qt::red); + m_textInfo->setFontPointSize(18); + m_textInfo->append("Aborted"); + m_runProcess->blockSignals(true); m_runProcess->kill(); + m_runProcess->blockSignals(false); } } @@ -389,4 +447,175 @@ void RunView::onRunDataReady() { } } +void RunView::readyReadStandardOutput() { + + auto appendErrorText = [&](const QString& text) { + m_textInfo->setTextColor(Qt::red); + m_textInfo->setFontPointSize(18); + m_textInfo->append(text); + }; + + auto appendNormalText = [&](const QString& text) { + m_textInfo->setTextColor(Qt::black); + m_textInfo->setFontPointSize(12); + m_textInfo->append(text); + }; + + auto appendH1Text = [&](const QString& text) { + m_textInfo->setTextColor(Qt::black); + m_textInfo->setFontPointSize(18); + m_textInfo->append(text); + }; + + auto appendH2Text = [&](const QString& text) { + m_textInfo->setTextColor(Qt::black); + m_textInfo->setFontPointSize(15); + m_textInfo->append(text); + }; + + QString data = m_runProcess->readAllStandardOutput(); + QStringList lines = data.split("\n"); + + // Write to stdout (pipe to file, for later viewing) + auto stdoutPath = m_basePath / "stdout"; + openstudio::filesystem::ofstream stdout_file(stdoutPath, std::ios_base::app); + if (!stdout_file) { + LOG(Debug, "Could not open " << stdoutPath << " for appending."); + } else { + stdout_file << openstudio::toString(data); + } + + for (const auto& line : lines) { + //std::cout << data.toStdString() << std::endl; + + QString trimmedLine = line.trimmed(); + + bool b = trimmedLine.contains("DEBUG"); + + // DLM: coordinate with openstudio-workflow-gem\lib\openstudio\workflow\adapters\output\socket.rb + if (trimmedLine.isEmpty()) { + continue; + } else if ((trimmedLine.contains("DEBUG")) || (trimmedLine.contains("] <-2>"))) { + m_textInfo->setFontPointSize(10); + m_textInfo->setTextColor(Qt::lightGray); + m_textInfo->append(line); + } else if ((trimmedLine.contains("INFO")) || (trimmedLine.contains("] <-1>"))) { + m_textInfo->setFontPointSize(10); + m_textInfo->setTextColor(Qt::gray); + m_textInfo->append(line); + } else if ((trimmedLine.contains("WARN")) || (trimmedLine.contains("] <0>"))) { + m_textInfo->setFontPointSize(12); + m_textInfo->setTextColor(Qt::darkYellow); + m_textInfo->append(line); + } else if ((trimmedLine.contains("ERROR")) || (trimmedLine.contains("] <1>"))) { + m_textInfo->setFontPointSize(12); + m_textInfo->setTextColor(Qt::darkRed); + m_textInfo->append(line); + } else if ((trimmedLine.contains("FATAL")) || (trimmedLine.contains("] <1>"))) { + m_textInfo->setFontPointSize(14); + m_textInfo->setTextColor(Qt::red); + m_textInfo->append(line); + + } else if (!m_hasSocketConnexion) { + // For socket fall back. Avoid doing all these compare if we know we don't need to + if (QString::compare(trimmedLine, "Starting state initialization", Qt::CaseInsensitive) == 0) { + appendH1Text("Initializing workflow."); + m_state = State::initialization; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Started", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Returned from state initialization", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Starting state os_measures", Qt::CaseInsensitive) == 0) { + appendH1Text("Processing OpenStudio Measures."); + m_state = State::os_measures; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state os_measures", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Starting state translator", Qt::CaseInsensitive) == 0) { + appendH1Text("Translating the OpenStudio Model to EnergyPlus."); + m_state = State::translator; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state translator", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Starting state ep_measures", Qt::CaseInsensitive) == 0) { + appendH1Text("Processing EnergyPlus Measures."); + m_state = State::ep_measures; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state ep_measures", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Starting state preprocess", Qt::CaseInsensitive) == 0) { + // ignore this state + m_state = State::preprocess; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state preprocess", Qt::CaseInsensitive) == 0) { + // ignore this state + } else if (QString::compare(trimmedLine, "Starting state simulation", Qt::CaseInsensitive) == 0) { + appendH1Text("Starting Simulation."); + m_state = State::simulation; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state simulation", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Starting state reporting_measures", Qt::CaseInsensitive) == 0) { + appendH1Text("Processing Reporting Measures."); + m_state = State::reporting_measures; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state reporting_measures", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Starting state postprocess", Qt::CaseInsensitive) == 0) { + appendH1Text("Gathering Reports."); + m_state = State::postprocess; + m_progressBar->setValue(m_state); + } else if (QString::compare(trimmedLine, "Returned from state postprocess", Qt::CaseInsensitive) == 0) { + // no-op + } else if (QString::compare(trimmedLine, "Failure", Qt::CaseInsensitive) == 0) { + appendErrorText("Failed."); + } else if (QString::compare(trimmedLine, "Complete", Qt::CaseInsensitive) == 0) { + appendH1Text("Completed."); + } else if (trimmedLine.startsWith("Applying", Qt::CaseInsensitive)) { + appendH2Text(line); + } else if (trimmedLine.startsWith("Applied", Qt::CaseInsensitive)) { + // no-op + } else { + appendNormalText(line); + } + } else { // m_hasSocketConnexion: we know it's stdout and not important socket info, so we put that in gray + m_textInfo->setFontPointSize(10); + m_textInfo->setTextColor(Qt::gray); + m_textInfo->append(line); + } + } +} + +void RunView::readyReadStandardError() { + auto appendErrorText = [&](const QString& text) { + m_textInfo->setTextColor(Qt::darkRed); + m_textInfo->setFontPointSize(18); + m_textInfo->append(text); + }; + + QString data = m_runProcess->readAllStandardError(); + QStringList lines = data.split("\n"); + + // Write to stderr (pipe to file, for later viewing) + auto stderrPath = m_basePath / "stderr"; + openstudio::filesystem::ofstream stderr_file(stderrPath, std::ios_base::app); + if (!stderr_file) { + LOG(Debug, "Could not open " << stderrPath << " for appending."); + } else { + stderr_file << openstudio::toString(data); + } + + for (const auto& line : lines) { + + QString trimmedLine = line.trimmed(); + + if (trimmedLine.isEmpty()) { + continue; + } else { + appendErrorText("stderr: " + line); + } + } +} + } // namespace openstudio diff --git a/src/openstudio_lib/RunTabView.hpp b/src/openstudio_lib/RunTabView.hpp index 45a245138..ac84f77e8 100644 --- a/src/openstudio_lib/RunTabView.hpp +++ b/src/openstudio_lib/RunTabView.hpp @@ -52,6 +52,7 @@ class QTextEdit; class QFileSystemWatcher; class QTcpServer; class QTcpSocket; +class QCheckBox; namespace openstudio { @@ -71,6 +72,8 @@ class RunView : public QWidget void onRunProcessFinished(int exitCode, QProcess::ExitStatus status); + void onRunProcessErrored(QProcess::ProcessError error); + //void onSimDirChanged(const QString &path); //void onFileChanged(const QString &path); @@ -80,6 +83,8 @@ class RunView : public QWidget void onNewConnection(); void onRunDataReady(); + void readyReadStandardError(); + void readyReadStandardOutput(); QToolButton* m_playButton; QProgressBar* m_progressBar; @@ -89,6 +94,10 @@ class RunView : public QWidget QPushButton* m_openSimDirButton; QTcpServer* m_runTcpServer; QTcpSocket* m_runSocket; + + QCheckBox* m_verboseOutputBox; + + openstudio::path m_basePath; //QFileSystemWatcher * m_simDirWatcher; //QFileSystemWatcher * m_eperrWatcher; @@ -106,6 +115,7 @@ class RunView : public QWidget complete = 9 }; State m_state = State::stopped; + bool m_hasSocketConnexion = false; }; class RunTabView : public MainTabView