Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fold experiments #8

Open
wants to merge 9 commits into
base: dashboard_dev
Choose a base branch
from
277 changes: 165 additions & 112 deletions artiq/dashboard/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,51 +91,105 @@ def about_to_close(self):

log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]


class _ExperimentDock(QtWidgets.QMdiSubWindow):
sigClosed = QtCore.pyqtSignal()


def __init__(self, manager, expurl):
QtWidgets.QMdiSubWindow.__init__(self)
super(_ExperimentDock, self).__init__()
qfm = QtGui.QFontMetrics(self.font())
self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing())
self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogContentsView))

self.layout = QtWidgets.QGridLayout()
self.manager = manager
self.expurl = expurl
self.scheduling = manager.get_submission_scheduling(expurl)
self.options = manager.get_submission_options(expurl)
self.hdf5_load_directory = os.path.expanduser("~")

master_layout = QtWidgets.QVBoxLayout()
self._create_argument_editor()
master_layout.addWidget(self.argeditor)

# Create a toggle button that will collapse/expand the options.
self.fold_toggle = QtWidgets.QToolButton(text="Collapse Options",
checkable=True)
self.fold_toggle.setChecked(False)
self.fold_toggle.setToolTip("Collapse/Expand options")
self.fold_toggle.setArrowType(QtCore.Qt.DownArrow)
self.fold_toggle.clicked.connect(self.on_fold_toggle)
master_layout.addWidget(self.fold_toggle)

# Create a container widget (with a grid layout) for all the foldable options.
self.foldable_container = QtWidgets.QWidget()
self.foldable_layout = QtWidgets.QGridLayout()
self.foldable_layout.setSpacing(5)
self.foldable_layout.setContentsMargins(5, 5, 5, 5)
self.foldable_container.setLayout(self.foldable_layout)
master_layout.addWidget(self.foldable_container)

# Create a container widget (with a horizontal layout) for
# always-visible buttons.
self.always_visible_container = QtWidgets.QWidget()
self.always_visible_layout = QtWidgets.QHBoxLayout()
self.always_visible_layout.setSpacing(5)
self.always_visible_layout.setContentsMargins(5, 5, 5, 5)
self.always_visible_container.setLayout(self.always_visible_layout)
master_layout.addWidget(self.always_visible_container)

# Set the master layout on the top widget.
top_widget = QtWidgets.QWidget()
top_widget.setLayout(self.layout)
top_widget.setLayout(master_layout)
self.setWidget(top_widget)
self.layout.setSpacing(5)
self.layout.setContentsMargins(5, 5, 5, 5)

self.manager = manager
self.expurl = expurl
# --- Create the various widget groups ---
# Place all “foldable” widgets into the foldable_layout.
self._create_due_date_widgets()
self._create_pipeline_widgets()
self._create_priority_widgets()
self._create_flush_widgets()
self._create_devarg_override_widgets()
self._create_log_level_widgets()
self._create_repo_rev_widgets()
# Place the submit and termination buttons into the always-visible container.
self._create_submit_widgets()
self._create_reqterm_widgets()

self.on_fold_toggle()

def on_fold_toggle(self):
"""Toggle the visibility of the options."""
if self.fold_toggle.isChecked():
self.foldable_container.show()
self.fold_toggle.setText("Collapse Options")
self.fold_toggle.setArrowType(QtCore.Qt.DownArrow)
else:
self.foldable_container.hide()
self.fold_toggle.setText("Expand Options")
self.fold_toggle.setArrowType(QtCore.Qt.RightArrow)

editor_class = self.manager.get_argument_editor_class(expurl)
def _create_argument_editor(self):
editor_class = self.manager.get_argument_editor_class(self.expurl)
self.argeditor = editor_class(self.manager, self, self.expurl)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
self.layout.setRowStretch(0, 1)

scheduling = manager.get_submission_scheduling(expurl)
options = manager.get_submission_options(expurl)

def _create_due_date_widgets(self):
datetime = QtWidgets.QDateTimeEdit()
datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
datetime_en = QtWidgets.QCheckBox("Due date:")
self.layout.addWidget(datetime_en, 1, 0)
self.layout.addWidget(datetime, 1, 1)
self.foldable_layout.addWidget(datetime_en, 1, 0)
self.foldable_layout.addWidget(datetime, 1, 1)

if scheduling["due_date"] is None:
if self.scheduling["due_date"] is None:
datetime.setDate(QtCore.QDate.currentDate())
else:
datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch(
int(scheduling["due_date"] * 1000)))
datetime_en.setChecked(scheduling["due_date"] is not None)
int(self.scheduling["due_date"] * 1000)))
datetime_en.setChecked(self.scheduling["due_date"] is not None)

def update_datetime(dt):
scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000
self.scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000
datetime_en.setChecked(True)
datetime.dateTimeChanged.connect(update_datetime)

Expand All @@ -144,133 +198,132 @@ def update_datetime_en(checked):
due_date = datetime.dateTime().toMSecsSinceEpoch() / 1000
else:
due_date = None
scheduling["due_date"] = due_date
self.scheduling["due_date"] = due_date
datetime_en.stateChanged.connect(update_datetime_en)

def _create_pipeline_widgets(self):
self.pipeline_name = QtWidgets.QLineEdit()
pipeline_name = self.pipeline_name
self.layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2)
self.layout.addWidget(pipeline_name, 1, 3)
self.foldable_layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2)
self.foldable_layout.addWidget(self.pipeline_name, 1, 3)

pipeline_name.setText(scheduling["pipeline_name"])
self.pipeline_name.setText(self.scheduling["pipeline_name"])

def update_pipeline_name(text):
scheduling["pipeline_name"] = text
pipeline_name.textChanged.connect(update_pipeline_name)
self.scheduling["pipeline_name"] = text
self.pipeline_name.textChanged.connect(update_pipeline_name)

def _create_priority_widgets(self):
self.priority = QtWidgets.QSpinBox()
priority = self.priority
priority.setRange(-99, 99)
self.layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0)
self.layout.addWidget(priority, 2, 1)

priority.setValue(scheduling["priority"])
self.priority.setRange(-99, 99)
self.foldable_layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0)
self.foldable_layout.addWidget(self.priority, 2, 1)
self.priority.setValue(self.scheduling["priority"])

def update_priority(value):
scheduling["priority"] = value
priority.valueChanged.connect(update_priority)
self.scheduling["priority"] = value
self.priority.valueChanged.connect(update_priority)

def _create_flush_widgets(self):
self.flush = QtWidgets.QCheckBox("Flush")
flush = self.flush
flush.setToolTip("Flush the pipeline (of current- and higher-priority "
"experiments) before starting the experiment")
self.layout.addWidget(flush, 2, 2)
self.flush.setToolTip("Flush the pipeline (of current- and higher-priority "
"experiments) before starting the experiment")
self.foldable_layout.addWidget(self.flush, 2, 2)

flush.setChecked(scheduling["flush"])
self.flush.setChecked(self.scheduling["flush"])

def update_flush(checked):
scheduling["flush"] = bool(checked)
flush.stateChanged.connect(update_flush)
def update_flush(state):
self.scheduling["flush"] = bool(state)
self.flush.stateChanged.connect(update_flush)

devarg_override = QtWidgets.QComboBox()
devarg_override.setEditable(True)
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
devarg_override.lineEdit().setClearButtonEnabled(True)
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
self.layout.addWidget(devarg_override, 2, 3)
def _create_devarg_override_widgets(self):
self.devarg_override = QtWidgets.QComboBox()
self.devarg_override.setEditable(True)
self.devarg_override.lineEdit().setPlaceholderText("Override device arguments")
self.devarg_override.lineEdit().setClearButtonEnabled(True)
self.devarg_override.insertItem(0, "core:analyze_at_run_end=True")
self.foldable_layout.addWidget(self.devarg_override, 2, 3)

devarg_override.setCurrentText(options["devarg_override"])
self.devarg_override.setCurrentText(self.options["devarg_override"])

def update_devarg_override(text):
options["devarg_override"] = text
devarg_override.editTextChanged.connect(update_devarg_override)
self.devarg_override = devarg_override

log_level = QtWidgets.QComboBox()
log_level.addItems(log_levels)
log_level.setCurrentIndex(1)
log_level.setToolTip("Minimum level for log entry production")
log_level_label = QtWidgets.QLabel("Logging level:")
log_level_label.setToolTip("Minimum level for log message production")
self.layout.addWidget(log_level_label, 3, 0)
self.layout.addWidget(log_level, 3, 1)

log_level.setCurrentIndex(log_levels.index(
log_level_to_name(options["log_level"])))
self.options["devarg_override"] = text
self.devarg_override.editTextChanged.connect(update_devarg_override)

def _create_log_level_widgets(self):
self.log_level = QtWidgets.QComboBox()
self.log_level.addItems(log_levels)
self.log_level.setCurrentIndex(1)
self.log_level.setToolTip("Minimum level for log entry production")
self.log_level_label = QtWidgets.QLabel("Logging level:")
self.log_level_label.setToolTip("Minimum level for log message production")
self.foldable_layout.addWidget(self.log_level_label, 3, 0)
self.foldable_layout.addWidget(self.log_level, 3, 1)

self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(self.options["log_level"])))

def update_log_level(index):
options["log_level"] = getattr(logging, log_level.currentText())
log_level.currentIndexChanged.connect(update_log_level)
self.log_level = log_level

if "repo_rev" in options:
repo_rev = QtWidgets.QLineEdit()
repo_rev.setPlaceholderText("current")
repo_rev.setClearButtonEnabled(True)
repo_rev_label = QtWidgets.QLabel("Rev / ref:")
repo_rev_label.setToolTip("Experiment repository revision "
"(commit ID) or reference (branch "
"or tag) to use")
self.layout.addWidget(repo_rev_label, 3, 2)
self.layout.addWidget(repo_rev, 3, 3)

if options["repo_rev"] is not None:
repo_rev.setText(options["repo_rev"])
self.options["log_level"] = getattr(logging, self.log_level.currentText())
self.log_level.currentIndexChanged.connect(update_log_level)

def _create_repo_rev_widgets(self):
if "repo_rev" in self.options:
self.repo_rev = QtWidgets.QLineEdit()
self.repo_rev.setPlaceholderText("current")
self.repo_rev.setClearButtonEnabled(True)
self.repo_rev_label = QtWidgets.QLabel("Rev / ref:")
self.repo_rev_label.setToolTip("Experiment repository revision "
"(commit ID) or reference (branch "
"or tag) to use")
self.foldable_layout.addWidget(self.repo_rev_label, 3, 2)
self.foldable_layout.addWidget(self.repo_rev, 3, 3)

if self.options["repo_rev"] is not None:
self.repo_rev.setText(self.options["repo_rev"])

def update_repo_rev(text):
if text:
options["repo_rev"] = text
self.options["repo_rev"] = text
else:
options["repo_rev"] = None
repo_rev.textChanged.connect(update_repo_rev)
self.repo_rev = repo_rev
self.options["repo_rev"] = None
self.repo_rev.textChanged.connect(update_repo_rev)

submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
def _create_submit_widgets(self):
self.submit = QtWidgets.QPushButton("Submit")
self.submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
submit.setShortcut("CTRL+RETURN")
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
self.submit.setToolTip("Schedule the experiment (Ctrl+Return)")
self.submit.setShortcut("CTRL+RETURN")
self.submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(submit, 1, 4, 2, 1)
submit.clicked.connect(self.submit_clicked)

reqterm = QtWidgets.QPushButton("Terminate instances")
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton))
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
reqterm.setShortcut("CTRL+BACKSPACE")
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(reqterm, 3, 4)
reqterm.clicked.connect(self.reqterm_clicked)

self.hdf5_load_directory = os.path.expanduser("~")
self.always_visible_layout.addWidget(self.submit)
self.submit.clicked.connect(self.submit_clicked)

def submit_clicked(self):
self.argeditor.about_to_submit()
try:
self.manager.submit(self.expurl)
except:
except Exception:
# May happen when experiment has been removed
# from repository/explist
logger.error("Failed to submit '%s'",
self.expurl, exc_info=True)

def _create_reqterm_widgets(self):
self.reqterm = QtWidgets.QPushButton("Terminate instances")
self.reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton))
self.reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
self.reqterm.setShortcut("CTRL+BACKSPACE")
self.reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.always_visible_layout.addWidget(self.reqterm)
self.reqterm.clicked.connect(self.reqterm_clicked)

def reqterm_clicked(self):
try:
self.manager.request_inst_term(self.expurl)
except:
except Exception:
# May happen when experiment has been removed
# from repository/explist
logger.error("Failed to request termination of instances of '%s'",
Expand Down Expand Up @@ -319,11 +372,11 @@ async def _recompute_sched_options_task(self):
return
sched_defaults = expdesc["scheduler_defaults"]

scheduling = self.manager.get_submission_scheduling(self.expurl)
scheduling.update(sched_defaults)
self.priority.setValue(scheduling["priority"])
self.pipeline_name.setText(scheduling["pipeline_name"])
self.flush.setChecked(scheduling["flush"])
self.scheduling = self.manager.get_submission_scheduling(self.expurl)
self.scheduling.update(sched_defaults)
self.priority.setValue(self.scheduling["priority"])
self.pipeline_name.setText(self.scheduling["pipeline_name"])
self.flush.setChecked(self.scheduling["flush"])

def _load_hdf5_clicked(self):
asyncio.ensure_future(self._load_hdf5_task())
Expand Down