Добавлены метрики Prometheus и дашборды Grafana (этап 2.5)

Все три Go-сервиса (ingest, device-control, rule-engine) отдают
/metrics в формате Prometheus: счётчики MQTT/RabbitMQ/ClickHouse
операций, гистограммы длительности батч-флашей и диспатча правил,
переходы устройств online/offline. Добавлены сервисы prometheus и
grafana в docker-compose с провижининг конфигом (datasource +
два готовых дашборда: "Ingest & Telemetry" и "Automation & Devices").
This commit is contained in:
2026-08-11 21:58:59 +05:00
parent 3ade5c2512
commit 81040eec62
31 changed files with 712 additions and 53 deletions
@@ -16,12 +16,14 @@ import (
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc"
devicecontrol "git.cactoz.su/cacto/home_automatization/proto/device_control"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/config"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/healthcheck"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/httpapi"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/metrics"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/mqttclient"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/rabbitmq"
"git.cactoz.su/cacto/home_automatization/services/device-control-service/internal/server"
@@ -74,6 +76,8 @@ func run(logger *slog.Logger) error {
}
checker := healthcheck.New(shadowStore, cfg.HealthCheckTimeout, cfg.HealthCheckInterval, func(deviceID string) {
metrics.HealthTransitionsTotal.WithLabelValues("offline").Inc()
pubCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := publisher.PublishStatusChanged(pubCtx, deviceID, shadow.StatusOffline); err != nil {
@@ -93,10 +97,14 @@ func run(logger *slog.Logger) error {
devicecontrol.RegisterDeviceControlServer(grpcServer, srv)
// Same server instance, second transport — for callers where a full
// gRPC client is impractical (Laravel/PHP; see README).
// gRPC client is impractical (Laravel/PHP; see README). /metrics rides
// along on this same port rather than opening a third one.
mux := http.NewServeMux()
mux.Handle("/", httpapi.NewRouter(srv, logger))
mux.Handle("/metrics", promhttp.Handler())
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.HTTPPort),
Handler: httpapi.NewRouter(srv, logger),
Handler: mux,
}
go func() {
@@ -145,8 +153,10 @@ func telemetryHandler(store *shadow.Store, publisher *rabbitmq.Publisher, logger
deviceID, ok := deviceIDFromTopic(topic)
if !ok {
logger.Warn("dropping telemetry from unparseable topic", "topic", topic)
metrics.MQTTEventsTotal.WithLabelValues("telemetry", "invalid").Inc()
return
}
metrics.MQTTEventsTotal.WithLabelValues("telemetry", "ok").Inc()
touch(context.Background(), store, publisher, deviceID, logger)
}
}
@@ -160,14 +170,17 @@ func ackHandler(store *shadow.Store, publisher *rabbitmq.Publisher, logger *slog
deviceID, ok := deviceIDFromTopic(topic)
if !ok {
logger.Warn("dropping ack from unparseable topic", "topic", topic)
metrics.MQTTEventsTotal.WithLabelValues("ack", "invalid").Inc()
return
}
var ack ackPayload
if err := json.Unmarshal(payload, &ack); err != nil {
logger.Warn("dropping invalid ack payload", "device_id", deviceID, "error", err)
metrics.MQTTEventsTotal.WithLabelValues("ack", "invalid").Inc()
return
}
metrics.MQTTEventsTotal.WithLabelValues("ack", "ok").Inc()
ctx := context.Background()
if len(ack.State) > 0 {
@@ -186,6 +199,8 @@ func touch(ctx context.Context, store *shadow.Store, publisher *rabbitmq.Publish
return
}
if becameOnline {
metrics.HealthTransitionsTotal.WithLabelValues("online").Inc()
pubCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := publisher.PublishStatusChanged(pubCtx, deviceID, shadow.StatusOnline); err != nil {