pve-exporter/collector/corosync.go
2026-03-20 11:32:00 +00:00

118 lines
3 KiB
Go

package collector
import (
"encoding/json"
"fmt"
"log/slog"
"strconv"
"github.com/prometheus/client_golang/prometheus"
)
func init() {
registerCollector("corosync", func(logger *slog.Logger) Collector {
return newCorosyncCollector(logger)
})
}
type corosyncCollector struct {
logger *slog.Logger
}
func newCorosyncCollector(logger *slog.Logger) *corosyncCollector {
return &corosyncCollector{logger: logger}
}
type clusterConfigNodesResponse struct {
Data []clusterConfigNodeEntry `json:"data"`
}
type clusterConfigNodeEntry struct {
Node string `json:"node"`
NodeID string `json:"nodeid"`
QuorumVotes string `json:"quorum_votes"`
}
var (
clusterQuorateDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "cluster", "quorate"),
"Whether the cluster is quorate.",
nil,
nil,
)
clusterNodesTotalDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "cluster", "nodes_total"),
"Total number of nodes in the cluster.",
nil,
nil,
)
clusterExpectedVotesDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "cluster", "expected_votes"),
"Total expected votes in the cluster.",
nil,
nil,
)
nodeOnlineDesc = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "node_online"),
"Whether a node is online.",
[]string{"name", "nodeid"},
nil,
)
)
func (c *corosyncCollector) Update(client *Client, ch chan<- prometheus.Metric) error {
// Fetch cluster status for quorate, node count, and node online state.
statusBody, err := client.Get("/cluster/status")
if err != nil {
return fmt.Errorf("failed to get /cluster/status: %w", err)
}
var statusResp clusterStatusResponse
if err := json.Unmarshal(statusBody, &statusResp); err != nil {
return fmt.Errorf("failed to parse /cluster/status response: %w", err)
}
var nodeCount int
for _, entry := range statusResp.Data {
switch entry.Type {
case "cluster":
ch <- prometheus.MustNewConstMetric(clusterQuorateDesc, prometheus.GaugeValue, float64(entry.Quorate))
case "node":
nodeCount++
ch <- prometheus.MustNewConstMetric(
nodeOnlineDesc,
prometheus.GaugeValue,
float64(entry.Online),
entry.Name,
strconv.Itoa(entry.NodeID),
)
}
}
ch <- prometheus.MustNewConstMetric(clusterNodesTotalDesc, prometheus.GaugeValue, float64(nodeCount))
// Fetch cluster config nodes for expected votes.
configBody, err := client.Get("/cluster/config/nodes")
if err != nil {
return fmt.Errorf("failed to get /cluster/config/nodes: %w", err)
}
var configResp clusterConfigNodesResponse
if err := json.Unmarshal(configBody, &configResp); err != nil {
return fmt.Errorf("failed to parse /cluster/config/nodes response: %w", err)
}
var expectedVotes float64
for _, node := range configResp.Data {
votes, err := strconv.ParseFloat(node.QuorumVotes, 64)
if err != nil {
c.logger.Warn("failed to parse quorum_votes", "node", node.Node, "err", err)
continue
}
expectedVotes += votes
}
ch <- prometheus.MustNewConstMetric(clusterExpectedVotesDesc, prometheus.GaugeValue, expectedVotes)
return nil
}