Skip to content

Commit

Permalink
Add Cassandra/Scylla export plugin #857
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolargo committed Jun 10, 2016
1 parent 5065ef1 commit 02e9aa7
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 5 deletions.
1 change: 1 addition & 0 deletions NEWS
Expand Up @@ -26,6 +26,7 @@ Enhancements and new features:
* Allow theme to be set in configuration file (issue #862)
* Display a warning message when Glances is outdated (issue #865)
* Refactor stats history and export to graph. History available through API (issue #696)
* Add Cassandra/Scylla export plugin (issue #857)

Bugs corrected:

Expand Down
12 changes: 12 additions & 0 deletions conf/glances.conf
Expand Up @@ -220,6 +220,18 @@ db=glances
prefix=localhost
#tags=foo:bar,spam:eggs

[cassandra]
# Configuration for the --export-cassandra option
# Also works for the ScyllaDB
# https://influxdb.com/ or http://www.scylladb.com/
host=localhost
port=9042
protocol_version=3
keyspace=glances
replication_factor=2
# If not define, table name is set to host key
table=localhost

[opentsdb]
# Configuration for the --export-opentsdb option
# http://opentsdb.net/
Expand Down
4 changes: 4 additions & 0 deletions docs/cmds.rst
Expand Up @@ -131,6 +131,10 @@ Command-Line Options

export stats to an InfluxDB server (influxdb lib needed)

.. option:: --export-cassandra

export stats to a Cassandra/Scylla server (cassandra lib needed)

.. option:: --export-opentsdb

export stats to an OpenTSDB server (potsdb lib needed)
Expand Down
2 changes: 0 additions & 2 deletions docs/config.rst
Expand Up @@ -47,8 +47,6 @@ A first section (called global) is available:
# Does Glances should check if a newer version is available on Pypi ?
check_update=true
Each plugin, export module and application monitoring process (AMP) can have a
section. Below an example for the CPU plugin:

Expand Down
3 changes: 2 additions & 1 deletion docs/glances.rst
Expand Up @@ -42,7 +42,8 @@ Monitor local machine and export stats to a CSV file:
$ glances --export-csv

Monitor local machine and export stats to a InfluxDB server with 5s
refresh time:
refresh time (also possible to export to OpenTSDB, Cassandra, Statsd,
ElasticSearch, RabbitMQ and Riemann):

$ glances -t 5 --export-influxdb

Expand Down
32 changes: 32 additions & 0 deletions docs/gw/cassandra.rst
@@ -0,0 +1,32 @@
.. _cassandra:

CASSANDRA
=========

You can export statistics to an ``Cassandra`` or ``Scylla`` server.
The connection should be defined in the Glances configuration file as
following:

.. code-block:: ini
[cassandra]
host=localhost
port=9042
protocol_version=3
keyspace=glances
replication_factor=2
table=localhost
and run Glances with:

.. code-block:: console
$ glances --export-cassandra
The data model is the following:

.. code-block:: ini
CREATE TABLE <table> (plugin text, time timeuuid, stat map<text,float>, PRIMARY KEY (plugin, time))
Only numerical stats are stored in the Cassandra table. All the stats are converted to float.
3 changes: 2 additions & 1 deletion docs/gw/index.rst
Expand Up @@ -4,13 +4,14 @@ Gateway To Other Services
=========================

Glances can exports stats to a CSV file. Also, it can act as a gateway
to providing stats to multiple services.
to providing stats to multiple services (see list below).

.. toctree::
:maxdepth: 2

csv
influxdb
cassandra
opentsdb
statsd
rabbitmq
Expand Down
152 changes: 152 additions & 0 deletions glances/exports/glances_cassandra.py
@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
#
# This file is part of Glances.
#
# Copyright (C) 2016 Nicolargo <nicolas@nicolargo.com>
#
# 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 <http://www.gnu.org/licenses/>.

"""Cassandra/Scylla interface class."""

import sys
from numbers import Number

from glances.compat import NoOptionError, NoSectionError
from glances.logger import logger
from glances.exports.glances_export import GlancesExport

from cassandra.cluster import Cluster
from cassandra.util import uuid_from_time
from cassandra import InvalidRequest
from datetime import datetime


class Export(GlancesExport):

"""This class manages the Cassandra/Scylla export module."""

def __init__(self, config=None, args=None):
"""Init the Cassandra export IF."""
super(Export, self).__init__(config=config, args=args)

# Load the Cassandra configuration file section
self.host = None
self.port = None
self.protocol_version = 3
self.keyspace = None
self.replication_factor = 2
self.table = None
self.export_enable = self.load_conf()
if not self.export_enable:
sys.exit(2)

# Init the Cassandra client
self.cluster, self.session = self.init()

def load_conf(self, section="cassandra"):
"""Load the Cassandra configuration in the Glances configuration file."""
if self.config is None:
return False
try:
self.host = self.config.get_value(section, 'host')
self.port = self.config.get_value(section, 'port')
self.keyspace = self.config.get_value(section, 'keyspace')
except NoSectionError:
logger.critical("No Cassandra configuration found")
return False
except NoOptionError as e:
logger.critical("Error in the Cassandra configuration (%s)" % e)
return False
else:
logger.debug("Load Cassandra from the Glances configuration file")

# Optionals keys
try:
self.protocol_version = self.config.get_value(section, 'protocol_version')
except NoOptionError:
pass
try:
self.replication_factor = self.config.get_value(section, 'replication_factor')
except NoOptionError:
pass
try:
self.table = self.config.get_value(section, 'table')
except NoOptionError:
self.table = self.host

return True

def init(self):
"""Init the connection to the InfluxDB server."""
if not self.export_enable:
return None

# Cluster
try:
cluster = Cluster([self.host],
port=int(self.port),
protocol_version=int(self.protocol_version))
session = cluster.connect()
except Exception as e:
logger.critical("Cannot connect to Cassandra cluster '%s:%s' (%s)" % (self.host, self.port, e))
sys.exit(2)

# Keyspace
try:
session.set_keyspace(self.keyspace)
except InvalidRequest as e:
logger.info("Create keyspace {} on the Cassandra cluster".format(self.keyspace))
c = "CREATE KEYSPACE %s WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '%s' }" % (self.keyspace, self.replication_factor)
session.execute(c)
session.set_keyspace(self.keyspace)

logger.info(
"Stats will be exported to Cassandra cluster {0} ({1}) in keyspace {2}".format(cluster.metadata.cluster_name,
cluster.metadata.all_hosts(),
self.keyspace))

# Table
try:
session.execute("CREATE TABLE %s (plugin text, time timeuuid, stat map<text,float>, PRIMARY KEY (plugin, time)) WITH CLUSTERING ORDER BY (time DESC)" % self.table)
except:
logger.debug("Cassandra table %s already exist" % self.table)

return cluster, session

def export(self, name, columns, points):
"""Write the points to the Cassandra cluster."""
logger.debug("Export {} stats to Cassandra".format(name))

# Remove non number stats and convert all to float (for Boolean)
data = {k: float(v) for (k, v) in dict(zip(columns, points)).iteritems() if isinstance(v, Number)}

# Write input to the Cassandra table
try:
self.session.execute(
"""
INSERT INTO localhost (plugin, time, stat)
VALUES (%s, %s, %s)
""",
(name, uuid_from_time(datetime.now()), data)
)
except Exception as e:
logger.error("Cannot export {} stats to Cassandra ({})".format(name, e))

def exit(self):
"""Close the Cassandra export module."""
# To ensure all connections are properly closed
self.session.shutdown()
self.cluster.shutdown()
# Call the father method
super(Export, self).exit()
4 changes: 3 additions & 1 deletion glances/main.py
Expand Up @@ -159,6 +159,8 @@ def init_args(self):
dest='export_csv', help='export stats to a CSV file')
parser.add_argument('--export-influxdb', action='store_true', default=False,
dest='export_influxdb', help='export stats to an InfluxDB server (influxdb lib needed)')
parser.add_argument('--export-cassandra', action='store_true', default=False,
dest='export_cassandra', help='export stats to a Cassandra or Scylla server (cassandra lib needed)')
parser.add_argument('--export-opentsdb', action='store_true', default=False,
dest='export_opentsdb', help='export stats to an OpenTSDB server (potsdb lib needed)')
parser.add_argument('--export-statsd', action='store_true', default=False,
Expand Down Expand Up @@ -328,7 +330,7 @@ def parse_args(self):
self.args = args

# Export is only available in standalone or client mode (issue #614)
export_tag = args.export_csv or args.export_elasticsearch or args.export_statsd or args.export_influxdb or args.export_opentsdb or args.export_rabbitmq
export_tag = args.export_csv or args.export_elasticsearch or args.export_statsd or args.export_influxdb or args.export_cassandra or args.export_opentsdb or args.export_rabbitmq
if not (self.is_standalone() or self.is_client()) and export_tag:
logger.critical("Export is only available in standalone or client mode")
sys.exit(2)
Expand Down

0 comments on commit 02e9aa7

Please sign in to comment.