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
+10 -3
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.
@@ -101,4 +108,4 @@ Contributions are welcome! Please open an issue or submit a pull request if you
## Support ## Support
If you encounter any issues or have questions, please open an issue on the project's GitHub repository. If you encounter any issues or have questions, please open an issue on the project's GitHub repository.
+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)
}
})
}
}