forked from M-Labs/artiq
dashboard: forward local log messages to docks, replace status bar (#411)
This commit is contained in:
parent
10267f39c9
commit
ab749560c2
|
@ -1,37 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from artiq.protocols.logging import SourceFilter
|
|
||||||
|
|
||||||
|
|
||||||
class LogWidgetHandler(logging.Handler):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
logging.Handler.__init__(self, *args, **kwargs)
|
|
||||||
self.log_widget = None
|
|
||||||
self.setFormatter(logging.Formatter("%(name)s:%(message)s"))
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
if self.log_widget is not None:
|
|
||||||
message = self.format(record)
|
|
||||||
self.log_widget.append_message((record.levelno, record.source,
|
|
||||||
record.created, message))
|
|
||||||
|
|
||||||
|
|
||||||
def init_log(args):
|
|
||||||
root_logger = logging.getLogger()
|
|
||||||
root_logger.setLevel(logging.NOTSET) # we use our custom filter only
|
|
||||||
flt = SourceFilter(logging.WARNING + args.quiet*10 - args.verbose*10,
|
|
||||||
"browser")
|
|
||||||
handlers = []
|
|
||||||
console_handler = logging.StreamHandler()
|
|
||||||
console_handler.setFormatter(logging.Formatter(
|
|
||||||
"%(levelname)s:%(source)s:%(name)s:%(message)s"))
|
|
||||||
handlers.append(console_handler)
|
|
||||||
|
|
||||||
widget_handler = LogWidgetHandler()
|
|
||||||
handlers.append(widget_handler)
|
|
||||||
|
|
||||||
for handler in handlers:
|
|
||||||
handler.addFilter(flt)
|
|
||||||
root_logger.addHandler(handler)
|
|
||||||
|
|
||||||
return widget_handler
|
|
|
@ -504,10 +504,9 @@ class ExperimentManager:
|
||||||
def on_dock_closed(self, expurl):
|
def on_dock_closed(self, expurl):
|
||||||
del self.open_experiments[expurl]
|
del self.open_experiments[expurl]
|
||||||
|
|
||||||
async def _submit_task(self, *args):
|
async def _submit_task(self, expurl, *args):
|
||||||
rid = await self.schedule_ctl.submit(*args)
|
rid = await self.schedule_ctl.submit(*args)
|
||||||
self.main_window.statusBar().showMessage(
|
logger.info("Submitted '%s', RID is %d", expurl, rid)
|
||||||
"Submitted RID {}".format(rid))
|
|
||||||
|
|
||||||
def submit(self, expurl):
|
def submit(self, expurl):
|
||||||
file, class_name, _ = self.resolve_expurl(expurl)
|
file, class_name, _ = self.resolve_expurl(expurl)
|
||||||
|
@ -529,6 +528,7 @@ class ExperimentManager:
|
||||||
if "repo_rev" in options:
|
if "repo_rev" in options:
|
||||||
expid["repo_rev"] = options["repo_rev"]
|
expid["repo_rev"] = options["repo_rev"]
|
||||||
asyncio.ensure_future(self._submit_task(
|
asyncio.ensure_future(self._submit_task(
|
||||||
|
expurl,
|
||||||
scheduling["pipeline_name"],
|
scheduling["pipeline_name"],
|
||||||
expid,
|
expid,
|
||||||
scheduling["priority"], scheduling["due_date"],
|
scheduling["priority"], scheduling["due_date"],
|
||||||
|
@ -545,9 +545,9 @@ class ExperimentManager:
|
||||||
rid, exc_info=True)
|
rid, exc_info=True)
|
||||||
|
|
||||||
def request_inst_term(self, expurl):
|
def request_inst_term(self, expurl):
|
||||||
self.main_window.statusBar().showMessage(
|
logger.info(
|
||||||
"Requesting termination of all instances "
|
"Requesting termination of all instances "
|
||||||
"of '{}'".format(expurl))
|
"of '%s'", expurl)
|
||||||
file, class_name, use_repository = self.resolve_expurl(expurl)
|
file, class_name, use_repository = self.resolve_expurl(expurl)
|
||||||
rids = []
|
rids = []
|
||||||
for rid, desc in self.schedule.items():
|
for rid, desc in self.schedule.items():
|
||||||
|
|
|
@ -157,7 +157,7 @@ class WaitingPanel(LayoutWidget):
|
||||||
|
|
||||||
|
|
||||||
class ExplorerDock(QtWidgets.QDockWidget):
|
class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
def __init__(self, status_bar, exp_manager, d_shortcuts,
|
def __init__(self, exp_manager, d_shortcuts,
|
||||||
explist_sub, explist_status_sub,
|
explist_sub, explist_status_sub,
|
||||||
schedule_ctl, experiment_db_ctl):
|
schedule_ctl, experiment_db_ctl):
|
||||||
QtWidgets.QDockWidget.__init__(self, "Explorer")
|
QtWidgets.QDockWidget.__init__(self, "Explorer")
|
||||||
|
@ -168,7 +168,6 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
top_widget = LayoutWidget()
|
top_widget = LayoutWidget()
|
||||||
self.setWidget(top_widget)
|
self.setWidget(top_widget)
|
||||||
|
|
||||||
self.status_bar = status_bar
|
|
||||||
self.exp_manager = exp_manager
|
self.exp_manager = exp_manager
|
||||||
self.d_shortcuts = d_shortcuts
|
self.d_shortcuts = d_shortcuts
|
||||||
self.schedule_ctl = schedule_ctl
|
self.schedule_ctl = schedule_ctl
|
||||||
|
@ -287,8 +286,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
if expname is not None:
|
if expname is not None:
|
||||||
expurl = "repo:" + expname
|
expurl = "repo:" + expname
|
||||||
self.d_shortcuts.set_shortcut(nr, expurl)
|
self.d_shortcuts.set_shortcut(nr, expurl)
|
||||||
self.status_bar.showMessage("Set shortcut F{} to '{}'"
|
logger.info("Set shortcut F%d to '%s'", nr+1, expurl)
|
||||||
.format(nr+1, expurl))
|
|
||||||
|
|
||||||
def update_scanning(self, scanning):
|
def update_scanning(self, scanning):
|
||||||
if scanning:
|
if scanning:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
@ -8,6 +9,9 @@ from artiq.gui.models import DictSyncModel
|
||||||
from artiq.tools import elide
|
from artiq.tools import elide
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Model(DictSyncModel):
|
class Model(DictSyncModel):
|
||||||
def __init__(self, init):
|
def __init__(self, init):
|
||||||
DictSyncModel.__init__(self,
|
DictSyncModel.__init__(self,
|
||||||
|
@ -55,13 +59,12 @@ class Model(DictSyncModel):
|
||||||
|
|
||||||
|
|
||||||
class ScheduleDock(QtWidgets.QDockWidget):
|
class ScheduleDock(QtWidgets.QDockWidget):
|
||||||
def __init__(self, status_bar, schedule_ctl, schedule_sub):
|
def __init__(self, schedule_ctl, schedule_sub):
|
||||||
QtWidgets.QDockWidget.__init__(self, "Schedule")
|
QtWidgets.QDockWidget.__init__(self, "Schedule")
|
||||||
self.setObjectName("Schedule")
|
self.setObjectName("Schedule")
|
||||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
||||||
QtWidgets.QDockWidget.DockWidgetFloatable)
|
QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||||
|
|
||||||
self.status_bar = status_bar
|
|
||||||
self.schedule_ctl = schedule_ctl
|
self.schedule_ctl = schedule_ctl
|
||||||
|
|
||||||
self.table = QtWidgets.QTableView()
|
self.table = QtWidgets.QTableView()
|
||||||
|
@ -118,10 +121,9 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
||||||
row = idx[0].row()
|
row = idx[0].row()
|
||||||
rid = self.table_model.row_to_key[row]
|
rid = self.table_model.row_to_key[row]
|
||||||
if graceful:
|
if graceful:
|
||||||
msg = "Requested termination of RID {}".format(rid)
|
logger.info("Requested termination of RID %d", rid)
|
||||||
else:
|
else:
|
||||||
msg = "Deleted RID {}".format(rid)
|
logger.info("Deleted RID %d", rid)
|
||||||
self.status_bar.showMessage(msg)
|
|
||||||
asyncio.ensure_future(self.delete(rid, graceful))
|
asyncio.ensure_future(self.delete(rid, graceful))
|
||||||
|
|
||||||
async def request_term_multiple(self, rids):
|
async def request_term_multiple(self, rids):
|
||||||
|
|
|
@ -12,7 +12,7 @@ from quamash import QEventLoop
|
||||||
from artiq import __artiq_dir__ as artiq_dir
|
from artiq import __artiq_dir__ as artiq_dir
|
||||||
from artiq.tools import verbosity_args, atexit_register_coroutine
|
from artiq.tools import verbosity_args, atexit_register_coroutine
|
||||||
from artiq.gui import state, applets, models, log
|
from artiq.gui import state, applets, models, log
|
||||||
from artiq.browser import datasets, files, experiments, log as browser_log
|
from artiq.browser import datasets, files, experiments
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -121,21 +121,20 @@ class Browser(QtWidgets.QMainWindow):
|
||||||
def main():
|
def main():
|
||||||
# initialize application
|
# initialize application
|
||||||
args = get_argparser().parse_args()
|
args = get_argparser().parse_args()
|
||||||
|
widget_log_handler = log.init_log(args, "browser")
|
||||||
|
|
||||||
app = QtWidgets.QApplication(["ARTIQ Browser"])
|
app = QtWidgets.QApplication(["ARTIQ Browser"])
|
||||||
loop = QEventLoop(app)
|
loop = QEventLoop(app)
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
atexit.register(loop.close)
|
atexit.register(loop.close)
|
||||||
|
|
||||||
widget_log_handler = browser_log.init_log(args)
|
|
||||||
|
|
||||||
datasets_sub = models.LocalModelManager(datasets.Model)
|
datasets_sub = models.LocalModelManager(datasets.Model)
|
||||||
datasets_sub.init({})
|
datasets_sub.init({})
|
||||||
|
|
||||||
smgr = state.StateManager(args.db_file)
|
smgr = state.StateManager(args.db_file)
|
||||||
|
|
||||||
main_window = Browser(datasets_sub, args.browse_root, args.select)
|
main_window = Browser(datasets_sub, args.browse_root, args.select)
|
||||||
widget_log_handler.log_widget = main_window.log
|
widget_log_handler.callback = main_window.log.append_message
|
||||||
smgr.register(main_window)
|
smgr.register(main_window)
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
|
|
|
@ -4,11 +4,12 @@ import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import atexit
|
import atexit
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from quamash import QEventLoop
|
from quamash import QEventLoop
|
||||||
|
|
||||||
from artiq import __artiq_dir__ as artiq_dir
|
from artiq import __artiq_dir__ as artiq_dir, __version__ as artiq_version
|
||||||
from artiq.tools import *
|
from artiq.tools import *
|
||||||
from artiq.protocols.pc_rpc import AsyncioClient
|
from artiq.protocols.pc_rpc import AsyncioClient
|
||||||
from artiq.protocols.broadcast import Receiver
|
from artiq.protocols.broadcast import Receiver
|
||||||
|
@ -89,7 +90,7 @@ class MdiArea(QtWidgets.QMdiArea):
|
||||||
def main():
|
def main():
|
||||||
# initialize application
|
# initialize application
|
||||||
args = get_argparser().parse_args()
|
args = get_argparser().parse_args()
|
||||||
init_logger(args)
|
widget_log_handler = log.init_log(args, "dashboard")
|
||||||
|
|
||||||
app = QtWidgets.QApplication(["ARTIQ Dashboard"])
|
app = QtWidgets.QApplication(["ARTIQ Dashboard"])
|
||||||
loop = QEventLoop(app)
|
loop = QEventLoop(app)
|
||||||
|
@ -125,9 +126,6 @@ def main():
|
||||||
# initialize main window
|
# initialize main window
|
||||||
main_window = MainWindow(args.server)
|
main_window = MainWindow(args.server)
|
||||||
smgr.register(main_window)
|
smgr.register(main_window)
|
||||||
status_bar = QtWidgets.QStatusBar()
|
|
||||||
status_bar.showMessage("Connected to {}".format(args.server))
|
|
||||||
main_window.setStatusBar(status_bar)
|
|
||||||
mdi_area = MdiArea()
|
mdi_area = MdiArea()
|
||||||
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||||
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
|
||||||
|
@ -142,7 +140,7 @@ def main():
|
||||||
smgr.register(expmgr)
|
smgr.register(expmgr)
|
||||||
d_shortcuts = shortcuts.ShortcutsDock(main_window, expmgr)
|
d_shortcuts = shortcuts.ShortcutsDock(main_window, expmgr)
|
||||||
smgr.register(d_shortcuts)
|
smgr.register(d_shortcuts)
|
||||||
d_explorer = explorer.ExplorerDock(status_bar, expmgr, d_shortcuts,
|
d_explorer = explorer.ExplorerDock(expmgr, d_shortcuts,
|
||||||
sub_clients["explist"],
|
sub_clients["explist"],
|
||||||
sub_clients["explist_status"],
|
sub_clients["explist_status"],
|
||||||
rpc_clients["schedule"],
|
rpc_clients["schedule"],
|
||||||
|
@ -161,12 +159,13 @@ def main():
|
||||||
atexit_register_coroutine(d_ttl_dds.stop)
|
atexit_register_coroutine(d_ttl_dds.stop)
|
||||||
|
|
||||||
d_schedule = schedule.ScheduleDock(
|
d_schedule = schedule.ScheduleDock(
|
||||||
status_bar, rpc_clients["schedule"], sub_clients["schedule"])
|
rpc_clients["schedule"], sub_clients["schedule"])
|
||||||
smgr.register(d_schedule)
|
smgr.register(d_schedule)
|
||||||
|
|
||||||
logmgr = log.LogDockManager(main_window)
|
logmgr = log.LogDockManager(main_window)
|
||||||
smgr.register(logmgr)
|
smgr.register(logmgr)
|
||||||
log_receiver.notify_cbs.append(logmgr.append_message)
|
log_receiver.notify_cbs.append(logmgr.append_message)
|
||||||
|
widget_log_handler.callback = logmgr.append_message
|
||||||
|
|
||||||
# lay out docks
|
# lay out docks
|
||||||
right_docks = [
|
right_docks = [
|
||||||
|
@ -194,6 +193,8 @@ def main():
|
||||||
if d_log0 is not None:
|
if d_log0 is not None:
|
||||||
main_window.tabifyDockWidget(d_schedule, d_log0)
|
main_window.tabifyDockWidget(d_schedule, d_log0)
|
||||||
|
|
||||||
|
logging.info("ARTIQ dashboard %s connected to %s", artiq_version, args.server)
|
||||||
|
|
||||||
# run
|
# run
|
||||||
main_window.show()
|
main_window.show()
|
||||||
loop.run_until_complete(main_window.exit_request.wait())
|
loop.run_until_complete(main_window.exit_request.wait())
|
||||||
|
|
|
@ -6,6 +6,7 @@ from functools import partial
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
from artiq.protocols.logging import SourceFilter
|
||||||
from artiq.gui.tools import (LayoutWidget, log_level_to_name,
|
from artiq.gui.tools import (LayoutWidget, log_level_to_name,
|
||||||
QDockWidgetCloseDetect)
|
QDockWidgetCloseDetect)
|
||||||
|
|
||||||
|
@ -159,10 +160,11 @@ class LogDock(QDockWidgetCloseDetect):
|
||||||
grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0)
|
grid.addWidget(QtWidgets.QLabel("Minimum level: "), 0, 0)
|
||||||
self.filter_level = QtWidgets.QComboBox()
|
self.filter_level = QtWidgets.QComboBox()
|
||||||
self.filter_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
|
self.filter_level.addItems(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
|
||||||
self.filter_level.setToolTip("Display entries at or above this level")
|
self.filter_level.setToolTip("Receive entries at or above this level")
|
||||||
grid.addWidget(self.filter_level, 0, 1)
|
grid.addWidget(self.filter_level, 0, 1)
|
||||||
self.filter_freetext = QtWidgets.QLineEdit()
|
self.filter_freetext = QtWidgets.QLineEdit()
|
||||||
self.filter_freetext.setPlaceholderText("freetext filter...")
|
self.filter_freetext.setPlaceholderText("freetext filter...")
|
||||||
|
self.filter_freetext.setToolTip("Receive entries containing this text")
|
||||||
grid.addWidget(self.filter_freetext, 0, 2)
|
grid.addWidget(self.filter_freetext, 0, 2)
|
||||||
|
|
||||||
scrollbottom = QtWidgets.QToolButton()
|
scrollbottom = QtWidgets.QToolButton()
|
||||||
|
@ -340,3 +342,37 @@ class LogDockManager:
|
||||||
return None
|
return None
|
||||||
dock = self.create_new_dock(False)
|
dock = self.create_new_dock(False)
|
||||||
return dock
|
return dock
|
||||||
|
|
||||||
|
|
||||||
|
class LogWidgetHandler(logging.Handler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
logging.Handler.__init__(self, *args, **kwargs)
|
||||||
|
self.callback = None
|
||||||
|
self.setFormatter(logging.Formatter("%(name)s:%(message)s"))
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
if self.callback is not None:
|
||||||
|
message = self.format(record)
|
||||||
|
self.callback((record.levelno, record.source,
|
||||||
|
record.created, message))
|
||||||
|
|
||||||
|
|
||||||
|
def init_log(args, local_source):
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(logging.NOTSET) # we use our custom filter only
|
||||||
|
flt = SourceFilter(logging.INFO + args.quiet*10 - args.verbose*10,
|
||||||
|
local_source)
|
||||||
|
handlers = []
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(logging.Formatter(
|
||||||
|
"%(levelname)s:%(source)s:%(name)s:%(message)s"))
|
||||||
|
handlers.append(console_handler)
|
||||||
|
|
||||||
|
widget_handler = LogWidgetHandler()
|
||||||
|
handlers.append(widget_handler)
|
||||||
|
|
||||||
|
for handler in handlers:
|
||||||
|
handler.addFilter(flt)
|
||||||
|
root_logger.addHandler(handler)
|
||||||
|
|
||||||
|
return widget_handler
|
||||||
|
|
|
@ -51,7 +51,7 @@ class StateManager(TaskObject):
|
||||||
# To help address this problem, state is restored in the opposite
|
# To help address this problem, state is restored in the opposite
|
||||||
# order as the stateful objects are registered.
|
# order as the stateful objects are registered.
|
||||||
for name, obj in reversed(list(self.stateful_objects.items())):
|
for name, obj in reversed(list(self.stateful_objects.items())):
|
||||||
logger.info("Restoring state of object '%s'", name)
|
logger.debug("Restoring state of object '%s'", name)
|
||||||
state = data.get(name, None)
|
state = data.get(name, None)
|
||||||
if state is not None:
|
if state is not None:
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue