mirror of
https://github.com/yavook/kiwi-simple-metrics.git
synced 2024-11-24 08:23:01 +00:00
External Metric working
This commit is contained in:
parent
627edab9e9
commit
5128cfefc7
3 changed files with 110 additions and 38 deletions
|
@ -4,3 +4,5 @@ echo "Dummy"
|
||||||
echo "95"
|
echo "95"
|
||||||
echo "normal"
|
echo "normal"
|
||||||
awk "BEGIN{srand(); r=rand(); print r * 100}"
|
awk "BEGIN{srand(); r=rand(); print r * 100}"
|
||||||
|
|
||||||
|
exit 0
|
|
@ -1,3 +1,5 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
from ..settings import SETTINGS
|
from ..settings import SETTINGS
|
||||||
|
@ -5,16 +7,112 @@ from ._report import Report, ReportData
|
||||||
|
|
||||||
|
|
||||||
def _hwdata() -> Iterator[ReportData]:
|
def _hwdata() -> Iterator[ReportData]:
|
||||||
yield ReportData(
|
def parse_output(exe: os.PathLike) -> ReportData:
|
||||||
name="Foo",
|
try:
|
||||||
value=69.42,
|
# check exe is executable
|
||||||
threshold=80,
|
assert os.access(exe, os.X_OK)
|
||||||
inverted=False,
|
|
||||||
format=SETTINGS.cpu.report,
|
# run exe
|
||||||
|
# => TimeoutExpired: execution took too long
|
||||||
|
execution = subprocess.run(
|
||||||
|
args=[exe],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
timeout=SETTINGS.external.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
# look at the first four output lines
|
||||||
|
# => UnicodeDecodeError: output is not decodable
|
||||||
|
output = execution.stdout.decode().split("\n")[:4]
|
||||||
|
|
||||||
|
# extract and check name (fail if empty)
|
||||||
|
assert (name := "".join(
|
||||||
|
char
|
||||||
|
for char in output[0]
|
||||||
|
if char.isprintable()
|
||||||
|
)[:100]) != ""
|
||||||
|
|
||||||
|
except (AssertionError,
|
||||||
|
subprocess.TimeoutExpired,
|
||||||
|
UnicodeDecodeError,
|
||||||
|
IndexError):
|
||||||
|
return ReportData.from_settings(
|
||||||
|
name=os.path.basename(exe)[:100],
|
||||||
|
value=100,
|
||||||
|
settings=SETTINGS.external,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# check exit status
|
||||||
|
assert execution.returncode == 0
|
||||||
|
|
||||||
|
# check output length
|
||||||
|
assert len(output) == 4
|
||||||
|
|
||||||
|
# extract threshold and value
|
||||||
|
threshold = float(output[1])
|
||||||
|
value = float(output[3])
|
||||||
|
|
||||||
|
# extract and check inversion
|
||||||
|
assert (inverted := output[2].strip().lower()) in (
|
||||||
|
"normal", "inverted",
|
||||||
|
)
|
||||||
|
|
||||||
|
except (ValueError, AssertionError):
|
||||||
|
return ReportData.from_settings(
|
||||||
|
name=name,
|
||||||
|
value=100,
|
||||||
|
settings=SETTINGS.external,
|
||||||
|
)
|
||||||
|
|
||||||
|
# success
|
||||||
|
return ReportData(
|
||||||
|
name=name,
|
||||||
|
value=value,
|
||||||
|
threshold=threshold,
|
||||||
|
inverted=inverted == "inverted",
|
||||||
|
format=SETTINGS.external.report,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield from (
|
||||||
|
parse_output(exe)
|
||||||
|
for exe in SETTINGS.external.executables
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def external() -> Report | None:
|
def external() -> Report | None:
|
||||||
|
"""
|
||||||
|
External Metric
|
||||||
|
=====
|
||||||
|
|
||||||
|
This metric's values are defined external executables (e.g. shell scripts).
|
||||||
|
Any executable with suitable output can be used as a value for this metric.
|
||||||
|
|
||||||
|
To comply, the executable's output must be UTF-8 decodable and start with
|
||||||
|
four consecutive lines holding the following information:
|
||||||
|
|
||||||
|
1. value name
|
||||||
|
2. percent threshold
|
||||||
|
3. the string "normal" or "inverted", without quotes
|
||||||
|
4. percent current value
|
||||||
|
|
||||||
|
The executable may produce additional output, which will be ignored.
|
||||||
|
Percentages may be floating point numbers and must use a decimal point "."
|
||||||
|
as a separator in that case.
|
||||||
|
The output is evaluated once execution finishes with exit status 0.
|
||||||
|
A report is generated for each executable. Its value name is stripped of
|
||||||
|
non-printable characters and limited to a length of 100.
|
||||||
|
|
||||||
|
Non-compliance will be reported as failed values, i.e. normal values with a
|
||||||
|
threshold of 0% and a value of 100%, in these cases:
|
||||||
|
|
||||||
|
- non-executable files and executables outputting non-UTF8:
|
||||||
|
reported as the files' basename
|
||||||
|
- executables with generally noncompliant outputs:
|
||||||
|
reported as the first line of output
|
||||||
|
- failure to parse any of the threshold, inversion or current value
|
||||||
|
- otherwise compliant executables with non-zero exit status
|
||||||
|
"""
|
||||||
|
|
||||||
return Report.aggregate(
|
return Report.aggregate(
|
||||||
settings=SETTINGS.external,
|
settings=SETTINGS.external,
|
||||||
get_data=_hwdata,
|
get_data=_hwdata,
|
||||||
|
|
|
@ -82,39 +82,11 @@ class DiskMS(MetricSettings):
|
||||||
|
|
||||||
|
|
||||||
class ExternalMS(MetricSettings):
|
class ExternalMS(MetricSettings):
|
||||||
"""
|
name: str = "External Metric"
|
||||||
External Metric
|
|
||||||
=====
|
|
||||||
|
|
||||||
This metric's values are defined external executables (e.g. bash scripts).
|
|
||||||
Any executable with suitable output can be used as a value for this metric.
|
|
||||||
|
|
||||||
To comply, the executable's output must start with four consecutive lines,
|
|
||||||
holding the following information:
|
|
||||||
|
|
||||||
1. value name (max. 100 characters)
|
|
||||||
2. percent threshold
|
|
||||||
3. the string "normal" or "inverted", without quotes
|
|
||||||
4. percent current value
|
|
||||||
|
|
||||||
Percentages may be floating point numbers and must use a decimal point "."
|
|
||||||
as a separator in that case.
|
|
||||||
|
|
||||||
Non-compliance will be reported as failed values as follows:
|
|
||||||
|
|
||||||
- non-executable files are reported as the files' basename
|
|
||||||
- executables with generally noncompliant outputs are reported as
|
|
||||||
the first line of their output truncated to 100 chars
|
|
||||||
- failure to parse the threshold or inversion results in
|
|
||||||
an upper threshold of 0%
|
|
||||||
- failure to parse the current value results in
|
|
||||||
an upper threshold of 0% and a value of 100%
|
|
||||||
- compliant executables with non-zero exit status are still
|
|
||||||
reported as a failed value
|
|
||||||
"""
|
|
||||||
|
|
||||||
name: str = "External Metrics"
|
|
||||||
threshold: float = 0
|
threshold: float = 0
|
||||||
|
|
||||||
# path to executable files
|
# path to executable files
|
||||||
executables: list[FilePath] = Field(default_factory=list)
|
executables: list[FilePath] = Field(default_factory=list)
|
||||||
|
|
||||||
|
# wait at most this many seconds for each executable
|
||||||
|
timeout: int = 60
|
||||||
|
|
Loading…
Reference in a new issue