diff --git a/README.md b/README.md index 4ae89b3..89c7b97 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ default: fetch_custom_attributes: True fetch_tags: True fetch_alarms: True + #exporter_metrics: True collect_only: vms: True vmguests: True @@ -113,6 +114,14 @@ limited: ``` Switching sections can be done by adding ?section=limited to the URL. +#### Montitoring the exporter metrics + +The exporter exposes metrics about its own behavior as [recommended](https://prometheus.io/docs/instrumenting/writing_clientlibs/#exposition) by Prometheus. + +Those metrics are available in the `/metrics` page. By default, those metrics are only exposed in the 'default' section (no need to collect it multiple times). This behavior can be controlled using the `VSPHERE_EXPORTER_METRICS` variable or using `exporter_metrics` in the configuration file. + +The metrics about the vmware_exporter process are only available on Linux (see [prometheus/client_python](https://github.com/prometheus/client_python#process-collector)). + #### Environment Variables | Variable | Precedence | Defaults | Description | | --------------------------------------| ---------------------- | -------- | --------------------------------------------------------------------------| @@ -124,6 +133,7 @@ Switching sections can be done by adding ?section=limited to the URL. | `VSPHERE_FETCH_CUSTOM_ATTRIBUTES` | config, env | False | Set to true to collect objects custom attributes as metric labels | | `VSPHERE_FETCH_TAGS` | config, env | False | Set to true to collect objects tags as metric labels | | `VSPHERE_FETCH_ALARMS` | config, env | False | Fetch objects triggered alarms, and in case of hosts hdw alarms as well | +| `VSPHERE_EXPORTER_METRICS` | config, env | True | Set fo false to disable exposing the exporter metric in section default | | `VSPHERE_COLLECT_HOSTS` | config, env | True | Set to false to disable collection of host metrics | | `VSPHERE_COLLECT_DATASTORES` | config, env | True | Set to false to disable collection of datastore metrics | | `VSPHERE_COLLECT_VMS` | config, env | True | Set to false to disable collection of virtual machine metrics | @@ -142,6 +152,7 @@ You can create new sections as well, with very similiar variables. For example, | `VSPHERE_LIMITED_FETCH_CUSTOM_ATTRIBUTES` | config, env | False | Set to true to collect objects custom attributes as metric labels | | `VSPHERE_LIMITED_FETCH_TAGS` | config, env | False | Set to true to collect objects tags as metric labels | | `VSPHERE_LIMITED_FETCH_ALARMS` | config, env | False | Fetch objects triggered alarms, and in case of hosts hdw alarms as well | +| `VSPHERE_LIMITED_EXPORTER_METRICS` | config, env | * | Expose exporter metrics ( * Enabled only in section 'default' by default) | | `VSPHERE_LIMITED_COLLECT_HOSTS` | config, env | True | Set to false to disable collection of host metrics | | `VSPHERE_LIMITED_COLLECT_DATASTORES` | config, env | True | Set to false to disable collection of datastore metrics | | `VSPHERE_LIMITED_COLLECT_VMS` | config, env | True | Set to false to disable collection of virtual machine metrics | diff --git a/tests/unit/test_vmware_exporter.py b/tests/unit/test_vmware_exporter.py index 9aa54a9..07e38e2 100644 --- a/tests/unit/test_vmware_exporter.py +++ b/tests/unit/test_vmware_exporter.py @@ -1,6 +1,7 @@ import contextlib import datetime from unittest import mock +from sys import platform import pytest import pytest_twisted @@ -1525,10 +1526,15 @@ def test_vmware_resource_async_render_GET(): b'vsphere_host': [b'127.0.0.1'], } + env = { + 'VSPHERE_EXPORTER_METRICS': False, + } + args = mock.Mock() args.config_file = None - resource = VMWareMetricsResource(args) + with mock.patch('vmware_exporter.vmware_exporter.os.environ', env): + resource = VMWareMetricsResource(args) with mock.patch('vmware_exporter.vmware_exporter.VmwareCollector') as Collector: Collector.return_value.collect.return_value = [] @@ -1649,6 +1655,39 @@ def test_vmware_resource_async_render_GET_section(): request.finish.assert_called_with() +@pytest.mark.skipif(platform.startswith("win"), reason="Requires Linux") +@pytest_twisted.inlineCallbacks +def test_vmware_resource_async_render_GET_exportermetrics(): + request = mock.Mock() + request.args = { + b'vsphere_host': [b'127.0.0.1'], + } + + env = { + 'VSPHERE_EXPORTER_METRICS': True, + } + + args = mock.Mock() + args.config_file = None + + with mock.patch('vmware_exporter.vmware_exporter.os.environ', env): + resource = VMWareMetricsResource(args) + + with mock.patch('vmware_exporter.vmware_exporter.VmwareCollector') as Collector: + Collector.return_value.collect.return_value = [] + yield resource._async_render_GET(request) + + request.setResponseCode.assert_called_with(200) + + metrics = [] + for s in request.write.call_args[0][0].decode("utf-8").splitlines(): + if not s.startswith("#"): + metrics.append(s.split(' ', 1)[0].split('{', 1)[0]) + + assert 'process_resident_memory_bytes' in metrics + assert 'vmware_exporter_build_info' in metrics + + def test_config_env_multiple_sections(): env = { 'VSPHERE_HOST': '127.0.0.10', @@ -1680,6 +1719,7 @@ def test_config_env_multiple_sections(): 'fetch_custom_attributes': True, 'fetch_tags': True, 'fetch_alarms': True, + 'exporter_metrics': True, 'collect_only': { 'datastores': True, 'hosts': True, @@ -1697,6 +1737,7 @@ def test_config_env_multiple_sections(): 'fetch_custom_attributes': False, 'fetch_tags': False, 'fetch_alarms': False, + 'exporter_metrics': False, 'collect_only': { 'datastores': True, 'hosts': True, diff --git a/vmware_exporter/vmware_exporter.py b/vmware_exporter/vmware_exporter.py index adca198..3840fdc 100755 --- a/vmware_exporter/vmware_exporter.py +++ b/vmware_exporter/vmware_exporter.py @@ -44,10 +44,12 @@ # Prometheus specific imports from prometheus_client.core import GaugeMetricFamily -from prometheus_client import CollectorRegistry, generate_latest +from prometheus_client import CollectorRegistry, generate_latest, Gauge +from prometheus_client.core import REGISTRY from .helpers import batch_fetch_properties, get_bool_env from .defer import parallelize, run_once_property +from .__init__ import __version__ from .__init__ import __version__ @@ -1890,6 +1892,7 @@ def configure(self, args): 'fetch_custom_attributes': get_bool_env('VSPHERE_FETCH_CUSTOM_ATTRIBUTES', False), 'fetch_tags': get_bool_env('VSPHERE_FETCH_TAGS', False), 'fetch_alarms': get_bool_env('VSPHERE_FETCH_ALARMS', False), + 'exporter_metrics': get_bool_env('VSPHERE_EXPORTER_METRICS', True), 'collect_only': { 'vms': get_bool_env('VSPHERE_COLLECT_VMS', True), 'vmguests': get_bool_env('VSPHERE_COLLECT_VMGUESTS', True), @@ -1917,6 +1920,7 @@ def configure(self, args): 'fetch_custom_attributes': get_bool_env('VSPHERE_{}_FETCH_CUSTOM_ATTRIBUTES'.format(section), False), 'fetch_tags': get_bool_env('VSPHERE_{}_FETCH_TAGS'.format(section), False), 'fetch_alarms': get_bool_env('VSPHERE_{}_FETCH_ALARMS'.format(section), False), + 'exporter_metrics': get_bool_env('VSPHERE_{}_EXPORTER_METRICS', section.lower() == "default"), 'collect_only': { 'vms': get_bool_env('VSPHERE_{}_COLLECT_VMS'.format(section), True), 'vmguests': get_bool_env('VSPHERE_{}_COLLECT_VMGUESTS'.format(section), True), @@ -1953,6 +1957,8 @@ def generate_latest_metrics(self, request): logging.info("{} is not a valid section, using default".format(section)) section = 'default' + get_exporter_metrics = self.config[section].get('exporter_metrics') + if self.config[section].get('vsphere_host') and self.config[section].get('vsphere_host') != "None": vsphere_host = self.config[section].get('vsphere_host') elif request.args.get(b'target', [None])[0]: @@ -1979,8 +1985,23 @@ def generate_latest_metrics(self, request): ) metrics = yield collector.collect() - registry = CollectorRegistry() - registry.register(ListCollector(metrics)) + do_metrics_registration = True + + if get_exporter_metrics: + registry = REGISTRY + if 'vmware_exporter_build_info' in registry._names_to_collectors: + do_metrics_registration = False + else: + g = Gauge('vmware_exporter_build_info', + 'A metric with a constant \'1\' value labeled by version of vmware-exporter.', + ["version"], registry=registry) + g.labels(version=__version__) + else: + registry = CollectorRegistry() + + if do_metrics_registration: + registry.register(ListCollector(metrics)) + output = generate_latest(registry) request.setHeader("Content-Type", "text/plain; charset=UTF-8")