Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring: Extract cluster and node collectors into separate files #198

Merged
merged 1 commit into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/pve_exporter/collector/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
Prometheus collecters for Proxmox VE cluster.
"""

import collections
from proxmoxer import ProxmoxAPI

from prometheus_client import CollectorRegistry, generate_latest

from pve_exporter.collector.cluster import (
StatusCollector,
ClusterResourcesCollector,
ClusterNodeCollector,
VersionCollector,
ClusterInfoCollector
)
from pve_exporter.collector.node import NodeConfigCollector

CollectorsOptions = collections.namedtuple('CollectorsOptions', [
'status',
'version',
'node',
'cluster',
'resources',
'config',
])


def collect_pve(config, host, cluster, node, options: CollectorsOptions):
"""Scrape a host and return prometheus text format for it"""

pve = ProxmoxAPI(host, **config)

registry = CollectorRegistry()
if cluster and options.status:
registry.register(StatusCollector(pve))
if cluster and options.resources:
registry.register(ClusterResourcesCollector(pve))
if cluster and options.node:
registry.register(ClusterNodeCollector(pve))
if cluster and options.cluster:
registry.register(ClusterInfoCollector(pve))
if cluster and options.version:
registry.register(VersionCollector(pve))
if node and options.config:
registry.register(NodeConfigCollector(pve))

return generate_latest(registry)
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,10 @@
"""
# pylint: disable=too-few-public-methods

import collections
import itertools
import logging
from proxmoxer import ProxmoxAPI

from prometheus_client import CollectorRegistry, generate_latest
from prometheus_client.core import GaugeMetricFamily

CollectorsOptions = collections.namedtuple('CollectorsOptions', [
'status',
'version',
'node',
'cluster',
'resources',
'config',
])

class StatusCollector:
"""
Expand All @@ -35,7 +23,7 @@ class StatusCollector:
def __init__(self, pve):
self._pve = pve

def collect(self): # pylint: disable=missing-docstring
def collect(self): # pylint: disable=missing-docstring
status_metrics = GaugeMetricFamily(
'pve_up',
'Node/VM/CT-Status is online/running',
Expand All @@ -57,6 +45,7 @@ def collect(self): # pylint: disable=missing-docstring

yield status_metrics


class VersionCollector:
"""
Collects Proxmox VE build information. E.g.:
Expand All @@ -71,7 +60,7 @@ class VersionCollector:
def __init__(self, pve):
self._pve = pve

def collect(self): # pylint: disable=missing-docstring
def collect(self): # pylint: disable=missing-docstring
version_items = self._pve.version.get().items()
version = {key: value for key, value in version_items if key in self.LABEL_WHITELIST}

Expand All @@ -85,6 +74,7 @@ def collect(self): # pylint: disable=missing-docstring

yield metric


class ClusterNodeCollector:
"""
Collects Proxmox VE cluster node information. E.g.:
Expand All @@ -98,7 +88,7 @@ class ClusterNodeCollector:
def __init__(self, pve):
self._pve = pve

def collect(self): # pylint: disable=missing-docstring
def collect(self): # pylint: disable=missing-docstring
nodes = [entry for entry in self._pve.cluster.status.get() if entry['type'] == 'node']
labels = ['id', 'level', 'name', 'nodeid']

Expand All @@ -114,6 +104,7 @@ def collect(self): # pylint: disable=missing-docstring

yield info_metrics


class ClusterInfoCollector:
"""
Collects Proxmox VE cluster information. E.g.:
Expand All @@ -126,7 +117,7 @@ class ClusterInfoCollector:
def __init__(self, pve):
self._pve = pve

def collect(self): # pylint: disable=missing-docstring
def collect(self): # pylint: disable=missing-docstring
clusters = [entry for entry in self._pve.cluster.status.get() if entry['type'] == 'cluster']

if clusters:
Expand All @@ -152,6 +143,7 @@ def collect(self): # pylint: disable=missing-docstring

yield info_metrics


class ClusterResourcesCollector:
"""
Collects Proxmox VE cluster resources information, i.e. memory, storage, cpu
Expand All @@ -161,7 +153,7 @@ class ClusterResourcesCollector:
def __init__(self, pve):
self._pve = pve

def collect(self): # pylint: disable=missing-docstring
def collect(self): # pylint: disable=missing-docstring
metrics = {
'maxdisk': GaugeMetricFamily(
'pve_disk_size_bytes',
Expand Down Expand Up @@ -252,72 +244,3 @@ def collect(self): # pylint: disable=missing-docstring
metrics[key].add_metric(label_values, metric_value)

return itertools.chain(metrics.values(), info_metrics.values())

class NodeConfigCollector:
"""
Collects Proxmox VE VM information directly from config, i.e. boot, name, onboot, etc.
For manual test: "pvesh get /nodes/<node>/<type>/<vmid>/config"

# HELP pve_onboot_status Proxmox vm config onboot value
# TYPE pve_onboot_status gauge
pve_onboot_status{id="qemu/113",node="XXXX",type="qemu"} 1.0
"""

def __init__(self, pve):
self._pve = pve
self._log = logging.getLogger(__name__)

def collect(self): # pylint: disable=missing-docstring
metrics = {
'onboot': GaugeMetricFamily(
'pve_onboot_status',
'Proxmox vm config onboot value',
labels=['id', 'node', 'type']),
}

node = None
for entry in self._pve.cluster.status.get():
if entry['type'] == 'node' and entry['local']:
node = entry['name']
break

# Scrape qemu config
vmtype = 'qemu'
for vmdata in self._pve.nodes(node).qemu.get():
config = self._pve.nodes(node).qemu(vmdata['vmid']).config.get().items()
for key, metric_value in config:
label_values = [f"{vmtype}/{vmdata['vmid']}", node, vmtype]
if key in metrics:
metrics[key].add_metric(label_values, metric_value)

# Scrape LXC config
vmtype = 'lxc'
for vmdata in self._pve.nodes(node).lxc.get():
config = self._pve.nodes(node).lxc(vmdata['vmid']).config.get().items()
for key, metric_value in config:
label_values = [f"{vmtype}/{vmdata['vmid']}", node, vmtype]
if key in metrics:
metrics[key].add_metric(label_values, metric_value)

return metrics.values()

def collect_pve(config, host, cluster, node, options: CollectorsOptions):
"""Scrape a host and return prometheus text format for it"""

pve = ProxmoxAPI(host, **config)

registry = CollectorRegistry()
if cluster and options.status:
registry.register(StatusCollector(pve))
if cluster and options.resources:
registry.register(ClusterResourcesCollector(pve))
if cluster and options.node:
registry.register(ClusterNodeCollector(pve))
if cluster and options.cluster:
registry.register(ClusterInfoCollector(pve))
if cluster and options.version:
registry.register(VersionCollector(pve))
if node and options.config:
registry.register(NodeConfigCollector(pve))

return generate_latest(registry)
59 changes: 59 additions & 0 deletions src/pve_exporter/collector/node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Prometheus collecters for Proxmox VE cluster.
"""
# pylint: disable=too-few-public-methods

import logging

from prometheus_client.core import GaugeMetricFamily


class NodeConfigCollector:
"""
Collects Proxmox VE VM information directly from config, i.e. boot, name, onboot, etc.
For manual test: "pvesh get /nodes/<node>/<type>/<vmid>/config"

# HELP pve_onboot_status Proxmox vm config onboot value
# TYPE pve_onboot_status gauge
pve_onboot_status{id="qemu/113",node="XXXX",type="qemu"} 1.0
"""

def __init__(self, pve):
self._pve = pve
self._log = logging.getLogger(__name__)

def collect(self): # pylint: disable=missing-docstring
metrics = {
'onboot': GaugeMetricFamily(
'pve_onboot_status',
'Proxmox vm config onboot value',
labels=['id', 'node', 'type']),
}

node = None
for entry in self._pve.cluster.status.get():
if entry['type'] == 'node' and entry['local']:
node = entry['name']
break

# Scrape qemu config
vmtype = 'qemu'
for vmdata in self._pve.nodes(node).qemu.get():
config = self._pve.nodes(node).qemu(
vmdata['vmid']).config.get().items()
for key, metric_value in config:
label_values = [f"{vmtype}/{vmdata['vmid']}", node, vmtype]
if key in metrics:
metrics[key].add_metric(label_values, metric_value)

# Scrape LXC config
vmtype = 'lxc'
for vmdata in self._pve.nodes(node).lxc.get():
config = self._pve.nodes(node).lxc(
vmdata['vmid']).config.get().items()
for key, metric_value in config:
label_values = [f"{vmtype}/{vmdata['vmid']}", node, vmtype]
if key in metrics:
metrics[key].add_metric(label_values, metric_value)

return metrics.values()
2 changes: 1 addition & 1 deletion src/pve_exporter/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from werkzeug.routing import Map, Rule
from werkzeug.wrappers import Request, Response
from werkzeug.exceptions import InternalServerError
from .collector import collect_pve
from pve_exporter.collector import collect_pve


class PveExporterApplication:
Expand Down