woj-server/internal/service/runner/config.go
Paul Pan 6956fe4ee1
feat: capture runtime status from cgroups
pkg/pool: task is now available to return interface{} as result
pkg/pool: use atomic instead of mutex
service/runner: ContainerRun will return metrics
2024-02-15 12:53:57 +08:00

230 lines
4.9 KiB
Go

package runner
import (
"encoding/json"
"errors"
"fmt"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"os"
"path/filepath"
)
var (
AllowedLanguages = map[string]struct {
Interpreter string
}{
"c": {""},
"cpp": {""},
"rust": {""},
"python3": {"/usr/bin/python3"},
"pypy3": {"/usr/bin/pypy3"},
}
)
type ConfigRuntime struct {
TimeLimit int `json:"TimeLimit"` // in ms
MemoryLimit int `json:"MemoryLimit"` // in mb
NProcLimit int `json:"NProcLimit"`
}
var (
DefaultPrebuildRuntime = ConfigRuntime{
TimeLimit: 300000,
MemoryLimit: 256,
NProcLimit: 64,
}
DefaultCompileRuntime = ConfigRuntime{
TimeLimit: 60000,
MemoryLimit: 256,
NProcLimit: 64,
}
DefaultCheckRuntime = ConfigRuntime{
TimeLimit: 60000,
MemoryLimit: 128,
NProcLimit: 64,
}
)
func (c *ConfigRuntime) Validate() error {
if c.TimeLimit <= 0 {
return errors.New("time limit <= 0")
}
if c.MemoryLimit <= 0 {
return errors.New("memory limit <= 0")
}
if c.NProcLimit <= 0 {
return errors.New("nproc limit <= 0")
}
return nil
}
type ConfigJudge struct {
Type string `json:"Type"`
Script string `json:"Script"`
Cmp string `json:"Cmp"`
}
func (c *ConfigJudge) Validate(base string, lang string) error {
if c.Type != "custom" && c.Type != "default" {
return fmt.Errorf("language %s has invalid type %s", lang, c.Type)
}
if c.Type == "custom" {
if c.Script == "" {
return fmt.Errorf("language %s has empty script", lang)
}
file := filepath.Join(base, "judge", c.Script)
_, err := os.Stat(file)
if err != nil {
return fmt.Errorf("language %s has invalid script %s", lang, c.Script)
}
}
if c.Type == "default" {
if c.Cmp == "" {
return fmt.Errorf("language %s has empty cmp", lang)
}
}
return nil
}
type ConfigLanguage struct {
Lang string `json:"Lang"`
Judge ConfigJudge `json:"Judge,omitempty"`
Runtime struct {
Compile ConfigRuntime `json:"Compile"`
Run ConfigRuntime `json:"Run"`
Check ConfigRuntime `json:"Check,omitempty"`
} `json:"Runtime"`
}
func (l *ConfigLanguage) JudgeScript() string {
if l.Judge.Type == "default" {
return "/woj/framework/template/default/" + l.Lang + ".Makefile"
} else {
return "/woj/problem/judge/" + l.Judge.Script
}
}
func (l *ConfigLanguage) JudgeInterpreter() string {
return AllowedLanguages[l.Lang].Interpreter
}
type Config struct {
Languages []ConfigLanguage `json:"Languages"`
Prebuild ConfigRuntime `json:"Prebuild,omitempty"`
Tasks []struct {
Id int `json:"Id"`
Points int32 `json:"Points"`
} `json:"Tasks"`
}
func (c *Config) Validate(base string) error {
// check per language config
for _, lang := range c.Languages {
if _, ok := AllowedLanguages[lang.Lang]; !ok {
return fmt.Errorf("language %s is not allowed", lang.Lang)
}
err := utils.NewMust().
// check judge config
Do(func() error { return lang.Judge.Validate(base, lang.Lang) }).
// check runtime limits
Do(func() error { return lang.Runtime.Compile.Validate() }).
Do(func() error { return lang.Runtime.Run.Validate() }).
Do(func() error { return lang.Runtime.Check.Validate() }).
Done()
if err != nil {
return err
}
}
// check prebuild config
if err := c.Prebuild.Validate(); err != nil {
return err
}
// check tasks
if len(c.Tasks) == 0 {
return errors.New("no tasks")
}
ids := map[int]struct{}{}
total := (1 + len(c.Tasks)) * len(c.Tasks) / 2
for _, task := range c.Tasks {
if task.Id <= 0 {
return fmt.Errorf("task %d has non-positive id", task.Id)
}
if task.Points < 0 {
return fmt.Errorf("task %d has negative points", task.Id)
}
if _, ok := ids[task.Id]; ok {
return fmt.Errorf("task %d has duplicate id", task.Id)
}
total -= task.Id
ids[task.Id] = struct{}{}
}
if total != 0 {
return errors.New("task ids are not continuous")
}
return nil
}
func (c *Config) FilterLanguage(lang string) (*ConfigLanguage, bool) {
for idx := range c.Languages {
if c.Languages[idx].Lang == lang {
return &c.Languages[idx], true
}
}
return &ConfigLanguage{}, false
}
func (s *service) ParseConfig(meta *JudgeMeta, skipCheck bool) (*Config, error) {
base := filepath.Join(ProblemDir, fmt.Sprintf("%d", meta.Run.Version))
file := filepath.Join(base, "config.json")
data, err := os.ReadFile(file)
if err != nil {
return &Config{}, err
}
// unmarshal
config := &Config{}
err = json.Unmarshal(data, config)
if err != nil {
return &Config{}, err
}
// fill default
for idx := range config.Languages {
if config.Languages[idx].Runtime.Compile.Validate() != nil {
config.Languages[idx].Runtime.Compile = DefaultCompileRuntime
}
if config.Languages[idx].Runtime.Check.Validate() != nil {
config.Languages[idx].Runtime.Check = DefaultCheckRuntime
}
}
if config.Prebuild.Validate() != nil {
config.Prebuild = DefaultPrebuildRuntime
}
if skipCheck {
return config, nil
}
err = config.Validate(base)
if err != nil {
return &Config{}, err
}
return config, nil
}