Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
518 lines
15 KiB
Go
518 lines
15 KiB
Go
package plugin
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type SchedulerStatus struct {
|
|
Now time.Time `json:"now"`
|
|
Lane string `json:"lane,omitempty"`
|
|
SchedulerTickSeconds int `json:"scheduler_tick_seconds"`
|
|
IdleSleepSeconds int `json:"idle_sleep_seconds,omitempty"`
|
|
NextDetectionAt *time.Time `json:"next_detection_at,omitempty"`
|
|
CurrentJobType string `json:"current_job_type,omitempty"`
|
|
CurrentPhase string `json:"current_phase,omitempty"`
|
|
LastIterationHadJobs bool `json:"last_iteration_had_jobs,omitempty"`
|
|
LastIterationDoneAt *time.Time `json:"last_iteration_done_at,omitempty"`
|
|
Waiting []SchedulerWaitingStatus `json:"waiting,omitempty"`
|
|
InProcessJobs []SchedulerJobStatus `json:"in_process_jobs,omitempty"`
|
|
JobTypes []SchedulerJobTypeStatus `json:"job_types,omitempty"`
|
|
}
|
|
|
|
type SchedulerWaitingStatus struct {
|
|
Reason string `json:"reason"`
|
|
JobType string `json:"job_type,omitempty"`
|
|
Since *time.Time `json:"since,omitempty"`
|
|
Until *time.Time `json:"until,omitempty"`
|
|
Details map[string]interface{} `json:"details,omitempty"`
|
|
}
|
|
|
|
type SchedulerJobStatus struct {
|
|
JobID string `json:"job_id"`
|
|
JobType string `json:"job_type"`
|
|
State string `json:"state"`
|
|
Stage string `json:"stage,omitempty"`
|
|
WorkerID string `json:"worker_id,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Progress float64 `json:"progress,omitempty"`
|
|
CreatedAt *time.Time `json:"created_at,omitempty"`
|
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
|
DurationSeconds float64 `json:"duration_seconds,omitempty"`
|
|
}
|
|
|
|
type SchedulerJobTypeStatus struct {
|
|
JobType string `json:"job_type"`
|
|
Enabled bool `json:"enabled"`
|
|
DetectionInFlight bool `json:"detection_in_flight"`
|
|
NextDetectionAt *time.Time `json:"next_detection_at,omitempty"`
|
|
DetectionIntervalMinutes int32 `json:"detection_interval_minutes,omitempty"`
|
|
LastDetectedAt *time.Time `json:"last_detected_at,omitempty"`
|
|
LastDetectedCount int `json:"last_detected_count,omitempty"`
|
|
LastDetectionError string `json:"last_detection_error,omitempty"`
|
|
LastDetectionSkipped string `json:"last_detection_skipped,omitempty"`
|
|
}
|
|
|
|
type schedulerDetectionInfo struct {
|
|
lastDetectedAt time.Time
|
|
lastDetectedCount int
|
|
lastErrorAt time.Time
|
|
lastError string
|
|
lastSkippedAt time.Time
|
|
lastSkippedReason string
|
|
}
|
|
|
|
type schedulerRunInfo struct {
|
|
lastRunStartedAt time.Time
|
|
lastRunCompletedAt time.Time
|
|
lastRunStatus string
|
|
}
|
|
|
|
type schedulerLoopState struct {
|
|
currentJobType string
|
|
currentPhase string
|
|
lastIterationHadJobs bool
|
|
lastIterationCompleted time.Time
|
|
}
|
|
|
|
func (r *Plugin) recordSchedulerDetectionSuccess(jobType string, count int) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.schedulerDetectionMu.Lock()
|
|
defer r.schedulerDetectionMu.Unlock()
|
|
info := r.schedulerDetection[jobType]
|
|
if info == nil {
|
|
info = &schedulerDetectionInfo{}
|
|
r.schedulerDetection[jobType] = info
|
|
}
|
|
info.lastDetectedAt = time.Now().UTC()
|
|
info.lastDetectedCount = count
|
|
info.lastError = ""
|
|
info.lastSkippedReason = ""
|
|
}
|
|
|
|
func (r *Plugin) recordSchedulerDetectionError(jobType string, err error) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.schedulerDetectionMu.Lock()
|
|
defer r.schedulerDetectionMu.Unlock()
|
|
info := r.schedulerDetection[jobType]
|
|
if info == nil {
|
|
info = &schedulerDetectionInfo{}
|
|
r.schedulerDetection[jobType] = info
|
|
}
|
|
info.lastErrorAt = time.Now().UTC()
|
|
info.lastError = err.Error()
|
|
}
|
|
|
|
func (r *Plugin) recordSchedulerDetectionSkip(jobType string, reason string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
if strings.TrimSpace(reason) == "" {
|
|
return
|
|
}
|
|
r.schedulerDetectionMu.Lock()
|
|
defer r.schedulerDetectionMu.Unlock()
|
|
info := r.schedulerDetection[jobType]
|
|
if info == nil {
|
|
info = &schedulerDetectionInfo{}
|
|
r.schedulerDetection[jobType] = info
|
|
}
|
|
info.lastSkippedAt = time.Now().UTC()
|
|
info.lastSkippedReason = reason
|
|
}
|
|
|
|
func (r *Plugin) snapshotSchedulerDetection(jobType string) schedulerDetectionInfo {
|
|
if r == nil {
|
|
return schedulerDetectionInfo{}
|
|
}
|
|
r.schedulerDetectionMu.Lock()
|
|
defer r.schedulerDetectionMu.Unlock()
|
|
info := r.schedulerDetection[jobType]
|
|
if info == nil {
|
|
return schedulerDetectionInfo{}
|
|
}
|
|
return *info
|
|
}
|
|
|
|
func (r *Plugin) recordSchedulerRunStart(jobType string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.schedulerRunMu.Lock()
|
|
defer r.schedulerRunMu.Unlock()
|
|
info := r.schedulerRun[jobType]
|
|
if info == nil {
|
|
info = &schedulerRunInfo{}
|
|
r.schedulerRun[jobType] = info
|
|
}
|
|
info.lastRunStartedAt = time.Now().UTC()
|
|
info.lastRunStatus = ""
|
|
}
|
|
|
|
func (r *Plugin) recordSchedulerRunComplete(jobType, status string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.schedulerRunMu.Lock()
|
|
defer r.schedulerRunMu.Unlock()
|
|
info := r.schedulerRun[jobType]
|
|
if info == nil {
|
|
info = &schedulerRunInfo{}
|
|
r.schedulerRun[jobType] = info
|
|
}
|
|
info.lastRunCompletedAt = time.Now().UTC()
|
|
info.lastRunStatus = status
|
|
}
|
|
|
|
func (r *Plugin) snapshotSchedulerRun(jobType string) schedulerRunInfo {
|
|
if r == nil {
|
|
return schedulerRunInfo{}
|
|
}
|
|
r.schedulerRunMu.Lock()
|
|
defer r.schedulerRunMu.Unlock()
|
|
info := r.schedulerRun[jobType]
|
|
if info == nil {
|
|
return schedulerRunInfo{}
|
|
}
|
|
return *info
|
|
}
|
|
|
|
func (r *Plugin) setSchedulerLoopState(jobType, phase string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.schedulerLoopMu.Lock()
|
|
r.schedulerLoopState.currentJobType = jobType
|
|
r.schedulerLoopState.currentPhase = phase
|
|
r.schedulerLoopMu.Unlock()
|
|
}
|
|
|
|
// setSchedulerLoopStateForJobType keeps the aggregate scheduler state and the
|
|
// owning lane state in sync while a specific job type is active.
|
|
func (r *Plugin) setSchedulerLoopStateForJobType(jobType, phase string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.setSchedulerLoopState(jobType, phase)
|
|
if jobType == "" {
|
|
return
|
|
}
|
|
if ls := r.lanes[JobTypeLane(jobType)]; ls != nil {
|
|
r.setLaneLoopState(ls, jobType, phase)
|
|
}
|
|
}
|
|
|
|
func (r *Plugin) snapshotSchedulerLoopState() schedulerLoopState {
|
|
if r == nil {
|
|
return schedulerLoopState{}
|
|
}
|
|
r.schedulerLoopMu.Lock()
|
|
defer r.schedulerLoopMu.Unlock()
|
|
return r.schedulerLoopState
|
|
}
|
|
|
|
// aggregateLaneLoopStates merges per-lane loop states into a single
|
|
// schedulerLoopState for the aggregate GetSchedulerStatus API. It picks
|
|
// the most recent iteration completion, any currently-active job type,
|
|
// and a phase that reflects whether any lane is actively working.
|
|
func (r *Plugin) aggregateLaneLoopStates() schedulerLoopState {
|
|
if r == nil || len(r.lanes) == 0 {
|
|
return r.snapshotSchedulerLoopState()
|
|
}
|
|
|
|
var agg schedulerLoopState
|
|
for _, ls := range r.lanes {
|
|
snap := r.snapshotLaneLoopState(ls)
|
|
if snap.lastIterationCompleted.After(agg.lastIterationCompleted) {
|
|
agg.lastIterationCompleted = snap.lastIterationCompleted
|
|
}
|
|
if snap.lastIterationHadJobs {
|
|
agg.lastIterationHadJobs = true
|
|
}
|
|
// Prefer showing an active phase over idle/sleeping.
|
|
if snap.currentJobType != "" {
|
|
agg.currentJobType = snap.currentJobType
|
|
agg.currentPhase = snap.currentPhase
|
|
}
|
|
}
|
|
// If no lane is actively processing, show the most interesting phase.
|
|
if agg.currentPhase == "" {
|
|
for _, ls := range r.lanes {
|
|
snap := r.snapshotLaneLoopState(ls)
|
|
if snap.currentPhase != "" {
|
|
agg.currentPhase = snap.currentPhase
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return agg
|
|
}
|
|
|
|
// --- Per-lane loop state helpers ---
|
|
|
|
func (r *Plugin) setLaneLoopState(ls *schedulerLaneState, jobType, phase string) {
|
|
if r == nil || ls == nil {
|
|
return
|
|
}
|
|
ls.loopMu.Lock()
|
|
ls.loop.currentJobType = jobType
|
|
ls.loop.currentPhase = phase
|
|
ls.loopMu.Unlock()
|
|
}
|
|
|
|
func (r *Plugin) recordLaneIterationComplete(ls *schedulerLaneState, hadJobs bool) {
|
|
if r == nil || ls == nil {
|
|
return
|
|
}
|
|
ls.loopMu.Lock()
|
|
ls.loop.lastIterationHadJobs = hadJobs
|
|
ls.loop.lastIterationCompleted = time.Now().UTC()
|
|
ls.loopMu.Unlock()
|
|
}
|
|
|
|
func (r *Plugin) snapshotLaneLoopState(ls *schedulerLaneState) schedulerLoopState {
|
|
if r == nil || ls == nil {
|
|
return schedulerLoopState{}
|
|
}
|
|
ls.loopMu.Lock()
|
|
defer ls.loopMu.Unlock()
|
|
return ls.loop
|
|
}
|
|
|
|
// GetLaneSchedulerStatus returns scheduler status scoped to a single lane.
|
|
func (r *Plugin) GetLaneSchedulerStatus(lane SchedulerLane) SchedulerStatus {
|
|
ls := r.lanes[lane]
|
|
if ls == nil {
|
|
return SchedulerStatus{Now: time.Now().UTC()}
|
|
}
|
|
now := time.Now().UTC()
|
|
loopState := r.snapshotLaneLoopState(ls)
|
|
idleSleep := LaneIdleSleep(lane)
|
|
allInProcess := r.listInProcessJobs(now)
|
|
laneInProcess := make([]SchedulerJobStatus, 0, len(allInProcess))
|
|
for _, job := range allInProcess {
|
|
if JobTypeLane(job.JobType) == lane {
|
|
laneInProcess = append(laneInProcess, job)
|
|
}
|
|
}
|
|
|
|
status := SchedulerStatus{
|
|
Now: now,
|
|
Lane: string(lane),
|
|
SchedulerTickSeconds: int(secondsFromDuration(r.schedulerTick)),
|
|
InProcessJobs: laneInProcess,
|
|
IdleSleepSeconds: int(idleSleep / time.Second),
|
|
CurrentJobType: loopState.currentJobType,
|
|
CurrentPhase: loopState.currentPhase,
|
|
LastIterationHadJobs: loopState.lastIterationHadJobs,
|
|
}
|
|
nextDetectionAt := r.earliestLaneDetectionAt(lane)
|
|
if nextDetectionAt.IsZero() && loopState.currentPhase == "sleeping" && !loopState.lastIterationCompleted.IsZero() {
|
|
nextDetectionAt = loopState.lastIterationCompleted.Add(idleSleep)
|
|
}
|
|
if !nextDetectionAt.IsZero() {
|
|
at := nextDetectionAt
|
|
status.NextDetectionAt = &at
|
|
}
|
|
if !loopState.lastIterationCompleted.IsZero() {
|
|
at := loopState.lastIterationCompleted
|
|
status.LastIterationDoneAt = &at
|
|
}
|
|
|
|
states, err := r.ListSchedulerStates()
|
|
if err != nil {
|
|
return status
|
|
}
|
|
|
|
waiting := make([]SchedulerWaitingStatus, 0)
|
|
jobTypes := make([]SchedulerJobTypeStatus, 0)
|
|
|
|
for _, state := range states {
|
|
if JobTypeLane(state.JobType) != lane {
|
|
continue
|
|
}
|
|
jobType := state.JobType
|
|
info := r.snapshotSchedulerDetection(jobType)
|
|
|
|
jobStatus := SchedulerJobTypeStatus{
|
|
JobType: jobType,
|
|
Enabled: state.Enabled,
|
|
DetectionInFlight: state.DetectionInFlight,
|
|
NextDetectionAt: state.NextDetectionAt,
|
|
DetectionIntervalMinutes: state.DetectionIntervalMinutes,
|
|
}
|
|
if !info.lastDetectedAt.IsZero() {
|
|
jobStatus.LastDetectedAt = timeToPtr(info.lastDetectedAt)
|
|
jobStatus.LastDetectedCount = info.lastDetectedCount
|
|
}
|
|
if info.lastError != "" {
|
|
jobStatus.LastDetectionError = info.lastError
|
|
}
|
|
if info.lastSkippedReason != "" {
|
|
jobStatus.LastDetectionSkipped = info.lastSkippedReason
|
|
}
|
|
jobTypes = append(jobTypes, jobStatus)
|
|
|
|
if state.DetectionInFlight {
|
|
waiting = append(waiting, SchedulerWaitingStatus{
|
|
Reason: "detection_in_flight",
|
|
JobType: jobType,
|
|
})
|
|
} else if state.Enabled && state.NextDetectionAt != nil && now.Before(*state.NextDetectionAt) {
|
|
waiting = append(waiting, SchedulerWaitingStatus{
|
|
Reason: "next_detection_at",
|
|
JobType: jobType,
|
|
Until: state.NextDetectionAt,
|
|
})
|
|
}
|
|
}
|
|
|
|
sort.Slice(jobTypes, func(i, j int) bool {
|
|
return jobTypes[i].JobType < jobTypes[j].JobType
|
|
})
|
|
|
|
status.Waiting = waiting
|
|
status.JobTypes = jobTypes
|
|
return status
|
|
}
|
|
|
|
func (r *Plugin) GetSchedulerStatus() SchedulerStatus {
|
|
now := time.Now().UTC()
|
|
|
|
// Aggregate loop state across all lanes instead of reading the
|
|
// legacy single-loop state which is no longer updated.
|
|
aggregated := r.aggregateLaneLoopStates()
|
|
|
|
status := SchedulerStatus{
|
|
Now: now,
|
|
SchedulerTickSeconds: int(secondsFromDuration(r.schedulerTick)),
|
|
InProcessJobs: r.listInProcessJobs(now),
|
|
IdleSleepSeconds: int(defaultSchedulerIdleSleep / time.Second),
|
|
CurrentJobType: aggregated.currentJobType,
|
|
CurrentPhase: aggregated.currentPhase,
|
|
LastIterationHadJobs: aggregated.lastIterationHadJobs,
|
|
}
|
|
nextDetectionAt := r.earliestNextDetectionAt()
|
|
if nextDetectionAt.IsZero() && aggregated.currentPhase == "sleeping" && !aggregated.lastIterationCompleted.IsZero() {
|
|
nextDetectionAt = aggregated.lastIterationCompleted.Add(defaultSchedulerIdleSleep)
|
|
}
|
|
if !nextDetectionAt.IsZero() {
|
|
at := nextDetectionAt
|
|
status.NextDetectionAt = &at
|
|
}
|
|
if !aggregated.lastIterationCompleted.IsZero() {
|
|
at := aggregated.lastIterationCompleted
|
|
status.LastIterationDoneAt = &at
|
|
}
|
|
|
|
states, err := r.ListSchedulerStates()
|
|
if err != nil {
|
|
return status
|
|
}
|
|
|
|
waiting := make([]SchedulerWaitingStatus, 0)
|
|
jobTypes := make([]SchedulerJobTypeStatus, 0, len(states))
|
|
|
|
for _, state := range states {
|
|
jobType := state.JobType
|
|
info := r.snapshotSchedulerDetection(jobType)
|
|
|
|
jobStatus := SchedulerJobTypeStatus{
|
|
JobType: jobType,
|
|
Enabled: state.Enabled,
|
|
DetectionInFlight: state.DetectionInFlight,
|
|
NextDetectionAt: state.NextDetectionAt,
|
|
DetectionIntervalMinutes: state.DetectionIntervalMinutes,
|
|
}
|
|
if !info.lastDetectedAt.IsZero() {
|
|
jobStatus.LastDetectedAt = timeToPtr(info.lastDetectedAt)
|
|
jobStatus.LastDetectedCount = info.lastDetectedCount
|
|
}
|
|
if info.lastError != "" {
|
|
jobStatus.LastDetectionError = info.lastError
|
|
}
|
|
if info.lastSkippedReason != "" {
|
|
jobStatus.LastDetectionSkipped = info.lastSkippedReason
|
|
}
|
|
jobTypes = append(jobTypes, jobStatus)
|
|
|
|
if state.DetectionInFlight {
|
|
waiting = append(waiting, SchedulerWaitingStatus{
|
|
Reason: "detection_in_flight",
|
|
JobType: jobType,
|
|
})
|
|
} else if state.Enabled && state.NextDetectionAt != nil && now.Before(*state.NextDetectionAt) {
|
|
waiting = append(waiting, SchedulerWaitingStatus{
|
|
Reason: "next_detection_at",
|
|
JobType: jobType,
|
|
Until: state.NextDetectionAt,
|
|
})
|
|
}
|
|
}
|
|
|
|
sort.Slice(jobTypes, func(i, j int) bool {
|
|
return jobTypes[i].JobType < jobTypes[j].JobType
|
|
})
|
|
|
|
status.Waiting = waiting
|
|
status.JobTypes = jobTypes
|
|
return status
|
|
}
|
|
|
|
func (r *Plugin) listInProcessJobs(now time.Time) []SchedulerJobStatus {
|
|
active := make([]SchedulerJobStatus, 0)
|
|
if r == nil {
|
|
return active
|
|
}
|
|
|
|
r.jobsMu.RLock()
|
|
for _, job := range r.jobs {
|
|
if job == nil {
|
|
continue
|
|
}
|
|
if !isActiveTrackedJobState(job.State) {
|
|
continue
|
|
}
|
|
start := timeToPtr(now)
|
|
if job.CreatedAt != nil && !job.CreatedAt.IsZero() {
|
|
start = job.CreatedAt
|
|
} else if job.UpdatedAt != nil && !job.UpdatedAt.IsZero() {
|
|
start = job.UpdatedAt
|
|
}
|
|
durationSeconds := 0.0
|
|
if start != nil {
|
|
durationSeconds = now.Sub(*start).Seconds()
|
|
}
|
|
active = append(active, SchedulerJobStatus{
|
|
JobID: job.JobID,
|
|
JobType: job.JobType,
|
|
State: strings.ToLower(job.State),
|
|
Stage: job.Stage,
|
|
WorkerID: job.WorkerID,
|
|
Message: job.Message,
|
|
Progress: job.Progress,
|
|
CreatedAt: job.CreatedAt,
|
|
UpdatedAt: job.UpdatedAt,
|
|
DurationSeconds: durationSeconds,
|
|
})
|
|
}
|
|
r.jobsMu.RUnlock()
|
|
|
|
sort.Slice(active, func(i, j int) bool {
|
|
if active[i].DurationSeconds != active[j].DurationSeconds {
|
|
return active[i].DurationSeconds > active[j].DurationSeconds
|
|
}
|
|
return active[i].JobID < active[j].JobID
|
|
})
|
|
|
|
return active
|
|
}
|