forked from Checkmk/checkmk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CMK-4335 Change-Id: I44740dd2853c7d0955eea9ff7e871c0522d80edb
- Loading branch information
Showing
4 changed files
with
341 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2 | ||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and | ||
# conditions defined in the file COPYING, which is part of this source code package. | ||
"""Render functions for check development | ||
These are meant to be exposed in the API | ||
""" | ||
from typing import Iterable, Optional, Tuple | ||
import math | ||
import time | ||
|
||
_DATE_FORMAT = "%b %d %Y" | ||
|
||
_TIME_UNITS = [ | ||
('years', 31536000), | ||
('days', 86400), | ||
('hours', 3600), | ||
('minutes', 60), | ||
('seconds', 1), | ||
('milliseconds', 1e-3), | ||
('microseconds', 1e-6), | ||
('nanoseconds', 1e-9), | ||
('picoseconds', 1e-12), | ||
('femtoseconds', 1e-15), | ||
# and while we're at it: | ||
('attoseconds', 1e-18), | ||
('zeptoseconds', 1e-21), | ||
('yoctoseconds', 1e-24), | ||
] | ||
|
||
# Karl Marx Gave The Proletariat Eleven Zeppelins, Yo! | ||
_SIZE_PREFIXES = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] | ||
|
||
|
||
def date(epoch): | ||
# type: (Optional[float]) -> str | ||
"""Render seconds since epoch as date | ||
In case None is given it returns "never". | ||
""" | ||
if epoch is None: | ||
return "never" | ||
return time.strftime(_DATE_FORMAT, time.localtime(float(epoch))) | ||
|
||
|
||
def datetime(epoch): | ||
# type: (Optional[float]) -> str | ||
"""Render seconds since epoch as date and time | ||
In case None is given it returns "never". | ||
""" | ||
if epoch is None: | ||
return "never" | ||
return time.strftime("%s %%H:%%M:%%S" % _DATE_FORMAT, time.localtime(float(epoch))) | ||
|
||
|
||
def _gen_timespan_chunks(seconds, nchunks): | ||
# type: (float, int) -> Iterable[str] | ||
try: | ||
start = next(i for i, (_, v) in enumerate(_TIME_UNITS) if seconds >= v) | ||
except StopIteration: | ||
start = len(_TIME_UNITS) - 1 | ||
|
||
for unit, scale in _TIME_UNITS[start:start + nchunks]: | ||
value = int(seconds / scale) | ||
yield "%.0f %s" % (value, unit if value != 1 else unit[:-1]) | ||
if unit.endswith("seconds"): | ||
break | ||
seconds %= scale | ||
|
||
|
||
def timespan(seconds): | ||
# type: (float) -> str | ||
"""Render a time span in seconds | ||
""" | ||
return " ".join(_gen_timespan_chunks(float(seconds), nchunks=2)) | ||
|
||
|
||
def _digits_left(value): | ||
# type: (float) -> int | ||
"""Return the number of didgits left of the decimal point""" | ||
return max(int(math.log(value, 10) + 1), 1) | ||
|
||
|
||
def _auto_scale(value, use_si_units): | ||
# type: (float, bool) -> Tuple[str, str] | ||
base = 1000 if use_si_units else 1024 | ||
exponent = min(max(int(math.log(value, base)), 0), len(_SIZE_PREFIXES) - 1) | ||
unit = (_SIZE_PREFIXES[exponent] + ("B" if use_si_units else "iB")).lstrip('i') | ||
scaled_value = float(value) / base**exponent | ||
fmt = "%%.%df" % max(3 - _digits_left(scaled_value), 0) | ||
return fmt % scaled_value, unit | ||
|
||
|
||
def disksize(bytes_): | ||
# type: (float) -> str | ||
"""Render a disk size in bytes using an appropriate SI prefix | ||
Example: | ||
>>> disksize(1024) | ||
"1.02 KB" | ||
""" | ||
value_str, unit = _auto_scale(float(bytes_), use_si_units=True) | ||
return "%s %s" % (value_str if unit != "B" else value_str.split('.')[0], unit) | ||
|
||
|
||
def bytes(bytes_): # pylint: disable=redefined-builtin | ||
# type: (float) -> str | ||
"""Render a number of bytes using an appropriate IEC prefix | ||
Example: | ||
>>> bytes(1024**2) | ||
"2.00 MiB" | ||
""" | ||
value_str, unit = _auto_scale(float(bytes_), use_si_units=False) | ||
return "%s %s" % (value_str if unit != "B" else value_str.split('.')[0], unit) | ||
|
||
|
||
def filesize(bytes_): | ||
# type: (float) -> str | ||
"""Render a file size in bytes | ||
Example: | ||
>>> filesize(12345678) | ||
"12,345,678" | ||
""" | ||
val_str = "%.0f" % float(bytes_) | ||
if len(val_str) <= 3: | ||
return "%s B" % val_str | ||
|
||
offset = len(val_str) % 3 | ||
groups = [val_str[0:offset]] + [val_str[i:i + 3] for i in range(offset, len(val_str), 3)] | ||
return "%s B" % ','.join(groups) | ||
|
||
|
||
def networkbandwidth(octets_per_sec): | ||
# type: (float) -> str | ||
"""Render network bandwidth using an appropriate SI prefix""" | ||
return "%s %sit/s" % _auto_scale(float(octets_per_sec) * 8, use_si_units=True) | ||
|
||
|
||
def nicspeed(octets_per_sec): | ||
# type: (float) -> str | ||
"""Render NIC speed using an appropriate SI prefix""" | ||
value_str, unit = _auto_scale(float(octets_per_sec) * 8, use_si_units=True) | ||
if '.' in value_str: | ||
value_str = value_str.rstrip("0").rstrip(".") | ||
return "%s %sit/s" % (value_str, unit) | ||
|
||
|
||
def iobandwidth(bytes_): | ||
# type: (float) -> str | ||
"""Render IO-bandwith using an appropriate SI prefix""" | ||
return "%s %s/s" % _auto_scale(float(bytes_), use_si_units=True) | ||
|
||
|
||
def percent(percentage): | ||
# type: (float) -> str | ||
"""Render percentage""" | ||
# There is another render.percent in cmk.utils. However, that deals extensively with | ||
# the rendering of small percentages (as is required for graphing applications) | ||
# However, we assume that if a percentage value is smaller that 0.01%, we can display | ||
# it as 0.00%. | ||
# If this is the case regularly, you probably want to display something different, | ||
# "parts per million" for instance. | ||
# Also, this way this module is completely stand alone. | ||
value = float(percentage) # be nice | ||
if not value: | ||
return "0%" | ||
digits_int = _digits_left(value) | ||
digits_frac = max(3 - digits_int, 0) | ||
if 99 < value < 100: # be more precise in this case | ||
digits_frac = _digits_left(1. / (100.0 - value)) | ||
fmt = "%%.%df%%%%" % digits_frac | ||
return fmt % value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2 | ||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and | ||
# conditions defined in the file COPYING, which is part of this source code package. | ||
"""The "render" namespace adds functions to render values in a human readable way. | ||
All of the render functions take a single numerical value as an argument, and return | ||
a string. | ||
""" | ||
from cmk.base.api.agent_based.render import ( # pylint: disable=redefined-builtin | ||
date, datetime, timespan, disksize, bytes, filesize, networkbandwidth, nicspeed, iobandwidth, | ||
percent, | ||
) | ||
|
||
__all__ = [ | ||
'date', | ||
'datetime', | ||
'timespan', | ||
'disksize', | ||
'bytes', | ||
'filesize', | ||
'networkbandwidth', | ||
'nicspeed', | ||
'iobandwidth', | ||
'percent', | ||
] |
135 changes: 135 additions & 0 deletions
135
tests-py3/unit/cmk/base/api/agent_based/test_render_api.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2 | ||
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and | ||
# conditions defined in the file COPYING, which is part of this source code package. | ||
import pytest # type: ignore[import] | ||
|
||
import cmk.base.api.agent_based.render as render | ||
|
||
|
||
@pytest.mark.parametrize("epoch, output", [ | ||
(0, "Jan 01 1970"), | ||
(1587901020, "Apr 26 2020"), | ||
(1587901020.0, "Apr 26 2020"), | ||
("1587901020", "Apr 26 2020"), | ||
]) | ||
def test_date(epoch, output): | ||
assert render.date(epoch=epoch) == output | ||
|
||
|
||
@pytest.mark.parametrize("epoch, output", [ | ||
(0, "Jan 01 1970 01:00:00"), | ||
(1587901020, "Apr 26 2020 13:37:00"), | ||
(1587901020.0, "Apr 26 2020 13:37:00"), | ||
("1587901020", "Apr 26 2020 13:37:00"), | ||
]) | ||
def test_datetime(epoch, output): | ||
assert render.datetime(epoch=epoch) == output | ||
|
||
|
||
@pytest.mark.parametrize("seconds, output", [ | ||
(0.00000001, "10 nanoseconds"), | ||
(0.1, "100 milliseconds"), | ||
(22, "22 seconds"), | ||
(158, "2 minutes 38 seconds"), | ||
(98, "1 minute 38 seconds"), | ||
(1234567, "14 days 6 hours"), | ||
(31536001, "1 year 0 days"), | ||
]) | ||
def test_timespan(seconds, output): | ||
assert render.timespan(seconds=seconds) == output | ||
|
||
|
||
@pytest.mark.parametrize("bytes_, output", [ | ||
(1, "1 B"), | ||
(2345, "2.35 KB"), | ||
(1024**2, "1.05 MB"), | ||
(1000**2, "1.00 MB"), | ||
(1234000, "1.23 MB"), | ||
(12340006, "12.3 MB"), | ||
(123400067, "123 MB"), | ||
(1234000678, "1.23 GB"), | ||
]) | ||
def test_disksize(bytes_, output): | ||
assert render.disksize(bytes_) == output | ||
|
||
|
||
@pytest.mark.parametrize("bytes_, output", [ | ||
(1, "1 B"), | ||
(2345, "2.29 KiB"), | ||
(1024**2, "1.00 MiB"), | ||
(1000**2, "977 KiB"), | ||
(1234000, "1.18 MiB"), | ||
(12340006, "11.8 MiB"), | ||
(123400067, "118 MiB"), | ||
(1234000678, "1.15 GiB"), | ||
]) | ||
def test_bytes(bytes_, output): | ||
assert render.bytes(bytes_) == output | ||
|
||
|
||
@pytest.mark.parametrize("bytes_, output", [ | ||
(1, "1 B"), | ||
(2345, "2,345 B"), | ||
(1024**2, "1,048,576 B"), | ||
(1000**2, "1,000,000 B"), | ||
(1234000678, "1,234,000,678 B"), | ||
]) | ||
def test_filesize(bytes_, output): | ||
assert render.filesize(bytes_) == output | ||
|
||
|
||
@pytest.mark.parametrize("octets_per_sec, output", [ | ||
(1, "8 Bit/s"), | ||
(2345, "18.8 KBit/s"), | ||
(1.25 * 10**5, "1 MBit/s"), | ||
(1.25 * 10**6, "10 MBit/s"), | ||
(1.25 * 10**7, "100 MBit/s"), | ||
(1234000678, "9.87 GBit/s"), | ||
]) | ||
def test_nicspeed(octets_per_sec, output): | ||
assert render.nicspeed(octets_per_sec) == output | ||
|
||
|
||
@pytest.mark.parametrize("octets_per_sec, output", [ | ||
(1, "8.00 Bit/s"), | ||
(2345, "18.8 KBit/s"), | ||
(1.25 * 10**5, "1.00 MBit/s"), | ||
(1.25 * 10**6, "10.0 MBit/s"), | ||
(1.25 * 10**7, "100 MBit/s"), | ||
(1234000678, "9.87 GBit/s"), | ||
]) | ||
def test_networkbandwitdh(octets_per_sec, output): | ||
assert render.networkbandwidth(octets_per_sec) == output | ||
|
||
|
||
@pytest.mark.parametrize("bytes_, output", [ | ||
(1, "1.00 B/s"), | ||
(2345, "2.35 KB/s"), | ||
(1024**2, "1.05 MB/s"), | ||
(1000**2, "1.00 MB/s"), | ||
(1234000678, "1.23 GB/s"), | ||
]) | ||
def test_iobandwidth(bytes_, output): | ||
assert render.iobandwidth(bytes_) == output | ||
|
||
|
||
@pytest.mark.parametrize("percentage, output", [ | ||
(0., "0%"), | ||
(0.001, "0.00%"), | ||
(0.01, "0.01%"), | ||
(1.0, "1.00%"), | ||
(10, "10.0%"), | ||
(99.8, "99.8%"), | ||
(99.9, "99.90%"), | ||
(99.92, "99.92%"), | ||
(99.9991, "99.9991%"), | ||
(99.9997, "99.9997%"), | ||
(100, "100%"), | ||
(100.01, "100%"), | ||
(100, "100%"), | ||
(123, "123%"), | ||
]) | ||
def test_percent(percentage, output): | ||
assert render.percent(percentage) == output |