def clear_item_state(user_key: _UserKey) -> None: """Deletes a stored matching the given key. This needs to be the same key as used with set_item_state(). In case the given item does not exist, the function returns without modification.""" get_value_store().pop(user_key, None)
def get_average( itemname: _UserKey, this_time: float, this_val: float, backlog_minutes: float, initialize_zero: bool = True, ) -> float: """Return new average based on current value and last average itemname : unique ID for storing this average until the next check this_time : timestamp of new value backlog : averaging horizon in minutes initialize_zero : assume average of 0.0 when now previous average is stored This function returns the new average value aₙ as the weighted sum of the current value xₙ and the last average: aₙ = (1 - w)xₙ + waₙ₋₁ = (1-w) ∑ᵢ₌₀ⁿ wⁱxₙ₋ᵢ This results in a so called "exponential moving average". The weight is chosen such that for long running timeseries the "backlog" (all recorded values in the last n minutes) will make up 50% of the weighted average. Assuming k values in the backlog, compute their combined weight such that they sum up to the backlog weight b (0.5 in our case): b = (1-w) ∑ᵢ₌₀ᵏ⁻¹ wⁱ => w = (1 - b) ** (1/k) ("geometric sum") """ value_store = get_value_store() last_time, last_average = value_store.get(itemname, (this_time, None)) # first call: take current value as average or assume 0.0 if last_average is None: average = 0.0 if initialize_zero else this_val value_store[itemname] = (this_time, average) return average # at the current rate, how many values are in the backlog? time_diff = this_time - last_time if time_diff <= 0: # Gracefully handle time-anomaly of target systems return last_average backlog_count = (backlog_minutes * 60.0) / time_diff backlog_weight = 0.50 # TODO: For the version in the new Check API change the above line to # backlog_weight = 0.5 ** min(1, (time - start_time) / (2 * backlog_minutes * 60.) # And add to doc string: # For shorter timeseries we give the backlog more than those 50% weight # with the advantage that a) the initial value becomes irrelevant, and # b) for beginning timeseries we reach a meaningful value more quickly. weight = (1 - backlog_weight)**(1.0 / backlog_count) average = (1.0 - weight) * this_val + weight * last_average value_store[itemname] = (this_time, average) return average
def test_value_store(): store = value_store.get_value_store() with pytest.raises(MKGeneralException): store["foo"] = 42 saved_prefix = value_store.get_item_state_prefix() with value_store.context(CheckPluginName("plugin"), "item"): assert len(store) == 0 assert not store assert "foo" not in store assert store.get("foo") is None with pytest.raises(KeyError): _ = store["foo"] with pytest.raises(TypeError): store[2] = "key must be string" # type: ignore[index] store["foo"] = 42 store["bar"] = 23 assert set(store) == {"foo", "bar"} del store["bar"] assert "foo" in store assert len(store) == 1 assert bool(store) assert store["foo"] == 42 assert value_store.get_item_state_prefix() == saved_prefix
def test_value_store(): store = value_store.get_value_store() assert len(store) == 0 assert not store assert "foo" not in store assert store.get("foo") is None with pytest.raises(KeyError): _ = store["foo"] with pytest.raises(TypeError): store[2] = "key must be string" # type: ignore[index] store["foo"] = 42 store["bar"] = 23 assert set(store) == {"foo", "bar"} del store["bar"] assert "foo" in store assert len(store) == 1 assert bool(store) assert store["foo"] == 42
def get_item_state(user_key: object, default: Any = None) -> Any: """Returns the currently stored item with the user_key. Returns None or the given default value in case there is currently no such item stored.""" return get_value_store().get(_stringify(user_key), default)
def set_item_state(user_key: object, state: Any) -> None: """Store arbitrary values until the next execution of a check. The user_key is the identifier of the stored value and needs to be unique per service.""" get_value_store()[_stringify(user_key)] = state