Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new print stat function #16

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ _________

* **print_result** - Formats nicely and prints results on stdout.
* **print_title** - Formats nicely a title and prints it on stdout.
* **print_stat** - Prints result statistic on stdout.

Processors
__________
Expand Down
13 changes: 11 additions & 2 deletions nornir_utils/plugins/functions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
from .print_result import print_result, print_title
from .print_result import (
print_result,
print_title,
)

__all__ = ("print_result", "print_title")
from .print_stat import print_stat

__all__ = (
"print_result",
"print_title",
"print_stat",
)
59 changes: 51 additions & 8 deletions nornir_utils/plugins/functions/print_result.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging
import pprint
import threading
from typing import List, cast
from itertools import islice
from typing import List, cast, Optional, Dict, Any
from collections import OrderedDict
import json

Expand All @@ -16,12 +17,31 @@
init(autoreset=True, strip=False)


def _slice_result(
result: AggregatedResult,
count: Optional[int] = None,
):
results: Dict[Any, Any] = dict(sorted(result.items()))
if isinstance(count, int):
length = len(results)
if count >= 0:
_ = [0, length and count]
elif (length + count) < 0:
_ = [0, length]
else:
_ = [length + count, length]
results = dict(islice(results.items(), *_))
return results


def print_title(title: str) -> None:
"""
Helper function to print a title.
"""
msg = "**** {} ".format(title)
print("{}{}{}{}".format(Style.BRIGHT, Fore.GREEN, msg, "*" * (80 - len(msg))))
print(
"{}{}{}{}".format(Style.BRIGHT, Fore.GREEN, msg, "*" * (80 - len(msg)))
)


def _get_color(result: Result, failed: bool) -> str:
Expand All @@ -47,7 +67,9 @@ def _print_individual_result(

color = _get_color(result, failed)
subtitle = (
"" if result.changed is None else " ** changed : {} ".format(result.changed)
""
if result.changed is None
else " ** changed : {} ".format(result.changed)
)
level_name = logging.getLevelName(result.severity_level)
symbol = "v" if task_group else "-"
Expand Down Expand Up @@ -82,23 +104,31 @@ def _print_result(
failed: bool = False,
severity_level: int = logging.INFO,
print_host: bool = False,
count: Optional[int] = None,
) -> None:
attrs = attrs or ["diff", "result", "stdout"]
if isinstance(attrs, str):
attrs = [attrs]

if isinstance(result, AggregatedResult):
msg = result.name
print("{}{}{}{}".format(Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg))))
for host, host_data in sorted(result.items()):
msg = "{}{}{}{}".format(
Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg))
)
if count != 0:
print(msg)
result = _slice_result(result, count)
for host, host_data in result.items():
title = (
""
if host_data.changed is None
else " ** changed : {} ".format(host_data.changed)
)
msg = "* {}{}".format(host, title)
print(
"{}{}{}{}".format(Style.BRIGHT, Fore.BLUE, msg, "*" * (80 - len(msg)))
"{}{}{}{}".format(
Style.BRIGHT, Fore.BLUE, msg, "*" * (80 - len(msg))
)
)
_print_result(host_data, attrs, failed, severity_level)
elif isinstance(result, MultiResult):
Expand All @@ -114,7 +144,9 @@ def _print_result(
_print_result(r, attrs, failed, severity_level)
color = _get_color(result[0], failed)
msg = "^^^^ END {} ".format(result[0].name)
print("{}{}{}{}".format(Style.BRIGHT, color, msg, "^" * (80 - len(msg))))
print(
"{}{}{}{}".format(Style.BRIGHT, color, msg, "^" * (80 - len(msg)))
)
elif isinstance(result, Result):
_print_individual_result(
result, attrs, failed, severity_level, print_host=print_host
Expand All @@ -126,6 +158,7 @@ def print_result(
vars: List[str] = None,
failed: bool = False,
severity_level: int = logging.INFO,
count: Optional[int] = None,
) -> None:
"""
Prints an object of type `nornir.core.task.Result`
Expand All @@ -135,9 +168,19 @@ def print_result(
vars: Which attributes you want to print
failed: if ``True`` assume the task failed
severity_level: Print only errors with this severity level or higher
count: Number of sorted results. It's acceptable
to use numbers with minus sign(-5 as example),
then results will be from the end of results list
"""
LOCK.acquire()
try:
_print_result(result, vars, failed, severity_level, print_host=True)
_print_result(
result,
vars,
failed=failed,
severity_level=severity_level,
print_host=True,
count=count,
)
finally:
LOCK.release()
109 changes: 109 additions & 0 deletions nornir_utils/plugins/functions/print_stat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import threading
from typing import Optional, Tuple
from nornir_utils.plugins.functions.print_result import (
_get_color,
_slice_result,
)

from colorama import Fore, Style, init

from nornir.core.task import AggregatedResult, MultiResult, Result


LOCK = threading.Lock()


init(autoreset=True, strip=False)


def _print_individual_stat(
result: Result,
res_sum: int = 0,
ch_sum: int = 0,
f_sum: int = 0,
) -> Tuple[int, int, int]:

f, ch = (result.failed, result.changed)

res_sum += 1
ch_sum += int(ch)
f_sum += int(f)

color = _get_color(result, f)
msg = "{:<35} ok={:<15} changed={:<15} failed={:<15}".format(
result.name, not f, ch, f
)
print(" {}{}{}".format(Style.BRIGHT, color, msg))
return res_sum, ch_sum, f_sum


def _print_stat(
result: Result,
count: Optional[int] = None,
res_sum: int = 0,
ch_sum: int = 0,
f_sum: int = 0,
) -> Tuple[int, int, int]:

if isinstance(result, AggregatedResult):
msg = result.name
msg = "{}{}{}{}".format(
Style.BRIGHT, Fore.CYAN, msg, "*" * (80 - len(msg))
)
if count != 0:
print(msg)
result = _slice_result(result, count)
for host, host_data in result.items():
msg_host = "* {} ".format(host)
print(
"{}{}{}{}".format(
Style.BRIGHT,
Fore.BLUE,
msg_host,
"*" * (80 - len(msg_host)),
)
)
res_sum, ch_sum, f_sum = _print_stat(
host_data, count, res_sum, ch_sum, f_sum
)
elif isinstance(result, MultiResult):
for res in result:
res_sum, ch_sum, f_sum = _print_stat(
res, count, res_sum, ch_sum, f_sum
)
elif isinstance(result, Result):
res_sum, ch_sum, f_sum = _print_individual_stat(
result, res_sum, ch_sum, f_sum
)

return res_sum, ch_sum, f_sum


def print_stat(result: Result, count: Optional[int] = None) -> None:
"""
Prints statistic for `nornir.core.task.Result` object

Arguments:
result: from a previous task
count: Number of sorted results. It's acceptable
to use numbers with minus sign(-5 as example),
then results will be from the end of results list
"""
LOCK.acquire()
try:
res_sum, ch_sum, f_sum = _print_stat(
result,
count=count,
res_sum=0,
ch_sum=0,
f_sum=0,
)
print()
for state, summary, color in zip(
("OK", "CHANGED", "FAILED"),
(res_sum - f_sum, ch_sum, f_sum),
(Fore.GREEN, Fore.YELLOW, Fore.RED),
):
print("{}{}{:<8}: {}".format(Style.BRIGHT, color, state, summary))
finally:
LOCK.release()