From f2170ff04795e71fb65188003f186fa21ccb90ce Mon Sep 17 00:00:00 2001 From: akagiyuu Date: Fri, 15 May 2026 20:43:11 +0700 Subject: [PATCH 1/4] feat: add aggregated metrics struct --- src/metrics.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/metrics.rs b/src/metrics.rs index ce4b7a0..20c2c33 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -3,7 +3,6 @@ use std::time::Duration; use byte_unit::Byte; #[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum Verdict { Accepted, WrongAnswer, @@ -15,7 +14,6 @@ pub enum Verdict { } #[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Metrics { pub verdict: Verdict, pub run_time: Duration, @@ -23,3 +21,11 @@ pub struct Metrics { pub stdout: Vec, pub stderr: Vec, } + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +pub struct AggregatedMetrics { + pub verdict: Verdict, + pub average_run_time: Duration, + pub average_memory_usage: Byte, +} From 4e77f0ac3079366295b82a390931aa791c47e8b5 Mon Sep 17 00:00:00 2001 From: akagiyuu Date: Fri, 15 May 2026 20:54:45 +0700 Subject: [PATCH 2/4] feat(judge): add batch run --- src/judge.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/judge.rs b/src/judge.rs index 904ce54..fc866da 100644 --- a/src/judge.rs +++ b/src/judge.rs @@ -1,6 +1,7 @@ use std::{env, io, marker::PhantomData, path::PathBuf, process::Stdio, time::Duration}; use bon::bon; +use byte_unit::Byte; use state_shift::{impl_state, type_state}; use tokio::{ fs, @@ -8,7 +9,7 @@ use tokio::{ }; use uuid::Uuid; -use crate::{Language, Metrics, Resource, Sandbox, Verdict}; +use crate::{AggregatedMetrics, Language, Metrics, Resource, Sandbox, Verdict}; const MAIN: &str = "main"; const CHECKER: &str = "checker"; @@ -203,4 +204,37 @@ impl Judge { memory_usage, }) } + + #[require(Compiled)] + pub async fn batch_run( + &self, + inputs: impl Iterator, + ) -> io::Result { + let mut verdict = Verdict::Accepted; + let mut total_run_time = Duration::ZERO; + let mut total_memory_usage = Byte::default(); + let mut count = 0; + + // running sequentially to enable early exit, saving resources + for input in inputs { + let metrics = self.run(input).await?; + total_run_time += metrics.run_time; + total_memory_usage = total_memory_usage + .add(metrics.memory_usage) + .expect("memory usage should not overflow u32"); + count += 1; + if metrics.verdict != Verdict::Accepted { + verdict = metrics.verdict; + break; + } + } + + Ok(AggregatedMetrics { + verdict, + average_run_time: total_run_time / count, + average_memory_usage: total_memory_usage + .divide(count as usize) + .expect("count must be greater than 0"), + }) + } } From cb9e4010909a38fcd20866392aa2e5b808060d62 Mon Sep 17 00:00:00 2001 From: akagiyuu Date: Fri, 15 May 2026 21:00:46 +0700 Subject: [PATCH 3/4] feat(judge): add streammed batch run --- Cargo.lock | 1 + Cargo.toml | 1 + src/judge.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 59988f4..c51c2e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,6 +358,7 @@ dependencies = [ "bon", "byte-unit", "cgroups-rs", + "futures-lite", "rand 0.10.1", "rstest", "serde", diff --git a/Cargo.toml b/Cargo.toml index 889bec3..272ae11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ A code runner library for online judge system bon = "3.8.1" byte-unit = "5.2.0" cgroups-rs = "0.5.0" +futures-lite = "2.6.1" serde = { version = "1.0.228", features = ["derive"], optional = true } state-shift = "2.1.1" tokio = { version = "1.48.0", features = ["full"] } diff --git a/src/judge.rs b/src/judge.rs index fc866da..59e572a 100644 --- a/src/judge.rs +++ b/src/judge.rs @@ -2,6 +2,7 @@ use std::{env, io, marker::PhantomData, path::PathBuf, process::Stdio, time::Dur use bon::bon; use byte_unit::Byte; +use futures_lite::{Stream, StreamExt}; use state_shift::{impl_state, type_state}; use tokio::{ fs, @@ -237,4 +238,37 @@ impl Judge { .expect("count must be greater than 0"), }) } + + #[require(Compiled)] + pub async fn streamed_batch_run( + &self, + mut inputs: impl Stream + std::marker::Unpin, + ) -> io::Result { + let mut verdict = Verdict::Accepted; + let mut total_run_time = Duration::ZERO; + let mut total_memory_usage = Byte::default(); + let mut count = 0; + + // running sequentially to enable early exit, saving resources + while let Some(input) = inputs.next().await { + let metrics = self.run(input).await?; + total_run_time += metrics.run_time; + total_memory_usage = total_memory_usage + .add(metrics.memory_usage) + .expect("memory usage should not overflow u32"); + count += 1; + if metrics.verdict != Verdict::Accepted { + verdict = metrics.verdict; + break; + } + } + + Ok(AggregatedMetrics { + verdict, + average_run_time: total_run_time / count, + average_memory_usage: total_memory_usage + .divide(count as usize) + .expect("count must be greater than 0"), + }) + } } From 7727387884878358a3e5c1e4fffe167e7f8d9c62 Mon Sep 17 00:00:00 2001 From: akagiyuu Date: Fri, 15 May 2026 21:07:46 +0700 Subject: [PATCH 4/4] fix: add serde::Serialize derive for Verdict --- src/metrics.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/metrics.rs b/src/metrics.rs index 20c2c33..3689706 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -3,6 +3,7 @@ use std::time::Duration; use byte_unit::Byte; #[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub enum Verdict { Accepted, WrongAnswer,