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

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

parent 4cffa912
Loading
Loading
Loading
Loading
+2 −0
Changes for .gitignore: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@
.DS_Store

*.txt
*.tmp
*.json

# Object files
*.o
+81 −78
Changes for comm/server/main.py: 81 added lines, 78 removed lines.
Original line number Diff line number Diff line
@@ -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")

    questions[_qname]["queue"].put(dat)
    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)

    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")

    # 回答をファイルに保存するとしたらここで処理する
    # 整形ルーティング (再配線) する
    #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())
+1 −1
Changes for comm/server/static/css/pynq-manager.css: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -43,7 +43,7 @@ body{

#client-control-pane{
    height: 330px;
    overflow: scroll;
    overflow-y: scroll;
}

#client-control-pane table th,
+0 −1
Changes for comm/server/static/js/pynq-manager.js: 0 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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){
+38 −0
Changes for comm/server/templates/part_question_status.html: 38 added lines, 0 removed lines.
Original line number Diff line number Diff line
<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>