2014-10-05 16:25:31 +08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import argparse
|
2014-12-10 19:11:13 +08:00
|
|
|
import time
|
2014-12-28 18:56:26 +08:00
|
|
|
import asyncio
|
2015-01-02 14:46:58 +08:00
|
|
|
import sys
|
|
|
|
from operator import itemgetter
|
2015-01-23 00:23:00 +08:00
|
|
|
from dateutil.parser import parse as parse_date
|
2014-10-23 18:48:03 +08:00
|
|
|
|
2014-12-10 13:04:18 +08:00
|
|
|
from prettytable import PrettyTable
|
|
|
|
|
2015-01-17 19:38:20 +08:00
|
|
|
from artiq.protocols.pc_rpc import Client
|
|
|
|
from artiq.protocols.sync_struct import Subscriber
|
|
|
|
from artiq.protocols import pyon
|
|
|
|
|
|
|
|
|
|
|
|
def clear_screen():
|
|
|
|
sys.stdout.write("\x1b[2J\x1b[H")
|
2014-10-05 16:25:31 +08:00
|
|
|
|
|
|
|
|
2015-01-23 00:52:13 +08:00
|
|
|
def get_argparser():
|
2014-12-29 12:48:14 +08:00
|
|
|
parser = argparse.ArgumentParser(description="ARTIQ CLI client")
|
2014-10-23 19:07:36 +08:00
|
|
|
parser.add_argument(
|
|
|
|
"-s", "--server", default="::1",
|
|
|
|
help="hostname or IP of the master to connect to")
|
|
|
|
parser.add_argument(
|
2014-12-28 18:56:26 +08:00
|
|
|
"--port", default=None, type=int,
|
2014-10-23 19:07:36 +08:00
|
|
|
help="TCP port to use to connect to the master")
|
2014-12-08 19:22:02 +08:00
|
|
|
|
|
|
|
subparsers = parser.add_subparsers(dest="action")
|
2014-12-10 13:04:18 +08:00
|
|
|
subparsers.required = True
|
2014-12-08 19:22:02 +08:00
|
|
|
|
2014-12-10 13:04:18 +08:00
|
|
|
parser_add = subparsers.add_parser("submit", help="submit an experiment")
|
2015-05-17 16:11:00 +08:00
|
|
|
parser_add.add_argument("-p", "--pipeline", default="main", type=str,
|
|
|
|
help="pipeline to run the experiment in "
|
|
|
|
"(default: %(default)s)")
|
2015-05-24 01:09:22 +08:00
|
|
|
parser_add.add_argument("-P", "--priority", default=0, type=int,
|
|
|
|
help="priority (higher value means sooner "
|
|
|
|
"scheduling, default: %(default)s)")
|
2015-05-28 17:20:58 +08:00
|
|
|
parser_add.add_argument("-t", "--timed", default=None, type=str,
|
|
|
|
help="set a due date for the experiment")
|
|
|
|
parser_add.add_argument("-f", "--flush", default=False, action="store_true",
|
|
|
|
help="flush the pipeline before preparing "
|
|
|
|
"the experiment")
|
2015-08-07 15:51:56 +08:00
|
|
|
parser_add.add_argument("-R", "--repository", default=False,
|
|
|
|
action="store_true",
|
|
|
|
help="use the experiment repository")
|
|
|
|
parser_add.add_argument("-r", "--revision", default=None,
|
|
|
|
help="use a specific repository revision "
|
|
|
|
"(defaults to head, ignored without -R)")
|
2015-07-15 17:08:12 +08:00
|
|
|
parser_add.add_argument("-c", "--class-name", default=None,
|
|
|
|
help="name of the class to run")
|
2015-03-08 22:43:04 +08:00
|
|
|
parser_add.add_argument("file",
|
|
|
|
help="file containing the experiment to run")
|
2015-01-07 21:37:07 +08:00
|
|
|
parser_add.add_argument("arguments", nargs="*",
|
|
|
|
help="run arguments")
|
2014-12-08 19:22:02 +08:00
|
|
|
|
2015-05-17 16:11:00 +08:00
|
|
|
parser_delete = subparsers.add_parser("delete",
|
|
|
|
help="delete an experiment "
|
|
|
|
"from the schedule")
|
|
|
|
parser_delete.add_argument("rid", type=int,
|
|
|
|
help="run identifier (RID)")
|
2014-12-10 13:04:18 +08:00
|
|
|
|
2015-01-02 14:46:58 +08:00
|
|
|
parser_set_parameter = subparsers.add_parser(
|
|
|
|
"set-parameter", help="add or modify a parameter")
|
|
|
|
parser_set_parameter.add_argument("name", help="name of the parameter")
|
|
|
|
parser_set_parameter.add_argument("value",
|
|
|
|
help="value in PYON format")
|
|
|
|
|
|
|
|
parser_del_parameter = subparsers.add_parser(
|
|
|
|
"del-parameter", help="delete a parameter")
|
|
|
|
parser_del_parameter.add_argument("name", help="name of the parameter")
|
|
|
|
|
|
|
|
parser_show = subparsers.add_parser(
|
2015-08-08 16:25:55 +08:00
|
|
|
"show", help="show schedule, log, devices or parameters")
|
2015-01-02 14:46:58 +08:00
|
|
|
parser_show.add_argument(
|
|
|
|
"what",
|
2015-08-08 16:25:55 +08:00
|
|
|
help="select object to show: schedule/log/devices/parameters")
|
2014-12-10 13:04:18 +08:00
|
|
|
|
2015-10-04 17:38:07 +08:00
|
|
|
subparsers.add_parser(
|
|
|
|
"scan-ddb", help="trigger a device database (re)scan")
|
|
|
|
|
|
|
|
parser_scan_repos = subparsers.add_parser(
|
|
|
|
"scan-repository", help="trigger a repository (re)scan")
|
|
|
|
parser_scan_repos.add_argument("revision", default=None, nargs="?",
|
|
|
|
help="use a specific repository revision "
|
|
|
|
"(defaults to head)")
|
2015-07-18 00:55:48 +08:00
|
|
|
|
2015-01-23 00:52:13 +08:00
|
|
|
return parser
|
2014-10-05 16:25:31 +08:00
|
|
|
|
|
|
|
|
2015-01-07 21:37:07 +08:00
|
|
|
def _parse_arguments(arguments):
|
|
|
|
d = {}
|
|
|
|
for argument in arguments:
|
|
|
|
name, value = argument.split("=")
|
|
|
|
d[name] = pyon.decode(value)
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
2014-12-10 13:04:18 +08:00
|
|
|
def _action_submit(remote, args):
|
2015-01-07 21:37:07 +08:00
|
|
|
try:
|
|
|
|
arguments = _parse_arguments(args.arguments)
|
|
|
|
except:
|
|
|
|
print("Failed to parse run arguments")
|
|
|
|
sys.exit(1)
|
|
|
|
|
2015-05-17 16:11:00 +08:00
|
|
|
expid = {
|
2014-12-10 13:04:18 +08:00
|
|
|
"file": args.file,
|
2015-07-15 17:08:12 +08:00
|
|
|
"class_name": args.class_name,
|
2015-01-30 20:36:54 +08:00
|
|
|
"arguments": arguments,
|
2014-12-10 13:04:18 +08:00
|
|
|
}
|
2015-08-07 15:51:56 +08:00
|
|
|
if args.repository:
|
|
|
|
expid["repo_rev"] = args.revision
|
2015-01-23 00:23:00 +08:00
|
|
|
if args.timed is None:
|
2015-05-17 16:11:00 +08:00
|
|
|
due_date = None
|
2014-12-10 13:04:18 +08:00
|
|
|
else:
|
2015-05-17 16:11:00 +08:00
|
|
|
due_date = time.mktime(parse_date(args.timed).timetuple())
|
2015-05-28 17:20:58 +08:00
|
|
|
rid = remote.submit(args.pipeline, expid,
|
|
|
|
args.priority, due_date, args.flush)
|
2015-05-17 16:11:00 +08:00
|
|
|
print("RID: {}".format(rid))
|
2014-12-10 13:04:18 +08:00
|
|
|
|
|
|
|
|
2015-05-17 16:11:00 +08:00
|
|
|
def _action_delete(remote, args):
|
|
|
|
remote.delete(args.rid)
|
2014-12-10 13:04:18 +08:00
|
|
|
|
|
|
|
|
2015-01-02 14:46:58 +08:00
|
|
|
def _action_set_parameter(remote, args):
|
2015-01-12 18:51:23 +08:00
|
|
|
remote.set(args.name, pyon.decode(args.value))
|
2015-01-02 14:46:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
def _action_del_parameter(remote, args):
|
2015-01-12 18:51:23 +08:00
|
|
|
remote.delete(args.name)
|
2015-01-02 14:46:58 +08:00
|
|
|
|
|
|
|
|
2015-10-04 17:38:07 +08:00
|
|
|
def _action_scan_ddb(remote, args):
|
|
|
|
remote.scan()
|
|
|
|
|
|
|
|
|
2015-07-18 00:55:48 +08:00
|
|
|
def _action_scan_repository(remote, args):
|
2015-08-08 23:23:25 +08:00
|
|
|
remote.scan_async(args.revision)
|
2015-07-18 00:55:48 +08:00
|
|
|
|
|
|
|
|
2015-05-17 16:11:00 +08:00
|
|
|
def _show_schedule(schedule):
|
2014-12-28 18:56:26 +08:00
|
|
|
clear_screen()
|
2015-05-17 16:11:00 +08:00
|
|
|
if schedule:
|
|
|
|
l = sorted(schedule.items(),
|
2015-05-28 18:24:26 +08:00
|
|
|
key=lambda x: (-x[1]["priority"],
|
|
|
|
x[1]["due_date"] or 0,
|
2015-05-24 01:09:22 +08:00
|
|
|
x[0]))
|
|
|
|
table = PrettyTable(["RID", "Pipeline", " Status ", "Prio",
|
2015-08-07 15:51:56 +08:00
|
|
|
"Due date", "Revision", "File", "Class name"])
|
2015-05-17 16:11:00 +08:00
|
|
|
for rid, v in l:
|
2015-05-24 01:09:22 +08:00
|
|
|
row = [rid, v["pipeline"], v["status"], v["priority"]]
|
2015-05-17 16:11:00 +08:00
|
|
|
if v["due_date"] is None:
|
2015-03-09 17:51:32 +08:00
|
|
|
row.append("")
|
|
|
|
else:
|
2015-05-17 16:11:00 +08:00
|
|
|
row.append(time.strftime("%m/%d %H:%M:%S",
|
|
|
|
time.localtime(v["due_date"])))
|
2015-08-07 15:51:56 +08:00
|
|
|
expid = v["expid"]
|
|
|
|
if "repo_rev" in expid:
|
|
|
|
row.append(expid["repo_rev"])
|
|
|
|
else:
|
|
|
|
row.append("Outside repo.")
|
|
|
|
row.append(expid["file"])
|
|
|
|
if expid["class_name"] is None:
|
2015-03-09 17:51:32 +08:00
|
|
|
row.append("")
|
|
|
|
else:
|
2015-08-07 15:51:56 +08:00
|
|
|
row.append(expid["class_name"])
|
2014-12-10 19:11:13 +08:00
|
|
|
table.add_row(row)
|
|
|
|
print(table)
|
|
|
|
else:
|
2015-05-17 16:11:00 +08:00
|
|
|
print("Schedule is empty")
|
2014-12-10 13:04:18 +08:00
|
|
|
|
|
|
|
|
2015-01-02 14:46:58 +08:00
|
|
|
def _show_devices(devices):
|
|
|
|
clear_screen()
|
|
|
|
table = PrettyTable(["Name", "Description"])
|
|
|
|
table.align["Description"] = "l"
|
|
|
|
for k, v in sorted(devices.items(), key=itemgetter(0)):
|
|
|
|
table.add_row([k, pyon.encode(v, True)])
|
|
|
|
print(table)
|
|
|
|
|
|
|
|
|
|
|
|
def _show_parameters(parameters):
|
|
|
|
clear_screen()
|
|
|
|
table = PrettyTable(["Parameter", "Value"])
|
|
|
|
for k, v in sorted(parameters.items(), key=itemgetter(0)):
|
|
|
|
table.add_row([k, str(v)])
|
|
|
|
print(table)
|
|
|
|
|
|
|
|
|
2014-12-28 18:56:26 +08:00
|
|
|
def _run_subscriber(host, port, subscriber):
|
2014-12-29 18:44:50 +08:00
|
|
|
if port is None:
|
2015-01-13 03:55:50 +08:00
|
|
|
port = 3250
|
2014-12-28 18:56:26 +08:00
|
|
|
loop = asyncio.get_event_loop()
|
2014-10-23 18:48:03 +08:00
|
|
|
try:
|
2014-12-28 18:56:26 +08:00
|
|
|
loop.run_until_complete(subscriber.connect(host, port))
|
|
|
|
try:
|
|
|
|
loop.run_until_complete(asyncio.wait_for(subscriber.receive_task,
|
|
|
|
None))
|
|
|
|
print("Connection to master lost")
|
|
|
|
finally:
|
|
|
|
loop.run_until_complete(subscriber.close())
|
2014-10-23 18:48:03 +08:00
|
|
|
finally:
|
2014-12-28 18:56:26 +08:00
|
|
|
loop.close()
|
|
|
|
|
|
|
|
|
2015-01-02 14:46:58 +08:00
|
|
|
def _show_dict(args, notifier_name, display_fun):
|
|
|
|
d = dict()
|
|
|
|
def init_d(x):
|
|
|
|
d.clear()
|
|
|
|
d.update(x)
|
|
|
|
return d
|
|
|
|
subscriber = Subscriber(notifier_name, init_d,
|
2015-01-14 22:21:59 +08:00
|
|
|
lambda mod: display_fun(d))
|
2015-01-02 14:46:58 +08:00
|
|
|
_run_subscriber(args.server, args.port, subscriber)
|
|
|
|
|
|
|
|
|
2015-08-08 16:25:55 +08:00
|
|
|
class _LogPrinter:
|
|
|
|
def __init__(self, init):
|
|
|
|
for rid, msg in init:
|
|
|
|
print(rid, msg)
|
|
|
|
|
|
|
|
def append(self, x):
|
|
|
|
rid, msg = x
|
|
|
|
print(rid, msg)
|
|
|
|
|
|
|
|
def insert(self, i, x):
|
|
|
|
rid, msg = x
|
|
|
|
print(rid, msg)
|
|
|
|
|
|
|
|
def pop(self, i=-1):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __delitem__(self, x):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __setitem__(self, k, v):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def _show_log(args):
|
|
|
|
subscriber = Subscriber("log", _LogPrinter)
|
|
|
|
_run_subscriber(args.server, args.port, subscriber)
|
|
|
|
|
|
|
|
|
2014-12-28 18:56:26 +08:00
|
|
|
def main():
|
2015-01-23 00:52:13 +08:00
|
|
|
args = get_argparser().parse_args()
|
2015-01-02 14:46:58 +08:00
|
|
|
action = args.action.replace("-", "_")
|
|
|
|
if action == "show":
|
2015-05-17 16:11:00 +08:00
|
|
|
if args.what == "schedule":
|
|
|
|
_show_dict(args, "schedule", _show_schedule)
|
2015-08-08 16:25:55 +08:00
|
|
|
elif args.what == "log":
|
|
|
|
_show_log(args)
|
2015-01-02 14:46:58 +08:00
|
|
|
elif args.what == "devices":
|
|
|
|
_show_dict(args, "devices", _show_devices)
|
|
|
|
elif args.what == "parameters":
|
|
|
|
_show_dict(args, "parameters", _show_parameters)
|
|
|
|
else:
|
|
|
|
print("Unknown object to show, use -h to list valid names.")
|
|
|
|
sys.exit(1)
|
2014-12-28 18:56:26 +08:00
|
|
|
else:
|
2015-01-13 03:55:50 +08:00
|
|
|
port = 3251 if args.port is None else args.port
|
2015-01-12 18:51:23 +08:00
|
|
|
target_name = {
|
|
|
|
"submit": "master_schedule",
|
2015-05-17 16:11:00 +08:00
|
|
|
"delete": "master_schedule",
|
2015-01-12 18:51:23 +08:00
|
|
|
"set_parameter": "master_pdb",
|
|
|
|
"del_parameter": "master_pdb",
|
2015-10-04 17:38:07 +08:00
|
|
|
"scan_ddb": "master_ddb",
|
2015-07-18 00:55:48 +08:00
|
|
|
"scan_repository": "master_repository"
|
2015-01-12 18:51:23 +08:00
|
|
|
}[action]
|
2015-01-02 14:46:58 +08:00
|
|
|
remote = Client(args.server, port, target_name)
|
2014-12-28 18:56:26 +08:00
|
|
|
try:
|
2015-01-02 14:46:58 +08:00
|
|
|
globals()["_action_" + action](remote, args)
|
2014-12-28 18:56:26 +08:00
|
|
|
finally:
|
|
|
|
remote.close_rpc()
|
2014-10-05 16:25:31 +08:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|