Implement Monlet MVP stack and UI updates

This commit is contained in:
Stanislav Rossovskii
2026-05-27 10:01:59 +04:00
parent dcea096327
commit edc51e9c59
145 changed files with 15618 additions and 248 deletions

View File

@@ -28,7 +28,7 @@ func (a *App) Run(ctx context.Context) error {
events := make(chan scheduler.Event, 256)
a.Metrics.ChecksConfigured.Set(float64(len(a.Cfg.Checks)))
a.Metrics.BuildInfo.WithLabelValues(a.Version, a.Cfg.Mode).Set(1)
a.Metrics.BuildInfo.WithLabelValues(a.Version, boolLabel(a.Cfg.PushesToServer()), boolLabel(a.Cfg.ExposesMetrics())).Set(1)
for _, c := range a.Cfg.Checks {
a.Metrics.CheckInterval.WithLabelValues(c.ID).Set(c.Interval.Duration.Seconds())
}
@@ -122,8 +122,11 @@ func (a *App) heartbeatLoop(ctx context.Context) {
AgentID: a.AgentID,
ObservedAt: time.Now().UTC(),
Hostname: a.Cfg.Hostname,
Version: a.Version,
Mode: a.Cfg.Mode,
Features: client.AgentFeatures{
Push: a.Cfg.PushesToServer(),
Metrics: a.Cfg.ExposesMetrics(),
},
Labels: a.Cfg.HeartbeatLabels(a.Version),
}
a.Metrics.SendAttempts.WithLabelValues("heartbeat").Inc()
err := a.Client.SendHeartbeat(ctx, hb)
@@ -210,3 +213,10 @@ func sleep(ctx context.Context, d time.Duration) bool {
return false
}
}
func boolLabel(v bool) string {
if v {
return "true"
}
return "false"
}

View File

@@ -30,22 +30,20 @@ func TestPrometheusOnlyDoesNotPush(t *testing.T) {
dir := t.TempDir()
cfg := &config.Config{
AgentID: "a1",
Mode: "prometheus_only",
StateDir: dir,
Server: config.ServerConfig{URL: srv.URL, Token: "t", HeartbeatInterval: config.Duration{Duration: 50 * time.Millisecond}, BatchInterval: config.Duration{Duration: 50 * time.Millisecond}},
Metrics: config.MetricsConfig{Enabled: false},
Server: config.ServerConfig{Enabled: boolPtr(false), URL: srv.URL, Token: "t", HeartbeatInterval: config.Duration{Duration: 50 * time.Millisecond}, BatchInterval: config.Duration{Duration: 50 * time.Millisecond}},
Metrics: config.MetricsConfig{Enabled: true, Listen: "127.0.0.1:0"},
Checks: []config.CheckConfig{{
ID: "c1",
Command: []string{"sh", "-c", "echo hi"},
Interval: config.Duration{Duration: 100 * time.Millisecond},
Timeout: config.Duration{Duration: 50 * time.Millisecond},
NotificationOwner: "server",
ID: "c1",
Command: "echo hi",
Interval: config.Duration{Duration: 100 * time.Millisecond},
Timeout: config.Duration{Duration: 50 * time.Millisecond},
}},
}
sp, _ := spool.Open(filepath.Join(dir, "spool"), 100, 1<<20)
a := &App{
Cfg: cfg, AgentID: "a1", Version: "t",
Client: client.New(srv.URL, "t", "a1", "t"),
Client: client.New(srv.URL, "t", "a1"),
Spool: sp, Metrics: metrics.New(),
Log: slog.New(slog.NewTextHandler(io.Discard, nil)),
}
@@ -62,16 +60,22 @@ func TestPrometheusOnlyDoesNotPush(t *testing.T) {
func TestEndToEndPushAndReplay(t *testing.T) {
var (
mu sync.Mutex
seenEventIDs = make(map[string]int)
hbCount int32
fail atomic.Bool
mu sync.Mutex
seenEventIDs = make(map[string]int)
lastHeartbeat client.HeartbeatRequest
hbCount int32
fail atomic.Bool
)
fail.Store(true)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/heartbeat":
var req client.HeartbeatRequest
_ = json.NewDecoder(r.Body).Decode(&req)
mu.Lock()
lastHeartbeat = req
mu.Unlock()
atomic.AddInt32(&hbCount, 1)
w.WriteHeader(http.StatusAccepted)
_, _ = io.WriteString(w, `{"accepted":true}`)
@@ -97,9 +101,10 @@ func TestEndToEndPushAndReplay(t *testing.T) {
cfg := &config.Config{
AgentID: "a1",
Hostname: "h",
Mode: "hybrid",
StateDir: dir,
Labels: map[string]string{"env": "test"},
Server: config.ServerConfig{
Enabled: boolPtr(true),
URL: srv.URL,
Token: "t",
HeartbeatInterval: config.Duration{Duration: 100 * time.Millisecond},
@@ -107,11 +112,10 @@ func TestEndToEndPushAndReplay(t *testing.T) {
},
Metrics: config.MetricsConfig{Enabled: false},
Checks: []config.CheckConfig{{
ID: "c1",
Command: []string{"sh", "-c", "echo hi"},
Interval: config.Duration{Duration: 150 * time.Millisecond},
Timeout: config.Duration{Duration: 100 * time.Millisecond},
NotificationOwner: "server",
ID: "c1",
Command: "echo hi",
Interval: config.Duration{Duration: 150 * time.Millisecond},
Timeout: config.Duration{Duration: 100 * time.Millisecond},
}},
}
sp, err := spool.Open(filepath.Join(dir, "spool"), 100, 1<<20)
@@ -122,7 +126,7 @@ func TestEndToEndPushAndReplay(t *testing.T) {
Cfg: cfg,
AgentID: "a1",
Version: "test",
Client: client.New(cfg.Server.URL, "t", "a1", "test"),
Client: client.New(cfg.Server.URL, "t", "a1"),
Spool: sp,
Metrics: metrics.New(),
Log: slog.New(slog.NewTextHandler(io.Discard, nil)),
@@ -168,4 +172,17 @@ func TestEndToEndPushAndReplay(t *testing.T) {
if atomic.LoadInt32(&hbCount) == 0 {
t.Fatal("no heartbeats observed")
}
if lastHeartbeat.Labels["env"] != "test" {
t.Fatalf("configured heartbeat label missing: %#v", lastHeartbeat.Labels)
}
if lastHeartbeat.Labels[config.AgentVersionLabel] != "test" {
t.Fatalf("version heartbeat label missing: %#v", lastHeartbeat.Labels)
}
if !lastHeartbeat.Features.Push || lastHeartbeat.Features.Metrics {
t.Fatalf("unexpected heartbeat features: %+v", lastHeartbeat.Features)
}
}
func boolPtr(v bool) *bool {
return &v
}