Compare commits

...

2 Commits

Author SHA1 Message Date
45490f3d1e
feat(parser/tierscore): new tiered scoring parser
All checks were successful
submodules sync / sync (push) Successful in 49s
build / build (push) Successful in 1m48s
build / trigger-build-image (push) Successful in 9s
2025-02-17 08:20:35 -05:00
dd4e375dd4
docs(internal/stage): units in struct fields 2025-02-17 07:07:29 -05:00
4 changed files with 140 additions and 13 deletions

View File

@ -13,6 +13,7 @@ import (
_ "github.com/joint-online-judge/JOJ3/internal/parser/resultdetail"
_ "github.com/joint-online-judge/JOJ3/internal/parser/resultstatus"
_ "github.com/joint-online-judge/JOJ3/internal/parser/sample"
_ "github.com/joint-online-judge/JOJ3/internal/parser/tierscore"
)
// this file does nothing but imports to ensure all the init() functions

View File

@ -0,0 +1,24 @@
// Package tierscore provides a parser for tiered scoring based on
// time and memory constraints. Leave the field empty or 0 to disable.
package tierscore
import "github.com/joint-online-judge/JOJ3/internal/stage"
var name = "tierscore"
type Tier struct {
TimeLessThan uint64 // ns
MemoryLessThan uint64 // bytes
Score int
}
type Conf struct {
Tiers []Tier
}
type TierScore struct{}
func init() {
stage.RegisterParser(name, &TierScore{})
}

View File

@ -0,0 +1,102 @@
package tierscore
import (
"fmt"
"strings"
"github.com/joint-online-judge/JOJ3/internal/stage"
)
func (*TierScore) Run(results []stage.ExecutorResult, confAny any) (
[]stage.ParserResult, bool, error,
) {
conf, err := stage.DecodeConf[Conf](confAny)
if err != nil {
return nil, true, err
}
var res []stage.ParserResult
forceQuit := false
for _, result := range results {
totalScore := 0
var commentBuilder strings.Builder
for i, tier := range conf.Tiers {
conditionsMet := true
var conditionDesc []string
if tier.TimeLessThan > 0 {
if result.Time < tier.TimeLessThan {
conditionDesc = append(
conditionDesc,
fmt.Sprintf(
"Time < `%d ms`",
tier.TimeLessThan/1e6,
),
)
} else {
conditionsMet = false
}
}
if tier.MemoryLessThan > 0 {
if result.Memory < tier.MemoryLessThan {
conditionDesc = append(
conditionDesc,
fmt.Sprintf(
"Memory < `%.2f MiB`",
float64(tier.MemoryLessThan)/(1024*1024),
),
)
} else {
conditionsMet = false
}
}
if conditionsMet {
totalScore += tier.Score
fmt.Fprintf(
&commentBuilder,
"Tier %d: +%d (meets %s)\n",
i,
tier.Score,
strings.Join(conditionDesc, " and "),
)
} else {
var required []string
if tier.TimeLessThan > 0 {
required = append(
required,
fmt.Sprintf(
"Time < `%d ms`",
tier.TimeLessThan/1e6,
),
)
}
if tier.MemoryLessThan > 0 {
required = append(
required,
fmt.Sprintf(
"Memory < `%.2f MiB`",
float64(tier.MemoryLessThan)/(1024*1024),
),
)
}
fmt.Fprintf(
&commentBuilder,
"Tier %d: +0 (requires %s)\n",
i+1,
strings.Join(required, " and "),
)
}
}
fmt.Fprintf(&commentBuilder, "Final score: %d", totalScore)
res = append(res, stage.ParserResult{
Score: totalScore,
Comment: commentBuilder.String(),
})
}
return res, forceQuit, nil
}

View File

@ -36,14 +36,14 @@ type Cmd struct {
Stdout *CmdFile `json:"stdout,omitempty"`
Stderr *CmdFile `json:"stderr,omitempty"`
CPULimit uint64 `json:"cpuLimit"`
RealCPULimit uint64 `json:"realCpuLimit"`
ClockLimit uint64 `json:"clockLimit"`
MemoryLimit uint64 `json:"memoryLimit"`
StackLimit uint64 `json:"stackLimit"`
CPULimit uint64 `json:"cpuLimit"` // ns
RealCPULimit uint64 `json:"realCpuLimit"` // deprecated: use clock limit instead (still working)
ClockLimit uint64 `json:"clockLimit"` // ns
MemoryLimit uint64 `json:"memoryLimit"` // byte
StackLimit uint64 `json:"stackLimit"` // byte
ProcLimit uint64 `json:"procLimit"`
CPURateLimit uint64 `json:"cpuRateLimit"`
CPUSetLimit string `json:"cpuSetLimit"`
CPURateLimit uint64 `json:"cpuRateLimit"` // limit cpu usage (1000 equals 1 cpu)
CPUSetLimit string `json:"cpuSetLimit"` // set the cpuSet for cgroup
CopyIn map[string]CmdFile `json:"copyIn"`
CopyInCached map[string]string `json:"copyInCached"`
@ -111,9 +111,9 @@ type ExecutorResult struct {
Status Status `json:"status"`
ExitStatus int `json:"exitStatus"`
Error string `json:"error,omitempty"`
Time uint64 `json:"time"`
Memory uint64 `json:"memory"`
RunTime uint64 `json:"runTime"`
Time uint64 `json:"time"` // ns (cgroup recorded time)
Memory uint64 `json:"memory"` // byte
RunTime uint64 `json:"runTime"` // ns (wall clock time)
Files map[string]string `json:"files,omitempty"`
FileIDs map[string]string `json:"fileIds,omitempty"`
FileError []FileError `json:"fileError,omitempty"`
@ -125,9 +125,9 @@ type ExecutorResultSummary struct {
Status Status `json:"status"`
ExitStatus int `json:"exitStatus"`
Error string `json:"error,omitempty"`
Time uint64 `json:"time"`
Memory uint64 `json:"memory"`
RunTime uint64 `json:"runTime"`
Time uint64 `json:"time"` // ns (cgroup recorded time)
Memory uint64 `json:"memory"` // byte
RunTime uint64 `json:"runTime"` // ns (wall clock time)
Files map[string]string `json:"files,omitempty"`
FileIDs map[string]string `json:"fileIds,omitempty"`
FileError []FileError `json:"fileError,omitempty"`