Skip to content

Commit 907de9c

Browse files
committed
judge: add tracing detail for judge case and fetch file
1 parent 0002801 commit 907de9c

6 files changed

Lines changed: 20 additions & 10 deletions

File tree

packages/hydrojudge/src/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const defaultEnv = `\
1010
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
1111
HOME=/w
1212
# modify to your python version installed
13-
PYTHONPATH=/lib/python3.12/site-packages
13+
PYTHONPATH=/lib/python3.13/site-packages
1414
`;
1515

1616
export const JudgeSettings = Schema.object({
@@ -28,7 +28,7 @@ export const JudgeSettings = Schema.object({
2828
singleTaskParallelism: Schema.number().default(2).min(1).step(1),
2929
rerun: Schema.number().description('Re-Run testcase if time-limit-exceeded (max per submission)').default(0).min(0).step(1),
3030
rate: Schema.number().default(1),
31-
env: Schema.string().default(defaultEnv),
31+
env: Schema.string().default(defaultEnv).role('textarea'),
3232
host: Schema.any(),
3333
secret: Schema.string().description('Judge Token Secret').default(randomstring(32)),
3434
disable: Schema.boolean().description('Disable builtin judge').default(false),

packages/hydrojudge/src/flow.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ function judgeSubtask(subtask: NormalizedSubtask, sid: string, judgeCase: Task['
4545
time: 0,
4646
memory: 0,
4747
message: '',
48-
} : await runner(ctx, ctxSubtask, runner);
48+
} : await (async () => {
49+
using span = ctx.startChildSpan('judge.case', { id: subtask.cases[cid].id, subtaskId: subtask.id });
50+
const r = await runner(ctx, ctxSubtask, runner);
51+
span.setAttributes({ status: r?.status, time: r?.time, memory: r?.memory });
52+
return r;
53+
})();
4954
if (res?.status !== STATUS.STATUS_CANCELED) {
5055
ctxSubtask.score = Score[ctxSubtask.subtask.type](ctxSubtask.score, res.score);
5156
ctxSubtask.status = Math.max(ctxSubtask.status, res.status);

packages/hydrojudge/src/hosts/hydro.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'path';
2+
import { SpanStatusCode } from '@opentelemetry/api';
23
import PQueue from 'p-queue';
34
import superagent from 'superagent';
45
import WebSocket from 'ws';
@@ -49,7 +50,7 @@ export default class Hydro implements Session {
4950
setInterval(() => { this.get(''); }, 30000000); // Cookie refresh only
5051
}
5152

52-
async fetchFile<T extends string | null>(namespace: T, files: Record<string, string>): Promise<T extends null ? string : null> {
53+
async fetchFile<T extends string | null>(namespace: T, files: Record<string, string>, ctx: JudgeTask): Promise<T extends null ? string : null> {
5354
if (!namespace) { // record-related resource (code)
5455
const name = Object.keys(files)[0].split('#')[0];
5556
const res = await this.post('judge/files', { id: name });
@@ -71,9 +72,11 @@ export default class Hydro implements Session {
7172
});
7273
for (const name in res.body.links) {
7374
queue.add(async () => {
75+
using span = ctx.startChildSpan('judge.fetchFile', { name });
7476
if (name.includes('/')) await fs.ensureDir(path.dirname(files[name]));
7577
const w = fs.createWriteStream(files[name]);
7678
await pipeRequest(this.get(res.body.links[name]), w, 60000, name);
79+
span.setStatus({ code: SpanStatusCode.OK });
7780
});
7881
}
7982
await queue.onIdle();

packages/hydrojudge/src/interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export { JudgeRequest } from '@hydrooj/common';
2727
export interface Session {
2828
getLang: (name: string, doThrow?: boolean) => LangConfig;
2929
getReporter: (task: JudgeTask) => { next: NextFunction, end: NextFunction };
30-
fetchFile: <T extends null | string>(namespace: T, files: Record<string, string>) => Promise<T extends null ? string : null>;
30+
fetchFile: <T extends null | string>(namespace: T, files: Record<string, string>, ctx: JudgeTask) => Promise<T extends null ? string : null>;
3131
postFile: (target: string, filename: string, file: string) => Promise<void>;
3232
config: { detail: DetailType, host?: string, trusted?: boolean };
3333
}

packages/hydrojudge/src/judge/hack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export async function judge(ctx: Context) {
1212
ctx.compileLocalFile('checker', ctx.config.checker, ctx.config.checker_type),
1313
ctx.compileLocalFile('validator', ctx.config.validator),
1414
(async () => {
15-
const f = await ctx.session.fetchFile(null, { [ctx.files.hack]: '' });
15+
const f = await ctx.session.fetchFile(null, { [ctx.files.hack]: '' }, ctx);
1616
ctx.pushClean(() => fs.unlink(f));
1717
return f;
1818
})(),

packages/hydrojudge/src/task.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class JudgeTask {
5151
logger.debug('%o', request);
5252
}
5353

54-
private startChildSpan(name: string, attributes?: Record<string, any>) {
54+
startChildSpan(name: string, attributes?: Record<string, any>) {
5555
const span = this.tracer.startSpan(name, { attributes }, this.mainContext);
5656
span[Symbol.dispose] = () => span.end();
5757
return span as typeof span & { [Symbol.dispose]: () => void };
@@ -142,13 +142,14 @@ export class JudgeTask {
142142
const allFilesToRemove = Object.keys(etags).filter((name) => !allFiles.has(name) && fs.existsSync(join(filePath, name)));
143143
await Promise.all(allFilesToRemove.map((name) => fs.remove(join(filePath, name))));
144144
if (filenames.length) {
145-
span.setAttribute('syncedFiles', filenames.length);
145+
span.setAttribute('files', filenames);
146146
logger.info(`Getting problem data: ${this.session?.config.host || 'local'}/${source}`);
147147
this.next({ message: 'Syncing testdata, please wait...' });
148+
this.mainContext = trace.setSpan(context.active(), span);
148149
await this.session.fetchFile(source, Object.fromEntries(
149150
files.filter((i) => filenames.includes(i.name))
150151
.map((i) => [i.name, join(filePath, i.name)]),
151-
));
152+
), this);
152153
this.compileCache = {};
153154
}
154155
if (allFilesToRemove.length || filenames.length) {
@@ -161,14 +162,15 @@ export class JudgeTask {
161162
logger.warn('CacheOpen Fail: %s %o %o', source, files, e);
162163
throw e;
163164
} finally {
165+
this.mainContext = trace.setSpan(context.active(), this.span);
164166
Lock.release(filePath);
165167
}
166168
}
167169

168170
async doSubmission() {
169171
this.folder = await this.cacheOpen(this.source, this.data);
170172
if (this.files?.code) {
171-
const target = await this.session.fetchFile(null, { [this.files.code]: '' });
173+
const target = await this.session.fetchFile(null, { [this.files.code]: '' }, this);
172174
this.code = { src: target };
173175
this.clean.push(() => fs.remove(target));
174176
}

0 commit comments

Comments
 (0)