From 6bceeef43f2988e6485e047068d0bd38021ae6f8 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Sun, 9 May 2021 18:17:14 +0200 Subject: Export to graphite #1854 --- README.rst | 3 +- conf/glances.conf | 15 +++++ docs/gw/graphite.rst | 33 +++++++++++ glances/exports/glances_graphite.py | 112 ++++++++++++++++++++++++++++++++++++ glances/exports/glances_opentsdb.py | 2 +- glances/exports/glances_statsd.py | 2 +- optional-requirements.txt | 1 + setup.py | 6 +- 8 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 docs/gw/graphite.rst create mode 100644 glances/exports/glances_graphite.py diff --git a/README.rst b/README.rst index e518768e..9caa49c4 100644 --- a/README.rst +++ b/README.rst @@ -75,6 +75,7 @@ Optional dependencies: - ``couchdb`` (for the CouchDB export module) - ``docker`` (for the Docker monitoring support) [Linux/macOS-only] - ``elasticsearch`` (for the Elastic Search export module) +- ``graphyte`` (For the Graphite export module) - ``hddtemp`` (for HDD temperature monitoring support) [Linux-only] - ``influxdb`` (for the InfluxDB version 1 export module) - ``influxdb-client`` (for the InfluxDB version 2 export module) [Only for Python >= 3.6] @@ -409,7 +410,7 @@ Gateway to other services Glances can export stats to: ``CSV`` file, ``JSON`` file, ``InfluxDB``, ``Cassandra``, ``CouchDB``, ``OpenTSDB``, ``Prometheus``, ``StatsD``, ``ElasticSearch``, ``RabbitMQ/ActiveMQ``, -``ZeroMQ``, ``Kafka``, ``Riemann`` and ``RESTful`` server. +``ZeroMQ``, ``Kafka``, ``Riemann``, ``Graphite`` and ``RESTful`` server. How to contribute ? =================== diff --git a/conf/glances.conf b/conf/glances.conf index 0ed47419..f1bd101d 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -586,6 +586,21 @@ port=6789 protocol=http path=/ +[graphite] +# Configuration for the --export graphite option +# https://graphiteapp.org/ +host=localhost +port=2003 +protocol=tcp +batch_size=1000 +# Prefix will be added for all measurement name +# Ex: prefix=foo +# => foo.cpu +# => foo.mem +# You can also use dynamic values +#prefix=`hostname` +prefix=glances + ############################################################################## # AMPS # * enable: Enable (true) or disable (false) the AMP diff --git a/docs/gw/graphite.rst b/docs/gw/graphite.rst new file mode 100644 index 00000000..33e765fe --- /dev/null +++ b/docs/gw/graphite.rst @@ -0,0 +1,33 @@ +.. _graphite: + +Graphite +======== + +You can export statistics to a ``Graphite`` server (time series server). + +The connection should be defined in the Glances configuration file as +following: + +.. code-block:: ini + + [graphite] + host=localhost + port=2003 + protocol=udp + batch_size=1000 + # Prefix will be added for all measurement name + # Ex: prefix=foo + # => foo.cpu + # => foo.mem + # You can also use dynamic values + #prefix=`hostname` + prefix=glances + +and run Glances with: + +.. code-block:: console + + $ glances --export graphite + + +Note: Only integer and float are supported in the Graphite datamodel. diff --git a/glances/exports/glances_graphite.py b/glances/exports/glances_graphite.py new file mode 100644 index 00000000..7d2c0d6e --- /dev/null +++ b/glances/exports/glances_graphite.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Glances. +# +# Copyright (C) 2021 Nicolargo +# +# Glances is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Glances is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +"""Graphite interface class.""" + +import sys +from numbers import Number + +from glances.compat import range +from glances.logger import logger +from glances.exports.glances_export import GlancesExport + +import graphyte + + +class Export(GlancesExport): + + """This class manages the Graphite export module.""" + + def __init__(self, config=None, args=None): + """Init the Graphite export IF.""" + super(Export, self).__init__(config=config, args=args) + + # Mandatories configuration keys (additional to host and port) + # N/A + + # Optionals configuration keys + self.prefix = None + self.protocol = None + self.batch_size = None + + # Load the configuration file + self.export_enable = self.load_conf('graphite', + mandatories=['host', + 'port'], + options=['prefix', + 'protocol', + 'batch_size']) + if not self.export_enable: + sys.exit(2) + + # Default prefix for stats is 'glances' + if self.prefix is None: + self.prefix = 'glances' + + if self.protocol is None: + self.protocol = 'tcp' + + if self.batch_size is None: + self.batch_size = 1000 + + # Convert config option type + self.port = int(self.port) + self.batch_size = int(self.batch_size) + + # Init the Graphite client + self.client = self.init() + + def init(self): + """Init the connection to the Graphite server.""" + if not self.export_enable: + return None + logger.info( + "Stats will be exported to Graphite server: {}:{}/{}".format(self.host, + self.port, + self.protocol)) + + return graphyte.Sender(self.host, + port=self.port, + prefix=self.prefix, + protocol=self.protocol, + batch_size=self.batch_size) + + def export(self, name, columns, points): + """Export the stats to the Graphite server.""" + for i in range(len(columns)): + if not isinstance(points[i], Number): + # Only Int and Float are supported in the Graphite datamodel + continue + stat_name = '{}.{}'.format(name, columns[i]) + stat_value = points[i] + try: + self.client.send(normalize(stat_name), + stat_value) + except Exception as e: + logger.error("Can not export stats to Graphite (%s)" % e) + logger.debug("Export {} stats to Graphite".format(name)) + + +def normalize(name): + """Normalize name for the Graphite convention""" + + # Name should not contain space + ret = name.replace(' ', '_') + + return ret diff --git a/glances/exports/glances_opentsdb.py b/glances/exports/glances_opentsdb.py index 5d2b6f5c..4cd2d4fd 100644 --- a/glances/exports/glances_opentsdb.py +++ b/glances/exports/glances_opentsdb.py @@ -44,7 +44,7 @@ class Export(GlancesExport): self.prefix = None self.tags = None - # Load the InfluxDB configuration file + # Load the configuration file self.export_enable = self.load_conf('opentsdb', mandatories=['host', 'port'], options=['prefix', 'tags']) diff --git a/glances/exports/glances_statsd.py b/glances/exports/glances_statsd.py index 0bae6a53..8ccfe7d9 100644 --- a/glances/exports/glances_statsd.py +++ b/glances/exports/glances_statsd.py @@ -43,7 +43,7 @@ class Export(GlancesExport): # Optionals configuration keys self.prefix = None - # Load the InfluxDB configuration file + # Load the configuration file self.export_enable = self.load_conf('statsd', mandatories=['host', 'port'], options=['prefix']) diff --git a/optional-requirements.txt b/optional-requirements.txt index fe7d440d..2a49ef2a 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -8,6 +8,7 @@ chevron couchdb docker>=2.0.0 elasticsearch +graphyte hddtemp influxdb influxdb-client; python_version >= "3.6" diff --git a/setup.py b/setup.py index f64e31d2..41feaad2 100755 --- a/setup.py +++ b/setup.py @@ -52,14 +52,14 @@ def get_install_requires(): def get_install_extras_require(): extras_require = { 'action': ['chevron'], - # Zeroconf 0.19.1 is the latest one compatible with Python 2 (issue #1293) 'browser': ['zeroconf==0.19.1' if PY2 else 'zeroconf>=0.19.1'], 'cloud': ['requests'], 'cpuinfo': ['py-cpuinfo<=4.0.0'], 'docker': ['docker>=2.0.0'], 'export': ['bernhard', 'cassandra-driver', 'couchdb', 'elasticsearch', - 'influxdb>=1.0.0', 'kafka-python', 'pika', 'paho-mqtt', 'potsdb', - 'prometheus_client', 'pyzmq', 'statsd'], + 'graphyte', 'influxdb>=1.0.0', 'kafka-python', 'pika', + 'paho-mqtt', 'potsdb', 'prometheus_client', 'pyzmq', + 'statsd'], 'folders': ['scandir'], # python_version<"3.5" 'gpu': ['py3nvml'], 'graph': ['pygal'], -- cgit v1.2.3