diff --git a/README.md b/README.md index ab503e5..08dc992 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,14 @@ 当前版本为 **0.0.1**:沙箱已有受控隔离、C/C++ 编译及 native 执行;平台已实现真实身份、 不可变题目、工件与幂等提交创建。事务 outbox、持久化 Coordinator、普通 Worker 和受控 -helper 工厂已接通 C/C++ 编译一次、多测试点评测;登录、题库、提交和个人成绩页面 +helper 工厂已接通 C17/C++20、固定 CPython 3.13 与 OpenJDK 21 的普通题评测;登录、题库、提交和个人成绩页面 使用真实接口。训练、比赛、社区等扩展页面仍为开发预览。 +题目工作台支持当前版本的“本题我的提交”、右侧详情与完整个人详情。 +性能展示读取受验证最终 AC 的 CPU 总和与内存峰值,只比较同版本、语言、精确工具链、 +预算与可信测量环境的最近有效记录。缺测、旧环境无法验证或其他记录不足五条时明确显示 +不可比较;不会填入示例成绩。口径、查询边界和权限见[提交模块说明](backend/src/kf_oj/modules/submissions/README.md)。 + ## 工程划分 | 目录 | 职责 | diff --git a/backend/src/kf_oj/modules/submissions/README.md b/backend/src/kf_oj/modules/submissions/README.md index aa7ba8e..62f6a94 100644 --- a/backend/src/kf_oj/modules/submissions/README.md +++ b/backend/src/kf_oj/modules/submissions/README.md @@ -23,3 +23,23 @@ submission、queued evaluation 和 outbox;同键同内容复用既有提交, Coordinator 通过正式 claim 创建短期领取权,Worker 不写业务数据库。 真实服务链与安全条件必须在独立 VM 验收;数据库 fixture 的租约或合成报告只能 验证控制规则、授权和投影,不能作为真实程序执行/停止证明。 + +`performance.py` 提供本人提交的只读匿名聚合。Coordinator 在受验证最终 AC 的同一事务 +写入不可变 `evaluation_performance`;平台只有 SELECT 权限。比较组绑定不可变版本、 +语言、精确镜像/工具链、编译与运行预算,以及运维核验后配置的测量环境纪元。 +没有原纪元的旧尝试不会通过当前配置被补造可信性,仍保留个人历史与正常成绩。 +单条受控回填与纪元变更规则见[协调器说明](../../../../../judge/crates/coordinator/README.md)。 + +历史可按 `problem_version_id` 与可选 wire `language` 过滤,始终带当前账号条件。 +`GET /api/v1/submissions/{submission_id}/performance` 每次先检查会话和所有权; +匿名拒绝、其他主体与不存在记录同为不可用,私有响应为 `no-store`。 +缓存命中仍查归属。每进程最多一个聚合查询,不排等待队列;SQL 期限两秒、请求总期限 +三秒、锁等待至多半秒。聚合 DTO 缓存三十秒且最多128项,新 AC 的索引水位改变即失效。 +缓存不保存逐条样本数组,不向浏览器发送其他账号的身份、源码或逐条用量。 + +每指标在同一个只读可重复读快照内,排除当前提交,取最近最多10,000条非缺测的其他 +有效 AC,响应带快照、最早样本及截断标志。少于五条时仅显示本次真实用量。 +CPU 为测试点运行 CPU 微秒之和,内存为测试点运行峰值字节的最大值,不包括编译。 +用量与桶边界以十进制字符串传输;零合法、缺测不补零。严格优于数按原整数的 `>` +计算,同值独立计数。响应至多24个连续桶,只有最后上界包含端点;浏览器仅格式化单位 +和绘制坐标,比例以服务端严格计数除以样本数,不按人数统计。 diff --git a/judge/crates/coordinator/src/performance.rs b/judge/crates/coordinator/src/performance.rs index b15671b..c62fee2 100644 --- a/judge/crates/coordinator/src/performance.rs +++ b/judge/crates/coordinator/src/performance.rs @@ -91,6 +91,36 @@ pub async fn record( mod tests { use super::*; + #[test] + fn grouping_separates_exact_image_version_and_execution_budgets() { + let bytes = include_bytes!( + "../../../../tests/fixtures/contracts-v2/judge-manifest.valid-python313.json" + ); + let value: Value = serde_json::from_slice(bytes).unwrap(); + let original: JobManifest = serde_json::from_value(value.clone()).unwrap(); + let epoch = "a".repeat(64); + let original_group = comparison_group(&original, &epoch).unwrap(); + for (pointer, replacement) in [ + ("/toolchain/image_digest", json!("b".repeat(64))), + ( + "/problem_version_id", + json!("33333333-3333-4333-8333-333333333333"), + ), + ("/run_limits/cpu_time_us", json!(500_001)), + ("/compile_limits/cpu_time_us", json!(5_000_001)), + ("/job_wall_time_us", json!(120_000_001)), + ] { + let mut changed = value.clone(); + *changed.pointer_mut(pointer).unwrap() = replacement; + let manifest: JobManifest = serde_json::from_value(changed).unwrap(); + assert_ne!( + original_group, + comparison_group(&manifest, &epoch).unwrap(), + "{pointer} must isolate the cohort" + ); + } + } + #[test] fn grouping_ignores_submission_identity_and_separates_epoch_and_language() { let bytes =