init monlet repo with stage 0-2 (docs, contract, agent MVP)
This commit is contained in:
113
agent/internal/scheduler/scheduler.go
Normal file
113
agent/internal/scheduler/scheduler.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/monlet/agent/internal/config"
|
||||
"github.com/monlet/agent/internal/eventid"
|
||||
"github.com/monlet/agent/internal/runner"
|
||||
)
|
||||
|
||||
// Event is a contract-shaped check result emitted by the scheduler.
|
||||
type Event struct {
|
||||
EventID string `json:"event_id"`
|
||||
CheckID string `json:"check_id"`
|
||||
ObservedAt time.Time `json:"observed_at"`
|
||||
Status string `json:"status"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
Output string `json:"output,omitempty"`
|
||||
OutputTruncated bool `json:"output_truncated,omitempty"`
|
||||
NotificationOwner string `json:"notification_owner,omitempty"`
|
||||
IncidentKey string `json:"incident_key,omitempty"`
|
||||
}
|
||||
|
||||
type Hooks struct {
|
||||
OnSkipped func(checkID string)
|
||||
OnResult func(ev Event)
|
||||
}
|
||||
|
||||
// Run starts one goroutine per check. Emits events to out. Blocks until ctx is done.
|
||||
func Run(ctx context.Context, agentID string, checks []config.CheckConfig, out chan<- Event, h Hooks) {
|
||||
var wg sync.WaitGroup
|
||||
for i := range checks {
|
||||
c := checks[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
runCheck(ctx, agentID, c, out, h)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func runCheck(ctx context.Context, agentID string, c config.CheckConfig, out chan<- Event, h Hooks) {
|
||||
t := time.NewTicker(c.Interval.Duration)
|
||||
defer t.Stop()
|
||||
var running bool
|
||||
var mu sync.Mutex
|
||||
|
||||
exec := func() {
|
||||
mu.Lock()
|
||||
if running {
|
||||
mu.Unlock()
|
||||
if h.OnSkipped != nil {
|
||||
h.OnSkipped(c.ID)
|
||||
}
|
||||
return
|
||||
}
|
||||
running = true
|
||||
mu.Unlock()
|
||||
defer func() {
|
||||
mu.Lock()
|
||||
running = false
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
res := runner.Run(ctx, c.Command, c.Timeout.Duration)
|
||||
id, err := eventid.New()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ev := Event{
|
||||
EventID: id,
|
||||
CheckID: c.ID,
|
||||
ObservedAt: time.Now().UTC(),
|
||||
Status: string(res.Status),
|
||||
ExitCode: res.ExitCode,
|
||||
DurationMs: res.DurationMs,
|
||||
Output: res.Output,
|
||||
OutputTruncated: res.OutputTruncated,
|
||||
NotificationOwner: c.NotificationOwner,
|
||||
IncidentKey: incidentKey(agentID, c),
|
||||
}
|
||||
if h.OnResult != nil {
|
||||
h.OnResult(ev)
|
||||
}
|
||||
select {
|
||||
case out <- ev:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// Fire first run immediately so smoke tests don't wait a full interval.
|
||||
go exec()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
go exec()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func incidentKey(agentID string, c config.CheckConfig) string {
|
||||
if c.DedupeKey != "" {
|
||||
return c.DedupeKey
|
||||
}
|
||||
return agentID + ":" + c.ID
|
||||
}
|
||||
Reference in New Issue
Block a user