From b6edc2c571e50bb668e932c3ee9ff5b3c45bc363 Mon Sep 17 00:00:00 2001 From: Boming Zhang Date: Tue, 18 Mar 2025 18:36:41 -0400 Subject: [PATCH] chore(internal/stage): organize models to separated files --- internal/executor/dummy/executor.go | 4 +- internal/stage/cmd.go | 47 ++++ internal/stage/executor.go | 90 ++++++++ .../stage/{file_error.go => fileerror.go} | 0 internal/stage/model.go | 205 ------------------ internal/stage/parser.go | 5 + internal/stage/stage.go | 44 ++++ internal/stage/{run_status.go => status.go} | 4 +- 8 files changed, 188 insertions(+), 211 deletions(-) create mode 100644 internal/stage/cmd.go rename internal/stage/{file_error.go => fileerror.go} (100%) delete mode 100644 internal/stage/model.go create mode 100644 internal/stage/stage.go rename internal/stage/{run_status.go => status.go} (99%) diff --git a/internal/executor/dummy/executor.go b/internal/executor/dummy/executor.go index ca4632b..e8aa3a4 100644 --- a/internal/executor/dummy/executor.go +++ b/internal/executor/dummy/executor.go @@ -1,8 +1,6 @@ package dummy -import ( - "github.com/joint-online-judge/JOJ3/internal/stage" -) +import "github.com/joint-online-judge/JOJ3/internal/stage" func (e *Dummy) Run(cmds []stage.Cmd) ([]stage.ExecutorResult, error) { var res []stage.ExecutorResult diff --git a/internal/stage/cmd.go b/internal/stage/cmd.go new file mode 100644 index 0000000..c0568e2 --- /dev/null +++ b/internal/stage/cmd.go @@ -0,0 +1,47 @@ +package stage + +// copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge/model/model.go +// CmdFile defines file from multiple source including local / memory / cached or pipe collector +type CmdFile struct { + Src *string `json:"src"` + Content *string `json:"content"` + FileID *string `json:"fileId"` + Name *string `json:"name"` + Max *int64 `json:"max"` + Symlink *string `json:"symlink"` + StreamIn bool `json:"streamIn"` + StreamOut bool `json:"streamOut"` + Pipe bool `json:"pipe"` +} + +// Cmd defines command and limits to start a program using in envexec +type Cmd struct { + Args []string `json:"args"` + Env []string `json:"env,omitempty"` + Stdin *CmdFile `json:"stdin,omitempty"` + Stdout *CmdFile `json:"stdout,omitempty"` + Stderr *CmdFile `json:"stderr,omitempty"` + + 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"` // 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"` + CopyInDir string `json:"copyInDir"` + + CopyOut []string `json:"copyOut"` + CopyOutCached []string `json:"copyOutCached"` + CopyOutMax uint64 `json:"copyOutMax"` + CopyOutDir string `json:"copyOutDir"` + + TTY bool `json:"tty,omitempty"` + StrictMemoryLimit bool `json:"strictMemoryLimit"` + DataSegmentLimit bool `json:"dataSegmentLimit"` + AddressSpaceLimit bool `json:"addressSpaceLimit"` +} diff --git a/internal/stage/executor.go b/internal/stage/executor.go index 2208d39..a88fc88 100644 --- a/internal/stage/executor.go +++ b/internal/stage/executor.go @@ -1,5 +1,11 @@ package stage +import ( + "encoding/json" + "fmt" + "strconv" +) + var executorMap = map[string]Executor{} type Executor interface { @@ -10,3 +16,87 @@ type Executor interface { func RegisterExecutor(name string, executor Executor) { executorMap[name] = executor } + +// ExecutorResult defines single command result +type ExecutorResult struct { + Status Status `json:"status"` + ExitStatus int `json:"exitStatus"` + Error string `json:"error,omitempty"` + 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"` + + Buffs map[string][]byte `json:"-"` +} + +type ExecutorResultSummary struct { + Status Status `json:"status"` + ExitStatus int `json:"exitStatus"` + Error string `json:"error,omitempty"` + 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"` +} + +func (r ExecutorResult) String() string { + d := ExecutorResultSummary{ + Status: r.Status, + ExitStatus: r.ExitStatus, + Error: r.Error, + Time: r.Time, + Memory: r.Memory, + RunTime: r.RunTime, + Files: make(map[string]string), + FileIDs: r.FileIDs, + FileError: r.FileError, + } + for k, v := range r.Files { + d.Files[k] = "len:" + strconv.Itoa(len(v)) + } + return fmt.Sprintf("%+v", d) +} + +func (r ExecutorResult) MarshalJSON() ([]byte, error) { + d := ExecutorResultSummary{ + Status: r.Status, + ExitStatus: r.ExitStatus, + Error: r.Error, + Time: r.Time, + Memory: r.Memory, + RunTime: r.RunTime, + Files: make(map[string]string), + FileIDs: r.FileIDs, + FileError: r.FileError, + } + for k, v := range r.Files { + d.Files[k] = "len:" + strconv.Itoa(len(v)) + } + return json.Marshal(d) +} + +func SummarizeExecutorResults(results []ExecutorResult) ExecutorResultSummary { + var summary ExecutorResultSummary + summary.Status = StatusAccepted + for _, result := range results { + if result.Status != StatusAccepted && + summary.Status == StatusAccepted { + summary.Status = result.Status + } + if result.ExitStatus != 0 && summary.ExitStatus == 0 { + summary.ExitStatus = result.ExitStatus + } + if result.Error != "" && summary.Error == "" { + summary.Error = result.Error + } + summary.Time += result.Time + summary.Memory += result.Memory + summary.RunTime += result.RunTime + } + return summary +} diff --git a/internal/stage/file_error.go b/internal/stage/fileerror.go similarity index 100% rename from internal/stage/file_error.go rename to internal/stage/fileerror.go diff --git a/internal/stage/model.go b/internal/stage/model.go deleted file mode 100644 index c8bf746..0000000 --- a/internal/stage/model.go +++ /dev/null @@ -1,205 +0,0 @@ -package stage - -import ( - "encoding/json" - "fmt" - "strconv" -) - -// copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge/model/model.go -// CmdFile defines file from multiple source including local / memory / cached or pipe collector -type CmdFile struct { - Src *string `json:"src"` - Content *string `json:"content"` - FileID *string `json:"fileId"` - Name *string `json:"name"` - Max *int64 `json:"max"` - Symlink *string `json:"symlink"` - StreamIn bool `json:"streamIn"` - StreamOut bool `json:"streamOut"` - Pipe bool `json:"pipe"` -} - -// Cmd defines command and limits to start a program using in envexec -type Cmd struct { - Args []string `json:"args"` - Env []string `json:"env,omitempty"` - Stdin *CmdFile `json:"stdin,omitempty"` - Stdout *CmdFile `json:"stdout,omitempty"` - Stderr *CmdFile `json:"stderr,omitempty"` - - 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"` // 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"` - CopyInDir string `json:"copyInDir"` - - CopyOut []string `json:"copyOut"` - CopyOutCached []string `json:"copyOutCached"` - CopyOutMax uint64 `json:"copyOutMax"` - CopyOutDir string `json:"copyOutDir"` - - TTY bool `json:"tty,omitempty"` - StrictMemoryLimit bool `json:"strictMemoryLimit"` - DataSegmentLimit bool `json:"dataSegmentLimit"` - AddressSpaceLimit bool `json:"addressSpaceLimit"` -} - -// PipeIndex defines indexing for a pipe fd -type PipeIndex struct { - Index int `json:"index"` - Fd int `json:"fd"` -} - -// PipeMap defines in / out pipe for multiple program -type PipeMap struct { - In PipeIndex `json:"in"` - Out PipeIndex `json:"out"` - Name string `json:"name"` - Max int64 `json:"max"` - Proxy bool `json:"proxy"` -} - -// Request defines single worker request -type Request struct { - RequestID string `json:"requestId"` - Cmd []Cmd `json:"cmd"` - PipeMapping []PipeMap `json:"pipeMapping"` -} - -// ExecutorResult defines single command result -type ExecutorResult struct { - Status Status `json:"status"` - ExitStatus int `json:"exitStatus"` - Error string `json:"error,omitempty"` - 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"` - - Buffs map[string][]byte `json:"-"` -} - -type ExecutorResultSummary struct { - Status Status `json:"status"` - ExitStatus int `json:"exitStatus"` - Error string `json:"error,omitempty"` - 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"` -} - -func (r ExecutorResult) String() string { - d := ExecutorResultSummary{ - Status: r.Status, - ExitStatus: r.ExitStatus, - Error: r.Error, - Time: r.Time, - Memory: r.Memory, - RunTime: r.RunTime, - Files: make(map[string]string), - FileIDs: r.FileIDs, - FileError: r.FileError, - } - for k, v := range r.Files { - d.Files[k] = "len:" + strconv.Itoa(len(v)) - } - return fmt.Sprintf("%+v", d) -} - -func (r ExecutorResult) MarshalJSON() ([]byte, error) { - d := ExecutorResultSummary{ - Status: r.Status, - ExitStatus: r.ExitStatus, - Error: r.Error, - Time: r.Time, - Memory: r.Memory, - RunTime: r.RunTime, - Files: make(map[string]string), - FileIDs: r.FileIDs, - FileError: r.FileError, - } - for k, v := range r.Files { - d.Files[k] = "len:" + strconv.Itoa(len(v)) - } - return json.Marshal(d) -} - -func SummarizeExecutorResults(results []ExecutorResult) ExecutorResultSummary { - var summary ExecutorResultSummary - summary.Status = StatusAccepted - for _, result := range results { - if result.Status != StatusAccepted && - summary.Status == StatusAccepted { - summary.Status = result.Status - } - if result.ExitStatus != 0 && summary.ExitStatus == 0 { - summary.ExitStatus = result.ExitStatus - } - if result.Error != "" && summary.Error == "" { - summary.Error = result.Error - } - summary.Time += result.Time - summary.Memory += result.Memory - summary.RunTime += result.RunTime - } - return summary -} - -type StageExecutor struct { - Name string - Cmds []Cmd -} -type StageParser struct { - Name string - Conf any -} - -type Stage struct { - Name string - Executor StageExecutor - Parsers []StageParser -} - -type ParserResult struct { - Score int `json:"score"` - Comment string `json:"comment"` -} - -type NonNullSlice[T any] []T - -func (s NonNullSlice[T]) MarshalJSON() ([]byte, error) { - if len(s) == 0 { - return []byte("[]"), nil - } - return json.Marshal([]T(s)) -} - -type StageResult struct { - Name string `json:"name"` - Results NonNullSlice[ParserResult] `json:"results"` - ForceQuit bool `json:"force_quit"` // underscore as it will dump to file -} - -type CaseDetail struct { - Index int `json:"index"` - ExecutorResult ExecutorResult `json:"executorResult"` - ParserScores map[string]int `json:"parserScores"` -} - -type StageDetail struct { - Name string `json:"name"` - CaseDetails []CaseDetail `json:"caseDetails"` -} diff --git a/internal/stage/parser.go b/internal/stage/parser.go index a4f5548..4a4eeb7 100644 --- a/internal/stage/parser.go +++ b/internal/stage/parser.go @@ -9,3 +9,8 @@ type Parser interface { func RegisterParser(name string, parser Parser) { parserMap[name] = parser } + +type ParserResult struct { + Score int `json:"score"` + Comment string `json:"comment"` +} diff --git a/internal/stage/stage.go b/internal/stage/stage.go new file mode 100644 index 0000000..70fe177 --- /dev/null +++ b/internal/stage/stage.go @@ -0,0 +1,44 @@ +package stage + +import "encoding/json" + +type StageExecutor struct { + Name string + Cmds []Cmd +} +type StageParser struct { + Name string + Conf any +} + +type Stage struct { + Name string + Executor StageExecutor + Parsers []StageParser +} + +type NonNullSlice[T any] []T + +func (s NonNullSlice[T]) MarshalJSON() ([]byte, error) { + if len(s) == 0 { + return []byte("[]"), nil + } + return json.Marshal([]T(s)) +} + +type StageResult struct { + Name string `json:"name"` + Results NonNullSlice[ParserResult] `json:"results"` + ForceQuit bool `json:"force_quit"` // underscore as it will dump to file +} + +type CaseDetail struct { + Index int `json:"index"` + ExecutorResult ExecutorResult `json:"executorResult"` + ParserScores map[string]int `json:"parserScores"` +} + +type StageDetail struct { + Name string `json:"name"` + CaseDetails []CaseDetail `json:"caseDetails"` +} diff --git a/internal/stage/run_status.go b/internal/stage/status.go similarity index 99% rename from internal/stage/run_status.go rename to internal/stage/status.go index b22eb07..303a60d 100644 --- a/internal/stage/run_status.go +++ b/internal/stage/status.go @@ -1,8 +1,6 @@ package stage -import ( - "fmt" -) +import "fmt" // copied from https://github.com/criyle/go-judge/blob/master/envexec/run_status.go // Status defines run task Status return status