def check_levels( value: float, *, levels_upper: Optional[Tuple[float, float]] = None, levels_lower: Optional[Tuple[float, float]] = None, metric_name: Optional[str] = None, render_func: Optional[Callable[[float], str]] = None, label: Optional[str] = None, boundaries: Optional[Tuple[Optional[float], Optional[float]]] = None, notice_only: bool = False, ) -> Generator[Union[Result, Metric], None, None]: """Generic function for checking a value against levels. Args: value: The currently measured value levels_upper: A pair of upper thresholds. If value is larger than these, the service goes to **WARN** or **CRIT**, respecively. levels_lower: A pair of lower thresholds. If value is smaller than these, the service goes to **WARN** or **CRIT**, respecively. metric_name: The name of the datasource in the RRD that corresponds to this value or None in order not to generate a metric. render_func: A single argument function to convert the value from float into a human readable string. label: The label to prepend to the output. boundaries: Minimum and maximum to add to the metric. notice_only: Only show up in service output if not OK (otherwise in details). See `notice` keyword of `Result` class. Example: >>> result, metric = check_levels( ... 23.0, ... levels_upper=(12., 42.), ... metric_name="temperature", ... label="Fridge", ... render_func=lambda v: "%.1f°" % v, ... ) >>> print(result.summary) Fridge: 23.0° (warn/crit at 12.0°/42.0°) >>> print(metric) Metric('temperature', 23.0, levels=(12.0, 42.0)) """ if render_func is None: render_func = lambda f: "%.2f" % f info_text = str(render_func(value)) # forgive wrong output type if label: info_text = "%s: %s" % (label, info_text) value_state, levels_text = _do_check_levels(value, levels_upper, levels_lower, render_func) if notice_only: yield Result(state=value_state, notice=info_text + levels_text) else: yield Result(state=value_state, summary=info_text + levels_text) if metric_name: yield Metric(metric_name, value, levels=levels_upper, boundaries=boundaries)
(2, (3, 6), (1, 0), int, (State.OK, "")), (1, (3, 6), (1, 0), int, (State.OK, "")), (0, (3, 6), (1, 0), int, (State.WARN, " (warn/crit below 1/0)")), (-1, (3, 6), (1, 0), int, (State.CRIT, " (warn/crit below 1/0)")), ]) def test_boundaries(value, levels_upper, levels_lower, render_func, result): assert utils._do_check_levels(value, levels_upper, levels_lower, render_func) == result @pytest.mark.parametrize("value, kwargs, result", [ (5, { "metric_name": "battery", "render_func": render.percent, }, [ Result(state=State.OK, summary="5.00%"), Metric("battery", 5.0), ]), (6, { "metric_name": "disk", "levels_upper": (4, 8), "render_func": lambda x: "%.2f years" % x, "label": "Disk Age", }, [ Result( state=State.WARN, summary="Disk Age: 6.00 years (warn/crit at 4.00 years/8.00 years)" ), Metric("disk", 6.0, levels=(4., 8.)), ]), (5e-7, {
def _add_node_name(result: Result, node_name: str) -> Result: return Result( state=result.state, summary=result.summary, details='\n'.join(f"[{node_name}]: {line}" for line in result.details.splitlines()), )
def aggregate_node_details( node_name: str, node_check_returns: Iterable[ABCCheckGenerated], ) -> Optional[Result]: """Aggregate the results of a node check into a single Result The results of the check on the node are aggregated into a single Result instance, showing all node results in its details. The state is the worst of all individual states (as checkmk would compute it for the service on the node). If no results for the nodes are available, None is returned. Example: To yield the summary results of every node of a cluster from within a cluster_check_function use for node_name, node_section in sections.values(): summary_result = aggregate_node_details( node_name, check_my_plugin(item, node_section), ) if summary_result is not None: yield summary_result Note that this will send no text to the services summary, only to the details page. Args: node_name (str): The name of the node node_check_returns (Sequence[Union[IgnoreResults, Result, Metric]]): The return values or generator of the nodes check_function call Returns: Optional[Result]: Aggregated node result. None if the node check returned nothing. """ # drop Metrics, we may be evaluating a generator try: returns_wo_metrics = [ r for r in node_check_returns if not isinstance(r, Metric) ] except IgnoreResultsError: return None results = [r for r in returns_wo_metrics if isinstance(r, Result)] if not results or len(results) != len( returns_wo_metrics): # latter: encountered IgnoreResults return None details_with_markers = [ "%s%s" % (r.details.strip(), state_markers[int(r.state)]) for r in results ] details_lines: List[str] = sum( (d.split('\n') for d in details_with_markers), []) return Result( state=state.worst(*(r.state for r in results)), details="\n".join("[%s]: %s" % (node_name, d) for d in details_lines), )
def test_result_invalid(state_, summary, notice, details): with pytest.raises((TypeError, ValueError)): _ = Result(state=state_, summary=summary, notice=notice, details=details)
) for node in secondary_nodes for r in self._node_results.results[node]) @staticmethod def _secondary_nodes_state( secondary_nodes: Sequence[str], levels: Tuple[float, float], ) -> State: count = len(secondary_nodes) return State.CRIT if count >= levels[1] else State(count >= levels[0]) def metrics(self, node_name: Optional[str]) -> CheckResult: used_node = node_name or self._pivoting if not (metrics := self._node_results.metrics.get(used_node, ())): return yield Result( state=State.OK, notice=f"[{used_node}] Metrics: {', '.join(m.name for m in metrics)}", ) yield from metrics class NodeCheckExecutor: def __init__(self, *, service_id: ServiceID, persist_value_store_changes: bool) -> None: self._service_id = service_id self._persist_value_store_changes = persist_value_store_changes def __call__( self, check_function: Callable[..., CheckResult], cluster_kwargs: _Kwargs, ) -> NodeResults: """Dispatch the check function results for all nodes"""
lambda tp: _check_timeperiod(tp, active_timeperiods)) determined_check_params = cmk.base.checking.legacy_determine_check_params( rules) assert expected_result == determined_check_params, ( "Determine params: Expected '%s' but got '%s'" % (expected_result, determined_check_params)) def _check_timeperiod(timeperiod, active_timeperiods): return timeperiod in active_timeperiods @pytest.mark.parametrize("subresults, aggregated_results", [ ([], cmk.base.checking.ITEM_NOT_FOUND), ([ Result(state=state.OK, notice="details"), ], (0, "Everything looks OK - 1 detail available\ndetails", [])), ([ Result(state=state.OK, summary="summary1", details="detailed info1"), Result(state=state.WARN, summary="summary2", details="detailed info2"), ], (1, "summary1, summary2(!)\ndetailed info1\ndetailed info2(!)", [])), ([ Result(state=state.OK, summary="summary"), Metric(name="name", value=42), ], (0, "summary\nsummary", [("name", 42.0, None, None, None, None)])), ]) def test_aggregate_result(subresults, aggregated_results): assert cmk.base.checking._aggregate_results( subresults) == aggregated_results
def unfit_for_clustering(*args, **kwargs): yield Result( state=state.UNKNOWN, summary=("This service is not ready to handle clustered data. " "Please change your configuration."), )
def test_node_returns_ok_and_warn(): node_results = _check_function_node((_OK_RESULT, _WARN_RESULT)) assert list(make_node_notice_results("test_node", node_results)) == [ Result(state=State.OK, notice="[test_node]: I am fine"), Result(state=State.WARN, notice="[test_node]: Watch out"), ]
], ) def test_parsing(string_table: StringTable, section: Section): assert parse(string_table) == section @pytest.mark.parametrize( "section ,results", [ pytest.param( [ SplunkMessage("foo", "info", "host", "2019-05-16T08:32:33+02:00", "msg") ], [ Result(state=State.OK, summary="2019-05-16T08:32:33+02:00 - host - msg") ], id="single message", ), pytest.param( [ SplunkMessage("foo", "info", "host", "2019-05-16T08:32:33+02:00", "msg"), SplunkMessage("bar", "info", "host", "2019-05-16T08:32:33+02:00", "msg2"), ], [ Result(state=State.OK, summary="2019-05-16T08:32:33+02:00 - host - msg"), Result(state=State.OK, summary="2019-05-16T08:32:33+02:00 - host - msg2"),
def test_node_returns_details_only(): node_results = _check_function_node((Result(state=State.OK, notice="This is detailed"), )) assert list(make_node_notice_results("test_node", node_results)) == [ Result(state=State.OK, notice="[test_node]: This is detailed"), ]
def test_node_returns_metric(): node_results = _check_function_node((_OK_RESULT, Metric("panic", 42))) assert list(make_node_notice_results("test_node", node_results)) == [ Result(state=State.OK, notice="[test_node]: I am fine"), ]
def test_result(state_, summary, notice, details, expected_triple): result = Result(state=state_, summary=summary, notice=notice, details=details) assert (result.state, result.summary, result.details) == expected_triple
num_threads=1234, max_threads=2468) params: Dict[str, Any] = {} result = set(check_cpu_threads(params, section)) assert result == { Metric('thread_usage', 50.0), Metric('threads', 1234.0), Result(state=State.OK, summary='1234'), Result(state=State.OK, summary='Usage: 50.00%') } @pytest.mark.parametrize('info, check_result', [ ([[u'0.88', u'0.83', u'0.87', u'2/2148', u'21050', u'8']], { Metric('threads', 2148.0, levels=(2000.0, 4000.0)), Result(state=State.WARN, summary='2148 (warn/crit at 2000/4000)'), }), ([[u'0.88', u'0.83', u'0.87', u'2/1748', u'21050', u'8'], [u'124069']], { Metric('threads', 1748.0, levels=(2000.0, 4000.0)), Result(state=State.OK, summary='1748'), Metric('thread_usage', 1.408893438328672), Result(state=State.OK, summary='Usage: 1.41%') }), ]) def test_cpu_threads_regression(info, check_result): section = parse_cpu(info) assert section is not None params = {'levels': (2000, 4000)} assert list(discover_cpu_threads(section)) == [Service()] assert set(check_cpu_threads(params, section)) == check_result
assert list(discovery_systemd_units_services_summary(section)) == discovered_services @pytest.mark.parametrize( "item, params, section, check_results", [ ( "virtualbox", { "else": 2, "states": {"active": 0, "failed": 2, "inactive": 0}, "states_default": 2, }, SECTION, [ Result(state=State.OK, summary="Status: active"), Result(state=State.OK, summary="LSB: VirtualBox Linux kernel module"), ], ), ( "foo", { "else": 2, "states": {"active": 0, "failed": 2, "inactive": 0}, "states_default": 2, }, SECTION, [ Result(state=State.CRIT, summary="Status: failed"), Result( state=State.OK,
def make_node_notice_results( node_name: str, node_check_results: CheckResult, *, force_ok: bool = False, ) -> Iterable[Result]: """Prepare results of a node for output in a cluster check function This function consumes everything from a check function (that is, a :class:`Result`, a :class:`Metric` or an :class:`IgnoreResults`) and returns an iterable of :class:`Result`s. The text is prepended with `'[node]: '`, and the text type is changed from `summary` to `notice` (see :class:`Result` for more details). Usage example: >>> def cluster_check_myplugin(item, section): ... '''A cluster check function that just passes along all nodes' results''' ... for node_name, node_section in sections.values(): ... yield from make_node_notice_results( ... node_name, ... check_myplugin(item, node_section), ... ) This will write text from every node to the services summary if the state is not OK, otherwise to the details text. Args: node_name: The name of the node node_check_results: The return values or generator of the nodes check_function call force_ok: If specified, the state of all results is chacnged to OK. In this case the state marker corresponding to the original state is appended to the text. Returns: The node results, with the text type `notice`. """ # consume potential generator and drop Metrics try: returns_wo_metrics = [ r for r in node_check_results if not isinstance(r, Metric) ] except IgnoreResultsError: return # check for encountered IgnoreResults (also tells mypy that it's all Results) results = [r for r in returns_wo_metrics if isinstance(r, Result)] if len(results) != len(returns_wo_metrics): return def _nodify(text: str, state: State) -> str: """Prepend node name and, if state is forced to OK, append state marker""" node_text = '\n'.join("[%s]: %s" % (node_name, line) for line in text.split('\n')) if not force_ok: return node_text return "%s%s" % (node_text.rstrip(), state_markers[int(state)]) for result in results: yield Result( state=State.OK if force_ok else result.state, notice=_nodify(result.summary, result.state), details=_nodify(result.details, result.state), )
# -*- 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] from cmk.base.api.agent_based.checking_classes import ( IgnoreResults, IgnoreResultsError, Metric, Result, state, ) from cmk.base.api.agent_based.clusterize import aggregate_node_details _OK_RESULT = Result(state=state.OK, summary="I am fine") _WARN_RESULT = Result(state=state.WARN, summary="Watch out") def _check_function_node(test_results): for res in test_results: yield res def test_node_returns_nothing(): assert aggregate_node_details("test_node", _check_function_node(())) is None assert aggregate_node_details("test_node", ()) is None def test_node_raises():
def check_levels_predictive( value: float, *, levels: Dict[str, Any], metric_name: str, render_func: Optional[Callable[[float], str]] = None, label: Optional[str] = None, boundaries: Optional[Tuple[Optional[float], Optional[float]]] = None, ) -> Generator[Union[Result, Metric], None, None]: """Generic function for checking a value against levels. Args: value: Currently measured value levels: Predictive levels. These are used automatically. Lower levels are imposed if the passed dictionary contains "lower" as key, upper levels are imposed if it contains "upper" or "levels_upper_min" as key. If value is lower/higher than these, the service goes to **WARN** or **CRIT**, respecively. metric_name: Name of the datasource in the RRD that corresponds to this value render_func: Single argument function to convert the value from float into a human readable string. readable fashion label: Label to prepend to the output. boundaries: Minimum and maximum to add to the metric. """ if render_func is None: render_func = "%.2f".format # validate the metric name, before we can get the levels. Metric.validate_name(metric_name) try: ref_value, levels_tuple = cmk.base.prediction.get_levels( check_api_utils.host_name(), check_api_utils.service_description(), metric_name, levels, "MAX", ) if ref_value: predictive_levels_msg = " (predicted reference: %s)" % render_func( ref_value) else: predictive_levels_msg = " (no reference for prediction yet)" except MKGeneralException as e: ref_value = None levels_tuple = (None, None, None, None) predictive_levels_msg = " (no reference for prediction: %s)" % e except Exception as e: if cmk.utils.debug.enabled(): raise yield Result(state=State.UNKNOWN, summary="%s" % e) return levels_upper = (None if levels_tuple[0] is None or levels_tuple[1] is None else (levels_tuple[0], levels_tuple[1])) levels_lower = (None if levels_tuple[2] is None or levels_tuple[3] is None else (levels_tuple[2], levels_tuple[3])) value_state, levels_text = _do_check_levels(value, levels_upper, levels_lower, render_func) if label: info_text = "%s: %s%s" % (label, render_func(value), predictive_levels_msg) else: info_text = "%s%s" % (render_func(value), predictive_levels_msg) yield Result(state=value_state, summary=info_text + levels_text) yield Metric(metric_name, value, levels=levels_upper, boundaries=boundaries) if ref_value: yield Metric("predict_%s" % metric_name, ref_value)
def test_node_returns_details_only(): node_results = _check_function_node((Result(state=State.OK, notice="This is detailed"), )) state, text = aggregate_node_details("test_node", node_results) assert state is State.OK assert text == "[test_node]: This is detailed"