From 5aca2d294013383cdbe86665712279ad4904e6f1 Mon Sep 17 00:00:00 2001 From: Kevin Carter Date: Wed, 1 Mar 2017 09:30:43 -0600 Subject: [PATCH] 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 * added fixes for pep8 Signed-off-by: Kevin Carter --- .gitignore | 3 ++ AUTHORS | 1 + ChangeLog | 5 +- monitorstack/cli.py | 91 +++++++++++++++++++++++++++------- monitorstack/plugins/uptime.py | 9 ++++ tests/test_uptime.py | 5 +- 6 files changed, 90 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 72364f9..b5a0080 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,9 @@ celerybeat-schedule # dotenv .env +# Editor files +.idea/ + # virtualenv venv/ ENV/ diff --git a/AUTHORS b/AUTHORS index a42a9eb..7579f64 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ +Kevin Carter Major Hayden diff --git a/ChangeLog b/ChangeLog index 9117a79..f93e999 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,6 @@ CHANGES ======= -* Uptime class -* Update README -* Import +* Couple of updates: telegraf line protocol, dynamic imports, metadata +* Proof of concept * Initial commit diff --git a/monitorstack/cli.py b/monitorstack/cli.py index 2cc1529..4caac9c 100755 --- a/monitorstack/cli.py +++ b/monitorstack/cli.py @@ -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() diff --git a/monitorstack/plugins/uptime.py b/monitorstack/plugins/uptime.py index 635761d..1b8e19e 100644 --- a/monitorstack/plugins/uptime.py +++ b/monitorstack/plugins/uptime.py @@ -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 } diff --git a/tests/test_uptime.py b/tests/test_uptime.py index f9758cf..ff764a9 100644 --- a/tests/test_uptime.py +++ b/tests/test_uptime.py @@ -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