mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
judge: support time_limit_rate & memory_limit_rate per problem (#541)
Co-authored-by: panda <panda_dtdyy@outlook.com>
This commit is contained in:
1 parent
f01a8b6671
commit
560e7c627f
13 files changed
+38
-19
No files matched your search
@@ -4,6 +4,7 @@ import {
|
||||
changeErrorType, fs, normalizeSubtasks, readSubtasksFromFiles, yaml,
|
||||
} from '@hydrooj/utils';
|
||||
import readYamlCases, { convertIniConfig } from '@hydrooj/utils/lib/cases';
|
||||
import { LangConfig } from '@hydrooj/utils/lib/lang';
|
||||
import { ProblemConfigFile } from 'hydrooj';
|
||||
import { getConfig } from './config';
|
||||
import { FormatError, SystemError } from './error';
|
||||
@@ -40,6 +41,8 @@ interface Args {
|
||||
next: NextFunction;
|
||||
key: string;
|
||||
isSelfSubmission: boolean;
|
||||
lang: string;
|
||||
langConfig: LangConfig;
|
||||
}
|
||||
|
||||
export default async function readCases(folder: string, cfg: ProblemConfigFile = {}, args: Args): Promise<ParsedConfig> {
|
||||
@@ -65,6 +68,8 @@ export default async function readCases(folder: string, cfg: ProblemConfigFile =
|
||||
} catch (e) {
|
||||
throw changeErrorType(e, FormatError);
|
||||
}
|
||||
const timeRate = +(config.time_limit_rate?.[args.lang] || args.langConfig.time_limit_rate) || 1;
|
||||
const memoryRate = +(config.memory_limit_rate?.[args.lang] || args.langConfig.memory_limit_rate) || 1;
|
||||
const checkFile = ensureFile(folder);
|
||||
const result = await readYamlCases(config, checkFile)
|
||||
.catch((e) => { throw changeErrorType(e, FormatError); });
|
||||
@@ -78,7 +83,7 @@ export default async function readCases(folder: string, cfg: ProblemConfigFile =
|
||||
throw new SystemError('Cannot parse testdata.', [e.message, ...(e.params || [])]);
|
||||
}
|
||||
}
|
||||
result.subtasks = normalizeSubtasks(result.subtasks || [], checkFile, config.time, config.memory);
|
||||
result.subtasks = normalizeSubtasks(result.subtasks || [], checkFile, config.time, config.memory, false, timeRate, memoryRate);
|
||||
if (result.key && args.key !== result.key) throw new FormatError('Incorrect secret key');
|
||||
if (!result.key) isValidConfig(result);
|
||||
return result;
|
||||
|
||||
@@ -14,7 +14,6 @@ export default async function compile(
|
||||
): Promise<Execute> {
|
||||
const target = lang.target || 'foo';
|
||||
const execute = copyIn['execute.sh'] ? '/bin/bash execute.sh' : lang.execute;
|
||||
const time = lang.time_limit_rate || 1;
|
||||
if (lang.compile) {
|
||||
const {
|
||||
status, stdout, stderr, fileIds,
|
||||
@@ -33,14 +32,12 @@ export default async function compile(
|
||||
execute,
|
||||
copyIn: { ...copyIn, [target]: { fileId: fileIds[target] } },
|
||||
clean: () => del(fileIds[target]),
|
||||
time,
|
||||
};
|
||||
}
|
||||
return {
|
||||
execute,
|
||||
copyIn: { ...copyIn, [lang.code_file]: code },
|
||||
clean: () => Promise.resolve(null),
|
||||
time,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ export interface Execute {
|
||||
execute: string;
|
||||
clean: () => Promise<any>;
|
||||
copyIn: Record<string, CopyInFile>;
|
||||
time?: number;
|
||||
}
|
||||
|
||||
export type NextFunction = (body: Partial<JudgeResultBody>) => Promise<void> | void;
|
||||
|
||||
@@ -20,7 +20,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
stdin: { src: c.input },
|
||||
copyIn: ctx.execute.copyIn,
|
||||
filename: ctx.config.filename,
|
||||
time: c.time * ctx.execute.time,
|
||||
time: c.time,
|
||||
memory: c.memory,
|
||||
cacheStdoutAndStderr: true,
|
||||
},
|
||||
@@ -32,7 +32,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
let message: any = '';
|
||||
let score = 0;
|
||||
if (status === STATUS.STATUS_ACCEPTED) {
|
||||
if (time > c.time * ctx.execute.time) {
|
||||
if (time > c.time) {
|
||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
} else if (memory > c.memory * 1024) {
|
||||
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function judge(ctx: Context) {
|
||||
{
|
||||
stdin: { src: input },
|
||||
copyIn: { ...validator.copyIn, ...judgeExtraFiles },
|
||||
time: parseTimeMS(ctx.config.time || '1s') * execute.time,
|
||||
time: parseTimeMS(ctx.config.time || '1s'),
|
||||
memory: parseMemoryMB(ctx.config.memory || '256m'),
|
||||
},
|
||||
);
|
||||
@@ -46,7 +46,7 @@ export async function judge(ctx: Context) {
|
||||
{
|
||||
stdin: { src: input },
|
||||
copyIn: execute.copyIn,
|
||||
time: parseTimeMS(ctx.config.time || '1s') * execute.time,
|
||||
time: parseTimeMS(ctx.config.time || '1s'),
|
||||
memory: parseMemoryMB(ctx.config.memory || '256m'),
|
||||
cacheStdoutAndStderr: true,
|
||||
},
|
||||
@@ -55,7 +55,7 @@ export async function judge(ctx: Context) {
|
||||
let { status } = res;
|
||||
let message: any = '';
|
||||
if (status === STATUS.STATUS_ACCEPTED) {
|
||||
if (time > ctx.config.time * execute.time) {
|
||||
if (time > ctx.config.time) {
|
||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
} else if (memory > ctx.config.memory * 1024) {
|
||||
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
|
||||
@@ -17,13 +17,13 @@ function judgeCase(c: NormalizedCase) {
|
||||
{
|
||||
execute: ctx.executeUser.execute,
|
||||
copyIn: ctx.executeUser.copyIn,
|
||||
time: c.time * ctx.executeUser.time,
|
||||
time: c.time,
|
||||
memory: c.memory,
|
||||
},
|
||||
{
|
||||
execute: `${ctx.executeInteractor.execute} /w/in /w/tout /w/out`,
|
||||
copyIn: ctx.executeInteractor.copyIn,
|
||||
time: c.time * 2 * ctx.executeInteractor.time,
|
||||
time: c.time * 2,
|
||||
memory: c.memory * 2,
|
||||
copyOut: ['/w/tout?'],
|
||||
env: { ...ctx.env, HYDRO_TESTCASE: c.id.toString() },
|
||||
@@ -33,7 +33,7 @@ function judgeCase(c: NormalizedCase) {
|
||||
let status: number;
|
||||
let score = 0;
|
||||
let message: any = '';
|
||||
if (time > c.time * ctx.executeUser.time) {
|
||||
if (time > c.time) {
|
||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
} else if (memory > c.memory * 1024) {
|
||||
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
|
||||
@@ -52,14 +52,14 @@ export const judge = async (ctx: Context) => {
|
||||
stdin: { content: ctx.input },
|
||||
copyIn: ctx.execute.copyIn,
|
||||
// Allow 2x limits for better debugging
|
||||
time: parseTimeMS(ctx.config.time || '1s') * ctx.execute.time * 2,
|
||||
time: parseTimeMS(ctx.config.time || '1s') * 2,
|
||||
memory: parseMemoryMB(ctx.config.memory || '128m'),
|
||||
},
|
||||
);
|
||||
const { code, time, memory } = res;
|
||||
let { status } = res;
|
||||
const message: string[] = [];
|
||||
if (time > parseTimeMS(ctx.config.time || '1s') * ctx.execute.time) {
|
||||
if (time > parseTimeMS(ctx.config.time || '1s')) {
|
||||
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||
} else if (memory > parseMemoryMB(ctx.config.memory || '128m') * 1024) {
|
||||
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||
|
||||
@@ -117,6 +117,8 @@ export class JudgeTask {
|
||||
next: this.next,
|
||||
isSelfSubmission: this.meta.problemOwner === this.request.uid,
|
||||
key: md5(`${this.source}/${getConfig('secret')}`),
|
||||
lang: this.lang,
|
||||
langConfig: this.session.getLang(this.lang),
|
||||
},
|
||||
);
|
||||
this.stat.judge = new Date();
|
||||
|
||||
@@ -201,6 +201,8 @@ export interface ProblemConfigFile {
|
||||
subtasks?: SubtaskConfig[];
|
||||
langs?: string[];
|
||||
validator?: string;
|
||||
time_limit_rate?: Record<string, number>;
|
||||
memory_limit_rate?: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface ProblemConfig {
|
||||
|
||||
@@ -40,6 +40,14 @@ const problemConfigSchema: JSONSchema7 = {
|
||||
time: { type: 'string', pattern: '^([0-9]+(?:\\.[0-9]*)?)([mu]?)s?$' },
|
||||
memory: { type: 'string', pattern: '^([0-9]+(?:\\.[0-9]*)?)([kKmMgG])[bB]?$' },
|
||||
score: { type: 'integer', maximum: 100, minimum: 1 },
|
||||
rateConfig: {
|
||||
type: 'object',
|
||||
patternProperties: {
|
||||
'^.+$': {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
redirect: { type: 'string', pattern: '[0-9a-zA-Z_-]+\\/[0-9]+' },
|
||||
@@ -89,6 +97,8 @@ const problemConfigSchema: JSONSchema7 = {
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
time_limit_rate: { $ref: '#/definitions/rateConfig' },
|
||||
memory_limit_rate: { $ref: '#/definitions/rateConfig' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ const configKey = [
|
||||
'memory', 'filename', 'checker_type', 'checker', 'interactor',
|
||||
'validator', 'user_extra_files', 'judge_extra_files', 'detail', 'outputs',
|
||||
'redirect', 'cases', 'subtasks', 'langs', 'key',
|
||||
'time_limit_rate', 'memory_limit_rate',
|
||||
];
|
||||
|
||||
const subtasksKey = [
|
||||
|
||||
@@ -343,6 +343,7 @@ export interface NormalizedSubtask extends Required<ParsedSubtask> {
|
||||
export function normalizeSubtasks(
|
||||
subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string,
|
||||
time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false,
|
||||
timeRate = 1, memoryRate = 1,
|
||||
): NormalizedSubtask[] {
|
||||
subtasks.sort((a, b) => (a.id - b.id));
|
||||
const subtaskScore = getScore(
|
||||
@@ -362,14 +363,14 @@ export function normalizeSubtasks(
|
||||
if: [],
|
||||
...s,
|
||||
score,
|
||||
time: parseTimeMS(s.time || time, !ignoreParseError),
|
||||
memory: parseMemoryMB(s.memory || memory, !ignoreParseError),
|
||||
time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate,
|
||||
memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate,
|
||||
cases: s.cases.map((c, index) => ({
|
||||
id: index + 1,
|
||||
...c,
|
||||
score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score),
|
||||
time: parseTimeMS(c.time || s.time || time, !ignoreParseError),
|
||||
memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError),
|
||||
time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate,
|
||||
memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate,
|
||||
input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null',
|
||||
output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null',
|
||||
})) as NormalizedCase[],
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface LangConfig {
|
||||
highlight: string;
|
||||
monaco: string;
|
||||
time_limit_rate: number;
|
||||
memory_limit_rate: number;
|
||||
domain: string[];
|
||||
display: string;
|
||||
target?: string;
|
||||
@@ -38,6 +39,7 @@ export function parseLang(config: string): Record<string, LangConfig> {
|
||||
entry.highlight ||= key;
|
||||
entry.monaco ||= entry.highlight;
|
||||
entry.time_limit_rate ||= 1;
|
||||
entry.memory_limit_rate ||= 1;
|
||||
entry.code_file ||= `foo.${key}`;
|
||||
entry.execute ||= '/w/foo';
|
||||
entry.key = key;
|
||||
|
||||
Reference in new issue
Block a user