Preferences framework

Add preferences for HPC/Batch scheduler
Add slurm and LSF support
Add batch job monitor
Support launcher specific user settings
Use internal timer, not global view timer
Add some more options to batch monitor
Improve naming
Add tests for job lists
This commit is contained in:
Jon Jenssen authored and jonjenssen committed 2026-10-19 12:54:19 +02:00
1 parent eb66255a70
commit a8dc7dedae
61 files changed
+2735 -165

No files matched your search

@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none">
<!-- Queue -->
<path d="M3 5H9"
stroke="black"
stroke-width="1"
stroke-linecap="round"/>
<path d="M3 10H9"
stroke="black"
stroke-width="1"
stroke-linecap="round"/>
<path d="M3 15H9"
stroke="black"
stroke-width="1"
stroke-linecap="round"/>
<!-- Monitor -->
<rect x="12"
y="4"
width="9"
height="7"
rx="1"
stroke="black"
stroke-width="1"/>
<!-- Trend/monitoring graph -->
<path d="M14 9L16 7L17 8L19 5"
stroke="black"
stroke-width="1"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 831 B

@@ -16,6 +16,7 @@
<file>AppLogo48x48.png</file>
<file>Axes16x16.png</file>
<file>AxesSync16x16.png</file>
<file>BatchMonitor.svg</file>
<file>BottomAxis16x16.png</file>
<file>Bullet.png</file>
<file>CascadeWindows.svg</file>
@@ -4,6 +4,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaGuiApplication.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaCompletionTypeCalculationScheduler.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaDefines.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaHpcDefines.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaFractureDefines.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferences.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSummary.cpp
@@ -13,6 +14,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaExperimentalFeatures.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesOsdu.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesOpm.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesBatch.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSumo.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesCloudApi.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaPorosityModel.cpp
@@ -0,0 +1,33 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaHpcDefines.h"
#include "cafAppEnum.h"
namespace caf
{
template <>
void caf::AppEnum<RiaDefines::BatchSchedulerType>::setUp()
{
addItem( RiaDefines::BatchSchedulerType::LOCAL_COMPUTER, "LOCAL_COMPUTER", "Local Computer" );
addItem( RiaDefines::BatchSchedulerType::SLURM, "SLURM", "Slurm" );
addItem( RiaDefines::BatchSchedulerType::LSF, "LSF", "LSF" );
setDefault( RiaDefines::BatchSchedulerType::LOCAL_COMPUTER );
}
} // namespace caf
@@ -0,0 +1,30 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
namespace RiaDefines
{
enum class BatchSchedulerType
{
LOCAL_COMPUTER,
SLURM,
LSF
};
}
@@ -25,6 +25,7 @@
#include "RiaColorTables.h"
#include "RiaGuiApplication.h"
#include "RiaLogging.h"
#include "RiaPreferencesBatch.h"
#include "RiaPreferencesCloudApi.h"
#include "RiaPreferencesGeoMech.h"
#include "RiaPreferencesGrid.h"
@@ -295,6 +296,9 @@ RiaPreferences::RiaPreferences()
CAF_PDM_InitFieldNoDefault( &m_opmPreferences, "opmPreferences", "opmPreferences" );
m_opmPreferences = new RiaPreferencesOpm();
CAF_PDM_InitFieldNoDefault( &m_batchPreferences, "batchPreferences", "batchPreferences" );
m_batchPreferences = new RiaPreferencesBatch();
CAF_PDM_InitFieldNoDefault( &m_systemPreferences, "systemPreferences", "systemPreferences" );
m_systemPreferences = new RiaPreferencesSystem;
@@ -462,6 +466,10 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering&
{
m_opmPreferences()->appendItems( uiOrdering );
}
else if ( uiConfigName == RiaPreferences::tabNameBatchScheduler() )
{
m_batchPreferences()->appendItems( uiOrdering );
}
#ifdef USE_ODB_API
else if ( uiConfigName == RiaPreferences::tabNameGeomech() )
{
@@ -639,6 +647,14 @@ QString RiaPreferences::tabNameOpmFlow()
return "OPM Flow";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaPreferences::tabNameBatchScheduler()
{
return "Batch Scheduler";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -693,6 +709,7 @@ QStringList RiaPreferences::tabNames()
names << tabNameScripting();
names << tabNameCloudApi();
names << tabNameOpmFlow();
names << tabNameBatchScheduler();
#ifdef USE_ODB_API
names << tabNameGeomech();
#endif
@@ -1045,6 +1062,14 @@ RiaPreferencesOpm* RiaPreferences::opmPreferences() const
return m_opmPreferences();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaPreferencesBatch* RiaPreferences::batchPreferences() const
{
return m_batchPreferences();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -50,6 +50,7 @@ class RiaPreferencesSumo;
class RiaPreferencesOpm;
class RiaPreferencesOpenTelemetry;
class RiaPreferencesCloudApi;
class RiaPreferencesBatch;
class QValidator;
@@ -137,6 +138,7 @@ public:
RiaPreferencesOpm* opmPreferences() const;
RiaPreferencesOpenTelemetry* openTelemetryPreferences() const;
RiaPreferencesCloudApi* cloudApiPreferences() const;
RiaPreferencesBatch* batchPreferences() const;
void importPreferenceValuesFromFile( const QString& fileName );
void exportPreferenceValuesToFile( const QString& fileName );
@@ -180,6 +182,7 @@ private:
static QString tabNameImportExport();
static QString tabNameOpmFlow();
static QString tabNameExperimental();
static QString tabNameBatchScheduler();
static double defaultMarginSize( QPageSize::PageSizeId pageSizeId );
@@ -254,6 +257,9 @@ private:
// RI Cloud API settings
caf::PdmChildField<RiaPreferencesCloudApi*> m_cloudApiPreferences;
// Batch Scheduler settings
caf::PdmChildField<RiaPreferencesBatch*> m_batchPreferences;
// 3d view
caf::PdmField<caf::AppEnum<RiaDefines::MeshModeType>> m_defaultMeshModeType;
caf::PdmField<caf::AppEnum<RiaDefines::RINavigationPolicy>> m_navigationPolicy;
@@ -0,0 +1,188 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaPreferencesBatch.h"
#include "RiaApplication.h"
#include "RiaHpcTools.h"
#include "RiaPreferences.h"
#include "cafPdmUiCheckBoxEditor.h"
#include "cafPdmUiComboBoxEditor.h"
#include <QInputDialog>
CAF_PDM_SOURCE_INIT( RiaPreferencesBatch, "RiaPreferencesBatch" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaPreferencesBatch::RiaPreferencesBatch()
{
CAF_PDM_InitFieldNoDefault( &m_maxParallelJobs, "maxParallelJobs", "Maximum number of jobs to run in parallel on local machine" );
m_maxParallelJobs = 1;
m_maxParallelJobs.setRange( 1, 100 );
CAF_PDM_InitFieldNoDefault( &m_batchScheduler, "batchScheduler", "Batch Scheduler to use for submitting jobs" );
CAF_PDM_InitField( &m_exclusive, "exclusive", false, "Exclusive - do not share assigned nodes with other jobs." );
caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_exclusive );
CAF_PDM_InitFieldNoDefault( &m_queueName, "queueName", "Default queue/partition name" );
CAF_PDM_InitFieldNoDefault( &m_additionalLsfOptions, "additionalLsfOptions", "" );
CAF_PDM_InitFieldNoDefault( &m_additionalSlurmOptions, "additionalSlurmOptions", "" );
m_additionalLsfOptions.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN );
m_additionalSlurmOptions.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaPreferencesBatch* RiaPreferencesBatch::current()
{
return RiaApplication::instance()->preferences()->batchPreferences();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaPreferencesBatch::appendItems( caf::PdmUiOrdering& uiOrdering )
{
caf::PdmUiGroup* genGrp = uiOrdering.addNewGroup( "General Settings" );
genGrp->add( &m_batchScheduler );
if ( m_batchScheduler() == RiaDefines::BatchSchedulerType::LOCAL_COMPUTER )
{
auto localGrp = uiOrdering.addNewGroup( "Local Computer Options" );
localGrp->add( &m_maxParallelJobs );
}
else
{
auto hpcGrp = uiOrdering.addNewGroup( "Batch Scheduler Options" );
hpcGrp->add( &m_queueName );
hpcGrp->addNewButton( "Select...",
[this]()
{
auto newName = RiaPreferencesBatch::selectQueue( m_batchScheduler(), m_queueName() );
m_queueName.setValueWithFieldChanged( newName );
},
{ .newRow = false, .totalColumnSpan = 1 } );
hpcGrp->add( &m_exclusive );
if ( m_batchScheduler() == RiaDefines::BatchSchedulerType::LSF )
{
hpcGrp->addNewLabel( "Additional LSF options can be specified here. For example: -R \"select[x86_64Linux] span[ptile=4]\"" );
hpcGrp->add( &m_additionalLsfOptions );
}
else if ( m_batchScheduler() == RiaDefines::BatchSchedulerType::SLURM )
{
hpcGrp->addNewLabel( "Additional Slurm options can be specified here. For example: --mem=1G" );
hpcGrp->add( &m_additionalSlurmOptions );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaPreferencesBatch::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_queueName )
{
auto attr = dynamic_cast<caf::PdmUiComboBoxEditorAttribute*>( attribute );
if ( attr )
{
attr->enableEditableContent = true;
attr->enableAutoComplete = false;
attr->adjustWidthToContents = true;
attr->notifyWhenTextIsEdited = false;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RiaPreferencesBatch::maxParallelJobs() const
{
return m_maxParallelJobs();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaDefines::BatchSchedulerType RiaPreferencesBatch::batchScheduler() const
{
return m_batchScheduler();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaPreferencesBatch::queueName() const
{
return m_queueName();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaPreferencesBatch::batchSchedulerOptions() const
{
if ( m_batchScheduler() == RiaDefines::BatchSchedulerType::LSF )
{
return m_additionalLsfOptions();
}
else if ( m_batchScheduler() == RiaDefines::BatchSchedulerType::SLURM )
{
return m_additionalSlurmOptions();
}
return "";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiaPreferencesBatch::exclusiveJob() const
{
return m_exclusive();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaPreferencesBatch::selectQueue( RiaDefines::BatchSchedulerType scheduler, QString currentQueue )
{
QStringList queues = RiaHpcTools::availableQueues( scheduler );
QString labelText = "Available Queues/Partitions:";
if ( queues.isEmpty() )
{
labelText = "Warning: failed to get available queues/partitions.";
queues.append( currentQueue );
}
int currentQueueIndex = currentQueue.isEmpty() ? 0 : queues.indexOf( currentQueue );
bool ok;
QString selectedQueue = QInputDialog::getItem( nullptr, "Select Queue/Partition", labelText, queues, currentQueueIndex, false, &ok );
if ( ok && !selectedQueue.isEmpty() )
{
return selectedQueue;
}
return currentQueue;
}
@@ -0,0 +1,58 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaHpcDefines.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
class RiaPreferencesBatch : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RiaPreferencesBatch();
static RiaPreferencesBatch* current();
void appendItems( caf::PdmUiOrdering& uiOrdering );
size_t maxParallelJobs() const;
RiaDefines::BatchSchedulerType batchScheduler() const;
QString queueName() const;
QString batchSchedulerOptions() const;
bool exclusiveJob() const;
static QString selectQueue( RiaDefines::BatchSchedulerType scheduler, QString currentQueue );
protected:
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute );
private:
caf::PdmField<size_t> m_maxParallelJobs;
caf::PdmField<caf::AppEnum<RiaDefines::BatchSchedulerType>> m_batchScheduler;
caf::PdmField<QString> m_queueName;
caf::PdmField<QString> m_additionalLsfOptions;
caf::PdmField<QString> m_additionalSlurmOptions;
caf::PdmField<bool> m_exclusive;
};
@@ -38,10 +38,6 @@ RiaPreferencesOpm::RiaPreferencesOpm()
CAF_PDM_InitFieldNoDefault( &m_opmFlowCommand, "opmFlowCommand", "Path to OPM Flow executable" );
m_opmFlowCommand.uiCapability()->setUiEditorTypeName( caf::PdmUiFilePathEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_maxParallelJobs, "maxParallelJobs", "Maximum number of jobs to run in parallel" );
m_maxParallelJobs = 1;
m_maxParallelJobs.setRange( 1, 100 );
CAF_PDM_InitField( &m_useWsl, "useWsl", false, "Use WSL to run OPM Flow" );
CAF_PDM_InitField( &m_useMpi, "useMpi", false, "Enable MPI" );
@@ -93,8 +89,6 @@ void RiaPreferencesOpm::appendItems( caf::PdmUiOrdering& uiOrdering )
opmGrp->add( &m_mpirunCommand );
}
opmGrp->add( &m_maxParallelJobs );
auto cmdGrp = uiOrdering.addNewGroup( "Default Command Line Settings" );
m_jobSettings->uiOrdering( cmdGrp );
}
@@ -179,11 +173,3 @@ RimOpmFlowJobSettings* RiaPreferencesOpm::createDefaultJobSettings() const
{
return m_jobSettings->clone();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RiaPreferencesOpm::maxParallelJobs() const
{
return m_maxParallelJobs();
}
@@ -44,7 +44,6 @@ public:
bool useWsl() const;
bool useMpi() const;
QString mpirunCommand() const;
size_t maxParallelJobs() const;
RimOpmFlowJobSettings* createDefaultJobSettings() const;
@@ -58,7 +57,6 @@ private:
caf::PdmField<bool> m_useMpi;
caf::PdmField<QString> m_mpirunCommand;
caf::PdmChildField<RimOpmFlowJobSettings*> m_jobSettings;
caf::PdmField<size_t> m_maxParallelJobs;
QStringList m_availableWslDists;
};
@@ -0,0 +1,66 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaTimer.h"
#include <QBasicTimer>
#include <QTimerEvent>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaTimer::RiaTimer()
: m_timer( std::make_unique<QBasicTimer>() )
, tick( this )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaTimer::~RiaTimer()
{
stop();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimer::timerEvent( QTimerEvent* event )
{
if ( event->timerId() == m_timer->timerId() )
{
tick.send();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimer::start( int intervalMs )
{
m_timer->start( intervalMs, this );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiaTimer::stop()
{
m_timer->stop();
}
+51
View File
@@ -0,0 +1,51 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <QObject>
#include <memory>
#include "cafSignal.h"
class QTimerEvent;
class QBasicTimer;
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
class RiaTimer : public QObject, public caf::SignalEmitter
{
Q_OBJECT
public:
RiaTimer();
~RiaTimer();
void start( int intervalMs = 1000 );
void stop();
caf::Signal<> tick;
protected:
void timerEvent( QTimerEvent* event ) override;
private:
std::unique_ptr<QBasicTimer> m_timer;
};
@@ -34,6 +34,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaOptionItemFactory.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaGitDiff.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaWslTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaHpcTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaCellDividingTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaFieldHandleTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaBoundingBoxTools.cpp
@@ -0,0 +1,327 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaHpcTools.h"
#include "RiaPreferencesOpm.h"
#include "RimProcess.h"
namespace
{
QStringList runUtilityCommand( QString cmdStr, QStringList arguments )
{
RimProcess proc( false /*no logging*/ );
proc.setUseWsl( RiaPreferencesOpm::current()->useWsl() );
proc.setCommand( cmdStr );
if ( !arguments.isEmpty() ) proc.addParameters( arguments );
if ( proc.execute() )
{
return proc.stdOut();
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void stopLsfJob( QString jobId )
{
runUtilityCommand( "bkill", { jobId } );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void stopSlurmJob( QString jobId )
{
runUtilityCommand( "scancel", { jobId } );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString decodeSlurmJobId( QStringList stdOut )
{
for ( const auto& line : stdOut )
{
if ( line.trimmed().startsWith( "Submitted batch job" ) )
{
auto parts = line.split( ' ', Qt::SkipEmptyParts );
if ( parts.size() > 3 )
{
return parts[3].trimmed();
}
}
}
return "";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString decodeLsfJobId( QStringList stdOut )
{
for ( const auto& line : stdOut )
{
if ( line.trimmed().startsWith( "Job <" ) )
{
auto parts = line.split( ' ', Qt::SkipEmptyParts );
if ( parts.size() > 1 )
{
auto candidate = parts[1].trimmed();
if ( candidate.startsWith( '<' ) && candidate.endsWith( '>' ) )
{
return candidate.mid( 1, candidate.length() - 2 );
}
}
}
}
return "";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList listSlurmJobs()
{
QStringList arguments;
arguments << "--format";
arguments << "%i %j %M %T %P %B"; // id name runtime state partition nodes
arguments << "--me";
arguments << "--noheader";
return runUtilityCommand( "squeue", arguments );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList listLsfJobs()
{
QStringList arguments;
arguments << "-o";
arguments << "jobid:10 job_name:25 run_time:12 stat:8 queue:15 exec_host:20"; // id name runtime state partition nodes
arguments << "-noheader";
return runUtilityCommand( "bjobs", arguments );
}
} // namespace
namespace RiaHpcTools
{
//--------------------------------------------------------------------------------------------------
/// Returns available cluster queues for the given scheduler type
//--------------------------------------------------------------------------------------------------
QStringList availableQueues( RiaDefines::BatchSchedulerType scheduler )
{
if ( scheduler == RiaDefines::BatchSchedulerType::SLURM )
{
auto rawOutput = runUtilityCommand( "sinfo", { "-a" } );
return decodeSlurmQueues( rawOutput );
}
else if ( scheduler == RiaDefines::BatchSchedulerType::LSF )
{
auto rawOutput = runUtilityCommand( "bqueues", { "-w" } );
return decodeLsfQueues( rawOutput );
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::vector<QString>> listJobs( RiaDefines::BatchSchedulerType scheduler )
{
if ( scheduler == RiaDefines::BatchSchedulerType::SLURM )
{
QStringList jobLines = listSlurmJobs();
return decodeSlurmJobs( jobLines );
}
else if ( scheduler == RiaDefines::BatchSchedulerType::LSF )
{
QStringList jobLines = listLsfJobs();
return decodeLsfJobs( jobLines );
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void stopJob( RiaDefines::BatchSchedulerType scheduler, QString jobId )
{
if ( scheduler == RiaDefines::BatchSchedulerType::SLURM )
{
stopSlurmJob( jobId );
}
else if ( scheduler == RiaDefines::BatchSchedulerType::LSF )
{
stopLsfJob( jobId );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString findJobId( RiaDefines::BatchSchedulerType scheduler, QStringList stdOut )
{
if ( scheduler == RiaDefines::BatchSchedulerType::SLURM )
{
return decodeSlurmJobId( stdOut );
}
else if ( scheduler == RiaDefines::BatchSchedulerType::LSF )
{
return decodeLsfJobId( stdOut );
}
return "";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList decodeSlurmQueues( QStringList stdOut )
{
std::set<QString> queues;
bool foundStart = false;
for ( auto& line : stdOut )
{
auto parts = line.split( " " );
if ( parts.isEmpty() ) continue;
auto firstPart = parts[0].trimmed();
if ( foundStart )
{
if ( firstPart.endsWith( '*' ) )
{
firstPart = firstPart.removeAt( firstPart.length() - 1 );
}
queues.insert( firstPart );
continue;
}
if ( firstPart == "PARTITION" )
{
foundStart = true;
continue;
}
}
QStringList retList;
for ( auto& q : queues )
{
retList.append( q );
}
return retList;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList decodeLsfQueues( QStringList stdOut )
{
std::set<QString> queues;
bool foundStart = false;
for ( auto& line : stdOut )
{
auto parts = line.split( " " );
if ( parts.isEmpty() ) continue;
auto firstPart = parts[0].trimmed();
if ( foundStart )
{
queues.insert( firstPart );
continue;
}
if ( firstPart == "QUEUE_NAME" )
{
foundStart = true;
continue;
}
}
QStringList retList;
for ( auto& q : queues )
{
retList.append( q );
}
return retList;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<std::vector<QString>> decodeSlurmJobs( QStringList stdOut )
{
std::vector<std::vector<QString>> jobs;
for ( auto& line : stdOut )
{
std::vector<QString> statusLine;
auto parts = line.split( ' ', Qt::SkipEmptyParts );
if ( parts.size() < 6 ) continue;
for ( auto& part : parts )
{
statusLine.push_back( part.trimmed() );
}
jobs.push_back( statusLine );
}
return jobs;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<std::vector<QString>> decodeLsfJobs( QStringList stdOut )
{
std::vector<std::vector<QString>> jobs;
for ( auto line : stdOut )
{
line = line.trimmed();
auto jobId = line.mid( 0, 10 ).trimmed();
auto jobName = line.mid( 11, 25 ).trimmed();
auto runTime = line.mid( 37, 12 ).trimmed();
auto status = line.mid( 50, 8 ).trimmed();
auto queue = line.mid( 59, 15 ).trimmed();
auto where = line.mid( 75, -1 ).trimmed();
std::vector<QString> statusLine;
statusLine.push_back( jobId );
statusLine.push_back( jobName );
statusLine.push_back( runTime );
statusLine.push_back( status );
statusLine.push_back( queue );
statusLine.push_back( where );
jobs.push_back( statusLine );
}
return jobs;
}
} // namespace RiaHpcTools
@@ -0,0 +1,46 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaHpcDefines.h"
#include <QString>
#include <QStringList>
#include <vector>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
namespace RiaHpcTools
{
QStringList availableQueues( RiaDefines::BatchSchedulerType scheduler );
void stopJob( RiaDefines::BatchSchedulerType scheduler, QString jobId );
QString findJobId( RiaDefines::BatchSchedulerType scheduler, QStringList stdOut );
std::vector<std::vector<QString>> listJobs( RiaDefines::BatchSchedulerType scheduler );
// helpers
QStringList decodeSlurmQueues( QStringList stdOut );
QStringList decodeLsfQueues( QStringList stdOut );
const std::vector<std::vector<QString>> decodeSlurmJobs( QStringList stdOut );
const std::vector<std::vector<QString>> decodeLsfJobs( QStringList stdOut );
} // namespace RiaHpcTools
@@ -79,7 +79,7 @@ QString RiaWslTools::convertToWslPath( QString windowsPath )
}
//--------------------------------------------------------------------------------------------------
/// Returns the input windows path as seen in the wsl world. I.e. c:\tmp -> /mnt/c/tmp
/// Return environment variables needed for WSL character mapping
//--------------------------------------------------------------------------------------------------
std::map<QString, QString> RiaWslTools::wslEnvironmentVariables()
{
+1
View File
@@ -109,6 +109,7 @@ list(
ProjectDataModel/GridCrossPlots/CMakeLists_files.cmake
ProjectDataModel/GridCrossPlots/CellFilters/CMakeLists_files.cmake
ProjectDataModel/Jobs/CMakeLists_files.cmake
ProjectDataModel/Batch/CMakeLists_files.cmake
ProjectDataModel/Summary/CMakeLists_files.cmake
ProjectDataModel/Summary/Cloud/CMakeLists_files.cmake
ProjectDataModel/Summary/Ensemble/CMakeLists_files.cmake
@@ -21,6 +21,7 @@
#include "RicNewOpmFlowJobFeature.h"
#include "Jobs/RimEnsembleJob.h"
#include "Jobs/RimGenericJob.h"
#include "Jobs/RimJobCollection.h"
#include "Jobs/RimOpmFlowJob.h"
#include "RimTools.h"
@@ -35,6 +36,22 @@
CAF_CMD_SOURCE_INIT( RicDuplicateJobFeature, "RicDuplicateJobFeature" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicDuplicateJobFeature::isCommandEnabled() const
{
if ( auto job = dynamic_cast<RimGenericJob*>( caf::SelectionManager::instance()->selectedItem() ) )
{
if ( job->isChildJob() )
{
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -28,6 +28,7 @@ class RicDuplicateJobFeature : public caf::CmdFeature
CAF_CMD_HEADER_INIT;
protected:
bool isCommandEnabled() const override;
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
};
@@ -49,17 +49,16 @@ void RicStopJobFeature::setupActionLook( QAction* actionToSetup )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicStopJobFeature::stopJob( RimGenericJob* job )
void RicStopJobFeature::stopJob( RimGenericJob* job )
{
if ( job == nullptr ) return false;
if ( QMessageBox::question( RiaGuiApplication::widgetToUseAsParent(),
job->name(),
"Do you want to stop this job?",
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
if ( job != nullptr )
{
return job->stop();
if ( QMessageBox::question( RiaGuiApplication::widgetToUseAsParent(),
job->name(),
"Do you want to stop this job?",
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
{
job->stop();
}
}
return false;
}
@@ -30,7 +30,7 @@ class RicStopJobFeature : public caf::CmdFeature
CAF_CMD_HEADER_INIT;
public:
static bool stopJob( RimGenericJob* job );
static void stopJob( RimGenericJob* job );
protected:
void onActionTriggered( bool isChecked ) override;
@@ -0,0 +1,9 @@
set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimBatchQueue.cpp
${CMAKE_CURRENT_LIST_DIR}/RimBatchQueueSlurm.cpp
${CMAKE_CURRENT_LIST_DIR}/RimBatchQueueLsf.cpp
${CMAKE_CURRENT_LIST_DIR}/RimBatchQueueLocal.cpp
${CMAKE_CURRENT_LIST_DIR}/RimBatchProcessMonitor.cpp
)
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
@@ -0,0 +1,48 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimBatchProcessMonitor.h"
#include "RimBatchQueue.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchProcessMonitor::RimBatchProcessMonitor( RimBatchQueue* batchQueue )
: m_batchQueue( batchQueue )
, RimProcessMonitor( 0, true )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchProcessMonitor::~RimBatchProcessMonitor()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchProcessMonitor::finished( int exitCode, QProcess::ExitStatus exitStatus )
{
if ( m_batchQueue.notNull() )
{
m_batchQueue->setFinished( exitStatus == QProcess::NormalExit && exitCode == 0 );
}
}
@@ -0,0 +1,40 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimProcessMonitor.h"
#include "cafPdmPointer.h"
class RimBatchQueue;
class RimBatchProcessMonitor : public RimProcessMonitor
{
Q_OBJECT
public:
RimBatchProcessMonitor( RimBatchQueue* batchQueue );
~RimBatchProcessMonitor() override;
protected:
void finished( int exitCode, QProcess::ExitStatus exitStatus ) override;
private:
caf::PdmPointer<RimBatchQueue> m_batchQueue;
};
@@ -0,0 +1,203 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimBatchQueue.h"
#include "RiaPreferencesBatch.h"
#include "RiaPreferencesOpm.h"
#include "RiaWslTools.h"
#include "ProcessControl/RimProcess.h"
#include "RimBatchQueueLocal.h"
#include "RimBatchQueueLsf.h"
#include "RimBatchQueueSlurm.h"
#include <QDateTime>
#include <QFile>
CAF_PDM_ABSTRACT_SOURCE_INIT( RimBatchQueue, "BatchQueue" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueue::RimBatchQueue( RiaDefines::BatchSchedulerType schedulerType )
: m_schedulerType( schedulerType )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueue* RimBatchQueue::createBatchQueue()
{
switch ( RiaPreferencesBatch::current()->batchScheduler() )
{
case RiaDefines::BatchSchedulerType::LSF:
return new RimBatchQueueLsf();
case RiaDefines::BatchSchedulerType::SLURM:
return new RimBatchQueueSlurm();
default:
case RiaDefines::BatchSchedulerType::LOCAL_COMPUTER:
break;
}
return new RimBatchQueueLocal();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<std::unique_ptr<RimProcess>, QString> RimBatchQueue::runCommand( QStringList command, std::shared_ptr<RimProcessMonitor> monitor )
{
QStringList cmdList;
if ( RiaPreferencesOpm::current()->useWsl() )
{
cmdList.append( RiaWslTools::wslCommand() );
cmdList.append( RiaPreferencesOpm::current()->wslOptions() );
}
cmdList.append( command );
auto proc = std::make_unique<RimProcess>( true, monitor );
QString cmd = cmdList.takeFirst();
proc->setCommand( cmd );
if ( !cmdList.isEmpty() ) proc->addParameters( cmdList );
if ( proc->start() )
{
return { std::move( proc ), QString() };
}
return { nullptr, QString( "Failed to run command." ) };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<bool, QString> RimBatchQueue::buildLaunchScript( QString workDir )
{
// write launch script to the working directory
QString filename = workDir + "/launch_job.sh";
QFile file( filename );
if ( !file.open( QIODevice::WriteOnly ) )
{
return { false, QString( "Failed to create launch script file %1." ).arg( filename ) };
}
// build launch script
QTextStream out( &file );
out << "#!/bin/sh\n";
auto additionalOptions = RiaPreferencesBatch::current()->batchSchedulerOptions();
if ( !additionalOptions.isEmpty() )
{
out << launchScriptDirective() << " " << additionalOptions << "\n\n";
}
if ( m_process != nullptr )
{
QString cmdLine = m_process->command();
for ( auto& p : m_process->parameters() )
{
cmdLine += " " + p;
}
out << cmdLine << "\n";
}
out << "\n";
file.close();
return { true, filename };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimBatchQueue::generateJobName()
{
if ( m_process == nullptr ) return "ResInsight_Job";
QString candidate = m_process->description();
candidate.replace( " ", "" );
candidate = candidate.left( 10 );
candidate = "RI_" + candidate;
return candidate;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueue::readStdOutErrIntoProcessLog()
{
if ( !m_stdOutFileName.isEmpty() )
{
QFile file( m_stdOutFileName );
if ( file.open( QIODevice::ReadOnly ) )
{
QTextStream in( &file );
while ( !in.atEnd() )
{
m_process->appendStdOut( in.readLine() );
}
file.close();
}
}
if ( !m_stdErrFileName.isEmpty() )
{
QFile file( m_stdErrFileName );
if ( file.open( QIODevice::ReadOnly ) )
{
QTextStream in( &file );
while ( !in.atEnd() )
{
m_process->appendStdErr( in.readLine() );
}
file.close();
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaDefines::BatchSchedulerType RimBatchQueue::schedulerType() const
{
return m_schedulerType;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueue::cleanUpOldLogFiles()
{
if ( !m_stdOutFileName.isEmpty() && QFile::exists( m_stdOutFileName ) )
{
QFile::remove( m_stdOutFileName );
}
if ( !m_stdErrFileName.isEmpty() && QFile::exists( m_stdErrFileName ) )
{
QFile::remove( m_stdErrFileName );
}
}
@@ -0,0 +1,68 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaHpcDefines.h"
#include "cafPdmObject.h"
#include <QString>
#include <QStringList>
#include <memory>
#include <utility>
class RimProcess;
class RimProcessMonitor;
//==================================================================================================
///
///
//==================================================================================================
class RimBatchQueue : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
static RimBatchQueue* createBatchQueue();
virtual void queueProcess( std::shared_ptr<RimProcess> process, int nProcessesPerTask ) = 0;
virtual void stopProcess() = 0;
virtual void setFinished( bool runOk ) {};
protected:
RimBatchQueue( RiaDefines::BatchSchedulerType schedulerType );
RiaDefines::BatchSchedulerType schedulerType() const;
std::pair<std::unique_ptr<RimProcess>, QString> runCommand( QStringList command, std::shared_ptr<RimProcessMonitor> monitor );
std::pair<bool, QString> buildLaunchScript( QString workDir );
QString generateJobName();
void readStdOutErrIntoProcessLog();
void cleanUpOldLogFiles();
virtual QString launchScriptDirective() const = 0;
QString m_stdOutFileName;
QString m_stdErrFileName;
std::shared_ptr<RimProcess> m_process;
private:
RiaDefines::BatchSchedulerType m_schedulerType;
};
@@ -0,0 +1,64 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimBatchQueueLocal.h"
#include "ProcessControl/RimProcess.h"
#include "ProcessControl/RimProcessQueue.h"
CAF_PDM_SOURCE_INIT( RimBatchQueueLocal, "BatchQueueLocal" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueueLocal::RimBatchQueueLocal()
: RimBatchQueue( RiaDefines::BatchSchedulerType::LOCAL_COMPUTER )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueueLocal::~RimBatchQueueLocal()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueueLocal::queueProcess( std::shared_ptr<RimProcess> process, int )
{
m_process = process;
RimProcessQueue::queueProcess( process.get() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueueLocal::stopProcess()
{
if ( m_process != nullptr ) RimProcessQueue::stopProcess( m_process->ID() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimBatchQueueLocal::launchScriptDirective() const
{
return "";
}
@@ -0,0 +1,40 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimBatchQueue.h"
//==================================================================================================
///
///
//==================================================================================================
class RimBatchQueueLocal : public RimBatchQueue
{
CAF_PDM_HEADER_INIT;
public:
RimBatchQueueLocal();
~RimBatchQueueLocal();
void queueProcess( std::shared_ptr<RimProcess> process, int nProcessesPerTask ) override;
void stopProcess() override;
private:
QString launchScriptDirective() const override;
};
@@ -0,0 +1,95 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimBatchQueueLsf.h"
CAF_PDM_SOURCE_INIT( RimBatchQueueLsf, "BatchQueueLsf" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueueLsf::RimBatchQueueLsf()
: RimBatchQueueSlurm( RiaDefines::BatchSchedulerType::LSF )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueueLsf::~RimBatchQueueLsf()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimBatchQueueLsf::generateCommand( QString script,
QString jobName,
QString workDir,
QString stdOutLog,
QString stdErrLog,
QString queueName,
bool exclusive,
int numberOfProcesses )
{
QStringList arguments;
arguments << "bsub";
// the queue to use
arguments << "-q";
arguments << queueName;
// the name of the job
arguments << "-J";
arguments << jobName;
// should we request exclusive access to a node?
if ( exclusive )
{
arguments << "-x";
}
// working directory and log file output
if ( !workDir.isEmpty() )
{
arguments << "-cwd";
arguments << workDir;
arguments << "-o";
arguments << stdOutLog;
arguments << "-e";
arguments << stdErrLog;
}
// number of tasks we are going to run (i.e. mpi processes)
arguments << "-n";
arguments << QString( "%1" ).arg( numberOfProcesses );
// blocking wait
arguments << "-K";
// the actual script to run
arguments << script;
return arguments;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimBatchQueueLsf::launchScriptDirective() const
{
return "#BSUB";
}
@@ -0,0 +1,46 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimBatchQueueSlurm.h"
//==================================================================================================
///
///
//==================================================================================================
class RimBatchQueueLsf : public RimBatchQueueSlurm
{
CAF_PDM_HEADER_INIT;
public:
RimBatchQueueLsf();
~RimBatchQueueLsf();
QStringList generateCommand( QString script,
QString jobName,
QString workDir,
QString stdOutLog,
QString stdErrLog,
QString queueName,
bool exclusive,
int numberOfProcesses ) override;
protected:
QString launchScriptDirective() const override;
};
@@ -0,0 +1,205 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimBatchQueueSlurm.h"
#include "RiaHpcTools.h"
#include "RiaLogging.h"
#include "RiaPreferencesBatch.h"
#include "RiaPreferencesOpm.h"
#include "RiaWslTools.h"
#include "ProcessControl/RimProcess.h"
#include "RimBatchProcessMonitor.h"
CAF_PDM_SOURCE_INIT( RimBatchQueueSlurm, "BatchQueueSlurm" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueueSlurm::RimBatchQueueSlurm( RiaDefines::BatchSchedulerType schedulerType )
: RimBatchQueue( schedulerType )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimBatchQueueSlurm::~RimBatchQueueSlurm()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueueSlurm::queueProcess( std::shared_ptr<RimProcess> process, int nProcessesPerTask )
{
if ( process == nullptr ) return;
m_process = process;
m_monitor = std::make_shared<RimBatchProcessMonitor>( this );
auto useWsl = RiaPreferencesOpm::current()->useWsl();
auto prefs = RiaPreferencesBatch::current();
QString workDir = m_process->workingDirectory();
// build launch script
auto [builtOk, script] = buildLaunchScript( workDir );
if ( !builtOk )
{
m_monitor->finished( 1, QProcess::ExitStatus::NormalExit );
RiaLogging::warning( QString( script ).toStdString() );
return;
}
QString jobName = generateJobName();
m_stdOutFileName = QString( "%1/%2.out" ).arg( workDir ).arg( jobName );
m_stdErrFileName = QString( "%1/%2.err" ).arg( workDir ).arg( jobName );
cleanUpOldLogFiles();
QString stdOut = m_stdOutFileName;
QString stdErr = m_stdErrFileName;
if ( useWsl )
{
workDir = RiaWslTools::convertToWslPath( workDir );
script = RiaWslTools::convertToWslPath( script );
stdOut = RiaWslTools::convertToWslPath( stdOut );
stdErr = RiaWslTools::convertToWslPath( stdErr );
}
QStringList arguments =
generateCommand( script, jobName, workDir, stdOut, stdErr, prefs->queueName(), prefs->exclusiveJob(), nProcessesPerTask );
auto [batchProcess, output] = runCommand( arguments, m_monitor );
if ( !batchProcess )
{
m_monitor->finished( 1, QProcess::ExitStatus::NormalExit );
RiaLogging::warning( output.toStdString() );
}
else
{
if ( m_process->monitor() ) m_process->monitor()->started();
m_batchProcess = std::move( batchProcess );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimBatchQueueSlurm::generateCommand( QString script,
QString jobName,
QString workDir,
QString stdOutLog,
QString stdErrLog,
QString queueName,
bool exclusive,
int numberOfProcesses )
{
QStringList arguments;
arguments << "sbatch";
arguments << "-p";
arguments << queueName;
arguments << "-J";
arguments << jobName;
if ( exclusive )
{
arguments << "--exclusive";
}
if ( !workDir.isEmpty() )
{
arguments << "-D";
arguments << workDir;
arguments << "-o";
arguments << stdOutLog;
arguments << "-e";
arguments << stdErrLog;
}
arguments << "-n";
arguments << QString( "%1" ).arg( numberOfProcesses );
arguments << "--wait";
arguments << script;
return arguments;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimBatchQueueSlurm::jobId() const
{
if ( m_batchProcess )
{
return RiaHpcTools::findJobId( schedulerType(), m_batchProcess->stdOut() );
}
return QString();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueueSlurm::stopProcess()
{
if ( m_batchProcess && m_process && m_process->monitor() )
{
auto id = jobId();
if ( !id.isEmpty() )
{
RiaLogging::info( QString( "Stopping batch job %1" ).arg( id ).toStdString() );
RiaHpcTools::stopJob( schedulerType(), id );
}
m_process->monitor()->finished( 1, QProcess::ExitStatus::CrashExit );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimBatchQueueSlurm::setFinished( bool runOk )
{
if ( m_batchProcess && m_process && m_process->monitor() )
{
auto jobId = RiaHpcTools::findJobId( schedulerType(), m_batchProcess->stdOut() );
readStdOutErrIntoProcessLog();
if ( runOk )
{
RiaLogging::info( QString( "Batch job %1 completed successfully" ).arg( jobId ).toStdString() );
m_process->monitor()->finished( 0, QProcess::ExitStatus::NormalExit );
}
else
{
RiaLogging::warning( QString( "Batch job %1 failed." ).arg( jobId ).toStdString() );
m_process->monitor()->finished( 1, QProcess::ExitStatus::CrashExit );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimBatchQueueSlurm::launchScriptDirective() const
{
return "#SBATCH";
}
@@ -0,0 +1,59 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimBatchQueue.h"
class RimProcessMonitor;
//==================================================================================================
///
///
//==================================================================================================
class RimBatchQueueSlurm : public RimBatchQueue
{
CAF_PDM_HEADER_INIT;
public:
RimBatchQueueSlurm( RiaDefines::BatchSchedulerType schedulerType = RiaDefines::BatchSchedulerType::SLURM );
~RimBatchQueueSlurm();
void queueProcess( std::shared_ptr<RimProcess> process, int nProcessesPerTask ) override;
void stopProcess() override;
protected:
void setFinished( bool runOk ) override;
virtual QString jobId() const;
virtual QStringList generateCommand( QString script,
QString jobName,
QString workDir,
QString stdOutLog,
QString stdErrLog,
QString queueName,
bool exclusive,
int numberOfProcesses );
QString launchScriptDirective() const override;
protected:
std::shared_ptr<RimProcessMonitor> m_monitor;
std::unique_ptr<RimProcess> m_batchProcess;
};
@@ -29,20 +29,13 @@
#include "RifOpmDeckFileTools.h"
#include "Ensemble/RimSummaryFileSetEnsemble.h"
#include "EnsembleFileSet/RimEnsembleFileSet.h"
#include "EnsembleFileSet/RimEnsembleFileSetTools.h"
#include "RimEclipseCase.h"
#include "RimEclipseCaseCollection.h"
#include "RimJobWellSettings.h"
#include "RimOilField.h"
#include "RimOpmFlowJob.h"
#include "RimOpmFlowJobSettings.h"
#include "RimProject.h"
#include "RimReservoirGridEnsemble.h"
#include "RimSummaryCaseMainCollection.h"
#include "RimSummaryEnsemble.h"
#include "RimSummaryEnsembleTools.h"
#include "RimTools.h"
#include "RiuGuiTheme.h"
@@ -70,6 +63,9 @@ RimEnsembleJob::RimEnsembleJob()
m_inputEnsemble = nullptr;
m_inputEnsemble.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitField( &m_outputEnsembleName, "OutputEnsembleName", QString( "wp-0" ), "Output Ensemble Name" );
m_outputEnsembleName.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitFieldNoDefault( &m_selectedRealizations, "SelectedRealizations", "Selected Realizations" );
m_selectedRealizations.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
m_selectedRealizations.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN );
@@ -120,6 +116,14 @@ QString RimEnsembleJob::jobInputFileKey()
return "OpmFlowInputEnsemble";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleJob::initAfterRead()
{
m_outputEnsembleName = QString( "wp-%1" ).arg( m_outputIterationNumber.value() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -134,15 +138,15 @@ void RimEnsembleJob::initAfterCopy()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimEnsembleJob::stop()
void RimEnsembleJob::stop()
{
if ( !isRunning() ) return false;
for ( auto& subJob : m_subJobs() )
if ( isRunning() )
{
subJob->stop();
for ( auto& subJob : m_subJobs() )
{
subJob->stop();
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
@@ -335,8 +339,8 @@ void RimEnsembleJob::subJobCompleted( const caf::SignalEmitter* emitter, bool ru
//--------------------------------------------------------------------------------------------------
void RimEnsembleJob::setFinished( bool runOk )
{
RimGenericJob::setFinished( runOk );
setDeletable( true );
setState( RimGenericJob::Completed );
updateConnectedEditors();
if ( runOk )
@@ -432,6 +436,7 @@ void RimEnsembleJob::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering&
auto genGrp = uiOrdering.addNewGroup( "General" );
genGrp->add( nameField() );
genGrp->add( &m_inputEnsemble );
genGrp->add( &m_outputEnsembleName );
genGrp->add( &m_createSummaryEnsemble );
genGrp->add( &m_createGridEnsemble );
@@ -508,3 +513,14 @@ std::vector<caf::PdmUiTreeViewItemTag> RimEnsembleJob::treeItemTags()
return tags;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleJob::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_outputIterationNumber )
{
m_outputEnsembleName = QString::fromStdString( outputIteration() );
}
}
@@ -19,8 +19,8 @@
#pragma once
#include "RimGenericJob.h"
#include "cafPdmUiTreeAttributes.h"
#include "cafPdmUiTreeAttributes.h"
#include "cafPdmPtrArrayField.h"
#include "cafPdmPtrField.h"
@@ -50,7 +50,7 @@ public:
void setInputEnsemble( RimReservoirGridEnsemble* ensemble );
bool execute() override;
bool stop() override;
void stop() override;
double percentageDone() const override;
const QStringList jobLog() const override;
bool matchesKeyValue( const QString& key, const QString& value ) const override;
@@ -72,8 +72,10 @@ protected:
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
std::vector<caf::PdmUiTreeViewItemTag> treeItemTags() override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void initAfterRead() override;
std::vector<RealizationInfo> setUpSelectedRealizations() const;
std::string outputIteration() const;
@@ -93,6 +95,7 @@ private:
caf::PdmChildField<RimOpmFlowJobSettings*> m_jobSettings;
caf::PdmChildField<RimJobWellSettings*> m_jobWellSettings;
caf::PdmPtrField<RimEnsembleFileSet*> m_outputEnsembleFileSet;
caf::PdmField<QString> m_outputEnsembleName;
caf::PdmField<std::vector<QDateTime>> m_datesInInputDeck;
caf::PdmField<std::vector<QString>> m_wellGroupsInInputDeck;
@@ -29,6 +29,9 @@ RimGenericJob::RimGenericJob()
: m_jobState( JobState::Idle )
{
CAF_PDM_InitObject( "Generic Job" );
CAF_PDM_InitField( &m_isChildJob, "IsChildJob", false, "Is Child Job" );
m_isChildJob.uiCapability()->setUiHidden( true );
}
//--------------------------------------------------------------------------------------------------
@@ -78,3 +81,76 @@ void RimGenericJob::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) c
menuBuilder << "RicDuplicateJobFeature";
menuBuilder << "RicViewJobLogFeature";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGenericJob::setStarted()
{
m_startTime = QDateTime::currentDateTime();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimGenericJob::runTimeSeconds()
{
if ( m_startTime.isValid() )
{
if ( isRunning() )
{
return m_startTime.secsTo( QDateTime::currentDateTime() );
}
else if ( m_stopTime.isValid() )
{
return m_startTime.secsTo( m_stopTime );
}
}
return 0.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGenericJob::setFinished( bool runOk )
{
m_stopTime = QDateTime::currentDateTime();
if ( runOk )
{
setState( RimGenericJob::Completed );
}
else
{
setState( RimGenericJob::Failed );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimGenericJob::runTimeString()
{
double seconds = runTimeSeconds();
int h = static_cast<int>( seconds / 3600 );
int m = static_cast<int>( ( seconds - h * 3600 ) / 60 );
int s = static_cast<int>( seconds - h * 3600 - m * 60 );
return QString( "%1:%2:%3" ).arg( h, 2, 10, QChar( '0' ) ).arg( m, 2, 10, QChar( '0' ) ).arg( s, 2, 10, QChar( '0' ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimGenericJob::isChildJob() const
{
return m_isChildJob();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimGenericJob::setIsChildJob( bool isChildJob )
{
m_isChildJob = isChildJob;
if ( isChildJob ) setDeletable( false );
}
@@ -22,6 +22,7 @@
#include "cafPdmPointer.h"
#include <QDateTime>
#include <QString>
#include <QStringList>
@@ -51,22 +52,31 @@ public:
~RimGenericJob() override;
virtual bool execute() = 0;
virtual bool stop() = 0;
virtual void stop() = 0;
virtual double percentageDone() const = 0;
virtual const QStringList jobLog() const = 0;
virtual bool matchesKeyValue( const QString& key, const QString& value ) const = 0;
virtual void processLogOutput( const QString& logLine ) {};
virtual void setFinished( bool runOk ) = 0;
virtual void setStarted() {};
virtual void setFinished( bool runOk );
virtual void setStarted();
void setIsChildJob( bool isChildJob );
bool isChildJob() const;
bool isRunning() const;
JobState state() const;
void setState( JobState state );
double runTimeSeconds();
QString runTimeString();
protected:
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
private:
JobState m_jobState;
caf::PdmField<bool> m_isChildJob;
JobState m_jobState;
QDateTime m_startTime;
QDateTime m_stopTime;
};
@@ -50,8 +50,7 @@ void RimJobMonitor::readyReadStandardOutput()
line = line.trimmed();
if ( line.size() == 0 ) continue;
m_job->processLogOutput( line );
m_stdOut.append( line );
appendStdOut( line );
}
}
}
@@ -81,3 +80,15 @@ void RimJobMonitor::started()
RimProcessMonitor::started();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimJobMonitor::appendStdOut( QString line )
{
if ( m_job != nullptr )
{
m_job->processLogOutput( line );
}
m_stdOut.append( line );
}
@@ -36,6 +36,7 @@ protected:
void readyReadStandardOutput() override;
void finished( int exitCode, QProcess::ExitStatus exitStatus ) override;
void started() override;
void appendStdOut( QString line ) override;
private:
caf::PdmPointer<RimGenericJob> m_job;
@@ -159,9 +159,6 @@ RimOpmFlowJob::RimOpmFlowJob()
CAF_PDM_InitField( &m_autoLoadResults, "AutoLoadResults", true, "Automatic loading of results from job" );
m_autoLoadResults.uiCapability()->setUiHidden( true );
CAF_PDM_InitField( &m_isChildJob, "IsChildJob", false, "Is Child Job" );
m_isChildJob.uiCapability()->setUiHidden( true );
caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_addToEnsemble );
caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_pauseBeforeRun );
caf::PdmUiNativeCheckBoxEditor::configureFieldForEditor( &m_useRestart );
@@ -272,7 +269,7 @@ void RimOpmFlowJob::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering&
return;
}
if ( m_isChildJob() )
if ( isChildJob() )
{
m_workDir.uiCapability()->setUiReadOnly( true );
uiOrdering.add( &m_workDir );
@@ -698,6 +695,15 @@ QString RimOpmFlowJob::deckExtension() const
return ".DATA";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimOpmFlowJob::numberOfProcesses()
{
if ( !RiaPreferencesOpm::current()->useMpi() ) return 1;
return m_jobSettings()->mpiProcesses();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -714,9 +720,6 @@ QStringList RimOpmFlowJob::command()
auto opmPref = RiaPreferencesOpm::current();
if ( opmPref->useWsl() )
{
cmd.append( RiaWslTools::wslCommand() );
cmd.append( opmPref->wslOptions() );
workDir = RiaWslTools::convertToWslPath( workDir );
dataFile = RiaWslTools::convertToWslPath( dataFile );
}
@@ -725,7 +728,7 @@ QStringList RimOpmFlowJob::command()
{
cmd.append( opmPref->mpirunCommand() );
cmd.append( QString( "-np" ) );
cmd.append( QString( "%1" ).arg( m_jobSettings->mpiProcesses() ) );
cmd.append( QString( "%1" ).arg( numberOfProcesses() ) );
}
cmd.append( opmPref->opmFlowCommand() );
@@ -924,6 +927,14 @@ void RimOpmFlowJob::onProgress( double percentageDone )
progressUpdate.send( percentageDone );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimOpmFlowJob::shouldUseWsl()
{
return RiaPreferencesOpm::current()->useWsl();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -1340,11 +1351,3 @@ void RimOpmFlowJob::setAutoLoadResults( bool autoLoadResults )
{
m_autoLoadResults = autoLoadResults;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimOpmFlowJob::setIsChildJob( bool isChildJob )
{
m_isChildJob = isChildJob;
}
@@ -63,7 +63,6 @@ public:
void setInputDataFile( QString filename );
void setJobSettings( RimOpmFlowJobSettings* jobSettings );
void setJobWellSettings( RimJobWellSettings* jobWellSettings );
void setIsChildJob( bool isChildJob );
void initAfterCopy();
@@ -90,6 +89,8 @@ protected:
bool onRun() override;
void onCompleted( bool success ) override;
void onProgress( double percentageDone ) override;
bool shouldUseWsl() override;
int numberOfProcesses() override;
bool openDeckFile();
void closeDeckFile();
@@ -125,7 +126,6 @@ private:
caf::PdmField<bool> m_addToEnsemble;
caf::PdmField<int> m_currentRunId;
caf::PdmField<bool> m_useRestart;
caf::PdmField<bool> m_isChildJob;
caf::PdmPtrField<RimWellPath*> m_wellPath;
caf::PdmPtrField<RimEclipseCase*> m_eclipseCase;
@@ -23,6 +23,7 @@
#include "RiaLogging.h"
#include "RiaQStringFormatter.h"
#include "Batch/RimBatchQueue.h"
#include "RimJobMonitor.h"
#include "RimProcess.h"
#include "RimProcessQueue.h"
@@ -30,7 +31,6 @@
#include "RiuGuiTheme.h"
#include "cafPdmUiTreeAttributes.h"
#include "cafPdmUiTreeViewEditor.h"
#include "cafProgressInfo.h"
#include <QMessageBox>
@@ -45,6 +45,7 @@ RimSingleJob::RimSingleJob()
, m_errorsDetected( 0 )
, m_warningsDetected( 0 )
, m_process( nullptr )
, m_queue( nullptr )
{
CAF_PDM_InitObject( "Generic Single Job" );
}
@@ -67,15 +68,14 @@ double RimSingleJob::percentageDone() const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimSingleJob::stop()
void RimSingleJob::stop()
{
if ( !m_process.isNull() )
if ( m_queue != nullptr )
{
RimProcessQueue::stopProcess( m_process->ID() );
RiaLogging::info( std::format( "Job \"{}\" stopped by user.", name() ) );
return true;
m_queue->stopProcess();
RiaLogging::info( std::format( "Stop job \"{}\" requested by user.", name() ) );
}
return false;
return;
}
//--------------------------------------------------------------------------------------------------
@@ -89,6 +89,7 @@ bool RimSingleJob::execute()
m_warningsDetected = 0;
m_percentageDone = 0.0;
m_process = nullptr;
setState( JobState::Idle );
onProgress( m_percentageDone );
@@ -120,11 +121,17 @@ bool RimSingleJob::execute()
setDeletable( false );
setState( JobState::Queued );
m_process = new RimProcess( true, new RimJobMonitor( this ) );
m_process = std::make_shared<RimProcess>( true /*log output*/, std::make_shared<RimJobMonitor>( this ) );
// on windows, should run using wsl?
m_process->setUseWsl( shouldUseWsl() );
m_process->setDescription( name() );
// build process to run
QString cmd = cmdLine.takeFirst();
m_process->setCommand( cmd );
if ( !cmdLine.isEmpty() ) m_process->addParameters( cmdLine );
m_process->setWorkingDirectory( workingDirectory() );
for ( const auto& [name, value] : environment() )
@@ -132,7 +139,9 @@ bool RimSingleJob::execute()
m_process->addEnvironmentVariable( name, value );
}
RimProcessQueue::queueProcess( m_process );
m_queue.reset( RimBatchQueue::createBatchQueue() );
m_queue->queueProcess( m_process, numberOfProcesses() );
onProgress( m_percentageDone );
return true;
@@ -143,11 +152,11 @@ bool RimSingleJob::execute()
//--------------------------------------------------------------------------------------------------
void RimSingleJob::setFinished( bool runOk )
{
setState( runOk ? JobState::Completed : JobState::Failed );
RimGenericJob::setFinished( runOk );
m_percentageDone = 100.0;
onProgress( m_percentageDone );
setDeletable( true );
setDeletable( !isChildJob() );
onCompleted( runOk );
}
@@ -159,15 +168,14 @@ void RimSingleJob::setStarted()
{
setState( RimSingleJob::JobState::Running );
onProgress( m_percentageDone );
RimGenericJob::setStarted();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<caf::PdmUiTreeViewItemTag> RimSingleJob::treeItemTags()
void RimSingleJob::defineObjectEditorAttribute( QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
{
std::vector<caf::PdmUiTreeViewItemTag> tags;
static auto warnColor = QColor( RiaColorTools::toQColor( cvf::Color3f( cvf::Color3f::DARK_YELLOW ) ) );
static auto contrastWarnColor =
QColor( RiaColorTools::toQColor( RiaColorTools::contrastColor( cvf::Color3f( cvf::Color3f::DARK_YELLOW ) ) ) );
@@ -176,54 +184,71 @@ std::vector<caf::PdmUiTreeViewItemTag> RimSingleJob::treeItemTags()
static auto contrastWaitColor =
QColor( RiaColorTools::toQColor( RiaColorTools::contrastColor( cvf::Color3f( cvf::Color3f::LIGHT_GRAY ) ) ) );
if ( state() == JobState::Failed )
if ( auto* treeItemAttribute = dynamic_cast<caf::PdmUiTreeViewItemAttribute*>( attribute ) )
{
auto txt = m_errorsDetected > 0 ? QString( "[%1]" ).arg( m_errorsDetected ) : "!!!";
auto tag = caf::PdmUiTreeViewItemTag::create( QColor( Qt::red ), RiuGuiTheme::getColorByVariableName( "backgroundColor1" ), txt );
tags.push_back( tag );
}
else if ( state() == JobState::Queued )
{
auto tag = caf::PdmUiTreeViewItemTag::create();
tag.text = "Waiting...";
tag.bgColor = waitColor;
tag.fgColor = contrastWaitColor;
tags.push_back( tag );
}
else if ( ( state() == JobState::Running ) || ( state() == JobState::Completed ) )
{
auto tag = caf::PdmUiTreeViewItemTag::create();
if ( state() == JobState::Running )
if ( state() == JobState::Failed )
{
tag.text = QString( "%1 %" ).arg( m_percentageDone, 0, 'f', 1 );
auto txt = m_errorsDetected > 0 ? QString( "[%1]" ).arg( m_errorsDetected ) : "!!!";
auto tag =
caf::PdmUiTreeViewItemAttribute::createTag( QColor( Qt::red ), RiuGuiTheme::getColorByVariableName( "backgroundColor1" ), txt );
treeItemAttribute->tags.push_back( std::move( tag ) );
}
else
else if ( state() == JobState::Queued )
{
tag.text = "Done";
auto tag = caf::PdmUiTreeViewItemAttribute::createTag();
tag->text = "Waiting...";
tag->bgColor = waitColor;
tag->fgColor = contrastWaitColor;
treeItemAttribute->tags.push_back( std::move( tag ) );
}
else if ( ( state() == JobState::Running ) || ( state() == JobState::Completed ) )
{
auto tag = caf::PdmUiTreeViewItemAttribute::createTag();
double factor = m_percentageDone / 100.0;
if ( state() == JobState::Running )
{
if ( m_percentageDone == 0.0 )
{
tag->text = "Submitted";
}
else
{
tag->text = QString( "%1 %" ).arg( m_percentageDone, 0, 'f', 1 );
}
}
else
{
tag->text = "Done";
}
cvf::Color3f viewColor = cvf::Color3f( cvf::Color3f::GREEN );
viewColor.set( viewColor.r() * factor, viewColor.g() * factor, viewColor.b() * factor );
cvf::Color3f viewTextColor = RiaColorTools::contrastColor( viewColor );
tag.bgColor = QColor( RiaColorTools::toQColor( viewColor ) );
tag.fgColor = QColor( RiaColorTools::toQColor( viewTextColor ) );
tags.push_back( tag );
double factor = m_percentageDone / 100.0;
cvf::Color3f viewColor = cvf::Color3f( cvf::Color3f::GREEN );
viewColor.set( viewColor.r() * factor, viewColor.g() * factor, viewColor.b() * factor );
cvf::Color3f viewTextColor = RiaColorTools::contrastColor( viewColor );
tag->bgColor = QColor( RiaColorTools::toQColor( viewColor ) );
tag->fgColor = QColor( RiaColorTools::toQColor( viewTextColor ) );
treeItemAttribute->tags.push_back( std::move( tag ) );
if ( ( state() == JobState::Running ) && !isChildJob() )
{
auto timeTag = caf::PdmUiTreeViewItemAttribute::createTag( QColor( Qt::black ),
QColor( Qt::white ),
runTimeString().toUtf8().constData() );
treeItemAttribute->tags.push_back( std::move( timeTag ) );
}
}
if ( m_warningsDetected > 0 )
{
auto warnTxt = QString( "[%1]" ).arg( m_warningsDetected );
auto warnTag = caf::PdmUiTreeViewItemAttribute::createTag( warnColor, contrastWarnColor, warnTxt );
treeItemAttribute->tags.push_back( std::move( warnTag ) );
auto iconTag = caf::PdmUiTreeViewItemAttribute::createTag();
iconTag->icon = caf::IconProvider( ":/warning.png" );
treeItemAttribute->tags.push_back( std::move( iconTag ) );
}
}
if ( m_warningsDetected > 0 )
{
auto warnTxt = QString( "[%1]" ).arg( m_warningsDetected );
auto warnTag = caf::PdmUiTreeViewItemTag::create( warnColor, contrastWarnColor, warnTxt );
tags.push_back( warnTag );
auto iconTag = caf::PdmUiTreeViewItemTag::create();
iconTag.icon = caf::IconProvider( ":/warning.png" );
tags.push_back( iconTag );
}
return tags;
}
//--------------------------------------------------------------------------------------------------
@@ -231,6 +256,6 @@ std::vector<caf::PdmUiTreeViewItemTag> RimSingleJob::treeItemTags()
//--------------------------------------------------------------------------------------------------
const QStringList RimSingleJob::jobLog() const
{
if ( m_process.isNull() ) return QStringList();
if ( m_process == nullptr ) return QStringList();
return m_process->stdOut();
}
@@ -27,8 +27,10 @@
#include <QStringList>
#include <map>
#include <memory>
class RimProcess;
class RimBatchQueue;
//==================================================================================================
///
@@ -43,7 +45,7 @@ public:
~RimSingleJob() override;
bool execute() override;
bool stop() override;
void stop() override;
double percentageDone() const override;
const QStringList jobLog() const override;
bool matchesKeyValue( const QString& key, const QString& value ) const override = 0;
@@ -61,6 +63,8 @@ protected:
virtual bool onRun() = 0;
virtual void onCompleted( bool success ) = 0;
virtual void onProgress( double percentageDone ) = 0;
virtual bool shouldUseWsl() = 0;
virtual int numberOfProcesses() = 0;
protected:
double m_percentageDone;
@@ -68,5 +72,6 @@ protected:
int m_errorsDetected;
private:
caf::PdmPointer<RimProcess> m_process;
std::shared_ptr<RimProcess> m_process;
std::shared_ptr<RimBatchQueue> m_queue;
};
@@ -19,7 +19,9 @@
#include "RimProcess.h"
#include "RiaLogging.h"
#include "RiaPreferencesOpm.h"
#include "RiaQStringFormatter.h"
#include "RiaWslTools.h"
#include "RimProcessMonitor.h"
#include "cafPdmFieldCapability.h"
@@ -35,13 +37,13 @@ size_t RimProcess::m_nextProcessId = 1;
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProcess::RimProcess( bool logStdOutErr /*true*/, RimProcessMonitor* monitor )
RimProcess::RimProcess( bool logStdOutErr, std::shared_ptr<RimProcessMonitor> monitor )
: m_enableLogging( logStdOutErr )
, m_qProcess( nullptr )
{
size_t defId = m_nextProcessId++;
if ( monitor == nullptr )
m_monitor = new RimProcessMonitor( defId, logStdOutErr );
m_monitor = std::make_shared<RimProcessMonitor>( defId, logStdOutErr );
else
{
m_monitor = monitor;
@@ -61,6 +63,9 @@ RimProcess::RimProcess( bool logStdOutErr /*true*/, RimProcessMonitor* monitor )
CAF_PDM_InitField( &m_id, "ID", defId, "ID" );
m_id.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitField( &m_useWsl, "UseWSL", false, "Use WSL to launch process" );
m_useWsl.uiCapability()->setUiReadOnly( true );
}
//--------------------------------------------------------------------------------------------------
@@ -68,11 +73,6 @@ RimProcess::RimProcess( bool logStdOutErr /*true*/, RimProcessMonitor* monitor )
//--------------------------------------------------------------------------------------------------
RimProcess::~RimProcess()
{
if ( m_monitor != nullptr )
{
delete m_monitor;
m_monitor = nullptr;
}
}
caf::PdmFieldHandle* RimProcess::userDescriptionField()
@@ -124,12 +124,28 @@ void RimProcess::setDescription( QString desc )
m_description = desc;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProcess::description() const
{
return m_description();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::shared_ptr<RimProcessMonitor> RimProcess::monitor() const
{
return m_monitor;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProcess::command() const
{
return m_command;
return m_command();
}
//--------------------------------------------------------------------------------------------------
@@ -143,19 +159,35 @@ QStringList RimProcess::parameters() const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimProcess::stdOut() const
const QStringList& RimProcess::stdOut() const
{
if ( m_monitor ) return m_monitor->stdOut();
return QStringList();
return m_emptyList;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimProcess::stdErr() const
const QStringList& RimProcess::stdErr() const
{
if ( m_monitor ) return m_monitor->stdErr();
return QStringList();
return m_emptyList;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProcess::appendStdOut( QString line )
{
if ( m_monitor ) m_monitor->appendStdOut( line );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProcess::appendStdErr( QString line )
{
if ( m_monitor ) m_monitor->appendStdErr( line );
}
//--------------------------------------------------------------------------------------------------
@@ -177,14 +209,20 @@ bool RimProcess::start( bool enableStdOut, bool enableStdErr )
m_qProcess = new QProcess();
QString cmd = commandLine();
if ( m_enableLogging ) RiaLogging::info( std::format( "Start process {}: {}", m_id(), cmd ) );
if ( m_enableLogging )
{
std::string wslTxt = m_useWsl() ? " using WSL" : "";
RiaLogging::info( std::format( "Start process {}{}: {}", m_id(), wslTxt, cmd ) );
}
m_monitor->clearStdOutErr();
QObject::connect( m_qProcess, SIGNAL( finished( int, QProcess::ExitStatus ) ), m_monitor, SLOT( finished( int, QProcess::ExitStatus ) ) );
if ( enableStdOut ) QObject::connect( m_qProcess, SIGNAL( readyReadStandardOutput() ), m_monitor, SLOT( readyReadStandardOutput() ) );
if ( enableStdErr ) QObject::connect( m_qProcess, SIGNAL( readyReadStandardError() ), m_monitor, SLOT( readyReadStandardError() ) );
QObject::connect( m_qProcess, SIGNAL( started() ), m_monitor, SLOT( started() ) );
QObject::connect( m_qProcess, SIGNAL( finished( int, QProcess::ExitStatus ) ), m_monitor.get(), SLOT( finished( int, QProcess::ExitStatus ) ) );
if ( enableStdOut )
QObject::connect( m_qProcess, SIGNAL( readyReadStandardOutput() ), m_monitor.get(), SLOT( readyReadStandardOutput() ) );
if ( enableStdErr )
QObject::connect( m_qProcess, SIGNAL( readyReadStandardError() ), m_monitor.get(), SLOT( readyReadStandardError() ) );
QObject::connect( m_qProcess, SIGNAL( started() ), m_monitor.get(), SLOT( started() ) );
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
for ( auto& [key, val] : m_environmentVariables )
@@ -197,17 +235,42 @@ bool RimProcess::start( bool enableStdOut, bool enableStdErr )
m_qProcess->setWorkingDirectory( m_workDir().path() );
}
if ( m_useWsl() )
{
m_arguments.prepend( m_command() );
m_arguments = RiaPreferencesOpm::current()->wslOptions() + m_arguments;
m_command = RiaWslTools::wslCommand();
}
m_qProcess->start( m_command, m_arguments );
if ( !m_qProcess->waitForStarted( -1 ) )
{
auto error = m_qProcess->errorString();
RiaLogging::error( std::format( "Failed to start process {}. {}.", m_id(), error ) );
if ( m_enableLogging )
{
auto error = m_qProcess->errorString();
RiaLogging::error( std::format( "Failed to start process {}. {}.", m_id(), error ) );
}
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProcess::writeToStdIn( QStringList text )
{
if ( m_qProcess == nullptr ) return;
for ( auto& line : text )
{
m_qProcess->write( line.toUtf8() + "\n" );
m_qProcess->waitForBytesWritten();
m_qProcess->closeWriteChannel();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -342,6 +405,14 @@ void RimProcess::addEnvironmentVariable( QString name, QString value )
m_environmentVariables.push_back( std::make_pair( name, value ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<std::pair<QString, QString>>& RimProcess::environmentVariables() const
{
return m_environmentVariables;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -349,3 +420,27 @@ void RimProcess::setWorkingDirectory( QString workDir )
{
m_workDir = workDir;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProcess::workingDirectory() const
{
return m_workDir().path();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimProcess::useWsl() const
{
return m_useWsl();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProcess::setUseWsl( bool useWsl )
{
m_useWsl = useWsl;
}
@@ -25,6 +25,7 @@
#include <QString>
#include <QStringList>
#include <memory>
#include <utility>
#include <vector>
@@ -36,7 +37,7 @@ class RimProcess : public caf::PdmObject
CAF_PDM_HEADER_INIT;
public:
RimProcess( bool logStdOutErr = true, RimProcessMonitor* monitor = nullptr );
RimProcess( bool logStdOutErr = true, std::shared_ptr<RimProcessMonitor> monitor = nullptr );
~RimProcess() override;
void setDescription( QString desc );
@@ -46,13 +47,21 @@ public:
void addEnvironmentVariable( QString name, QString value );
void setWorkingDirectory( QString directory );
const std::vector<std::pair<QString, QString>>& environmentVariables() const;
void setWorkingDirectory( QString directory );
QString workingDirectory() const;
QString commandLine() const;
QString command() const;
QStringList parameters() const;
size_t ID() const;
QString command() const;
QStringList parameters() const;
size_t ID() const;
QString description() const;
std::shared_ptr<RimProcessMonitor> monitor() const;
bool useWsl() const;
void setUseWsl( bool useWsl );
// blocking run
bool execute( bool enableStdOut = true, bool enableStdErr = true );
@@ -62,9 +71,13 @@ public:
void cleanUpAfterRun();
void terminate();
void notifyErrorFinish();
void writeToStdIn( QStringList stdIn );
QStringList stdErr() const;
QStringList stdOut() const;
const QStringList& stdErr() const;
const QStringList& stdOut() const;
void appendStdOut( QString line );
void appendStdErr( QString line );
protected:
caf::PdmFieldHandle* userDescriptionField() override;
@@ -81,11 +94,13 @@ private:
caf::PdmField<QString> m_description;
caf::PdmField<size_t> m_id;
caf::PdmField<caf::FilePath> m_workDir;
caf::PdmField<bool> m_useWsl;
std::vector<std::pair<QString, QString>> m_environmentVariables;
static size_t m_nextProcessId;
RimProcessMonitor* m_monitor;
bool m_enableLogging;
QPointer<QProcess> m_qProcess;
static size_t m_nextProcessId;
std::shared_ptr<RimProcessMonitor> m_monitor;
bool m_enableLogging;
QPointer<QProcess> m_qProcess;
QStringList m_emptyList;
};
@@ -164,7 +164,7 @@ void RimProcessMonitor::clearStdOutErr()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimProcessMonitor::stdOut() const
const QStringList& RimProcessMonitor::stdOut() const
{
return m_stdOut;
}
@@ -172,7 +172,7 @@ QStringList RimProcessMonitor::stdOut() const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimProcessMonitor::stdErr() const
const QStringList& RimProcessMonitor::stdErr() const
{
return m_stdErr;
}
@@ -184,3 +184,27 @@ void RimProcessMonitor::setProcessId( size_t processId )
{
m_processId = processId;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProcessMonitor::appendStdOut( QString line )
{
m_stdOut.append( line );
if ( m_logStdOutErr )
{
RiaLogging::info( addPrefix( line ).toStdString() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProcessMonitor::appendStdErr( QString line )
{
m_stdErr.append( line );
if ( m_logStdOutErr )
{
RiaLogging::error( addPrefix( line ).toStdString() );
}
}
@@ -30,9 +30,12 @@ class RimProcessMonitor : public QObject
public:
explicit RimProcessMonitor( size_t processId, bool logStdOutErr = true );
void clearStdOutErr();
QStringList stdOut() const;
QStringList stdErr() const;
void clearStdOutErr();
const QStringList& stdOut() const;
const QStringList& stdErr() const;
virtual void appendStdOut( QString line );
virtual void appendStdErr( QString line );
void setProcessId( size_t processId );
@@ -20,7 +20,7 @@
#include "RimProcess.h"
#include "RiaPreferencesOpm.h"
#include "RiaPreferencesBatch.h"
//--------------------------------------------------------------------------------------------------
///
@@ -70,7 +70,7 @@ void RimProcessQueue::launchNextProcessIfPossible()
{
if ( m_waitingProcesses.empty() ) return;
if ( m_runningProcesses.size() >= RiaPreferencesOpm::current()->maxParallelJobs() ) return;
if ( m_runningProcesses.size() >= RiaPreferencesBatch::current()->maxParallelJobs() ) return;
auto nextProcess = m_waitingProcesses.front();
m_waitingProcesses.pop_front();
@@ -91,6 +91,7 @@ set(SOURCE_UNITTEST_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaWeightedHarmonicMeanCalculator-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaCellDividingTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaFilePathTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaHpcTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/Intersect-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RifPerforationIntervalReader-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWellPathCompletions-Test.cpp
@@ -0,0 +1,134 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "gtest/gtest.h"
#include "RiaHpcTools.h"
#include <QStringList>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaHpcTools, decodeSlurmQueues )
{
QStringList input;
input.append( "PARTITION AVAIL TIMELIMIT NODES STATE NODELIST" );
input.append( "normal* up infinite 1 drain* node-21-03" );
input.append( "normal* up infinite 70 mix node-20-[00-27],node-21-[00-02,04-31]" );
input.append( "normal* up infinite 1 idle node-21-32" );
input.append(
"extra up infinite 53 drain* node-22-[00,03,08-10,16-19,26,43],node-23-[00,16-19,21,42-43],node-24-[14,27,33]" );
input.append( "extra up infinite 1 drain node-25-37" );
input.append(
"extra up infinite 200 alloc node-22-[01-02,04-07,11-15,20-25],node-23-[15,20],node-24-[00-13,15-26,28-32,34-43]" );
input.append( "extra up infinite 51 idle node-22-[27-42],node-23-[01-14,22-41],node-25-43" );
auto queueNames = RiaHpcTools::decodeSlurmQueues( input );
ASSERT_EQ( queueNames.length(), 2 );
ASSERT_TRUE( queueNames.contains( "normal" ) );
ASSERT_TRUE( queueNames.contains( "extra" ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaHpcTools, decodeLsfQueues )
{
QStringList input;
input.append( "QUEUE_NAME PRIO STATUS MAX JL/U JL/P JL/H NJOBS PEND RUN SUSP RSV" );
input.append( "urgent 99 Open:Active - - - - 0 0 0 0 0" );
input.append( "wait 90 Open:Active - - - - 0 0 0 0 0" );
input.append( "extra 60 Open:Active - - - - 0 0 0 0 0" );
input.append( "super 60 Open:Active - - - - 0 0 0 0 0" );
input.append( "normal 50 Open:Active - - - - 0 0 0 0 0" );
auto queueNames = RiaHpcTools::decodeLsfQueues( input );
ASSERT_EQ( queueNames.length(), 5 );
ASSERT_TRUE( queueNames.contains( "normal" ) );
ASSERT_TRUE( queueNames.contains( "extra" ) );
ASSERT_TRUE( queueNames.contains( "super" ) );
ASSERT_TRUE( queueNames.contains( "wait" ) );
ASSERT_TRUE( queueNames.contains( "urgent" ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaHpcTools, decodeSlurmJobs )
{
QStringList input;
input.append( "80 RI_DROGON-2 0:51 RUNNING simulations compute_node" );
input.append( "79 RI_DROGON-1 0:54 RUNNING simulations compute_node" );
input.append( "78 RI_DROGON-0 0:57 RUNNING simulations compute_node" );
input.append( "77 RI_DROGON-3 1:00 RUNNING normal compute_node" );
auto jobs = RiaHpcTools::decodeSlurmJobs( input );
ASSERT_TRUE( jobs.size() == 4 );
ASSERT_TRUE( jobs[0].size() == 6 );
ASSERT_TRUE( jobs[1].size() == 6 );
ASSERT_TRUE( jobs[2].size() == 6 );
ASSERT_TRUE( jobs[3].size() == 6 );
ASSERT_TRUE( jobs[0][0] == "80" );
ASSERT_TRUE( jobs[1][0] == "79" );
ASSERT_TRUE( jobs[2][0] == "78" );
ASSERT_TRUE( jobs[3][0] == "77" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaHpcTools, decodeLsfJobs )
{
QStringList input;
input.append( "612950 flow TEST-B 22 second(s) RUN mr 3*st-rsb04-04-01 " );
input.append( "612949 flow TEST-A 30 second(s) RUN mr 5*st-rsb03-03-08 " );
input.append( "612952 flow TEST-C 12 second(s) RUN jr 7*st-rsv17-17-16" );
input.append( "612951 flow TEST-D 15 second(s) RUN mr 6*st-rsb04-04-02 " );
auto jobs = RiaHpcTools::decodeLsfJobs( input );
ASSERT_TRUE( jobs.size() == 4 );
ASSERT_TRUE( jobs[0].size() == 6 );
ASSERT_TRUE( jobs[1].size() == 6 );
ASSERT_TRUE( jobs[2].size() == 6 );
ASSERT_TRUE( jobs[3].size() == 6 );
ASSERT_TRUE( jobs[0][0] == "612950" );
ASSERT_TRUE( jobs[1][0] == "612949" );
ASSERT_TRUE( jobs[2][0] == "612952" );
ASSERT_TRUE( jobs[3][0] == "612951" );
ASSERT_TRUE( jobs[0][1] == "flow TEST-B" );
ASSERT_TRUE( jobs[1][1] == "flow TEST-A" );
ASSERT_TRUE( jobs[2][1] == "flow TEST-C" );
ASSERT_TRUE( jobs[3][1] == "flow TEST-D" );
ASSERT_TRUE( jobs[0][4] == "mr" );
ASSERT_TRUE( jobs[1][4] == "mr" );
ASSERT_TRUE( jobs[2][4] == "jr" );
ASSERT_TRUE( jobs[3][4] == "mr" );
}
@@ -50,6 +50,8 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiuTreeViewEventFilter.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuViewer.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuViewerCommands.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuBatchQueueMonitor.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuBatchMonitorWorker.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuCellAndNncPickEventHandler.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuPickItemInfo.cpp
${CMAKE_CURRENT_LIST_DIR}/RiuWellLogTrack.cpp
@@ -0,0 +1,76 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiuBatchMonitorWorker.h"
#include "RiaHpcTools.h"
#include <QMutexLocker>
#include <QStringList>
#include <QThread>
#include <QWidget>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuBatchMonitorWorker::RiuBatchMonitorWorker( RiaDefines::BatchSchedulerType scheduler, int interval, QObject* parent )
: QObject( parent )
, m_keepRunning( true )
, m_monitoringIntervalSeconds( interval )
, m_scheduler( scheduler )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuBatchMonitorWorker::stopMonitoring()
{
// signal to worker thread it is time to stop
m_keepRunning = false;
// wait for thread to finish, but don't wait forever
QMutexLocker locker( &m_mutex );
m_waitForStop.wait( &m_mutex, 8000 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuBatchMonitorWorker::gatherInformation()
{
while ( m_keepRunning )
{
auto jobInfo = RiaHpcTools::listJobs( m_scheduler );
emit informationGathered( jobInfo );
// poll exit flag 5 times per second to respond quickly to program exit
int i = 0;
const int maxI = m_monitoringIntervalSeconds * 5;
while ( i < maxI && m_keepRunning )
{
QThread::msleep( 200 );
i++;
}
}
emit finished();
m_mutex.lock();
m_waitForStop.wakeAll();
m_mutex.unlock();
}
@@ -0,0 +1,55 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaHpcDefines.h"
#include <atomic>
#include <vector>
#include <QMutex>
#include <QObject>
#include <QStringList>
#include <QWaitCondition>
class QWidget;
class RiuBatchMonitorWorker : public QObject
{
Q_OBJECT
public:
explicit RiuBatchMonitorWorker( RiaDefines::BatchSchedulerType scheduler, int interval, QObject* parent = nullptr );
void stopMonitoring();
public slots:
void gatherInformation();
signals:
void informationGathered( const std::vector<std::vector<QString>> jobInfo );
void finished();
private:
std::atomic<bool> m_keepRunning;
int m_monitoringIntervalSeconds;
QMutex m_mutex;
QWaitCondition m_waitForStop;
RiaDefines::BatchSchedulerType m_scheduler;
};
@@ -0,0 +1,185 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiuBatchQueueMonitor.h"
#include "RiaApplication.h"
#include "RiaGuiApplication.h"
#include "RiaLogging.h"
#include "RiaPreferencesBatch.h"
#include "RiuBatchMonitorWorker.h"
#include "RiuMessagePanel.h"
#include "RiuPlotMainWindow.h"
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QTableWidget>
#include <QThread>
#include <QWidget>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuBatchQueueMonitor::RiuBatchQueueMonitor( QWidget* pParent )
: QWidget( pParent )
, m_monitorWorker( nullptr )
{
m_checkBoxAutoUpdate = new QCheckBox( "Enable Job Monitor", this );
m_checkBoxAutoUpdate->setChecked( false );
m_onlyResInsightJobs = new QCheckBox( "Only Show ResInsight Jobs", this );
m_onlyResInsightJobs->setChecked( true );
QLabel* label = new QLabel( "Refresh Interval:", this );
m_refreshInterval = new QComboBox( this );
m_refreshInterval->addItem( "5 seconds", QVariant( 5 ) );
m_refreshInterval->addItem( "10 seconds", QVariant( 10 ) );
m_refreshInterval->addItem( "30 seconds", QVariant( 30 ) );
m_refreshInterval->setCurrentIndex( 1 );
m_refreshInterval->setEditable( false );
QHBoxLayout* pTopLayout = new QHBoxLayout;
pTopLayout->addWidget( m_checkBoxAutoUpdate );
pTopLayout->addSpacing( 5 );
pTopLayout->addWidget( m_onlyResInsightJobs );
pTopLayout->addSpacing( 10 );
pTopLayout->addWidget( label );
pTopLayout->addWidget( m_refreshInterval );
pTopLayout->addStretch();
m_jobView = new QTableWidget( this );
m_jobView->setColumnCount( 6 );
m_jobView->setRowCount( 1 );
m_jobView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
m_jobView->setSizeAdjustPolicy( QAbstractScrollArea::AdjustToContents );
m_jobView->verticalHeader()->setVisible( false );
QStringList headerNames;
headerNames << "Job ID" << "Job Name" << "Run Time" << "State" << "Queue/Partition" << "Where";
m_jobView->setHorizontalHeaderLabels( headerNames );
QVBoxLayout* pLayout = new QVBoxLayout();
pLayout->addLayout( pTopLayout );
pLayout->addWidget( m_jobView, 1 );
pLayout->setContentsMargins( 0, 0, 0, 0 );
setLayout( pLayout );
connect( m_checkBoxAutoUpdate, SIGNAL( clicked() ), this, SLOT( toggleUpdates() ) );
connect( m_refreshInterval, SIGNAL( currentIndexChanged( int ) ), this, SLOT( updateInterval() ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuBatchQueueMonitor::~RiuBatchQueueMonitor()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuBatchQueueMonitor::slotUpdateView( const std::vector<std::vector<QString>> information )
{
m_jobView->setRowCount( 0 );
for ( auto& line : information )
{
int newRowIndex = m_jobView->rowCount();
m_jobView->insertRow( newRowIndex );
if ( line.size() < 2 ) continue;
// only show jobs with ResInsight prefix?
if ( m_onlyResInsightJobs->isChecked() && !line[1].startsWith( "RI_" ) ) continue;
for ( int i = 0; i < line.size(); i++ )
{
auto item = new QTableWidgetItem( line[i] );
if ( i == 2 ) // time should be right-aligned
{
item->setTextAlignment( Qt::AlignRight | Qt::AlignVCenter );
}
m_jobView->setItem( newRowIndex, i, item );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuBatchQueueMonitor::toggleUpdates()
{
if ( m_checkBoxAutoUpdate->isChecked() )
{
auto schedulerType = RiaPreferencesBatch::current()->batchScheduler();
int interval = m_refreshInterval->currentData().toInt();
if ( interval < 5 ) interval = 5; // minimum interval of 5 seconds
if ( interval > 60 ) interval = 60; // maximum interval of 60 seconds
m_monitorWorker = new RiuBatchMonitorWorker( schedulerType, interval );
auto thread = new QThread( this );
m_monitorWorker->moveToThread( thread );
connect( thread, &QThread::started, m_monitorWorker, &RiuBatchMonitorWorker::gatherInformation );
connect( thread, &QThread::finished, m_monitorWorker, &QObject::deleteLater );
connect( thread, &QThread::finished, thread, &QObject::deleteLater );
connect( m_monitorWorker, &RiuBatchMonitorWorker::informationGathered, this, &RiuBatchQueueMonitor::slotUpdateView );
connect( m_monitorWorker, &RiuBatchMonitorWorker::finished, thread, &QThread::quit );
thread->start();
}
else
{
stopMonitoring();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuBatchQueueMonitor::stopMonitoring()
{
if ( m_monitorWorker != nullptr )
{
m_monitorWorker->stopMonitoring();
m_monitorWorker = nullptr;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuBatchQueueMonitor::updateInterval()
{
stopMonitoring();
if ( m_checkBoxAutoUpdate->isChecked() )
{
toggleUpdates();
}
}
@@ -0,0 +1,54 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <QStringList>
#include <QWidget>
class QTableWidget;
class QCheckBox;
class QComboBox;
class RiuBatchMonitorWorker;
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
class RiuBatchQueueMonitor : public QWidget
{
Q_OBJECT
public:
RiuBatchQueueMonitor( QWidget* parent );
~RiuBatchQueueMonitor() override;
void stopMonitoring();
private slots:
void slotUpdateView( const std::vector<std::vector<QString>> information );
void toggleUpdates();
void updateInterval();
private:
QCheckBox* m_checkBoxAutoUpdate;
QCheckBox* m_onlyResInsightJobs;
QComboBox* m_refreshInterval;
QTableWidget* m_jobView;
RiuBatchMonitorWorker* m_monitorWorker;
};
@@ -95,6 +95,14 @@ QString RiuDockWidgetTools::mainWindowProcessMonitorName()
return "dockProcessMonitor_mainWindow";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiuDockWidgetTools::mainWindowBatchQueueMonitorName()
{
return "dockBatchQueueMonitor_mainWindow";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -455,6 +463,8 @@ QIcon RiuDockWidgetTools::dockIcon( const QString dockWidgetName )
return QIcon( ":/SummaryEnsemble.svg" );
else if ( dockWidgetName == plotWindowQuickAccessName() || dockWidgetName == mainWindowQuickAccessName() )
return QIcon( ":/pinned.svg" );
else if ( dockWidgetName == mainWindowBatchQueueMonitorName() )
return QIcon( ":/BatchMonitor.svg" );
return QIcon( ":/view.svg" );
}
@@ -57,6 +57,7 @@ public:
static QString mainWindowPropertyEditorName();
static QString mainWindowResultInfoName();
static QString mainWindowProcessMonitorName();
static QString mainWindowBatchQueueMonitorName();
static QString mainWindowResultPlotName();
static QString mainWindowDepthPlotName();
static QString mainWindowRelPermPlotName();
@@ -56,6 +56,7 @@
#include "Workflow/RimWorkflowJob.h"
#include "Workflow/RimWorkflowTaskInput.h"
#include "RiuBatchQueueMonitor.h"
#include "RiuCellSelectionTool.h"
#include "RiuDepthQwtPlot.h"
#include "RiuDockWidgetTools.h"
@@ -370,6 +371,8 @@ void RiuMainWindow::closeEvent( QCloseEvent* event )
}
}
m_batchMonitor->stopMonitoring();
if ( auto proj = RimProject::current() )
{
proj->mainWindowDockState = dockWidgetStateString();
@@ -974,6 +977,15 @@ void RiuMainWindow::createDockPanels()
procAndMsgTabs = dockManager()->addDockWidget( ads::DockWidgetArea::RightDockWidgetArea, dockWidget, bottomArea );
}
{
auto dockWidget =
RiuDockWidgetTools::createDockWidget( "Batch Queue Monitor", RiuDockWidgetTools::mainWindowBatchQueueMonitorName(), dockManager() );
m_batchMonitor = new RiuBatchQueueMonitor( dockWidget );
dockWidget->setWidget( m_batchMonitor );
dockManager()->addDockWidgetTabToArea( dockWidget, procAndMsgTabs );
}
{
auto dockWidget = RiuDockWidgetTools::createDockWidget( "Messages", RiuDockWidgetTools::mainWindowMessagesName(), dockManager() );
@@ -49,6 +49,7 @@ class RimWorkflowFieldBinding;
class RiuMessagePanel;
class RiuProcessMonitor;
class RiuBatchQueueMonitor;
class RiuResultInfoPanel;
class RiuResultQwtPlot;
class RiuDepthQwtPlot;
@@ -183,6 +184,7 @@ private:
RiuResultInfoPanel* m_resultInfoPanel;
RiuProcessMonitor* m_processMonitor;
RiuBatchQueueMonitor* m_batchMonitor;
QPointer<RiuMessagePanel> m_messagePanel;
RiuResultQwtPlot* m_resultQwtPlot;
@@ -25,7 +25,6 @@
#include "cafUiProcess.h"
#include <QDockWidget>
#include <QHBoxLayout>
#include <QLabel>
#include <QPlainTextEdit>
@@ -6,7 +6,7 @@
//==================================================================================================
/// Reproduction of https://github.com/OPM/ResInsight/issues/14884
///
/// Modeled directly after RiaPreferencesHpc::appendItems() (branch origin/hpc_interface), which
/// Modeled directly after RiaPreferencesBatch::appendItems() (branch origin/hpc_interface), which
/// crashed in caf::PdmUiCheckBoxEditor::configureAndUpdateUi() when switching the "Batch Scheduler"
/// enum directly between SLURM and LSF: both options share the exact same three fields
/// (queue name, exclusive checkbox, scheduler options) inside two *differently named* ui groups.