package snapshot import ( "encoding/json" "fmt" ) // pendingReason mirrors the apiserver rawPendReason (convert_pure.go), // surfaced by schedulerclient as the JSON-encoded string // Extensions["pending_reason"]. type pendingReason struct { MainReasonName string `json:"main_reason_name"` } // reasonScenario maps a PREASON_* name to the human-readable scenario. // This mirrors mcp-server/internal/pendreason.go (module-local copy: the // advisor and mcp-server are separate modules; lifting the table into // schedulerclient is a deliberate P1 cleanup, not forgotten). func reasonScenario(name string) string { switch name { case "bad_resreq": return "the resource request is invalid or cannot be satisfied" case "no_resource": return "insufficient resource capacity" case "match_fail": return "no host matches the job's host-selection condition" case "limit_global": return "a global quota is exhausted" case "limit_host": return "a host-level quota is exhausted" case "queue_closed": return "the queue is closed (not accepting new jobs)" case "queue_draining": return "the queue is draining (no new jobs until it empties)" case "time_window": return "outside the queue's dispatch window" case "host_unavail": return "the host is unavailable" default: return fmt.Sprintf("unknown reason (%s)", name) } } // parsePendingReason extracts the main reason name + scenario from a job's // Extensions. Returns nil when absent or unparseable — a missing reason is // reported as such, never guessed. func parsePendingReason(ext map[string]any) *JobReason { raw, ok := ext["pending_reason"] if !ok { return nil } var pr pendingReason switch v := raw.(type) { case string: if v == "" { return nil } if err := json.Unmarshal([]byte(v), &pr); err != nil { return nil } case map[string]any: b, _ := json.Marshal(v) if err := json.Unmarshal(b, &pr); err != nil { return nil } default: return nil } if pr.MainReasonName == "" { return nil } return &JobReason{ MainReasonName: pr.MainReasonName, Scenario: reasonScenario(pr.MainReasonName), } } // round2 keeps snapshot numbers at two decimals so the JSON stays small. func round2(f float64) float64 { return float64(int(f*100+0.5*sign(f))) / 100 } func sign(f float64) float64 { if f < 0 { return -1 } return 1 } func frac(a, b int) float64 { if b == 0 { return 0 } return float64(a) / float64(b) }