dashboard: support reloading arguments from HDF5

This commit is contained in:
Sebastien Bourdeauducq 2016-05-05 00:51:30 +08:00
parent c50555e11c
commit 597d7c389e
3 changed files with 79 additions and 13 deletions

View File

@ -38,6 +38,8 @@ unreleased [2.x]
current directory. current directory.
* The ``parent`` keyword argument of ``HasEnvironment`` (and ``EnvExperiment``) * The ``parent`` keyword argument of ``HasEnvironment`` (and ``EnvExperiment``)
has been replaced. Pass the parent as first argument instead. has been replaced. Pass the parent as first argument instead.
* In the dashboard's experiment windows, partial or full argument recomputation
takes into account the repository revision field.
1.0rc4 (unreleased) 1.0rc4 (unreleased)

View File

@ -1,12 +1,15 @@
import logging import logging
import asyncio import asyncio
import os
from functools import partial from functools import partial
from collections import OrderedDict from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
import h5py
from artiq.gui.tools import LayoutWidget, log_level_to_name from artiq.gui.tools import LayoutWidget, log_level_to_name
from artiq.gui.entries import argty_to_entry from artiq.gui.entries import argty_to_entry
from artiq.protocols import pyon
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -83,12 +86,21 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments") recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
recompute_arguments.setIcon(QtWidgets.QApplication.style().standardIcon( recompute_arguments.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload)) QtWidgets.QStyle.SP_BrowserReload))
recompute_arguments.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
QtWidgets.QSizePolicy.Maximum)
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked) recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
fix_layout = LayoutWidget()
fix_layout.addWidget(recompute_arguments) load_hdf5 = QtWidgets.QPushButton("Load HDF5")
self.setItemWidget(widget_item, 1, fix_layout) 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)
def _get_group(self, name): def _get_group(self, name):
if name in self._groups: if name in self._groups:
@ -143,6 +155,9 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
pass pass
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
class _ExperimentDock(QtWidgets.QMdiSubWindow): class _ExperimentDock(QtWidgets.QMdiSubWindow):
sigClosed = QtCore.pyqtSignal() sigClosed = QtCore.pyqtSignal()
@ -222,7 +237,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
flush.stateChanged.connect(update_flush) flush.stateChanged.connect(update_flush)
log_level = QtWidgets.QComboBox() log_level = QtWidgets.QComboBox()
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
log_level.addItems(log_levels) log_level.addItems(log_levels)
log_level.setCurrentIndex(1) log_level.setCurrentIndex(1)
log_level.setToolTip("Minimum level for log entry production") log_level.setToolTip("Minimum level for log entry production")
@ -236,6 +250,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
def update_log_level(index): def update_log_level(index):
options["log_level"] = getattr(logging, log_level.currentText()) options["log_level"] = getattr(logging, log_level.currentText())
log_level.currentIndexChanged.connect(update_log_level) log_level.currentIndexChanged.connect(update_log_level)
self.log_level = log_level
if "repo_rev" in options: if "repo_rev" in options:
repo_rev = QtWidgets.QLineEdit() repo_rev = QtWidgets.QLineEdit()
@ -253,7 +268,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
options["repo_rev"] = text options["repo_rev"] = text
else: else:
options["repo_rev"] = None options["repo_rev"] = None
repo_rev.textEdited.connect(update_repo_rev) repo_rev.textChanged.connect(update_repo_rev)
self.repo_rev = repo_rev
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
@ -275,6 +291,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.layout.addWidget(reqterm, 3, 4) self.layout.addWidget(reqterm, 3, 4)
reqterm.clicked.connect(self.reqterm_clicked) reqterm.clicked.connect(self.reqterm_clicked)
self.hdf5_load_directory = os.path.expanduser("~")
def submit_clicked(self): def submit_clicked(self):
try: try:
self.manager.submit(self.expurl) self.manager.submit(self.expurl)
@ -296,18 +314,59 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
def _recompute_arguments_clicked(self): def _recompute_arguments_clicked(self):
asyncio.ensure_future(self._recompute_arguments_task()) asyncio.ensure_future(self._recompute_arguments_task())
async def _recompute_arguments_task(self): async def _recompute_arguments_task(self, overrides=dict()):
try: try:
arginfo = await self.manager.compute_arginfo(self.expurl) arginfo = await self.manager.compute_arginfo(self.expurl)
except: except:
logger.error("Could not recompute arguments of '%s'", logger.error("Could not recompute arguments of '%s'",
self.expurl, exc_info=True) self.expurl, exc_info=True)
return
for k, v in overrides.items():
arginfo[k][0]["default"] = v
self.manager.initialize_submission_arguments(self.expurl, arginfo) self.manager.initialize_submission_arguments(self.expurl, arginfo)
self.argeditor.deleteLater() self.argeditor.deleteLater()
self.argeditor = _ArgumentEditor(self.manager, self, self.expurl) self.argeditor = _ArgumentEditor(self.manager, self, self.expurl)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5) self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
def _load_hdf5_clicked(self):
dialog = QtWidgets.QFileDialog(self.manager.main_window,
"Load HDF5", self.hdf5_load_directory,
"HDF5 files (*.h5 *.hdf5);;All files (*.*)")
dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
def on_accept():
filename = dialog.selectedFiles()[0]
self.hdf5_load_directory = os.path.dirname(filename)
asyncio.ensure_future(self._load_hdf5_task(filename))
dialog.accepted.connect(on_accept)
dialog.open()
async def _load_hdf5_task(self, 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:
self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(expid["log_level"])))
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)
def closeEvent(self, event): def closeEvent(self, event):
self.sigClosed.emit() self.sigClosed.emit()
QtWidgets.QMdiSubWindow.closeEvent(self, event) QtWidgets.QMdiSubWindow.closeEvent(self, event)
@ -315,12 +374,14 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
def save_state(self): def save_state(self):
return { return {
"args": self.argeditor.save_state(), "args": self.argeditor.save_state(),
"geometry": bytes(self.saveGeometry()) "geometry": bytes(self.saveGeometry()),
"hdf5_load_directory": self.hdf5_load_directory
} }
def restore_state(self, state): def restore_state(self, state):
self.argeditor.restore_state(state["args"]) self.argeditor.restore_state(state["args"])
self.restoreGeometry(QtCore.QByteArray(state["geometry"])) self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
self.hdf5_load_directory = state["hdf5_load_directory"]
class ExperimentManager: class ExperimentManager:
@ -490,8 +551,10 @@ class ExperimentManager:
async def compute_arginfo(self, expurl): async def compute_arginfo(self, expurl):
file, class_name, use_repository = self.resolve_expurl(expurl) file, class_name, use_repository = self.resolve_expurl(expurl)
description = await self.experiment_db_ctl.examine(file, if use_repository:
use_repository) revision = self.get_submission_options(expurl)["repo_rev"]
description = await self.experiment_db_ctl.examine(
file, use_repository, revision)
return description[class_name]["arginfo"] return description[class_name]["arginfo"]
async def open_file(self, file): async def open_file(self, file):

View File

@ -120,8 +120,9 @@ class ExperimentDB:
asyncio.ensure_future( asyncio.ensure_future(
exc_to_warning(self.scan_repository(new_cur_rev))) exc_to_warning(self.scan_repository(new_cur_rev)))
async def examine(self, filename, use_repository=True): async def examine(self, filename, use_repository=True, revision=None):
if use_repository: if use_repository:
if revision is None:
revision = self.cur_rev revision = self.cur_rev
wd, _ = self.repo_backend.request_rev(revision) wd, _ = self.repo_backend.request_rev(revision)
filename = os.path.join(wd, filename) filename = os.path.join(wd, filename)