artiq/artiq/dashboard/experiments.py

860 lines
33 KiB
Python
Raw Normal View History

2015-11-27 19:30:05 +08:00
import logging
import asyncio
import os
2015-11-27 19:30:05 +08:00
from functools import partial
from collections import OrderedDict
2016-02-15 07:23:47 +08:00
from PyQt5 import QtCore, QtGui, QtWidgets
import h5py
2015-11-27 19:30:05 +08:00
2019-11-10 15:55:17 +08:00
from sipyco import pyon
from artiq.gui.entries import procdesc_to_entry, ScanEntry
from artiq.gui.fuzzy_select import FuzzySelectWidget
from artiq.gui.tools import (LayoutWidget, WheelFilter,
log_level_to_name, get_open_file_name)
from artiq.tools import parse_devarg_override, unparse_devarg_override
2015-11-27 19:30:05 +08:00
logger = logging.getLogger(__name__)
# Experiment URLs come in two forms:
# 1. repo:<experiment name>
# (file name and class name to be retrieved from explist)
# 2. file:<class name>@<file name>
2016-02-15 07:23:47 +08:00
class _ArgumentEditor(QtWidgets.QTreeWidget):
def __init__(self, manager, dock, expurl):
self.manager = manager
self.expurl = expurl
2016-02-15 07:23:47 +08:00
QtWidgets.QTreeWidget.__init__(self)
self.setColumnCount(3)
self.header().setStretchLastSection(False)
if hasattr(self.header(), "setSectionResizeMode"):
set_resize_mode = self.header().setSectionResizeMode
else:
set_resize_mode = self.header().setResizeMode
2016-02-15 07:23:47 +08:00
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
2015-11-27 19:30:05 +08:00
self.header().setVisible(False)
2016-05-08 22:18:44 +08:00
self.setSelectionMode(self.NoSelection)
self.setHorizontalScrollMode(self.ScrollPerPixel)
self.setVerticalScrollMode(self.ScrollPerPixel)
2015-11-27 19:30:05 +08:00
self.setStyleSheet("QTreeWidget {background: " +
self.palette().midlight().color().name() + " ;}")
self.viewport().installEventFilter(WheelFilter(self.viewport(), True))
2015-11-27 19:30:05 +08:00
self._groups = dict()
self._arg_to_widgets = dict()
arguments = self.manager.get_submission_arguments(self.expurl)
2015-11-27 19:30:05 +08:00
if not arguments:
2016-02-15 07:23:47 +08:00
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
2015-11-27 19:30:05 +08:00
gradient = QtGui.QLinearGradient(
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing()*2.5)
gradient.setColorAt(0, self.palette().base().color())
gradient.setColorAt(1, self.palette().midlight().color())
for name, argument in arguments.items():
widgets = dict()
self._arg_to_widgets[name] = widgets
entry = procdesc_to_entry(argument["desc"])(argument)
2016-02-15 07:23:47 +08:00
widget_item = QtWidgets.QTreeWidgetItem([name])
2017-02-03 08:41:36 +08:00
if argument["tooltip"]:
widget_item.setToolTip(0, argument["tooltip"])
widgets["entry"] = entry
widgets["widget_item"] = widget_item
for col in range(3):
widget_item.setBackground(col, gradient)
font = widget_item.font(0)
font.setBold(True)
widget_item.setFont(0, font)
if argument["group"] is None:
2015-11-27 19:30:05 +08:00
self.addTopLevelItem(widget_item)
else:
self._get_group(argument["group"]).addChild(widget_item)
fix_layout = LayoutWidget()
widgets["fix_layout"] = fix_layout
fix_layout.addWidget(entry)
self.setItemWidget(widget_item, 1, fix_layout)
2016-02-15 07:23:47 +08:00
recompute_argument = QtWidgets.QToolButton()
recompute_argument.setToolTip("Re-run the experiment's build "
"method and take the default value")
2016-05-08 22:18:44 +08:00
recompute_argument.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload))
recompute_argument.clicked.connect(
partial(self._recompute_argument_clicked, name))
tool_buttons = LayoutWidget()
tool_buttons.addWidget(recompute_argument, 1)
disable_other_scans = QtWidgets.QToolButton()
widgets["disable_other_scans"] = disable_other_scans
disable_other_scans.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogResetButton))
disable_other_scans.setToolTip("Disable all other scans in "
"this experiment")
disable_other_scans.clicked.connect(
partial(self._disable_other_scans, name))
tool_buttons.layout.setRowStretch(0, 1)
tool_buttons.layout.setRowStretch(3, 1)
tool_buttons.addWidget(disable_other_scans, 2)
if not isinstance(entry, ScanEntry):
disable_other_scans.setVisible(False)
self.setItemWidget(widget_item, 2, tool_buttons)
2015-11-27 19:30:05 +08:00
2016-02-15 07:23:47 +08:00
widget_item = QtWidgets.QTreeWidgetItem()
self.addTopLevelItem(widget_item)
2016-02-15 07:23:47 +08:00
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
2016-05-08 22:18:44 +08:00
recompute_arguments.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload))
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
load_hdf5 = QtWidgets.QPushButton("Load HDF5")
load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton))
load_hdf5.clicked.connect(dock._load_hdf5_clicked)
buttons = LayoutWidget()
buttons.addWidget(recompute_arguments, 1, 1)
buttons.addWidget(load_hdf5, 1, 2)
buttons.layout.setColumnStretch(0, 1)
buttons.layout.setColumnStretch(1, 0)
buttons.layout.setColumnStretch(2, 0)
buttons.layout.setColumnStretch(3, 1)
self.setItemWidget(widget_item, 1, buttons)
2015-11-27 19:30:05 +08:00
def _get_group(self, name):
if name in self._groups:
return self._groups[name]
2016-02-15 07:23:47 +08:00
group = QtWidgets.QTreeWidgetItem([name])
for col in range(3):
group.setBackground(col, self.palette().mid())
group.setForeground(col, self.palette().brightText())
font = group.font(col)
2015-11-27 19:30:05 +08:00
font.setBold(True)
group.setFont(col, font)
2015-11-27 19:30:05 +08:00
self.addTopLevelItem(group)
self._groups[name] = group
return group
2023-09-18 16:13:45 +08:00
def update_argument(self, name, argument):
widgets = self._arg_to_widgets[name]
# Qt needs a setItemWidget() to handle layout correctly,
# simply replacing the entry inside the LayoutWidget
# results in a bug.
widgets["entry"].deleteLater()
widgets["entry"] = procdesc_to_entry(argument["desc"])(argument)
widgets["disable_other_scans"].setVisible(
isinstance(widgets["entry"], ScanEntry))
widgets["fix_layout"].deleteLater()
widgets["fix_layout"] = LayoutWidget()
widgets["fix_layout"].addWidget(widgets["entry"])
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
self.updateGeometries()
def _recompute_argument_clicked(self, name):
asyncio.ensure_future(self._recompute_argument(name))
async def _recompute_argument(self, name):
try:
expdesc, _ = await self.manager.compute_expdesc(self.expurl)
except:
2015-12-08 18:57:53 +08:00
logger.error("Could not recompute argument '%s' of '%s'",
name, self.expurl, exc_info=True)
return
argument = self.manager.get_submission_arguments(self.expurl)[name]
procdesc = expdesc["arginfo"][name][0]
state = procdesc_to_entry(procdesc).default_state(procdesc)
argument["desc"] = procdesc
argument["state"] = state
2023-09-18 16:13:45 +08:00
self.update_argument(name, argument)
def _disable_other_scans(self, current_name):
for name, widgets in self._arg_to_widgets.items():
if (name != current_name
and isinstance(widgets["entry"], ScanEntry)):
2016-09-06 22:50:12 +08:00
widgets["entry"].disable()
2015-11-27 19:30:05 +08:00
def save_state(self):
expanded = []
for k, v in self._groups.items():
if v.isExpanded():
expanded.append(k)
return {
"expanded": expanded,
"scroll": self.verticalScrollBar().value()
}
2015-11-27 19:30:05 +08:00
def restore_state(self, state):
for e in state["expanded"]:
try:
self._groups[e].setExpanded(True)
except KeyError:
pass
self.verticalScrollBar().setValue(state["scroll"])
2015-11-27 19:30:05 +08:00
# Hooks that allow user-supplied argument editors to react to imminent user
# actions. Here, we always keep the manager-stored submission arguments
# up-to-date, so no further action is required.
def about_to_submit(self):
pass
def about_to_close(self):
pass
2015-11-27 19:30:05 +08:00
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
2016-02-16 06:58:44 +08:00
class _ExperimentDock(QtWidgets.QMdiSubWindow):
sigClosed = QtCore.pyqtSignal()
def __init__(self, manager, expurl):
2016-02-16 06:58:44 +08:00
QtWidgets.QMdiSubWindow.__init__(self)
qfm = QtGui.QFontMetrics(self.font())
self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing())
2016-02-16 06:58:44 +08:00
self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogContentsView))
self.layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget()
top_widget.setLayout(self.layout)
self.setWidget(top_widget)
self.layout.setSpacing(5)
self.layout.setContentsMargins(5, 5, 5, 5)
2015-11-27 19:30:05 +08:00
self.manager = manager
self.expurl = expurl
2015-11-27 19:30:05 +08:00
editor_class = self.manager.get_argument_editor_class(expurl)
self.argeditor = editor_class(self.manager, self, self.expurl)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
self.layout.setRowStretch(0, 1)
2015-11-27 19:30:05 +08:00
scheduling = manager.get_submission_scheduling(expurl)
options = manager.get_submission_options(expurl)
2015-11-27 19:30:05 +08:00
2016-02-15 07:23:47 +08:00
datetime = QtWidgets.QDateTimeEdit()
datetime.setDisplayFormat("MMM d yyyy hh:mm:ss")
2016-02-15 07:23:47 +08:00
datetime_en = QtWidgets.QCheckBox("Due date:")
self.layout.addWidget(datetime_en, 1, 0)
self.layout.addWidget(datetime, 1, 1)
if 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)
2016-05-08 22:18:44 +08:00
def update_datetime(dt):
scheduling["due_date"] = dt.toMSecsSinceEpoch()/1000
datetime_en.setChecked(True)
datetime.dateTimeChanged.connect(update_datetime)
2016-05-08 22:18:44 +08:00
def update_datetime_en(checked):
if checked:
due_date = datetime.dateTime().toMSecsSinceEpoch()/1000
else:
due_date = None
scheduling["due_date"] = due_date
datetime_en.stateChanged.connect(update_datetime_en)
self.pipeline_name = QtWidgets.QLineEdit()
pipeline_name = self.pipeline_name
2016-02-15 07:23:47 +08:00
self.layout.addWidget(QtWidgets.QLabel("Pipeline:"), 1, 2)
self.layout.addWidget(pipeline_name, 1, 3)
2015-11-27 19:30:05 +08:00
pipeline_name.setText(scheduling["pipeline_name"])
2016-05-08 22:18:44 +08:00
def update_pipeline_name(text):
scheduling["pipeline_name"] = text
pipeline_name.textChanged.connect(update_pipeline_name)
2015-11-27 19:30:05 +08:00
self.priority = QtWidgets.QSpinBox()
priority = self.priority
priority.setRange(-99, 99)
2016-02-15 07:23:47 +08:00
self.layout.addWidget(QtWidgets.QLabel("Priority:"), 2, 0)
self.layout.addWidget(priority, 2, 1)
priority.setValue(scheduling["priority"])
2016-05-08 22:18:44 +08:00
def update_priority(value):
scheduling["priority"] = value
priority.valueChanged.connect(update_priority)
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)
flush.setChecked(scheduling["flush"])
2016-05-08 22:18:44 +08:00
def update_flush(checked):
scheduling["flush"] = bool(checked)
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)
devarg_override.setCurrentText(options["devarg_override"])
def update_devarg_override(text):
options["devarg_override"] = text
devarg_override.editTextChanged.connect(update_devarg_override)
self.devarg_override = devarg_override
2016-02-15 07:23:47 +08:00
log_level = QtWidgets.QComboBox()
log_level.addItems(log_levels)
log_level.setCurrentIndex(1)
log_level.setToolTip("Minimum level for log entry production")
2016-02-15 07:23:47 +08:00
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"])))
2016-05-08 22:18:44 +08:00
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
2015-11-27 19:30:05 +08:00
if "repo_rev" in options:
2016-02-15 07:23:47 +08:00
repo_rev = QtWidgets.QLineEdit()
repo_rev.setPlaceholderText("current")
repo_rev.setClearButtonEnabled(True)
2016-02-15 07:23:47 +08:00
repo_rev_label = QtWidgets.QLabel("Revision:")
repo_rev_label.setToolTip("Experiment repository revision "
"(commit ID) 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"])
2016-05-08 22:18:44 +08:00
def update_repo_rev(text):
if text:
options["repo_rev"] = text
else:
options["repo_rev"] = None
repo_rev.textChanged.connect(update_repo_rev)
self.repo_rev = repo_rev
2016-02-15 07:23:47 +08:00
submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
submit.setShortcut("CTRL+RETURN")
2016-02-15 07:23:47 +08:00
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.layout.addWidget(submit, 1, 4, 2, 1)
2015-11-27 19:30:05 +08:00
submit.clicked.connect(self.submit_clicked)
2016-02-15 07:23:47 +08:00
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")
2016-02-15 07:23:47 +08:00
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("~")
2015-11-27 19:30:05 +08:00
def submit_clicked(self):
self.argeditor.about_to_submit()
try:
self.manager.submit(self.expurl)
except:
# May happen when experiment has been removed
# from repository/explist
2015-12-08 18:57:53 +08:00
logger.error("Failed to submit '%s'",
self.expurl, exc_info=True)
def reqterm_clicked(self):
try:
self.manager.request_inst_term(self.expurl)
except:
# May happen when experiment has been removed
# from repository/explist
2015-12-08 18:57:53 +08:00
logger.error("Failed to request termination of instances of '%s'",
self.expurl, exc_info=True)
2015-11-27 19:30:05 +08:00
def _recompute_arguments_clicked(self):
asyncio.ensure_future(self._recompute_arguments_task())
async def _recompute_arguments_task(self, overrides=dict()):
try:
expdesc, ui_name = await self.manager.compute_expdesc(self.expurl)
except:
logger.error("Could not recompute experiment description of '%s'",
2015-12-08 18:57:53 +08:00
self.expurl, exc_info=True)
return
arginfo = expdesc["arginfo"]
for k, v in overrides.items():
# Some values (e.g. scans) may have multiple defaults in a list
if ("default" in arginfo[k][0]
and isinstance(arginfo[k][0]["default"], list)):
arginfo[k][0]["default"].insert(0, v)
else:
arginfo[k][0]["default"] = v
self.manager.initialize_submission_arguments(self.expurl, arginfo, ui_name)
argeditor_state = self.argeditor.save_state()
self.argeditor.deleteLater()
editor_class = self.manager.get_argument_editor_class(self.expurl)
self.argeditor = editor_class(self.manager, self, self.expurl)
self.argeditor.restore_state(argeditor_state)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self)
reset_sched = menu.addAction("Reset scheduler settings")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == reset_sched:
asyncio.ensure_future(self._recompute_sched_options_task())
async def _recompute_sched_options_task(self):
try:
expdesc, _ = await self.manager.compute_expdesc(self.expurl)
except:
logger.error("Could not recompute experiment description of '%s'",
self.expurl, exc_info=True)
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"])
def _load_hdf5_clicked(self):
2016-05-08 22:16:04 +08:00
asyncio.ensure_future(self._load_hdf5_task())
async def _load_hdf5_task(self):
try:
filename = await get_open_file_name(
self.manager.main_window, "Load HDF5",
self.hdf5_load_directory,
"HDF5 files (*.h5 *.hdf5);;All files (*.*)")
except asyncio.CancelledError:
return
self.hdf5_load_directory = os.path.dirname(filename)
try:
with h5py.File(filename, "r") as f:
expid = f["expid"][()]
expid = pyon.decode(expid)
arguments = expid["arguments"]
except:
logger.error("Could not retrieve expid from HDF5 file",
exc_info=True)
return
try:
if "devarg_override" in expid:
self.devarg_override.setCurrentText(
unparse_devarg_override(expid["devarg_override"]))
self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(expid["log_level"])))
2016-05-08 22:18:44 +08:00
if ("repo_rev" in expid and
expid["repo_rev"] != "N/A" and
hasattr(self, "repo_rev")):
self.repo_rev.setText(expid["repo_rev"])
except:
logger.error("Could not set submission options from HDF5 expid",
exc_info=True)
return
await self._recompute_arguments_task(arguments)
2016-02-16 06:58:44 +08:00
def closeEvent(self, event):
self.argeditor.about_to_close()
2016-02-16 06:58:44 +08:00
self.sigClosed.emit()
QtWidgets.QMdiSubWindow.closeEvent(self, event)
2015-11-30 11:40:50 +08:00
def save_state(self):
return {
"args": self.argeditor.save_state(),
"geometry": bytes(self.saveGeometry()),
"hdf5_load_directory": self.hdf5_load_directory
}
2015-11-30 11:40:50 +08:00
def restore_state(self, state):
self.argeditor.restore_state(state["args"])
self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
self.hdf5_load_directory = state["hdf5_load_directory"]
2015-11-30 11:40:50 +08:00
2015-11-27 19:30:05 +08:00
class _QuickOpenDialog(QtWidgets.QDialog):
"""Modal dialog for opening/submitting experiments from a
FuzzySelectWidget."""
closed = QtCore.pyqtSignal()
def __init__(self, manager):
super().__init__(manager.main_window)
self.setModal(True)
self.manager = manager
2020-06-26 10:12:03 +08:00
self.setWindowTitle("Quick open...")
layout = QtWidgets.QGridLayout(self)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
# Find matching experiment names. Open experiments are preferred to
# matches from the repository to ease quick window switching.
open_exps = list(self.manager.open_experiments.keys())
repo_exps = set("repo:" + k
for k in self.manager.explist.keys()) - set(open_exps)
choices = [(o, 100) for o in open_exps] + [(r, 0) for r in repo_exps]
self.select_widget = FuzzySelectWidget(choices)
layout.addWidget(self.select_widget)
self.select_widget.aborted.connect(self.close)
self.select_widget.finished.connect(self._open_experiment)
font_metrics = QtGui.QFontMetrics(self.select_widget.line_edit.font())
self.select_widget.setMinimumWidth(font_metrics.averageCharWidth() * 70)
def done(self, r):
if self.select_widget:
self.select_widget.abort()
self.closed.emit()
QtWidgets.QDialog.done(self, r)
def _open_experiment(self, exp_name, modifiers):
if modifiers & QtCore.Qt.ControlModifier:
try:
self.manager.submit(exp_name)
except:
# Not all open_experiments necessarily still exist in the explist
# (e.g. if the repository has been re-scanned since).
logger.warning("failed to submit experiment '%s'",
exp_name,
exc_info=True)
else:
self.manager.open_experiment(exp_name)
self.close()
2015-11-27 19:30:05 +08:00
class ExperimentManager:
#: Global registry for custom argument editor classes, indexed by the experiment
#: `argument_ui` string; can be populated by dashboard plugins such as ndscan.
#: If no handler for a requested UI name is found, the default built-in argument
#: editor will be used.
argument_ui_classes = dict()
def __init__(self, main_window, dataset_sub,
2015-11-27 19:30:05 +08:00
explist_sub, schedule_sub,
2015-12-06 18:39:27 +08:00
schedule_ctl, experiment_db_ctl):
self.main_window = main_window
2015-11-27 19:30:05 +08:00
self.schedule_ctl = schedule_ctl
2015-12-06 18:39:27 +08:00
self.experiment_db_ctl = experiment_db_ctl
2015-11-27 19:30:05 +08:00
self.dock_states = dict()
2015-11-27 19:30:05 +08:00
self.submission_scheduling = dict()
self.submission_options = dict()
self.submission_arguments = dict()
self.argument_ui_names = dict()
2015-11-27 19:30:05 +08:00
self.datasets = dict()
dataset_sub.add_setmodel_callback(self.set_dataset_model)
2015-11-27 19:30:05 +08:00
self.explist = dict()
explist_sub.add_setmodel_callback(self.set_explist_model)
self.schedule = dict()
schedule_sub.add_setmodel_callback(self.set_schedule_model)
self.open_experiments = dict()
self.is_quick_open_shown = False
quick_open_shortcut = QtWidgets.QShortcut(
QtCore.Qt.CTRL + QtCore.Qt.Key_P,
main_window)
quick_open_shortcut.setContext(QtCore.Qt.ApplicationShortcut)
quick_open_shortcut.activated.connect(self.show_quick_open)
def set_dataset_model(self, model):
self.datasets = model
2015-11-27 19:30:05 +08:00
def set_explist_model(self, model):
self.explist = model.backing_store
def set_schedule_model(self, model):
self.schedule = model.backing_store
def resolve_expurl(self, expurl):
if expurl[:5] == "repo:":
expinfo = self.explist[expurl[5:]]
return expinfo["file"], expinfo["class_name"], True
elif expurl[:5] == "file:":
class_name, file = expurl[5:].split("@", maxsplit=1)
return file, class_name, False
else:
raise ValueError("Malformed experiment URL")
def get_argument_editor_class(self, expurl):
ui_name = self.argument_ui_names.get(expurl, None)
if not ui_name and expurl[:5] == "repo:":
ui_name = self.explist.get(expurl[5:], {}).get("argument_ui", None)
if ui_name:
result = self.argument_ui_classes.get(ui_name, None)
if result:
return result
logger.warning("Ignoring unknown argument UI '%s'", ui_name)
return _ArgumentEditor
def get_submission_scheduling(self, expurl):
if expurl in self.submission_scheduling:
return self.submission_scheduling[expurl]
2015-11-27 19:30:05 +08:00
else:
# mutated by _ExperimentDock
2015-11-27 19:30:05 +08:00
scheduling = {
"pipeline_name": "main",
"priority": 0,
"due_date": None,
"flush": False
}
if expurl[:5] == "repo:":
scheduling.update(self.explist[expurl[5:]]["scheduler_defaults"])
self.submission_scheduling[expurl] = scheduling
2015-11-27 19:30:05 +08:00
return scheduling
def get_submission_options(self, expurl):
if expurl in self.submission_options:
return self.submission_options[expurl]
2015-11-27 19:30:05 +08:00
else:
# mutated by _ExperimentDock
2015-11-27 19:30:05 +08:00
options = {
"log_level": logging.WARNING,
"devarg_override": ""
2015-11-27 19:30:05 +08:00
}
if expurl[:5] == "repo:":
options["repo_rev"] = None
self.submission_options[expurl] = options
2015-11-27 19:30:05 +08:00
return options
def initialize_submission_arguments(self, expurl, arginfo, ui_name):
arguments = OrderedDict()
2017-02-03 08:41:36 +08:00
for name, (procdesc, group, tooltip) in arginfo.items():
state = procdesc_to_entry(procdesc).default_state(procdesc)
arguments[name] = {
"desc": procdesc,
"group": group,
2017-02-03 08:41:36 +08:00
"tooltip": tooltip,
"state": state, # mutated by entries
}
self.submission_arguments[expurl] = arguments
self.argument_ui_names[expurl] = ui_name
return arguments
2023-09-18 16:13:45 +08:00
def set_argument_value(self, expurl, name, value):
try:
argument = self.submission_arguments[expurl][name]
if argument["desc"]["ty"] == "Scannable":
ty = value["ty"]
argument["state"]["selected"] = ty
argument["state"][ty] = value
else:
argument["state"] = value
if expurl in self.open_experiments.keys():
self.open_experiments[expurl].argeditor.update_argument(name, argument)
except:
logger.warn("Failed to set value for argument \"{}\" in experiment: {}.".format(name, expurl), exc_info=1)
def get_submission_arguments(self, expurl):
if expurl in self.submission_arguments:
return self.submission_arguments[expurl]
2015-11-27 19:30:05 +08:00
else:
if expurl[:5] != "repo:":
raise ValueError("Submission arguments must be preinitialized "
"when not using repository")
class_desc = self.explist[expurl[5:]]
return self.initialize_submission_arguments(expurl,
class_desc["arginfo"], class_desc.get("argument_ui", None))
2015-11-27 19:30:05 +08:00
def open_experiment(self, expurl):
if expurl in self.open_experiments:
dock = self.open_experiments[expurl]
if dock.isMinimized():
dock.showNormal()
2016-02-16 06:58:44 +08:00
self.main_window.centralWidget().setActiveSubWindow(dock)
return dock
try:
dock = _ExperimentDock(self, expurl)
except:
logger.warning("Failed to create experiment dock for %s, "
"attempting to reset arguments", expurl,
exc_info=True)
del self.submission_arguments[expurl]
dock = _ExperimentDock(self, expurl)
self.open_experiments[expurl] = dock
dock.setAttribute(QtCore.Qt.WA_DeleteOnClose)
2016-02-16 06:58:44 +08:00
self.main_window.centralWidget().addSubWindow(dock)
dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
if expurl in self.dock_states:
try:
dock.restore_state(self.dock_states[expurl])
except:
logger.warning("Failed to restore dock state when opening "
"experiment %s", expurl,
exc_info=True)
2015-11-30 11:40:50 +08:00
return dock
2015-11-27 19:30:05 +08:00
def on_dock_closed(self, expurl):
dock = self.open_experiments[expurl]
self.dock_states[expurl] = dock.save_state()
del self.open_experiments[expurl]
2015-11-27 19:30:05 +08:00
async def _submit_task(self, expurl, *args):
2015-11-27 19:30:05 +08:00
rid = await self.schedule_ctl.submit(*args)
logger.info("Submitted '%s', RID is %d", expurl, rid)
2015-11-27 19:30:05 +08:00
def submit(self, expurl):
file, class_name, _ = self.resolve_expurl(expurl)
scheduling = self.get_submission_scheduling(expurl)
options = self.get_submission_options(expurl)
arguments = self.get_submission_arguments(expurl)
2015-11-30 00:21:27 +08:00
argument_values = dict()
for name, argument in arguments.items():
entry_cls = procdesc_to_entry(argument["desc"])
2015-11-30 00:21:27 +08:00
argument_values[name] = entry_cls.state_to_value(argument["state"])
2015-11-27 19:30:05 +08:00
try:
devarg_override = parse_devarg_override(options["devarg_override"])
except:
logger.error("Failed to parse device argument overrides for %s", expurl)
return
2015-11-27 19:30:05 +08:00
expid = {
"devarg_override": devarg_override,
2015-11-27 19:30:05 +08:00
"log_level": options["log_level"],
"file": file,
"class_name": class_name,
2015-11-27 19:30:05 +08:00
"arguments": argument_values,
}
if "repo_rev" in options:
expid["repo_rev"] = options["repo_rev"]
2015-11-27 19:30:05 +08:00
asyncio.ensure_future(self._submit_task(
expurl,
2015-11-27 19:30:05 +08:00
scheduling["pipeline_name"],
expid,
scheduling["priority"], scheduling["due_date"],
scheduling["flush"]))
async def _request_term_multiple(self, rids):
for rid in rids:
try:
await self.schedule_ctl.request_termination(rid)
except:
# May happen if the experiment has terminated by itself
# while we were terminating others.
logger.debug("failed to request termination of RID %d",
rid, exc_info=True)
def request_inst_term(self, expurl):
logger.info(
"Requesting termination of all instances "
"of '%s'", expurl)
file, class_name, use_repository = self.resolve_expurl(expurl)
2015-11-27 19:30:05 +08:00
rids = []
for rid, desc in self.schedule.items():
expid = desc["expid"]
if use_repository:
repo_match = "repo_rev" in expid
else:
repo_match = "repo_rev" not in expid
2016-05-08 22:18:44 +08:00
if (repo_match and
("file" in expid and expid["file"] == file) and
2016-05-08 22:18:44 +08:00
expid["class_name"] == class_name):
2015-11-27 19:30:05 +08:00
rids.append(rid)
asyncio.ensure_future(self._request_term_multiple(rids))
async def compute_expdesc(self, expurl):
file, class_name, use_repository = self.resolve_expurl(expurl)
if use_repository:
revision = self.get_submission_options(expurl)["repo_rev"]
else:
revision = None
description = await self.experiment_db_ctl.examine(
file, use_repository, revision)
class_desc = description[class_name]
return class_desc, class_desc.get("argument_ui", None)
async def open_file(self, file):
description = await self.experiment_db_ctl.examine(file, False)
for class_name, class_desc in description.items():
expurl = "file:{}@{}".format(class_name, file)
self.initialize_submission_arguments(expurl, class_desc["arginfo"],
class_desc.get("argument_ui", None))
if expurl in self.open_experiments:
self.open_experiments[expurl].close()
self.open_experiment(expurl)
2015-11-27 19:30:05 +08:00
def save_state(self):
for expurl, dock in self.open_experiments.items():
self.dock_states[expurl] = dock.save_state()
2015-11-30 11:40:50 +08:00
return {
"scheduling": self.submission_scheduling,
"options": self.submission_options,
"arguments": self.submission_arguments,
"docks": self.dock_states,
"argument_uis": self.argument_ui_names,
"open_docks": set(self.open_experiments.keys())
2015-11-30 11:40:50 +08:00
}
2015-11-27 19:30:05 +08:00
2015-11-30 11:40:50 +08:00
def restore_state(self, state):
2015-11-27 19:30:05 +08:00
if self.open_experiments:
raise NotImplementedError
self.dock_states = state["docks"]
2015-11-30 11:40:50 +08:00
self.submission_scheduling = state["scheduling"]
self.submission_options = state["options"]
self.submission_arguments = state["arguments"]
self.argument_ui_names = state.get("argument_uis", {})
for expurl in state["open_docks"]:
self.open_experiment(expurl)
def show_quick_open(self):
if self.is_quick_open_shown:
return
self.is_quick_open_shown = True
dialog = _QuickOpenDialog(self)
def closed():
self.is_quick_open_shown = False
dialog.closed.connect(closed)
dialog.show()