From 78a462dacd87698ad5c02695535b1b5090d15046 Mon Sep 17 00:00:00 2001 From: Oleksandr Liemieshko Date: Thu, 3 Nov 2016 20:19:28 -0700 Subject: [PATCH] Add: function analyze Add argument -a --analyze Add conf parameter 'analyze' Add analizys for scripts: 'df-m', 'df-i' Change-Id: I0697777da6be773103b337c001df8aaeb4290db1 (cherry picked from commit abdb347e6b9c5a23a43421228aeb77edc997f0b9) --- specs/python-timmy.spec | 2 +- timmy/analyze.py | 123 ++++++++++++++++++++++++++++++++++++++++ timmy/cli.py | 11 ++++ timmy/conf.py | 1 + timmy/env.py | 2 +- 5 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 timmy/analyze.py diff --git a/specs/python-timmy.spec b/specs/python-timmy.spec index ceb734a..ec5f11f 100644 --- a/specs/python-timmy.spec +++ b/specs/python-timmy.spec @@ -4,7 +4,7 @@ %global pypi_name timmy Name: python-%{pypi_name} -Version: 1.23.7 +Version: 1.24.0 Release: 1%{?dist}~mos0 Summary: Log collector tool for OpenStack Fuel diff --git a/timmy/analyze.py b/timmy/analyze.py new file mode 100644 index 0000000..a0ce128 --- /dev/null +++ b/timmy/analyze.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# Copyright 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, 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. + +from timmy.env import project_name +import logging +import sys + + +logger = logging.getLogger(project_name) + + +def analyze(node_manager): + col_msg = 'Column "%s" not found in output of "%s" from node "%s"' + green = 0 + unknown = 1 + yellow = 2 + red = 3 + + def parse_df_m(data, script, node): + column_use = "Use%" + full = 100 + near_full = 80 + health = green + details = [] + if column_use not in data[0]: + logger.warning(col_msg % (column_use, script, node.repr)) + health = unknown + index = data[0].split().index(column_use) + for line in data[2:]: + value = int(line.split()[index][:-1]) + if value >= full: + health = red + details.append(line) + elif value >= near_full: + health = yellow if health < yellow else health + details.append(line) + return health, details + + def parse_df_i(data, script, node): + column_use = "IUse%" + full = 100 + near_full = 80 + health = green + details = [] + if column_use not in data[0]: + logger.warning(col_msg % (column_use, script, node.repr)) + health = unknown + index = data[0].split().index(column_use) + for line in data[2:]: + if "%" in line.split()[index]: + value = int(line.split()[index][:-1]) + if value >= full: + health = red + details.append(line) + elif value >= near_full: + health = yellow if health < yellow else health + details.append(line) + return health, details + + fn_mapping = {"df-m": parse_df_m, + "df-i": parse_df_i} + results = {} + for node in node_manager.nodes.values(): + for script, output_file in node.mapscr.items(): + if script in fn_mapping: + with open(output_file, "r") as f: + data = [l.rstrip() for l in f.readlines()] + health, details = fn_mapping[script](data, script, node) + if node.repr not in results: + results[node.repr] = [] + results[node.repr].append({"script": script, + "output_file": output_file, + "health": health, + "details": details}) + node_manager.analyze_results = results + + +def analyze_print_results(node_manager): + code_colors = {3: ["RED", "\033[91m"], + 2: ["YELLOW", "\033[93m"], + 0: ["GREEN", "\033[92m"], + 1: ["BLUE", "\033[94m"]} + color_end = "\033[0m" + print("Nodes health analysis:") + for node, result in node_manager.analyze_results.items(): + node_health = max([x["health"] for x in result]) + node_color = code_colors[node_health][1] + health_repr = code_colors[node_health][0] + print(" %s%s: %s%s" % (node_color, node, health_repr, color_end)) + if node_health == 0: + continue + for r in result: + if r['health'] == 0: + continue + color = code_colors[r["health"]][1] + sys.stdout.write(color) + for key, value in r.items(): + if key == "health": + value = code_colors[value][0] + if key == "details" and len(value) > 0: + if len(value) > 1: + print(" details:") + for d in value: + print(" - %s" % d) + else: + print(" details: %s" % value[0]) + elif key != "details": + print(" %s: %s" % (key, value)) + sys.stdout.write(color_end) diff --git a/timmy/cli.py b/timmy/cli.py index fad826d..ee9c78b 100755 --- a/timmy/cli.py +++ b/timmy/cli.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from timmy.analyze import analyze, analyze_print_results from timmy.env import project_name, version from timmy.nodes import Node from timmy.tools import signal_wrapper @@ -171,6 +172,9 @@ def parser_init(add_help=False): parser.add_argument('-m', '--module', metavar='INVENTORY MODULE', default='fuel', help='Use module to get node data') + parser.add_argument('-a', '--analyze', action='store_true', + help=('Analyze collected outputs to determine node or' + 'service health and print results')) parser.add_argument('-v', '--verbose', action='count', default=0, help=('This works for -vvvv, -vvv, -vv, -v, -v -v,' 'etc, If no -v then logging.WARNING is ' @@ -286,6 +290,8 @@ def main(argv=None): # this is mainly to see the path in logs instad of "" conf['archive_dir'] = os.getcwd() conf['archive_name'] = os.path.split(args.dest_file)[1] + if args.analyze: + conf['analyze'] = True logger.info('Using rqdir: %s, rqfile: %s' % (conf['rqdir'], conf['rqfile'])) nm = pretty_run(args.quiet, 'Initializing node data', @@ -315,6 +321,9 @@ def main(argv=None): if nm.has(Node.ckey, Node.skey): pretty_run(args.quiet, 'Executing commands and scripts', nm.run_commands, args=(args.maxthreads,)) + if conf['analyze']: + pretty_run(args.quiet, 'Analyzing outputs', analyze, + args=[nm]) if nm.has('scripts_all_pairs'): pretty_run(args.quiet, 'Executing paired scripts', nm.run_scripts_all_pairs, args=(args.maxthreads,)) @@ -350,6 +359,8 @@ def main(argv=None): for line in output_dict[node.ip]['output']: name = output_dict[node.ip]['name'].rjust(maxlength) print("%s: %s" % (name, line)) + if conf['analyze']: + analyze_print_results(nm) if nm.has(Node.ckey, Node.skey, Node.fkey, Node.flkey) and not args.quiet: print('Outputs and/or files available in "%s".' % nm.conf['outdir']) if all([not args.no_archive, nm.has(*Node.conf_archive_general), diff --git a/timmy/conf.py b/timmy/conf.py index d20bd7c..5ebcbd0 100644 --- a/timmy/conf.py +++ b/timmy/conf.py @@ -72,6 +72,7 @@ def init_default_conf(): conf['do_print_results'] = False '''Clean - erase previous results in outdir and archive_dir dir, if any.''' conf['clean'] = True + conf['analyze'] = False return conf diff --git a/timmy/env.py b/timmy/env.py index 401b14a..41f0d97 100644 --- a/timmy/env.py +++ b/timmy/env.py @@ -16,7 +16,7 @@ # under the License. project_name = 'timmy' -version = '1.23.7' +version = '1.24.0' if __name__ == '__main__': import sys