diff --git a/backend/entity/src/submit.rs b/backend/entity/src/submit.rs index 8c135bef..991d516f 100644 --- a/backend/entity/src/submit.rs +++ b/backend/entity/src/submit.rs @@ -12,9 +12,11 @@ pub struct Model { pub user_id: Option, pub problem_id: i32, #[sea_orm(column_type = "Timestamp", on_insert = "current_timestamp", indexed)] - pub upload: DateTime, + pub upload_at: DateTime, #[sea_orm(nullable, indexed)] pub time: Option, + #[sea_orm(nullable)] + pub accuracy: Option, #[sea_orm(default_value = "false")] pub committed: bool, pub lang: String, diff --git a/backend/src/controller/util/router.rs b/backend/src/controller/util/router.rs index 4c23d4a8..c7db861e 100644 --- a/backend/src/controller/util/router.rs +++ b/backend/src/controller/util/router.rs @@ -161,13 +161,12 @@ impl Upstream { let res = info.into_inner(); - let langs: BTreeMap = match res.langs { - Some(x) => x.list, - None => Vec::new(), - } - .into_iter() - .map(|x| (x.lang_uid.clone(), x)) - .collect(); + let langs: BTreeMap = res + .langs + .list + .into_iter() + .map(|x| (x.lang_uid.clone(), x)) + .collect(); *self.accuracy.write() = res.accuracy; *self.langs.write() = langs; diff --git a/backend/src/controller/util/state.rs b/backend/src/controller/util/state.rs index f4d2cb52..93f87828 100644 --- a/backend/src/controller/util/state.rs +++ b/backend/src/controller/util/state.rs @@ -2,37 +2,39 @@ use std::cmp; use tokio::sync::broadcast::*; -use crate::grpc::backend::{self, submit_status, JudgeResultState as BackendState, SubmitStatus}; -use crate::grpc::judger::{judge_response, JudgeResponse, JudgeResultState as JudgeState}; +use crate::grpc::backend::{ + self, judge_result, submit_status, StateCode as BackendCode, SubmitStatus, +}; +use crate::grpc::judger::{judge_response, JudgeResponse, JudgeResultState as JudgeCode}; -impl Into for JudgeState { - fn into(self) -> BackendState { +impl Into for JudgeCode { + fn into(self) -> BackendCode { match self { - JudgeState::Ac => BackendState::Ac, - JudgeState::Wa => BackendState::Wa, - JudgeState::Tle => BackendState::Tle, - JudgeState::Mle => BackendState::Mle, - JudgeState::Re => BackendState::Re, - JudgeState::Ce => BackendState::Ce, - JudgeState::Ole => BackendState::Ole, - JudgeState::Na => BackendState::Na, - JudgeState::Rf => BackendState::Rf, + JudgeCode::Ac => BackendCode::Ac, + JudgeCode::Wa => BackendCode::Wa, + JudgeCode::Tle => BackendCode::Tle, + JudgeCode::Mle => BackendCode::Mle, + JudgeCode::Re => BackendCode::Re, + JudgeCode::Ce => BackendCode::Ce, + JudgeCode::Ole => BackendCode::Ole, + JudgeCode::Na => BackendCode::Na, + JudgeCode::Rf => BackendCode::Rf, } } } -impl Into for BackendState { - fn into(self) -> JudgeState { +impl Into for BackendCode { + fn into(self) -> JudgeCode { match self { - BackendState::Ac => JudgeState::Ac, - BackendState::Wa => JudgeState::Wa, - BackendState::Tle => JudgeState::Tle, - BackendState::Mle => JudgeState::Mle, - BackendState::Re => JudgeState::Re, - BackendState::Ce => JudgeState::Ce, - BackendState::Ole => JudgeState::Ole, - BackendState::Na => JudgeState::Na, - BackendState::Rf => JudgeState::Rf, + BackendCode::Ac => JudgeCode::Ac, + BackendCode::Wa => JudgeCode::Wa, + BackendCode::Tle => JudgeCode::Tle, + BackendCode::Mle => JudgeCode::Mle, + BackendCode::Re => JudgeCode::Re, + BackendCode::Ce => JudgeCode::Ce, + BackendCode::Ole => JudgeCode::Ole, + BackendCode::Na => JudgeCode::Na, + BackendCode::Rf => JudgeCode::Rf, } } } @@ -50,7 +52,11 @@ impl State { tx: &mut Sender>, res: JudgeResponse, ) { - match res.task.unwrap_or_default() { + if res.task.is_none() { + log::warn!("mismatch proto(judger)"); + return; + } + match res.task.unwrap() { judge_response::Task::Case(case) => { tx.send(Ok(SubmitStatus { task: Some(submit_status::Task::Case(case)), @@ -59,16 +65,20 @@ impl State { } judge_response::Task::Result(res) => { tx.send(Ok(SubmitStatus { + // TODO: rework the judger.proto task: Some(submit_status::Task::Result(backend::JudgeResult { - status: res.status() as i32, - max_time: Some(res.max_time), - max_mem: Some(res.max_mem), + info: Some(judge_result::Info::Committed(judge_result::Committed { + code: JudgeCode::from_i32(res.status).unwrap_or_default().into(), + accuracy: res.accuracy, + time: res.max_time, + memory: res.max_mem, + })), })), })) .ok(); self.time = cmp::max(self.time, res.max_time); self.mem = cmp::max(self.mem, res.max_mem); - if res.status() == JudgeState::Ac { + if res.status() == JudgeCode::Ac { self.pass += 1; } } diff --git a/backend/src/endpoint/mod.rs b/backend/src/endpoint/mod.rs index aca9b433..0ded3c7a 100644 --- a/backend/src/endpoint/mod.rs +++ b/backend/src/endpoint/mod.rs @@ -1,6 +1,7 @@ pub mod contest; mod education; pub mod problem; +pub mod submit; pub mod testcase; pub mod token; pub mod user; diff --git a/backend/src/endpoint/submit.rs b/backend/src/endpoint/submit.rs new file mode 100644 index 00000000..0f643111 --- /dev/null +++ b/backend/src/endpoint/submit.rs @@ -0,0 +1,127 @@ +use super::endpoints::*; +use super::tools::*; + +use super::util::stream::*; +use super::util::time::into_prost; +use crate::grpc::backend::submit_set_server::*; +use crate::grpc::backend::*; + +use entity::{submit::*, *}; + +impl Filter for Entity { + fn read_filter(query: S, auth: &Auth) -> Result { + Ok(query) + } + + fn write_filter(query: S, auth: &Auth) -> Result { + if let Some(perm) = auth.user_perm() { + if perm.can_manage_submit() || perm.can_root() { + return Ok(query); + } + } + Err(Error::Unauthenticated) + } +} + +impl From for SubmitId { + fn from(value: i32) -> Self { + SubmitId { id: value } + } +} + +impl From for i32 { + fn from(value: SubmitId) -> Self { + value.id + } +} + +impl From for SubmitInfo { + fn from(value: Model) -> Self { + // TODO: solve devation aand uncommitted submit! + let state: JudgeResult = match value.committed { + true => todo!(), + false => todo!(), + }; + SubmitInfo { + id: value.id.into(), + upload_time: into_prost(value.upload_at), + score: value.score, + state, + } + } +} + +#[async_trait] +impl SubmitSet for Arc { + async fn list( + &self, + req: Request, + ) -> Result, Status> { + let (auth, req) = self.parse_request(req).await?; + + let mut reverse = false; + let mut pager: Pager = match req.request.ok_or(Error::NotInPayload("request"))? { + list_request::Request::Create(create) => { + Pager::sort_search(create.sort_by(), create.reverse) + } + list_request::Request::Pager(old) => { + reverse = old.reverse; + as HasParentPager>::from_raw(old.session)? + } + }; + + let list = pager + .fetch(req.size, req.offset.unwrap_or_default(), reverse, &auth) + .await? + .into_iter() + .map(|x| x.into()) + .collect(); + + let next_session = pager.into_raw(); + + Ok(Response::new(ListSubmitResponse { list, next_session })) + } + + async fn list_by_problem( + &self, + req: Request, + ) -> Result, tonic::Status> { + todo!() + } + + async fn info(&self, req: Request) -> Result, Status> { + todo!() + } + + async fn create( + &self, + req: Request, + ) -> Result, Status> { + todo!() + } + + async fn remove( + &self, + req: Request, + ) -> std::result::Result, Status> { + todo!() + } + + #[doc = " Server streaming response type for the Follow method."] + type FollowStream = TonicStream; + + #[doc = " are not guarantee to yield status"] + async fn follow( + &self, + req: Request, + ) -> std::result::Result, Status> { + todo!() + } + + async fn rejudge( + &self, + req: Request, + ) -> std::result::Result, Status> { + todo!() + } +} diff --git a/backend/src/endpoint/user.rs b/backend/src/endpoint/user.rs index 82783b06..3f844bce 100644 --- a/backend/src/endpoint/user.rs +++ b/backend/src/endpoint/user.rs @@ -14,10 +14,10 @@ impl Filter for Entity { } fn write_filter(query: S, auth: &Auth) -> Result { - let (user_id,perm)=auth.ok_or_default()?; - if perm.can_root() || perm.can_manage_user() { - return Ok(query); - } + let (user_id, perm) = auth.ok_or_default()?; + if perm.can_root() || perm.can_manage_user() { + return Ok(query); + } Ok(query.filter(Column::Id.eq(user_id))) } } diff --git a/backend/src/endpoint/util/mod.rs b/backend/src/endpoint/util/mod.rs index 81922673..4935f047 100644 --- a/backend/src/endpoint/util/mod.rs +++ b/backend/src/endpoint/util/mod.rs @@ -5,4 +5,5 @@ pub mod filter; pub mod hash; pub mod macro_tool; pub mod pagination; +pub mod stream; pub mod time; diff --git a/backend/src/endpoint/util/pagination.rs b/backend/src/endpoint/util/pagination.rs index dd2e3db7..7ad06b48 100644 --- a/backend/src/endpoint/util/pagination.rs +++ b/backend/src/endpoint/util/pagination.rs @@ -517,3 +517,30 @@ impl PagerTrait for user::Entity { user::Entity::read_filter(select, auth) } } +#[tonic::async_trait] +impl PagerTrait for submit::Entity { + const TYPE_NUMBER: i32 = 539267; + + const COL_ID: Self::Column = submit::Column::Id; + + const COL_TEXT: &'static [Self::Column] = &[submit::Column::Id]; + + const COL_SELECT: &'static [Self::Column] = &[ + submit::Column::Committed, + submit::Column::Id, + submit::Column::Time, + submit::Column::Memory, + submit::Column::PassCase, + submit::Column::UploadAt, + ]; + + type ParentMarker = HasParent; + + fn get_id(model: &Self::Model) -> i32 { + model.id + } + + async fn query_filter(select: Select, auth: &Auth) -> Result, Error> { + submit::Entity::read_filter(select, auth) + } +} diff --git a/judger/src/grpc/server.rs b/judger/src/grpc/server.rs index 3fbc4641..07517a78 100644 --- a/judger/src/grpc/server.rs +++ b/judger/src/grpc/server.rs @@ -18,6 +18,11 @@ use super::proto::prelude::{judger_server::Judger, *}; pub type UUID = String; +fn accuracy() -> u64 { + let config = CONFIG.get().unwrap(); + (1000 * 1000 / config.kernel.kernel_hz) as u64 +} + macro_rules! report { ($result:expr,$tx:expr) => { match $result { @@ -58,6 +63,7 @@ macro_rules! report { status: res as i32, max_time: 0, max_mem: 0, + accuracy: accuracy(), })), })) .await @@ -138,6 +144,7 @@ impl Judger for Server { status: x as i32, max_time: result.time().total_us, max_mem: result.mem().peak, + accuracy: accuracy(), })), })) .await @@ -153,6 +160,7 @@ impl Judger for Server { } as i32, max_time: result.time().total_us, max_mem: result.mem().peak, + accuracy: accuracy(), })), })) .await @@ -176,9 +184,9 @@ impl Judger for Server { let modules = self.factory.list_module(); Ok(Response::new(JudgeInfo { - langs: Some(Langs { list: modules }), + langs: Langs { list: modules }, memory: config.platform.available_memory, - accuracy: (1000 * 1000 / config.kernel.kernel_hz) as u64, + accuracy: accuracy(), cpu_factor: config.platform.cpu_time_multiplier as f32, })) } diff --git a/proto/backend.proto b/proto/backend.proto index cc4d24ed..8790aa84 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -90,7 +90,7 @@ message Testcases { repeated TestcaseId list = 1; } // Submit // I don't want to write docs -enum JudgeResultState { +enum StateCode{ AC = 0; NA = 1; WA = 2; @@ -101,10 +101,23 @@ enum JudgeResultState { MLE = 7; OLE = 8; } +message JudgeResult { + message Uncommitted{ -message SubmitId { required int32 id = 1; } + } + message Committed{ + required StateCode code=1; + required uint64 accuracy=2; + required uint64 time=3; + required uint64 memory=4; + } + oneof info{ + Uncommitted uncommitted=1; + Committed committed =2; + }; +} -message Submits { repeated SubmitInfo list = 1; } +message SubmitId { required int32 id = 1; } message SubmitUpload { required SubmitId id = 1; @@ -114,19 +127,13 @@ message SubmitUpload { message SubmitInfo { required SubmitId id = 1; required google.protobuf.Timestamp upload_time = 3; - required uint64 time = 4; - required uint64 peak_mem = 5; required uint32 score = 6; required JudgeResult state = 7; } -message JudgeResult { - // assertion code - required JudgeResultState status = 1; - // the time in nanosecond - optional uint64 max_time = 2; - // the peak memory usage - optional uint64 max_mem = 3; +message ListSubmitResponse { + repeated SubmitInfo list = 1; + required string next_session = 2; } message SubmitStatus { @@ -154,13 +161,14 @@ message RejudgeRequest { } service SubmitSet { - rpc List(ListRequest) returns (Submits); - rpc SearchByText(TextSearchRequest) returns (Submits); + rpc List(ListRequest) returns (ListSubmitResponse); rpc Info(SubmitId) returns (SubmitInfo); rpc Create(CreateSubmitRequest) returns (SubmitId); rpc Remove(SubmitId) returns (google.protobuf.Empty); + rpc ListByProblem(ListByRequest) returns (ListSubmitResponse); + // are not guarantee to yield status rpc Follow(SubmitId) returns (stream SubmitStatus); rpc Rejudge(RejudgeRequest) returns (google.protobuf.Empty); diff --git a/proto/judger.proto b/proto/judger.proto index 8d4e86f5..e0fb731d 100644 --- a/proto/judger.proto +++ b/proto/judger.proto @@ -1,25 +1,25 @@ -syntax = "proto3"; +syntax = "proto2"; package oj.judger; import "google/protobuf/empty.proto"; message JudgeRequest { - string lang_uid = 1; - bytes code = 2; + required string lang_uid = 1; + required bytes code = 2; // memory limit in byte - uint64 memory = 3; + required uint64 memory = 3; // time limit in nanosecond - uint64 time = 4; - JudgeMatchRule rule = 5; + required uint64 time = 4; + required JudgeMatchRule rule = 5; // len must > 0 repeated TestIO tests = 6; } message TestIO { - bytes input = 1; - bytes output = 2; -} + required bytes input = 1; + required bytes output = 2; +} message JudgeResponse { oneof task { @@ -31,11 +31,13 @@ message JudgeResponse { message JudgeResult { // assertion code - JudgeResultState status = 1; + required JudgeResultState status = 1; // the time in nanosecond - uint64 max_time = 2; + required uint64 max_time = 2; // the peak memory usage - uint64 max_mem = 3; + required uint64 max_mem = 3; + // max possible deviation in nanosecond + required uint64 accuracy = 4; } enum JudgeResultState { @@ -74,24 +76,24 @@ message Langs { repeated LangInfo list = 1; } // info of a language(extension) message LangInfo { // unique id - string lang_uid = 1; + required string lang_uid = 1; // human readable name - string lang_name = 2; + required string lang_name = 2; // human readable description - string info = 3; + required string info = 3; // file extension of the language - string lang_ext = 4; + required string lang_ext = 4; } message JudgeInfo { // max support memory in byte - uint64 memory = 1; + required uint64 memory = 1; // max possible deviation in nanosecond - uint64 accuracy = 2; + required uint64 accuracy = 2; // list of supported languages(extension) - Langs langs = 3; + required Langs langs = 3; // cpu factor, the total time limit is calculated by time * cpu_factor - float cpu_factor = 4; + required float cpu_factor = 4; } service Judger { diff --git a/readme.md b/readme.md index 4f9bfcb0..ba08d8a2 100644 --- a/readme.md +++ b/readme.md @@ -23,13 +23,19 @@ Performance-oriented contest management system for IOI like contest ## Quick Start -copy ``docker/simple/docker-compose.yml`` file to your server and run `docker-compose up -d` - > do not use `docker/docker-compose.yml` file, it is for development only +Copy ``docker/simple/docker-compose.yml`` file to your server and run `docker compose up -d`, then open `http://localhost:80` in your browser. + +login as `admin@admin` and start play arounds + ## Setup for development -install `cargo`, `just` and `docker`, then run ``just prepare``. +1. install following package: + +- From system package manager: `protobuf-devel`, `autoconf` ,`gettext`, `libtool`, `gcc`, `libcap-devel`, `systemd-devel`, `yajl-devel`, `libgcrypt-devel` ,`glibc-static`, `libseccomp-devel`, `python36`, `git` +- From rustup: `rustup`, `cargo`, `just` +- From their website: `docker`, `docker-compose` Then start reading documents in subfolder of your interest.