3 Commits
Author SHA1 Message Date
lostakj f8f9e0c852 Updated subscription date
Build Docker image on push / docker (push) Successful in 22s
Build and push Docker image on tag / docker (push) Successful in 22s
2026-08-17 21:50:23 +02:00
lostakj e5a5e6416b Added cluster mode metric
Build Docker image on push / docker (push) Successful in 22s
Build and push Docker image on tag / docker (push) Successful in 22s
2026-08-17 21:29:09 +02:00
lostakj c4287c37bc Aktualizovat README.md
Build Docker image on push / docker (push) Successful in 39s
2026-04-10 09:34:29 +00:00
5 changed files with 101 additions and 13 deletions
+9 -2
View File
@@ -58,12 +58,19 @@ metrics:
sdn: true # Enable collection of software-defined network (SDN) metrics. sdn: true # Enable collection of software-defined network (SDN) metrics.
``` ```
The cluster state collector exposes `pve_cluster_mode`, where `1` means that
Proxmox VE is configured as a cluster and `0` means that it is a standalone node.
The `pve_node_subscription_status` metric uses `0` when no subscription is
configured, `1` for an active subscription, and `2` for an expired or otherwise
unusable subscription.
## Build ## Build
To build the Docker image for PVE Exporter, use the following command: To build the Docker image for PVE Exporter, use the following command:
```sh ```sh
docker build --rm -t harbor.imtm.cz/private/pve-exporter:version . docker build --rm -t gitea.lostak.dev/lostakj/pve-exporter:version .
``` ```
Replace `version` with the appropriate version tag you want to use. Replace `version` with the appropriate version tag you want to use.
@@ -73,7 +80,7 @@ Replace `version` with the appropriate version tag you want to use.
To run the Docker image, use the following command: To run the Docker image, use the following command:
```sh ```sh
docker run --rm -d -p 9090:9090 --name pve-exporter harbor.imtm.cz/private/pve-exporter:version docker run --rm -d -p 9090:9090 --name pve-exporter gitea.lostak.dev/lostakj/pve-exporter:version
``` ```
Replace `version` with the appropriate version tag you used during the build. Replace `version` with the appropriate version tag you used during the build.
+16 -3
View File
@@ -12,8 +12,9 @@ type PveClusterStateCollector struct {
apiClient *proxmox.PveApiClient // PVE API client instance. apiClient *proxmox.PveApiClient // PVE API client instance.
registry *TTLRegistry // TTL metrics registry. registry *TTLRegistry // TTL metrics registry.
nodes *TTLGaugeVec // Count of nodes prometheus gauge. nodes *TTLGaugeVec // Count of nodes prometheus gauge.
quorate *TTLGaugeVec // Cluster quorum state prometheus gauge. quorate *TTLGaugeVec // Cluster quorum state prometheus gauge.
clusterMode *TTLGaugeVec // PVE cluster mode prometheus gauge.
} }
// Create new instance of PVE cluster state collector. // Create new instance of PVE cluster state collector.
@@ -42,6 +43,17 @@ func NewPveClusterStateCollector(apiClient *proxmox.PveApiClient, registry *TTLR
5*time.Minute, 5*time.Minute,
) )
c.registry.Register(c.quorate) c.registry.Register(c.quorate)
// PVE cluster mode gauge.
c.clusterMode = NewTTLGaugeVec(
prometheus.GaugeOpts{
Name: "pve_cluster_mode",
Help: "PVE cluster mode (1 = cluster, 0 = standalone node).",
},
[]string{"cluster"},
5*time.Minute,
)
c.registry.Register(c.clusterMode)
c.registry.StartCleanupLoop(5 * time.Second) c.registry.StartCleanupLoop(5 * time.Second)
return &c return &c
@@ -54,9 +66,10 @@ func (c *PveClusterStateCollector) CollectMetrics() error {
return err return err
} }
l := prometheus.Labels{"cluster": cluster.Name} l := prometheus.Labels{"cluster": cluster.GetClusterName()}
c.nodes.With(l).Set(float64(cluster.Nodes)) c.nodes.With(l).Set(float64(cluster.Nodes))
c.quorate.With(l).Set(float64(cluster.Quorate)) c.quorate.With(l).Set(float64(cluster.Quorate))
c.clusterMode.With(l).Set(cluster.GetClusterModeNumeric())
return nil return nil
} }
+2 -2
View File
@@ -39,7 +39,7 @@ func NewPveSubscriptionCollector(apiClient *proxmox.PveApiClient, registry *TTLR
c.status = NewTTLGaugeVec( c.status = NewTTLGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{
Name: "pve_node_subscription_status", Name: "pve_node_subscription_status",
Help: "Node subscription status.", Help: "Node subscription status (0 = not found, 1 = active, 2 = expired or otherwise unusable).",
}, },
[]string{"cluster", "node"}, []string{"cluster", "node"},
5*time.Minute, 5*time.Minute,
@@ -111,7 +111,7 @@ func (c *PveSubscriptionCollector) CollectMetrics() error {
c.info.With(subsLabels).Set(1) c.info.With(subsLabels).Set(1)
// Subscription state. // Subscription state.
c.status.With(labels).Set(subscription.GetActiveNumeric()) c.status.With(labels).Set(subscription.GetStatusNumeric())
// Subscription sockets count. // Subscription sockets count.
c.sockets.With(labels).Set(float64(subscription.Sockets)) c.sockets.With(labels).Set(float64(subscription.Sockets))
+19 -5
View File
@@ -430,13 +430,18 @@ func (r *PveSdnResource) GetStatusNumeric() float64 {
return 0 return 0
} }
// GetActiveNumeric returns the numeric state of a subscription. // GetStatusNumeric returns the numeric state of a subscription.
// Returns 1 if the subscription status is "active", otherwise returns 0. // A missing subscription is 0, an active subscription is 1, and an expired
func (r *PveSubscription) GetActiveNumeric() float64 { // or otherwise unusable subscription is 2.
if r.Status == "active" { func (r *PveSubscription) GetStatusNumeric() float64 {
switch r.Status {
case "notfound":
return 0
case "active":
return 1 return 1
default:
return 2
} }
return 0
} }
// GetSmartPassedState returns the numeric health state of a disk. // GetSmartPassedState returns the numeric health state of a disk.
@@ -486,6 +491,15 @@ func (r *PveQemuStatus) IsRunning() bool {
return false return false
} }
// GetClusterModeNumeric returns whether the PVE installation is configured as a cluster.
// The cluster status API includes an object with type "cluster" only in cluster mode.
func (r *PveClusterStatus) GetClusterModeNumeric() float64 {
if r.Type == "cluster" {
return 1
}
return 0
}
// GetClusterName returns name of cluster for labeling purposes. // GetClusterName returns name of cluster for labeling purposes.
// If there is no cluster then return standalone node with node name. Otherwise, it returns cluster name. // If there is no cluster then return standalone node with node name. Otherwise, it returns cluster name.
func (r *PveClusterStatus) GetClusterName() string { func (r *PveClusterStatus) GetClusterName() string {
+54
View File
@@ -0,0 +1,54 @@
package proxmox
import "testing"
func TestPveClusterStatusGetClusterModeNumeric(t *testing.T) {
tests := []struct {
name string
status PveClusterStatus
want float64
}{
{
name: "cluster mode",
status: PveClusterStatus{Type: "cluster"},
want: 1,
},
{
name: "standalone node",
status: PveClusterStatus{},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.status.GetClusterModeNumeric(); got != tt.want {
t.Fatalf("GetClusterModeNumeric() = %v, want %v", got, tt.want)
}
})
}
}
func TestPveSubscriptionGetStatusNumeric(t *testing.T) {
tests := []struct {
status string
want float64
}{
{status: "notfound", want: 0},
{status: "active", want: 1},
{status: "expired", want: 2},
{status: "invalid", want: 2},
{status: "suspended", want: 2},
{status: "new", want: 2},
{status: "unknown", want: 2},
}
for _, tt := range tests {
t.Run(tt.status, func(t *testing.T) {
subscription := PveSubscription{Status: tt.status}
if got := subscription.GetStatusNumeric(); got != tt.want {
t.Fatalf("GetStatusNumeric() = %v, want %v", got, tt.want)
}
})
}
}