Skip to content

Commit

Permalink
provide auto registration facility for collectors (#18)
Browse files Browse the repository at this point in the history
To provide more than one simple collector we refactor the registration
of collectors to do this automatically.
From now all collectors that follows the convention for new collectors.
We also add a real life collector for load average (1, 5, 15).
Finally we prepare the new major release.
  • Loading branch information
cmeissner authored Mar 22, 2021
1 parent 854fd18 commit 98addf0
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[bumpversion]
commit = True
tag = False
current_version = 0.1.0
current_version = 1.0.0

[bumpversion:file:setup.py]
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch

### Breaks

## 1.0.0 - (2021-03-22)

### New

* now it is simplier to add new collectors. You have to simply follow the naming convention
* add loadavg collector as a real life example

### Breaks

* change load and registration behavior for collectors

## 0.1.0 - (2021-03-04)

### Changes
Expand Down
19 changes: 19 additions & 0 deletions docs/plugins/p3exporter.collector.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,22 @@ p3exporter.collector package
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

p3exporter.collector.loadavg module
-----------------------------------

.. automodule:: p3exporter.collector.loadavg
:members:
:undoc-members:
:show-inheritance:

p3exporter.collector.my module
------------------------------

.. automodule:: p3exporter.collector.my
:members:
:undoc-members:
:show-inheritance:
3 changes: 3 additions & 0 deletions p3.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
exporter_name: "Python prammable Prometheus exporter"
collectors:
- loadavg
- my
credentials:
7 changes: 2 additions & 5 deletions p3exporter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import sys
import time

from prometheus_client.core import REGISTRY

from p3exporter.collector import MyCollector, CollectorConfig
from p3exporter.collector import Collector, CollectorConfig
from p3exporter.web import create_app


Expand Down Expand Up @@ -43,8 +41,7 @@ def main():
cfg = yaml.load(config_file, Loader=yaml.SafeLoader)
collector_config = CollectorConfig(**cfg)

collector = MyCollector(collector_config)
REGISTRY.register(collector)
Collector(collector_config)

app = create_app(collector_config)

Expand Down
42 changes: 23 additions & 19 deletions p3exporter/collector/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Entry point for collector sub module."""
import random
import time
import inflection
import logging

from prometheus_client.core import GaugeMetricFamily
from importlib import import_module
from prometheus_client.core import REGISTRY


class CollectorConfig(object):
Expand All @@ -16,6 +17,7 @@ def __init__(self, **kwargs):
:raises Exception: Raises an exception if credentials are not well configured.
"""
self.exporter_name = kwargs.pop('exporter_name', None)
self.collectors = kwargs.pop('collectors', [])
self.credentials = kwargs.pop('credentials', None)

# do some fancy checks on configuration parameters
Expand All @@ -27,23 +29,25 @@ def __init__(self, **kwargs):
raise Exception('Credential is not fully configured.')


class MyCollector(object):
"""A sample collector.
class Collector(object):
"""Base class to load collectors.
It does not really do much. It only runs a method and return the time it runs as a gauge metric.
All collectors have to be placed inside the directory `collector`. You have to follow the naming convention:
1. Place the collector code in a <name>.py file (e.g. `my.py`)
2. Within the file <name>.py` a class <Name>Collector (e.g. `MyController`) needs to be defined.
This is the main collector class which will be imported, instantiate and registered automatically.
"""

def __init__(self, config: CollectorConfig):
"""Instanciate a MyCollector object."""
pass

def collect(self):
"""Collect the metrics."""
self.timer = time.perf_counter()
_run_process()
runtime = time.perf_counter() - self.timer
yield GaugeMetricFamily('my_process_runtime', 'Time a process runs in seconds', value=runtime)

def _run_process():
"""Sample function to ran a command for metrics."""
time.sleep(random.random()) # nosec
for c in config.collectors:
try:
collector_module = import_module("p3exporter.collector.{}".format(c), package=None)
collector_class = getattr(collector_module, "{0}Collector".format(inflection.camelize(c)))
collector = collector_class(config)
REGISTRY.register(collector)
logging.info("Collector '{0}' was loaded and registred sucessfully".format(c))
except ModuleNotFoundError as e:
logging.warning("Collector '{0}' not loaded: {1}".format(c, e.msg))
except AttributeError as e:
logging.warning("Collector '{0}' not loaded: {1}".format(c, e))
20 changes: 20 additions & 0 deletions p3exporter/collector/loadavg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os

from p3exporter.collector import CollectorConfig
from prometheus_client.core import GaugeMetricFamily

class LoadavgCollector(object):

def __init__(self, config: CollectorConfig):
"""Instanciate a CpuCollector object."""
pass

def collect(self):
"""Collect load avg for 1, 5 and 15 minutes interval.
Returns three gauge metrics. One for each load.
"""
self.avg1, self.avg5, self.avg15 = os.getloadavg()
yield GaugeMetricFamily('load_avg_1', "1m load average.", value=self.avg1)
yield GaugeMetricFamily('load_avg_5', "5m load average.", value=self.avg5)
yield GaugeMetricFamily('load_avg_15', "15m load average.", value=self.avg15)
26 changes: 26 additions & 0 deletions p3exporter/collector/my.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import random
import time

from p3exporter.collector import CollectorConfig
from prometheus_client.core import GaugeMetricFamily

class MyCollector(object):
"""A sample collector.
It does not really do much. It only runs a method and return the time it runs as a gauge metric.
"""

def __init__(self, config: CollectorConfig):
"""Instanciate a MyCollector object."""
pass

def collect(self):
"""Collect the metrics."""
self.timer = time.perf_counter()
_run_process()
runtime = time.perf_counter() - self.timer
yield GaugeMetricFamily('my_process_runtime', 'Time a process runs in seconds', value=runtime)

def _run_process():
"""Sample function to ran a command for metrics."""
time.sleep(random.random()) # nosec
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ flake8
flake8-colors
flake8-docstrings
flask
inflection
twine
setuptools
PyYAML
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
inflection
prometheus-client
PyYAML
flask
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='p3exporter',
version='0.1.0',
version='1.0.0',
description='Python Programmable Prometheus exporter',
long_description=long_description,
long_description_content_type='text/markdown',
Expand All @@ -31,6 +31,7 @@
zip_safe=False,
package_data={'p3exporter': ['static/*', 'templates/*']},
install_requires=[
'inflection',
'prometheus-client',
'PyYAML',
'flask',
Expand Down

0 comments on commit 98addf0

Please sign in to comment.