Upload files to "internal/metrics"
This commit is contained in:
1 parent
24c2629699
commit
6343da4536
1 file changed
+267
@@ -0,0 +1,267 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Safronov Grigorii
|
||||||
|
*
|
||||||
|
* Licensed under the CDDL, Version 1.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* https://opensource.org/licenses/CDDL-1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Файл: internal/metrics/collector.go
|
||||||
|
// Назначение: Периодический сбор метрик из storage и cluster и публикация
|
||||||
|
// их в Prometheus-реестре. Работает на Linux и OpenIndiana.
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CollectorDeps зависимости коллектора
|
||||||
|
type CollectorDeps struct {
|
||||||
|
Storage StorageStatsProvider
|
||||||
|
Coordinator CoordinatorStatsProvider
|
||||||
|
Logger Logger
|
||||||
|
Interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageStatsProvider интерфейс для получения статистики storage
|
||||||
|
type StorageStatsProvider interface {
|
||||||
|
GetStats() map[string]interface{}
|
||||||
|
GetDatabaseCount() int
|
||||||
|
GetTotalDocuments() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// CoordinatorStatsProvider интерфейс для получения статистики cluster
|
||||||
|
type CoordinatorStatsProvider interface {
|
||||||
|
GetClusterStatus() ClusterStatusLike
|
||||||
|
GetActiveNodes() []NodeInfoLike
|
||||||
|
GetAllNodes() []NodeInfoLike
|
||||||
|
IsLeader() bool
|
||||||
|
GetCurrentTerm() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClusterStatusLike минимальный интерфейс статуса кластера
|
||||||
|
type ClusterStatusLike interface {
|
||||||
|
GetTotalNodes() int
|
||||||
|
GetActiveNodes() int
|
||||||
|
GetFailedNodes() int
|
||||||
|
GetLeaderID() string
|
||||||
|
GetHealth() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfoLike минимальный интерфейс узла
|
||||||
|
type NodeInfoLike interface {
|
||||||
|
GetID() string
|
||||||
|
GetIP() string
|
||||||
|
GetPort() int
|
||||||
|
GetStatus() string
|
||||||
|
GetLastSeen() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger минимальный интерфейс логгера
|
||||||
|
type Logger interface {
|
||||||
|
Debug(msg string)
|
||||||
|
Info(msg string)
|
||||||
|
Warn(msg string)
|
||||||
|
Error(msg string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collector собирает метрики и публикует в реестр
|
||||||
|
type Collector struct {
|
||||||
|
deps CollectorDeps
|
||||||
|
registry *Registry
|
||||||
|
|
||||||
|
// Метрики
|
||||||
|
mStorageDatabases *MetricFamily
|
||||||
|
mStorageDocuments *MetricFamily
|
||||||
|
mClusterNodes *MetricFamily
|
||||||
|
mClusterLeader *MetricFamily
|
||||||
|
mClusterTerm *MetricFamily
|
||||||
|
mNodeLastSeen *MetricFamily
|
||||||
|
mHTTPRequests *MetricFamily
|
||||||
|
mHTTPDuration *MetricFamily
|
||||||
|
mReplicationTotal *MetricFamily
|
||||||
|
mReplicationFailed *MetricFamily
|
||||||
|
mBackpressureLevel *MetricFamily
|
||||||
|
mMigrationTasks *MetricFamily
|
||||||
|
|
||||||
|
stop chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCollector создаёт новый коллектор
|
||||||
|
func NewCollector(deps CollectorDeps) *Collector {
|
||||||
|
if deps.Interval <= 0 {
|
||||||
|
deps.Interval = 15 * time.Second
|
||||||
|
}
|
||||||
|
r := DefaultRegistry()
|
||||||
|
|
||||||
|
c := &Collector{
|
||||||
|
deps: deps,
|
||||||
|
registry: r,
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mStorageDatabases = r.RegisterGauge("futriis_storage_databases_total",
|
||||||
|
"Total number of databases")
|
||||||
|
c.mStorageDocuments = r.RegisterGauge("futriis_storage_documents_total",
|
||||||
|
"Total number of documents across all collections")
|
||||||
|
c.mClusterNodes = r.RegisterGauge("futriis_cluster_nodes",
|
||||||
|
"Number of cluster nodes by status")
|
||||||
|
c.mClusterLeader = r.RegisterGauge("futriis_cluster_has_leader",
|
||||||
|
"Whether a leader is elected (1) or not (0)")
|
||||||
|
c.mClusterTerm = r.RegisterGauge("futriis_cluster_raft_term",
|
||||||
|
"Current Raft term")
|
||||||
|
c.mNodeLastSeen = r.RegisterGauge("futriis_node_last_seen_seconds",
|
||||||
|
"Unix timestamp of last node contact")
|
||||||
|
c.mHTTPRequests = r.RegisterCounter("futriis_http_requests_total",
|
||||||
|
"Total number of HTTP requests")
|
||||||
|
c.mHTTPDuration = r.RegisterHistogram("futriis_http_request_duration_seconds",
|
||||||
|
"HTTP request duration in seconds",
|
||||||
|
[]float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5})
|
||||||
|
c.mReplicationTotal = r.RegisterCounter("futriis_replication_total",
|
||||||
|
"Total replication operations")
|
||||||
|
c.mReplicationFailed = r.RegisterCounter("futriis_replication_failed_total",
|
||||||
|
"Failed replication operations")
|
||||||
|
c.mBackpressureLevel = r.RegisterGauge("futriis_backpressure_level",
|
||||||
|
"Current backpressure level (0=none,4=critical)")
|
||||||
|
c.mMigrationTasks = r.RegisterGauge("futriis_migration_tasks",
|
||||||
|
"Migration tasks by status")
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start запускает периодический сбор метрик
|
||||||
|
func (c *Collector) Start() {
|
||||||
|
go c.loop()
|
||||||
|
if c.deps.Logger != nil {
|
||||||
|
c.deps.Logger.Info(fmt.Sprintf("Prometheus metrics collector started (interval=%v)", c.deps.Interval))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop останавливает коллектор
|
||||||
|
func (c *Collector) Stop() {
|
||||||
|
close(c.stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) loop() {
|
||||||
|
ticker := time.NewTicker(c.deps.Interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
c.collect()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.stop:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
c.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect собирает все метрики
|
||||||
|
func (c *Collector) collect() {
|
||||||
|
c.collectStorage()
|
||||||
|
c.collectCluster()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) collectStorage() {
|
||||||
|
if c.deps.Storage == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil && c.deps.Logger != nil {
|
||||||
|
c.deps.Logger.Error(fmt.Sprintf("metrics: storage collect panic: %v", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.mStorageDatabases.WithLabels().Set(float64(c.deps.Storage.GetDatabaseCount()))
|
||||||
|
c.mStorageDocuments.WithLabels().Set(float64(c.deps.Storage.GetTotalDocuments()))
|
||||||
|
|
||||||
|
stats := c.deps.Storage.GetStats()
|
||||||
|
if dbs, ok := stats["databases"].([]map[string]interface{}); ok {
|
||||||
|
for _, db := range dbs {
|
||||||
|
name, _ := db["name"].(string)
|
||||||
|
docs, _ := db["documents"].(int64)
|
||||||
|
size, _ := db["size_bytes"].(int64)
|
||||||
|
c.registry.RegisterGauge("futriis_database_documents_total",
|
||||||
|
"Documents per database").WithLabels(LabelPair{"database", name}).Set(float64(docs))
|
||||||
|
c.registry.RegisterGauge("futriis_database_size_bytes",
|
||||||
|
"Size of database in bytes").WithLabels(LabelPair{"database", name}).Set(float64(size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) collectCluster() {
|
||||||
|
if c.deps.Coordinator == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil && c.deps.Logger != nil {
|
||||||
|
c.deps.Logger.Error(fmt.Sprintf("metrics: cluster collect panic: %v", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
status := c.deps.Coordinator.GetClusterStatus()
|
||||||
|
if status != nil {
|
||||||
|
c.mClusterNodes.WithLabels(LabelPair{"status", "total"}).Set(float64(status.GetTotalNodes()))
|
||||||
|
c.mClusterNodes.WithLabels(LabelPair{"status", "active"}).Set(float64(status.GetActiveNodes()))
|
||||||
|
c.mClusterNodes.WithLabels(LabelPair{"status", "failed"}).Set(float64(status.GetFailedNodes()))
|
||||||
|
if status.GetLeaderID() != "" {
|
||||||
|
c.mClusterLeader.WithLabels().Set(1)
|
||||||
|
} else {
|
||||||
|
c.mClusterLeader.WithLabels().Set(0)
|
||||||
|
}
|
||||||
|
c.registry.RegisterGauge("futriis_cluster_health",
|
||||||
|
"Cluster health status (1=healthy, 0.5=degraded, 0=critical)").
|
||||||
|
WithLabels(LabelPair{"health", status.GetHealth()}).Set(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.deps.Coordinator.IsLeader() {
|
||||||
|
c.mClusterLeader.WithLabels(LabelPair{"role", "self"}).Set(1)
|
||||||
|
} else {
|
||||||
|
c.mClusterLeader.WithLabels(LabelPair{"role", "self"}).Set(0)
|
||||||
|
}
|
||||||
|
c.mClusterTerm.WithLabels().Set(float64(c.deps.Coordinator.GetCurrentTerm()))
|
||||||
|
|
||||||
|
for _, node := range c.deps.Coordinator.GetActiveNodes() {
|
||||||
|
c.mNodeLastSeen.WithLabels(
|
||||||
|
LabelPair{"node_id", node.GetID()},
|
||||||
|
LabelPair{"ip", node.GetIP()},
|
||||||
|
).Set(float64(node.GetLastSeen()) / 1000.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPRequestObserved регистрирует факт HTTP-запроса
|
||||||
|
func (c *Collector) HTTPRequestObserved(method, path string, status int, duration time.Duration) {
|
||||||
|
c.mHTTPRequests.WithLabels(
|
||||||
|
LabelPair{"method", method},
|
||||||
|
LabelPair{"path", path},
|
||||||
|
LabelPair{"status", fmt.Sprintf("%d", status)},
|
||||||
|
).Inc()
|
||||||
|
c.mHTTPDuration.WithLabels(
|
||||||
|
LabelPair{"method", method},
|
||||||
|
LabelPair{"path", path},
|
||||||
|
).Observe(duration.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplicationObserved регистрирует операцию репликации
|
||||||
|
func (c *Collector) ReplicationObserved(success bool) {
|
||||||
|
c.mReplicationTotal.WithLabels().Inc()
|
||||||
|
if !success {
|
||||||
|
c.mReplicationFailed.WithLabels().Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBackpressureLevel устанавливает уровень backpressure
|
||||||
|
func (c *Collector) SetBackpressureLevel(level int) {
|
||||||
|
c.mBackpressureLevel.WithLabels().Set(float64(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMigrationTasks устанавливает количество задач миграции
|
||||||
|
func (c *Collector) SetMigrationTasks(status string, count int) {
|
||||||
|
c.mMigrationTasks.WithLabels(LabelPair{"status", status}).Set(float64(count))
|
||||||
|
}
|
||||||
Reference in new issue
Block a user