diff --git a/judge/admin.py b/judge/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/judge/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/judge/client.py b/judge/client.py new file mode 100644 index 00000000..47e9f6f2 --- /dev/null +++ b/judge/client.py @@ -0,0 +1,147 @@ +# coding=utf-8 +import json +import time +import commands +from Queue import Queue +from thread_pool import ThreadPool +from settings import MAX_RUNNING_NUMBER, LRUN_GID, LRUN_UID, USE_TMPFS + + +class JudgeClientException(Exception): + pass + + +class JudgeClient(object): + def __init__(self, language, exec_file_path, max_cpu_time, + max_real_time, max_memory, test_case_dir): + # 语言 c cpp 或者 java + self.language = language + # 可执行文件路径,比如 /root/1/a.out /root/1/Main.class + self.exec_file_path = exec_file_path + # 最大的cpu时间 + self.max_cpu_time = max_cpu_time + # 最大实际运行时间 + self.max_real_time = max_real_time + # 最大cpu占用,注意不要小于500000 + self.max_memory = max_memory + # 测试用例文件路径,比如/root/testcase/1/ + self.test_case_dir = test_case_dir + # 判题结果队列 + self.result_queue = Queue() + # 线程池 + self.thread_pool = ThreadPool(size=MAX_RUNNING_NUMBER, + result_queue=self.result_queue) + self.thread_pool.start() + # 测试用例配置项 + self.test_case_config = {} + self.load_test_case_config() + + def load_test_case_config(self): + # 读取测试用例配置项 转换为dict + # try: + # f = open(self.test_case_dir + "config") + # self.test_case_config = json.loads(f.read()) + # except IOError: + # raise JudgeClientException("Test case config file not found") + # except ValueError: + # raise JudgeClientException("Test case config file format error") + return {"test_case_number": 1, + "test_cases": + { + "1": {"input_name": "1.in", + "output_name": "1.out", + "output_md5": "yyy", + "output_size": 100}, + + "2": {"input_name": "2.in", + "output_name": "2.out", + "output_md5": "yyy", + "output_size": 100} + } + } + + def generate_command(self, test_case_id): + """ + 设置相关运行限制 进制访问网络 如果启用tmpfs 就把代码输出写入tmpfs,否则写入硬盘 + """ + command = "lrun" + \ + " --max-cpu-time " + str(self.max_cpu_time) + \ + " --max-real-time " + str(self.max_real_time) + \ + " --max-memory " + str(self.max_memory) + \ + " --network false" + \ + " --uid " + str(LRUN_UID) + \ + " --gid " + str(LRUN_GID) + if USE_TMPFS: + command += (" --tmpfs /var " + + str(int(self.test_case_config["test_cases"][str(test_case_id)]["output_size"] * 1.2))) + + if self.language == "java": + command += (" java " + self.exec_file_path) + else: + command += (" " + self.exec_file_path) + # fixme 输出路径 + command += (" 0<" + self.test_case_dir + str(test_case_id) + ".in" + + " 1>" + "/var/" + str(test_case_id) + ".out" + + " 3>&2") + return command + + def parse_lrun_output(self, output): + lines = output.split("\n") + if len(lines) != 7: + raise JudgeClientException("Lrun result parse error") + result = {} + # 将lrun输出的各种带下划线 不带下划线的字符串统一处理 + translate = {"MEMORY": "memory", + "CPUTIME": "cpu_time", + "CPU_TIME": "cpu_time", + "REALTIME": "real_time", + "REAL_TIME": "real_time", + "EXITCODE": "exit_code", + "EXCEED": "exceed"} + for line in lines: + name = line[:9].strip(" ") + value = line[9:] + print name, value + if name == "MEMORY": + result[translate[name]] = int(value) + elif name == "CPUTIME": + result[translate[name]] = float(value) * 1000 + elif name == "REALTIME": + result[translate[name]] = float(value) * 1000 + elif name == "EXITCODE": + result[translate[name]] = int(value) + elif name == "EXCEED": + if result == "none": + result[translate[name]] = None + else: + result[translate[name]] = translate[value] + return result + + def judge_one(self, test_case_id): + command = self.generate_command(test_case_id + 1) + status_code, output = commands.getstatusoutput(command) + if status_code: + raise JudgeClientException(output) + return output + + def run(self): + # 添加到任务队列 + for i in range(self.test_case_config["test_case_number"]): + self.thread_pool.append_job(self.judge_one, i) + + self.thread_pool.join() + self.thread_pool.stop() + + # 先判断lrun的返回结果 看是否有超过限制的 在判断输出结果 + for i in range(self.test_case_config["test_case_number"]): + result = self.parse_lrun_output(self.result_queue.get(block=False)) + + # todo + + +client = JudgeClient(language="c", + exec_file_path="/root/a.out", + max_cpu_time=1000, + max_real_time=2000, + max_memory=600000, + test_case_dir="/root/test_case/p1/") diff --git a/judge/compiler.py b/judge/compiler.py new file mode 100644 index 00000000..9bad5790 --- /dev/null +++ b/judge/compiler.py @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/judge/migrations/__init__.py b/judge/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/judge/models.py b/judge/models.py deleted file mode 100644 index 71a83623..00000000 --- a/judge/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. diff --git a/judge/settings.py b/judge/settings.py new file mode 100644 index 00000000..43ca379e --- /dev/null +++ b/judge/settings.py @@ -0,0 +1,9 @@ +# coding=utf-8 +# 单个判题端最多同时运行的程序个数,因为判题端会同时运行多组测试数据,比如一共有5组测试数据 +# 如果MAX_RUNNING_NUMBER大于等于5,那么这5组数据就会同时进行评测,然后返回结果。 +# 如果MAX_RUNNING_NUMBER小于5,为3,那么就会同时运行前三组测试数据,然后再运行后两组数据 +# 这样可以避免同时运行的程序过多导致的cpu占用太高 +MAX_RUNNING_NUMBER = 10 +USE_TMPFS = True +LRUN_UID = 1001 +LRUN_GID = 1002 \ No newline at end of file diff --git a/judge/tests.py b/judge/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/judge/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/judge/thread_pool.py b/judge/thread_pool.py new file mode 100644 index 00000000..1349510c --- /dev/null +++ b/judge/thread_pool.py @@ -0,0 +1,115 @@ +# coding=utf8 + +""" + A simple thread pool + + Usage: + + pool = ThreadPool(size=10) # size: how many threads in pool [default: 1] + pool.start() # start all threads to work! + pool.append_job(myjob, *args, **kwargs) + pool.join() # wait all jobs done + pool.stop() # kill all threads in pool +""" + + +import threading +from Queue import Queue, Empty + +# macros: thread's states +RUNNING = 1 +STOPPED = 0 + + +class ThreadWorker(threading.Thread): + + def __init__(self, pool): + super(ThreadWorker, self).__init__() + self.pool = pool + # subthreads terminates once the main thread end + self.setDaemon(True) + self.state = STOPPED + + def start(self): + self.state = RUNNING + super(ThreadWorker, self).start() + + def stop(self): + self.state = STOPPED + + def run(self): + + while self.state is RUNNING: + # don't use `Queue.empty` to check but use Exception `Empty`, + # because another thread may put a job right after your checking + try: + job, args, kwargs = self.pool.jobs.get(block=False) + except Empty: + continue + else: + # do job + try: + result = job(*args, **kwargs) + self.pool.results.put(result) # collect the result + except Exception, e: + self.stop() + raise e + finally: + self.pool.jobs.task_done() + + +class ThreadPool(object): + + def __init__(self, size, result_queue): + self.size = size + self.jobs = Queue() + self.results = result_queue + self.threads = [] + + def start(self): + """start all threads""" + for i in range(self.size): + self.threads.append(ThreadWorker(self)) + + for thread in self.threads: + thread.start() + + def append_job(self, job, *args, **kwargs): + self.jobs.put((job, args, kwargs)) + + def join(self): + """waiting all jobs done""" + self.jobs.join() + + def stop(self): + """kill all threads""" + for thread in self.threads: # stop all threads + thread.stop() + + for thread in self.threads: # waiting completing + if thread.isAlive(): + thread.join() + + del self.threads[:] + + +if __name__ == '__main__': + '''Time this test should get about 1s''' + + from time import sleep + + thread_pool = ThreadPool(size=10, result_queue=Queue()) + + def job(i): + print "Hello! %d" % i + sleep(i) + + return 1 + + thread_pool.start() + + for x in range(10): + thread_pool.append_job(job, x) + + thread_pool.join() + thread_pool.stop() \ No newline at end of file diff --git a/judge/views.py b/judge/views.py deleted file mode 100644 index 91ea44a2..00000000 --- a/judge/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here.