Commit 89ba9abd authored by Kento HASEGAWA's avatar Kento HASEGAWA

Make it possible to save all the answers sent from clients (#9)

parent 4cffa912
......@@ -3,6 +3,8 @@
.DS_Store
*.txt
*.tmp
*.json
# Object files
*.o
......
......@@ -5,6 +5,7 @@ This is intended to run on the host server (Raspberry Pi).
"""
import argparse
import datetime
import glob
import json
import os
......@@ -27,6 +28,25 @@ questions = None
clients = None
current_seed = 1
def update_answer_list(qname):
global app_args
global questions
questions[qname]['answers'] = []
_folder_name = os.path.splitext(qname)[0]
_answers_path = "{}/{}".format(app_args['out'], _folder_name)
if os.path.isdir(_answers_path):
answer_log_file = glob.glob("{}/*.json".format(_answers_path))
answer_log_file.sort()
answer_log_file.reverse()
for v2 in answer_log_file:
with open(v2, "r") as fp:
answer_log = json.load(fp)
questions[qname]['answers'].append(answer_log)
@app.before_request
def before_request():
global app_args
......@@ -57,12 +77,15 @@ def before_request():
questions[_name] = {
"path": v,
"status": "Not solved",
"answer": {},
"answer": "",
"queue": Queue(),
"board_size": board_size,
"line_num": line_num
"line_num": line_num,
"answers": [],
}
update_answer_list(_name)
# 既に回答されているものを読み込む
answer_path = os.path.abspath(app_args["out"])
answer_list = glob.glob("{}/T03_A*.txt".format(answer_path))
......@@ -91,15 +114,55 @@ def before_request():
def post():
global questions
global app_args
_client = request.form["client"]
_qname = request.form["qname"]
_answer = request.form["answer"]
_cputime = request.form["cputime"]
dat = {"client": _client, "answer": _answer, "cputime": _cputime}
receive_time = datetime.datetime.now()
receive_time_str = receive_time.strftime("%Y%m%d%H%M%S")
dat = {
"client": _client,
"answer": _answer,
"cputime": _cputime,
"timestamp": receive_time.strftime("%Y/%m/%d %H:%M:%S")
}
# Create a temporary file to execute "nlcheck.py"
tmp_path = "{}/{}".format(app_args['out'], "tmp")
if not os.path.isdir(tmp_path):
os.makedirs(tmp_path)
tmp_file_name = "{}-{}.tmp".format(receive_time_str, _client.replace(":", "."))
tmp_file_path = "{}/{}".format(tmp_path, tmp_file_name)
with open(tmp_file_path, "w") as fp:
fp.write(_answer)
probpath = "{}/{}".format(app_args["question"], _qname)
# Format check
cmd = "/usr/bin/python /home/pi/adc2018/conmgr/adc2018/server/nlcheck.py --input {} --target {}".format(probpath, tmp_file_path)
print("`{}`".format(cmd))
p = subprocess.run(cmd.strip().split(" "), stdout=subprocess.PIPE)
nlcheck = p.stdout.decode().strip()
pattern = r"judge += +\[(.+?), ([0-9.]+)\]"
m = re.match(pattern, nlcheck)
print(nlcheck, m)
if m and (m.group(1) == "True"):
dat["nlcheck"] = float(m.group(2))
else:
dat["nlcheck"] = -1
save_path = "{}/{}".format(app_args['out'], os.path.splitext(_qname)[0])
if not os.path.isdir(save_path):
os.makedirs(save_path)
questions[_qname]["queue"].put(dat)
file_name = "{}-{}.json".format(receive_time_str, _client.replace(":", "."))
save_file_path = "{}/{}".format(save_path, file_name)
with open(save_file_path, "w") as fp:
json.dump(dat, fp, indent=4)
res = {"status": "OK"}
......@@ -132,116 +195,54 @@ def start():
qnumber = _question_name.replace(".txt", "").replace("NL_Q", "")
probpath = "{}/{}".format(app_args["question"], _question_name)
tmppath = "{}/T03_A{}_tmp.txt".format(app_args["out"], qnumber)
infopath = "{}/T03_A{}_info.txt".format(app_args["out"], qnumber)
outpath = "{}/T03_A{}.txt".format(app_args["out"], qnumber)
res = {}
if line_num >= 0:
if line_num < app_args["line_num_th"]:
# LINE_NUMが閾値未満のとき,PYNQに問題を配信して問題を解かせる
res = solve_questions(_question_name, qstr)
# 一旦回答をテンポラリファイルに保存
with open(tmppath, "w") as fp:
fp.write(res["answer"]["answer"])
else:
# LINE_NUMが閾値以上のとき,PYNQでは解けないのでRaspberry Piに解かせる
# 文字数多くなるとコマンドラインで載りきらないからパイプで渡す
#boardstr = BoardStr.conv_boardstr(_q_lines, 'random', current_seed)
cmd = "/opt/python3.6/bin/python3.6 /home/pi/pynq-router/solver/gen_boardstr.py -t random -s {} {} | /home/pi/pynq-router/sw_huge/sim - {} {}".format(current_seed, probpath, current_seed, tmppath)
print("`{}`".format(cmd))
time_start = time.time()
subprocess.call(cmd.strip(), shell=True)
time_done = time.time()
elapsed = time_done - time_start
res["answer"] = {}
res["answer"]["client"] = "192.168.4.1"
res["answer"]["answer"] = "Solved on Raspberry Pi!"
res["answer"]["cputime"] = elapsed
res["status"] = "Solved on Raspberry Pi"
current_seed += 1
# CPU time
print("CPU_time:{}".format(res["answer"]["cputime"]))
with open(infopath, "w") as f:
f.write("CPU_time:{}\n".format(res["answer"]["cputime"]))
f.write("memory:551250\n")
# 回答をファイルに保存するとしたらここで処理する
# LINE_NUMが閾値未満のとき,PYNQに問題を配信して問題を解かせる
res = solve_questions(_question_name, qstr)
# 整形ルーティング (再配線) する
#cmd = "/home/pi/pynq-router/resolver/solver --reroute --output {} {} {}".format(outpath, probpath, tmppath)
cmd = "/home/pi/adc2017/pynq-router/resolver/solver --reroute --output {} {} {}".format(outpath, probpath, tmppath)
print("`{}`".format(cmd))
subprocess.call(cmd.strip().split(" "))
# たまに整形ルーティングが失敗する
# そのときは tmp を答えファイルとしてコピーする
#if not os.path.exists(outpath) and os.path.exists(tmppath):
# cmd = "/bin/cp {} {}".format(tmppath, outpath)
# subprocess.call(cmd.strip().split(" "))
# 回答ファイルが正しく出力されないときは,正しく解けなかったとき
if not os.path.exists(outpath):
res["status"] = "DNF"
else:
# Format check
#cmd = "/usr/bin/python /home/pi/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))
subprocess.call(cmd.strip().split(" "))
# 最終結果だけを保存
questions[_question_name]["status"] = res["status"]
questions[_question_name]["answer"] = res["answer"]
return json.dumps(res)
def solve_questions(qname, qstr):
"""
このメソッドでは,問題データをソルバのクライアントに送りつける.
結果は/postに送られてくるので,結果の集計はそちらで行う.
"""
global clients
global questions
global current_seed
global app_args
def worker(host, qname, qstr, qseed, q):
def worker(host, qname, qstr, qseed, request_time):
_url = "http://{}/start".format(host)
try:
r = requests.post(_url, data={"client": host, "qname": qname, "question": qstr, "qseed": qseed})
r = requests.post(_url, data={"client": host, "qname": qname, "question": qstr, "qseed": qseed, "request_time": request_time})
client_res = json.loads(r.text)
q.put(client_res)
except Exception as e:
sys.stderr.write(str(e) + "\n")
threads = []
q = Queue()
request_time = time.time()
for c in clients:
_th = threading.Thread(name=c, target=worker, args=(c, qname, qstr, current_seed, q))
_th = threading.Thread(name=c, target=worker, args=(c, qname, qstr, current_seed, request_time))
_th.start()
threads.append(_th)
current_seed += 1
# PYNQが解き終わるまで待つ(ここでは最大10秒)
cnt = 0
while cnt < app_args["timeout"] and questions[qname]["queue"].qsize() < 1:
time.sleep(1)
cnt += 1
res = {"status": "Done", "answers": [], "answer": ""}
while not questions[qname]["queue"].empty():
_r = questions[qname]["queue"].get()
res["answers"].append(_r)
# res["answers"]に,回答を得られたものの結果が,返ってきた順に入る.
# 解の品質等を決めて最終的な回答を与える場合はここで処理する(今はとりあえず最初の答え)
# TODO: 答えが無い場合の処理
if len(res["answers"]) > 0:
res["answer"] = res["answers"][0]
else:
res["answer"] = { "client": "None", "answer": "" }
res["status"] = "DNF"
#print(res)
res = {"status": "Processed"}
return res
......@@ -295,11 +296,14 @@ def question_status():
global app_args
global questions
global clients
qname = request.args.get("qname", "")
update_answer_list(qname)
qdata = questions[qname]
return render_template("part_question_status.html", qname=qname, qdata=qdata)
return render_template("part_question_status.html", qname=qname, qdata=qdata, clients=clients)
@app.route("/")
def index():
......@@ -321,7 +325,6 @@ if __name__ == "__main__":
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("-o", "--out", action="store", type=str, default="./answers", help="Path to the output folder.")
parser.add_argument("-l", "--line-num-th", action="store", type=int, default=128, help="Line number threshold.")
parser.add_argument("-t", "--timeout", action="store", type=int, default=300, help="Timeout.")
parser.add_argument("--debug", action="store_true", default=False, help="Debug mode.")
args = vars(parser.parse_args())
......
......@@ -43,7 +43,7 @@ body{
#client-control-pane{
height: 330px;
overflow: scroll;
overflow-y: scroll;
}
#client-control-pane table th,
......
......@@ -63,7 +63,6 @@ var PynqManager = (function(){
}).done(function(res){
var answer = res;
$("#solving-question-status").text(answer["status"]);
$("#solved-result").text(answer["answer"]["answer"]);
$("#solved-client").text(answer["answer"]["client"]);
if (after !== null){
......
<h3>問題 【{{qname}}】</h3>
<p>
<button class="btn btn-primary btn-lg start-button" type="button" data-qname="{{qname}}">Start</button>
</p>
<div class="row">
<div class="col-6">
<h3>問題 【{{qname}}】</h3>
<p>
<button class="btn btn-primary btn-lg start-button" type="button" data-qname="{{qname}}">Start</button>
</p>
</div>
<div class="col-6">
<p>処理結果</p>
<table class="table table-bordered">
<tr>
<th>クライアント</th>
<th>Status</th>
</tr>
{% for c in clients %}
<tr>
<td>{{c}}</td>
<td></td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div>
<h4>処理結果一覧</h4>
<table class="table table-bordered table-striped">
<tr>
<th>Timestamp</th>
<th>Client</th>
<th>Score</th>
</tr>
{% for v in qdata.answers %}
<tr>
<td>{{v.timestamp}}</td>
<td>{{v.client}}</td>
<td>{{v.nlcheck}}</td>
</tr>
{% endfor %}
</table>
</div>
<p>状況:<span id="solving-question-status">{{qdata.status}}</span></p>
<h4>結果</h4>
<p>クライアント:<span id="solved-client">{{qdata.answer.client}}</span></p>
......
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