Couple of updates: telegraf line protocol, dynamic imports, metadata (#1)

* Couple of updates: telegraf line protocol, dynamic imports, metadata

This change uses the pkgutil module to import the plugins and load the
selected module. This same method is also used to list all of the
available modules.

To test run without installing the app pah has been added to the module
main using the sys path as found at runtime.

The telegraf line protocol has been added to allow this project to be
run with an exec stanza within telegraf allowing it to store metrics in
InfluxDB.

Meta has been added to the example plugin allowing additional meta data
to be returned in the result. This provides the ability to add tags or
other metadata into a given monitoring system using a simple key=value
format.

Signed-off-by: Kevin Carter <kevin.carter@rackspace.com>

* added fixes for pep8

Signed-off-by: Kevin Carter <kevin.carter@rackspace.com>
This commit is contained in:
Kevin Carter 2017-03-01 09:30:43 -06:00 committed by Major Hayden
parent 5f9ddd6dba
commit 5aca2d2940
6 changed files with 90 additions and 24 deletions

3
.gitignore vendored
View File

@ -78,6 +78,9 @@ celerybeat-schedule
# dotenv
.env
# Editor files
.idea/
# virtualenv
venv/
ENV/

View File

@ -1 +1,2 @@
Kevin Carter <kevin.carter@rackspace.com>
Major Hayden <major@mhtx.net>

View File

@ -1,7 +1,6 @@
CHANGES
=======
* Uptime class
* Update README
* Import
* Couple of updates: telegraf line protocol, dynamic imports, metadata
* Proof of concept
* Initial commit

View File

@ -13,15 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Handle all shell commands/arguments/options."""
import importlib
import json
import os
import pkgutil
import sys
import time
import click
CONTEXT_SETTINGS = dict(auto_envvar_prefix='MonitorStack')
context_settings = dict(auto_envvar_prefix='MonitorStack')
def current_time():
"""Return the current time in nanoseconds"""
return int(time.time() * 1000000000)
class Context(object):
@ -45,41 +53,49 @@ class Context(object):
pass_context = click.make_pass_decorator(Context, ensure=True)
cmd_folder = os.path.abspath(os.path.join(os.path.dirname(__file__),
'plugins'))
class MonitorStackCLI(click.MultiCommand):
"""Create a complex command finder."""
@property
def cmd_folder(self):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
'plugins'
)
)
def list_commands(self, ctx):
"""Get a list of all available commands."""
rv = []
for filename in os.listdir(cmd_folder):
if filename.endswith('.py') and not filename.startswith('__'):
rv.append(filename[:-3])
rv.sort()
return rv
rv = list()
for _, pkg_name, _ in pkgutil.iter_modules([self.cmd_folder]):
rv.append(pkg_name)
else:
return sorted(rv)
def get_command(self, ctx, name):
"""Load a command and run it."""
try:
if sys.version_info[0] == 2:
name = name.encode('ascii', 'replace')
mod = __import__('monitorstack.plugins.' + name,
None, None, ['cli'])
except ImportError:
return
return mod.cli
for _, pkg_name, _ in pkgutil.iter_modules([self.cmd_folder]):
if pkg_name == name:
mod = importlib.import_module(
'monitorstack.plugins.{}'.format(name)
)
return getattr(mod, 'cli')
else:
raise SystemExit('Module "{}" Not Found.'.format(name))
VALID_OUTPUT_FORMATS = [
'json',
'line',
'telegraf'
]
@click.command(cls=MonitorStackCLI, context_settings=CONTEXT_SETTINGS)
@click.command(cls=MonitorStackCLI, context_settings=context_settings)
@click.option(
'-f', '--format', 'output_format',
type=click.Choice(VALID_OUTPUT_FORMATS),
@ -106,8 +122,45 @@ def process_result(result, output_format, verbose):
for key, value in result['variables'].items():
click.echo("{} {}".format(key, value))
elif output_format == 'telegraf':
def line_format(sets, quote=False):
store = list()
for k, v in sets.items():
k = k.replace(' ', '_')
for v_type in [int, float]:
try:
v = v_type(v)
except ValueError:
pass # v was not a int, float, or long
else:
break
if not isinstance(v, (int, float, bool)) and quote:
store.append('{}="{}"'.format(k, v))
else:
store.append('{}={}'.format(k, v))
return ','.join(store).rstrip(',')
resultant = [result['measurement_name']]
if 'meta' in result:
resultant.append(line_format(sets=result['meta']))
resultant.append(line_format(sets=result['variables'], quote=True))
resultant.append(current_time())
click.echo(' '.join(resultant))
elif output_format == 'csv':
pass
if __name__ == '__main__':
topdir = os.path.normpath(
os.path.join(
os.path.abspath(
sys.argv[0]
),
os.pardir,
os.pardir
)
)
sys.path.insert(0, topdir)
cli()

View File

@ -12,8 +12,13 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Base monitoring class."""
import platform
import click
from monitorstack.cli import pass_context
@ -27,6 +32,10 @@ def cli(ctx):
output = {
'exit_code': 0,
'message': 'uptime is ok',
'measurement_name': 'system_uptime',
'meta': {
'platform': platform.platform()
},
'variables': {
'uptime': uptime
}

View File

@ -11,11 +11,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for the base class."""
import click
from click.testing import CliRunner
import json
from click.testing import CliRunner
from monitorstack.cli import cli