diff --git a/.dockerignore b/.dockerignore index 1d3ed4c..c10b279 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,9 @@ config.yml +alerts +build +dashboards +dist +kubernetes +openshift +systemd +tests \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4ade0e9..a5079ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,18 @@ -FROM python:3.7-alpine +FROM python:3.12.3-alpine LABEL MAINTAINER="Daniel Pryor " LABEL NAME=vmware_exporter WORKDIR /opt/vmware_exporter/ -COPY . /opt/vmware_exporter/ - +COPY requirements.txt setup.py README.md MANIFEST.in /opt/vmware_exporter/ RUN set -x; buildDeps="gcc python3-dev musl-dev libffi-dev openssl openssl-dev rust cargo" \ && apk add --no-cache --update $buildDeps \ - && pip install -r requirements.txt . \ + && pip install -r requirements.txt \ && apk del $buildDeps +COPY vmware_exporter/ /opt/vmware_exporter/vmware_exporter +RUN pip install . + + EXPOSE 9272 diff --git a/docker-compose.yml b/docker-compose.yml index 64d4dc3..8272d59 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,16 +2,14 @@ version: '2' services: vmware_exporter: # Using the latest tag, but you can use vers(v0.9.5 for example - image: pryorda/vmware_exporter:latest + image: docker.io/anthony84657/vmware_exporter:beta + build: + context: . + dockerfile: Dockerfile ports: - - "9275:9272" - environment: - VSPHERE_HOST: "vcenter-host" - VSPHERE_USER: "username" - VSPHERE_PASSWORD: "P@ssw0rd" - VSPHERE_IGNORE_SSL: "True" - VSPHERE_COLLECT_VMS: "False" - VSPHERE_COLLECT_VMGUESTS: "False" + - "9272:9272" + volumes: + - ./config.yml:/config.yml:ro restart: always #FOR DEBUG UNCOMMENT NEXT LINE - #command: ["-l","DEBUG"] + command: [ "-l", "DEBUG", "-c" , "/config.yml" ] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d924920..09f7071 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,26 @@ -prometheus-client==0.0.19 -pytz -pyvmomi>=6.5 -twisted>=14.0.2 -pyyaml>=5.1 -service-identity +attrs==24.2.0 +Automat==24.8.1 +certifi==2024.8.30 +cffi==1.17.1 +charset-normalizer==3.3.2 +constantly==23.10.4 +cryptography==43.0.1 +hyperlink==21.0.0 +idna==3.10 +incremental==24.7.2 +prometheus_client==0.0.19 +pyasn1==0.6.1 +pyasn1_modules==0.4.1 +pycparser==2.22 +pytz==2024.2 +pyvmomi==8.0.3.0.1 +PyYAML==6.0.2 +requests==2.32.3 +service-identity==24.1.0 +setuptools==72.1.0 +six==1.16.0 +Twisted==24.7.0 +typing_extensions==4.12.2 +urllib3==2.2.3 +wheel==0.44.0 +zope.interface==7.0.3 diff --git a/vmware_exporter/vmware_exporter.py b/vmware_exporter/vmware_exporter.py index e497fa2..8c658a0 100755 --- a/vmware_exporter/vmware_exporter.py +++ b/vmware_exporter/vmware_exporter.py @@ -94,10 +94,10 @@ def __init__( # label names and ammount will be needed later to insert labels from custom attributes self._labelNames = { - 'vms': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name'], - 'vm_perf': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name'], - 'vmguests': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name'], - 'snapshots': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name'], + 'vms': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name', 'uuid', 'instance_uuid', 'moid'], + 'vm_perf': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name', 'uuid', 'instance_uuid', 'moid'], + 'vmguests': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name', 'uuid', 'instance_uuid','moid'], + 'snapshots': ['vm_name', 'ds_name', 'host_name', 'dc_name', 'cluster_name', 'uuid', 'instance_uuid','moid'], 'datastores': ['ds_name', 'dc_name', 'ds_cluster'], 'hosts': ['host_name', 'dc_name', 'cluster_name'], 'host_perf': ['host_name', 'dc_name', 'cluster_name'], @@ -155,6 +155,10 @@ def _create_metric_containers(self): 'vmware_vm_guest_disk_capacity', 'Disk capacity metric per partition', labels=self._labelNames['vmguests'] + ['partition', ]), + 'vmware_vm_guest_disk_used_percent': GaugeMetricFamily( + 'vmware_vm_guest_disk_used_percent', + 'Disk capacity used per partition in percent', + labels=self._labelNames['vmguests'] + ['partition', ]), 'vmware_vm_guest_tools_running_status': GaugeMetricFamily( 'vmware_vm_guest_tools_running_status', 'VM tools running status', @@ -738,6 +742,8 @@ def vm_inventory(self): 'runtime.host', 'parent', 'summary.config.vmPathName', + 'summary.config.uuid', + 'summary.config.instanceUuid', ] if self.collect_only['vms'] is True: @@ -1104,6 +1110,15 @@ def vm_labels(self): if host_moid in host_labels: labels[moid] = labels[moid] + host_labels[host_moid] + if 'summary.config.uuid' in row: + labels[moid] += [row['summary.config.uuid']] + else: + labels[moid] += ["no_uuid"] + if 'summary.config.instanceUuid' in row: + labels[moid] += [row['summary.config.instanceUuid']] + else: + labels[moid] += ["no_instanceUuid"] + labels[moid] += [moid] """ this code was in vm_inventory before but I have the feeling it is best placed here where @@ -1188,7 +1203,7 @@ def updateMetricsLabelNames(self, metrics, metric_types): for metric_name in self._metricNames.get(metric_type, []): metric = metrics.get(metric_name) labelnames = metric._labelnames - metric._labelnames = labelnames[0:len(self._labelNames[metric_type])] + metric._labelnames = list(labelnames[0:len(self._labelNames[metric_type])]) metric._labelnames += customAttributesLabelNames metric._labelnames += labelnames[len(self._labelNames[metric_type]):] metric._labelnames = list(map(lambda x: re.sub('[^a-zA-Z0-9_]', '_', x), metric._labelnames)) @@ -1592,6 +1607,13 @@ def _vmware_get_vms(self, metrics): metrics['vmware_vm_guest_disk_capacity'].add_metric( labels + [disk.diskPath], disk.capacity ) + try: + percent_used = ((disk.capacity - disk.freeSpace) / disk.capacity) * 100 + metrics["vmware_vm_guest_disk_used_percent"].add_metric( + labels + [disk.diskPath], percent_used + ) + except ZeroDivisionError: + pass if 'guest.toolsStatus' in row: metrics['vmware_vm_guest_tools_running_status'].add_metric( @@ -1939,6 +1961,8 @@ def _async_render_GET(self, request): logging.error(traceback.format_exc()) request.setResponseCode(500) request.write(b'# Collection failed') + if request._disconnected: + return request.finish() # We used to call request.processingFailed to send a traceback to browser @@ -1982,10 +2006,11 @@ def generate_latest_metrics(self, request): registry = CollectorRegistry() registry.register(ListCollector(metrics)) output = generate_latest(registry) - request.setHeader("Content-Type", "text/plain; charset=UTF-8") request.setResponseCode(200) request.write(output) + if request._disconnected: + return request.finish()