cli/pkg/lab/collector/collector.go
Claude 5e9a9c2790 feat: integrate lab dashboard as core lab serve
Port the standalone lab dashboard (lab.lthn.io) into the core CLI as
pkg/lab/ with collectors, handlers, and HTML templates. The dashboard
monitors machines, Docker containers, Forgejo, HuggingFace models,
training runs, and InfluxDB metrics with SSE live updates.

New command: core lab serve --bind :8080

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:53:52 +00:00

82 lines
1.5 KiB
Go

package collector
import (
"context"
"log/slog"
"sync"
"time"
)
type Collector interface {
Name() string
Collect(ctx context.Context) error
}
type Registry struct {
mu sync.Mutex
entries []entry
logger *slog.Logger
}
type entry struct {
c Collector
interval time.Duration
cancel context.CancelFunc
}
func NewRegistry(logger *slog.Logger) *Registry {
return &Registry{logger: logger}
}
func (r *Registry) Register(c Collector, interval time.Duration) {
r.mu.Lock()
defer r.mu.Unlock()
r.entries = append(r.entries, entry{c: c, interval: interval})
}
func (r *Registry) Start(ctx context.Context) {
r.mu.Lock()
defer r.mu.Unlock()
for i := range r.entries {
e := &r.entries[i]
cctx, cancel := context.WithCancel(ctx)
e.cancel = cancel
go r.run(cctx, e.c, e.interval)
}
}
func (r *Registry) run(ctx context.Context, c Collector, interval time.Duration) {
r.logger.Info("collector started", "name", c.Name(), "interval", interval)
// Run immediately on start.
if err := c.Collect(ctx); err != nil {
r.logger.Warn("collector error", "name", c.Name(), "err", err)
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
r.logger.Info("collector stopped", "name", c.Name())
return
case <-ticker.C:
if err := c.Collect(ctx); err != nil {
r.logger.Warn("collector error", "name", c.Name(), "err", err)
}
}
}
}
func (r *Registry) Stop() {
r.mu.Lock()
defer r.mu.Unlock()
for _, e := range r.entries {
if e.cancel != nil {
e.cancel()
}
}
}