Commit 062b86e2 authored by Kento HASEGAWA's avatar Kento HASEGAWA

Merge branch 'comm' into 'master'

Improve UI and add a stoppable thread for a solver

See merge request adc2018/adc2018-system!3
parents c592b577 55491c32
...@@ -17,65 +17,17 @@ from urllib.parse import urlparse ...@@ -17,65 +17,17 @@ from urllib.parse import urlparse
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../../solver') sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../../solver')
import BoardStr import BoardStr
import pynqrouter import adc2018solver as pynqrouter
app = Flask(__name__) app = Flask(__name__)
args = {} args = {}
pynq_thread = None # pynq_thread = None
client_baseurl = "" client_baseurl = ""
# Reference: https://teratail.com/questions/52593
class StoppableThread(threading.Thread):
def __init__(self, target, qname, qstr, qseed):
super(StoppableThread, self).__init__(target=target)
self._th_stop = threading.Event()
self._qname = qname
self._qstr = qstr
self._qseed = int(qseed)
self._answer = None
def run(self):
global client_baseurl
global pynq_thread
global args
# Main funciton (the solver should be placed here)
boardstr = BoardStr.conv_boardstr(self._qstr.split('\n'), 'random', self._qseed)
result = pynqrouter.solve(boardstr, self._qseed)
if result['solved']:
answer = result['solution']
else:
answer = None
pynq_thread = None
return
res = {
"client": client_baseurl,
"qname": self._qname,
"answer": answer,
"cputime": result['elapsed']
}
self._answer = answer
r = requests.post("http://{}/post".format(args["host"]), data=res)
if args["verbose"]:
print(res)
pynq_thread = None
def stop(self):
self._th_stop.set()
def stopped(self):
return self._th_stop.isSet()
def get_answer(self):
return self._answer
@app.route('/start', methods=["POST"]) @app.route('/start', methods=["POST"])
def start(): def start():
global pynq_thread # global pynq_thread
global args global args
global client_baseurl global client_baseurl
...@@ -84,13 +36,19 @@ def start(): ...@@ -84,13 +36,19 @@ def start():
if args["verbose"]: if args["verbose"]:
print(request.form) print(request.form)
if pynq_thread is None: if pynqrouter.solver_thread is not None:
pynqrouter.stop_solver()
if pynqrouter.solver_thread is None:
qstr = request.form["question"] qstr = request.form["question"]
qname = request.form["qname"] qname = request.form["qname"]
qseed = request.form["qseed"] qseed = request.form["qseed"]
pynq_thread = StoppableThread(target=StoppableThread, qname=qname, qstr=qstr, qseed=qseed)
boardstr = BoardStr.conv_boardstr(qstr.split('\n'), 'random', int(qseed))
option = {"name": qname, "host": args['host'], "client": client_baseurl}
pynq_thread.start() pynqrouter.start_solver(boardstr, qseed, option)
# pynq_thread = pynqrouter.start_solver(boardstr, qseed, option)
# 実行だけ開始する # 実行だけ開始する
ans["status"] = "Processing" ans["status"] = "Processing"
...@@ -106,26 +64,27 @@ def start(): ...@@ -106,26 +64,27 @@ def start():
@app.route('/stop') @app.route('/stop')
def stop(): def stop():
global pynq_thread # global pynq_thread
if pynq_thread is None: if pynqrouter.solver_thread is None:
ans = {"status": "No threads"} ans = {"status": "No threads"}
else: else:
pynq_thread.stop() # pynq_thread.stop()
pynqrouter.stop_solver()
ans = {"status": "Stopped"} ans = {"status": "Stopped"}
pynq_thread = None # pynq_thread = None
return json.dumps(ans) return json.dumps(ans)
@app.route("/status") @app.route("/status")
def status(): def status():
global pynq_thread # global pynq_thread
res_mes = "" res_mes = ""
if pynq_thread is None: if pynqrouter.solver_thread is None:
res_mes = "Ready" res_mes = "Ready"
else: else:
res_mes = "Working" res_mes = "Working"
......
...@@ -185,7 +185,7 @@ def start(): ...@@ -185,7 +185,7 @@ def start():
else: else:
# Format check # Format check
#cmd = "/usr/bin/python /home/pi/conmgr/adc2017/server/nlcheck.py --input {} --target {}".format(probpath, outpath) #cmd = "/usr/bin/python /home/pi/conmgr/adc2017/server/nlcheck.py --input {} --target {}".format(probpath, outpath)
cmd = "/usr/bin/python /home/pi/adc2017/conmgr/adc2017/server/nlcheck.py --input {} --target {}".format(probpath, outpath) cmd = "/usr/bin/python /home/pi/adc2018/conmgr/adc2018/server/nlcheck.py --input {} --target {}".format(probpath, outpath)
print("`{}`".format(cmd)) print("`{}`".format(cmd))
subprocess.call(cmd.strip().split(" ")) subprocess.call(cmd.strip().split(" "))
...@@ -318,7 +318,6 @@ def main(args): ...@@ -318,7 +318,6 @@ def main(args):
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="PYNQ control panel.") parser = argparse.ArgumentParser(description="PYNQ control panel.")
parser.add_argument("--no-gui", action="store_false", dest="gui", default=True, help="No GUI")
parser.add_argument("-c", "--client", action="store", type=str, default=None, required=True, help="Client list.") parser.add_argument("-c", "--client", action="store", type=str, default=None, required=True, help="Client list.")
parser.add_argument("-q", "--question", action="store", type=str, default="./problems", help="Path to the question folder.") parser.add_argument("-q", "--question", action="store", type=str, default="./problems", help="Path to the question folder.")
parser.add_argument("-o", "--out", action="store", type=str, default="./answers", help="Path to the output folder.") parser.add_argument("-o", "--out", action="store", type=str, default="./answers", help="Path to the output folder.")
...@@ -328,9 +327,6 @@ if __name__ == "__main__": ...@@ -328,9 +327,6 @@ if __name__ == "__main__":
args = vars(parser.parse_args()) args = vars(parser.parse_args())
app_args = args app_args = args
if args["gui"]: if args["debug"]:
if args["debug"]: app.debug = True
app.debug = True app.run(host='0.0.0.0', threaded=True)
app.run(host='0.0.0.0', threaded=True)
else:
main(args)
This source diff could not be displayed because it is too large. You can view the blob instead.
html, body{
/*width: 800px;*/
/*height: 500px;*/
font-size: 11px;
overflow: hidden;
}
body{
padding-top: 10px;
}
/* scrollbar settings */
::-webkit-scrollbar{
width: 25px;
border: 1px solid rgba(0, 0, 50, .2);
}
::-webkit-scrollbar-thumb{
background-color: rgba(0, 0, 50, .5);
}
#question-table-wrapper{
height: 300px;
overflow-y: scroll;
overflow-x: hidden;
}
#question-table-wrapper th.large-cell,
#question-table-wrapper td.large-cell{
width: 24%;
}
#question-table-wrapper th.small-cell,
#question-table-wrapper td.small-cell{
width: 14%;
}
#question-table-wrapper tr.question-row,
#question-table-wrapper tr.question-row td{
cursor: pointer;
}
#question-table-wrapper tr.question-row.q-selected{
background-color: rgba(200, 100, 100, .3);
}
#client-control-pane{
height: 330px;
overflow: scroll;
}
#client-control-pane table th,
#client-control-pane table td{
width: 50%;
}
#question-control-pane h3{
display: inline-block;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -81,3 +81,77 @@ var PynqManager = (function(){ ...@@ -81,3 +81,77 @@ var PynqManager = (function(){
return PynqManager; return PynqManager;
})(); })();
$(function(){
var pynqClients = {}
var pm = null;
var refresh_question_table = function(){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_question_table"
}).done(function(d){
$("#question-table-wrapper").find("#question-table").remove();
$("#question-table-wrapper").html(d);
$(".question-row td").click(function(){
var $tr = $(this).parent("tr.question-row");
$(".question-row").removeClass("q-selected");
$tr.addClass("q-selected");
var qname = $tr.data("qname");
show_question_status(qname);
return false;
});
});
}
var refresh_client_table = function(){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_client_table"
}).done(function(d){
$("#client-control-pane").html("");
$("#client-control-pane").html(d);
$.ajax({
type: "GET",
dataType: "json",
url: "/get_clients"
}).done(function(d){
pynqClients = d;
pm = PynqManager(pynqClients);
pm.getStatus();
});
});
}
var show_question_status = function(qname){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_question_status",
data: {qname: qname}
}).done(function(d){
$("#client-control-pane").html("");
$("#client-control-pane").html(d);
$("#client-control-pane").find(".start-button").eq(0).click(function(){
var qname = $(this).data("qname");
pm.sendQuestion(qname, after=refresh_question_table);
});
});
}
refresh_question_table();
refresh_client_table();
$("#view-server-status-button").click(function(){
refresh_client_table();
return false;
});
});
...@@ -6,144 +6,31 @@ ...@@ -6,144 +6,31 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/css/bootstrap.min.css"> <link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/pynq-manager.css">
<script src="/static/js/jquery.min.js"></script> <script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script> <script src="/static/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/pynq-manager.js"></script> <script src="/static/js/pynq-manager.js"></script>
<style>
html, body{
/*width: 800px;*/
/*height: 500px;*/
font-size: 11px;
overflow: hidden;
}
body{
margin-top: 20px;
}
/* scrollbar settings */
::-webkit-scrollbar{
width: 30px;
border: 1px solid rgba(0, 0, 50, .2);
}
::-webkit-scrollbar-thumb{
background-color: rgba(0, 0, 50, .5);
}
#question-table-wrapper{
height: 350px;
overflow: scroll;
}
#question-table-wrapper th.large-cell,
#question-table-wrapper td.large-cell{
width: 24%;
}
#question-table-wrapper th.small-cell,
#question-table-wrapper td.small-cell{
width: 14%;
}
#client-control-pane table th,
#client-control-pane table td{
width: 50%;
}
</style>
<script>
$(function(){
var pynqClients = {}
var pm = null;
var refresh_question_table = function(){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_question_table"
}).done(function(d){
$("#question-table-wrapper").find("#question-table").remove();
$("#question-table-wrapper").html(d);
$(".show-question-status-button").click(function(){
var qname = $(this).data("qname");
show_question_status(qname);
return false;
});
});
}
var refresh_client_table = function(){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_client_table"
}).done(function(d){
$("#client-control-pane").html("");
$("#client-control-pane").html(d);
$.ajax({
type: "GET",
dataType: "json",
url: "/get_clients"
}).done(function(d){
pynqClients = d;
pm = PynqManager(pynqClients);
pm.getStatus();
});
});
}
var show_question_status = function(qname){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_question_status",
data: {qname: qname}
}).done(function(d){
$("#client-control-pane").html("");
$("#client-control-pane").html(d);
$("#client-control-pane").find(".start-button").eq(0).click(function(){
var qname = $(this).data("qname");
pm.sendQuestion(qname, after=refresh_question_table);
});
});
}
refresh_question_table();
refresh_client_table();
$("#view-server-status-button").click(function(){
refresh_client_table();
return false;
});
});
</script>
</head> </head>
<body> <body>
<div id="wrapper"> <div id="wrapper">
<div id="contorol-panel-wrapper" class="container"> <div id="contorol-panel-wrapper" class="container-fluid">
<div class="row"> <div class="row">
<div class="col" id="question-control-pane"> <div class="col-5" id="question-control-pane">
<h3>問題一覧</h3> <h3>問題一覧</h3>&nbsp;
<p><a href="#" id="view-server-status-button">サーバ状況</a></p> <span><a href="#" id="view-server-status-button">クライアント状況</a></span>
<div id="question-table-wrapper">
<p>Loading...</p>
</div>
</div>
<div id="question-table-wrapper"> <div class="col-7" id="client-control-pane">
<p>Loading...</p> <p>Loading...</p>
</div> </div>
</div> </div>
<div class="col" id="client-control-pane">
<p>Loading...</p>
</div>
</div>
</div> </div>
......
<h3>問題 【{{qname}}】</h3> <h3>問題 【{{qname}}】</h3>
<p> <p>
<button class="btn btn-default start-button" type="button" data-qname="{{qname}}">Start</button> <button class="btn btn-primary btn-lg start-button" type="button" data-qname="{{qname}}">Start</button>
</p> </p>
<p>状況:<span id="solving-question-status">{{qdata.status}}</span></p> <p>状況:<span id="solving-question-status">{{qdata.status}}</span></p>
<h4>結果</h4> <h4>結果</h4>
......
<table class="table table-bordered" id="question-table"> <table class="table table-bordered table-hover" id="question-table">
<tr> <tr>
<th class="large-cell">ファイル名</th> <th class="large-cell">File Name</th>
<th class="large-cell">Size</th> <th class="large-cell">Size</th>
<th class="small-cell">Line</th> <th class="small-cell">Line</th>
<th class="large-cell">ステータス</th> <th class="large-cell">Status</th>
<th class="small-cell">操作</th>
</tr> </tr>
{% for k, v in questions.items() %} {% for k, v in questions.items() %}
<tr> <tr class="question-row" data-qname="{{k}}">
<td class="large-cell">{{k}}</td> <td class="large-cell">{{k}}</td>
<td class="large-cell">{{v.board_size}}</td> <td class="large-cell">{{v.board_size}}</td>
<td class="small-cell">{{v.line_num}}</td> <td class="small-cell">{{v.line_num}}</td>
<td class="large-cell">{{v.status}}</td> <td class="large-cell">{{v.status}}</td>
<td class="small-cell">
<a href="#" class="show-question-status-button" data-qname="{{k}}">選択</a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
#!/opt/python3.6/bin/python3.6
# -*- coding: utf-8 -*-
"""
PYNQ 上で pynqrouter を実行する。
"""
from pynq import Overlay
from pynq import PL
from pynq import MMIO
import argparse
import requests
import sys
import time
import threading
import BoardStr
# Settings -- pynqrouter
IP = 'SEG_pynqrouter_0_Reg'
OFFSET_BOARD = 65536 # 0x10000 ~ 0x1ffff
OFFSET_SEED = 131072 # 0x20000
OFFSET_STATUS = 131080 # 0x20008
MAX_X = 72
MAX_Y = 72
MAX_Z = 8
BITWIDTH_Z = 3
# Settings -- LED
IP_LED = 'SEG_axi_gpio_0_Reg'
solver_thread = None
class StoppableThread(threading.Thread):
def __init__(self, target, args=()):
super(StoppableThread, self).__init__(target=target, args=args)
self._status = 'running'
def stop(self):
if self._status == 'running':
self._status = 'stopping'
def stopped(self):
self._status = 'stopped'
def is_running(self):
return (self._status == 'running')
def is_stopping(self):
return (self._status == 'stopping')
def is_stopped(self):
return (self._status == 'stopped')
def start_solver(boardstr, seed, option):
global solver_thread
if solver_thread is None:
solver_thread = StoppableThread(target=solve, args=(boardstr, int(seed), False, option))
solver_thread.start()
def stop_solver():
global solver_thread
if solver_thread is not None:
if solver_thread.is_running():
solver_thread.stop()
solver_thread.join()
solver_thread = None
def solve(boardstr, seed=12345, zero_padding=False, option=dict()):
global solver_thread
print('boardstr:')
print(boardstr)
print('seed:')
print(seed)
print('')
# ボード文字列から X, Y, Z を読んでくる
size_x = (ord(boardstr[1]) - ord('0')) * 10 + (ord(boardstr[2]) - ord('0'))
size_y = (ord(boardstr[4]) - ord('0')) * 10 + (ord(boardstr[5]) - ord('0'))
size_z = (ord(boardstr[7]) - ord('0'))
# Overlay 読み込み
OL = Overlay('pynqrouter_lines128_length256.bit')
OL.download()
print(OL.ip_dict)
print('Overlay loaded!')
# MMIO 接続 (pynqrouter)
mmio = MMIO(int(PL.ip_dict[IP][0], 16), int(PL.ip_dict[IP][1], 16))
# MMIO 接続 & リセット (LED)
mmio_led = MMIO(int(PL.ip_dict[IP_LED][0], 16), int(PL.ip_dict[IP_LED][1], 16))
mmio_led.write(0, 0)
# 入力データをセット
imem = pack(boardstr)
for i in range(len(imem)):
mmio.write(OFFSET_BOARD + (i * 4), imem[i])
mmio.write(OFFSET_SEED, seed)
# スタート
# ap_start (0 番地の 1 ビット目 = 1)
mmio.write(0, 1)
print('Start!')
time_start = time.time()
# ap_done (0 番地の 2 ビット目 = 2) が立つまで待ってもいいが
# done は一瞬だけ立つだけのことがあるから
# ap_idle (0 番地の 3 ビット目 = 4) を待ったほうが良い
iteration = 0
while (mmio.read(0) & 4) == 0:
# 動いてるっぽく見えるようにLチカさせる
iteration += 1
if iteration == 10000:
mmio_led.write(0, 3)
elif 20000 <= iteration:
mmio_led.write(0, 12)
iteration = 0
if (solver_thread is not None) and (not solver_thread.is_running()):
solver_thread.stopped()
solver_thread = None
return { 'solved': False, 'solution': '', 'elapsed': -1.0 }
# 完了の確認
print('Done!')
print('control:', mmio.read(0))
time_done = time.time()
elapsed = time_done - time_start
print('elapsed:', elapsed)
print('')
# 状態の取得
status = int(mmio.read(OFFSET_STATUS))
print('status:', status)
if status != 0:
# 解けなかったらLEDを消す
mmio_led.write(0, 0)
sys.stderr.write('Cannot solve it!\n')
res = {'client': option['client'], 'qname': option['name'], 'answer': '', 'cputime': -1}
if "host" in option:
requests.post("http://{}/post".format(option['host']), data=res)
solver_thread = None
return { 'solved': False, 'solution': '', 'elapsed': -1.0 }
print('Solved!')
# 解けたらLEDを全部つける
mmio_led.write(0, 15)
# 出力
omem = []
for i in range(len(imem)):
omem.append(mmio.read(OFFSET_BOARD + (i * 4)))
boards = unpack(omem)
# 回答の生成
solution = ('SIZE ' + str(size_x) + 'X' + str(size_y) + 'X' + str(size_z) + '\n')
for z in range(size_z):
solution += ('LAYER ' + str(z + 1) + '\n')
for y in range(size_y):
for x in range(size_x):
if x != 0:
solution += ','
i = ((x * MAX_X + y) << BITWIDTH_Z) | z
if zero_padding:
solution += '{0:0>2}'.format(boards[i]) # 2桁の0詰め
else:
solution += str(boards[i]) # 普通に表示
solution += '\n'
if solver_thread is not None:
solver_thread.stopped()
res = {'client': option['client'], 'qname': option['name'], 'answer': solution, 'cputime': elapsed}
if "host" in option:
r = requests.post("http://{}/post".format(option['host']), data=res)
solver_thread = None
return { 'solved': True, 'solution': solution, 'elapsed': elapsed }
def main():
parser = argparse.ArgumentParser(description="Solver with pynqrouter")
# 入出力
parser.add_argument('-i', '--input', action='store', nargs='?', default=None, type=str,
help='Path to input problem file')
parser.add_argument('-b', '--boardstr', action='store', nargs='?', default=None, type=str,
help='Problem boardstr (if you want to solve directly)')
parser.add_argument('-o', '--output', action='store', nargs='?', default=None, type=str,
help='Path to output answer file')
# ソルバオプション
parser.add_argument('-t', '--terminals', action='store', nargs='?', default='initial', type=str,
help='Terminals order (initial, edgefirst, or random) (default: initial)')
parser.add_argument('-s', '--seed', action='store', nargs='?', default=12345, type=int,
help='Random seed')
# 出力オプション
parser.add_argument('-p', '--print', action='store_true', default=False,
help='Enable to print the solution')
parser.add_argument('-z', '--zero-padding', action='store_true', default=False,
help='Enable to do zero-padding')
args = parser.parse_args()
if args.input is not None:
# 問題ファイルの読み込み
with open(args.input, 'r') as f:
lines = f.readlines()
# 問題ファイルを boardstr に変換
boardstr = BoardStr.conv_boardstr(lines, args.terminals, args.seed)
elif args.boardstr is not None:
boardstr = args.boardstr
else:
sys.stderr.write('Specify at least "input" or "boardstr"!\n')
sys.exit(1)
result = solve(boardstr, args.seed)
if result['solved'] == False:
sys.exit(1)
# 表示 & ファイル出力
if args.print:
print('')
print('SOLUTION')
print('========')
print(result['solution'])
if args.output is not None:
with open(args.output, 'w') as f:
f.write(result['solution'])
def pack(_str):
"""
ボードストリング文字列をMMIOメモリにパックする
"""
_mem = [ 0 for _ in range(MAX_X * MAX_Y * MAX_Z // 4) ]
for i in range(len(_str)):
_index = i // 4;
_offset = i % 4;
if _offset == 0:
_mem[_index] = _mem[_index] | (ord(_str[i]))
elif _offset == 1:
_mem[_index] = _mem[_index] | (ord(_str[i]) << 8)
elif _offset == 2:
_mem[_index] = _mem[_index] | (ord(_str[i]) << 16)
elif _offset == 3:
_mem[_index] = _mem[_index] | (ord(_str[i]) << 24)
return _mem
def unpack(_mem):
"""
MMIOメモリからボード情報をアンパックする
"""
_boards = [ 0 for _ in range(MAX_X * MAX_Y * MAX_Z) ]
for i in range(len(_mem)):
_boards[4 * i ] = (_mem[i]) & 255
_boards[4 * i + 1] = (_mem[i] >> 8) & 255
_boards[4 * i + 2] = (_mem[i] >> 16) & 255
_boards[4 * i + 3] = (_mem[i] >> 24) & 255
return _boards
if __name__ == '__main__':
main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment