# HG changeset patch # User drewp@bigasterisk.com # Date 1653968399 25200 # Node ID 32e2c91eeaf34bcb5ea53078cf8516b9ac2b2b85 # Parent 354ffd78c0fe03f66e051e55d946f25b6a14c4d6 easy api over prometheus, for ease of porting. Might be slow diff -r 354ffd78c0fe -r 32e2c91eeaf3 rdfdb/metrics.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rdfdb/metrics.py Mon May 30 20:39:59 2022 -0700 @@ -0,0 +1,160 @@ +# copy from light9 version- lib me +"""for easier porting, and less boilerplate, allow these styles using the +form of the call to set up the right type of metric automatically: + + from metrics import metrics + metrics.setProcess('pretty_name') + + @metrics('loop').time() # a common one to get the fps of each service. Gets us qty and time + def frame(): + if err: + metrics('foo_errors').incr() # if you incr it, it's a counter + + @metrics('foo_calls').time() # qty & time because it's a decorator + def foo(): + + metrics('goal_fps').set(f) # a gauge because we called set() + + with metrics('recompute'): ... # ctxmgr also makes a timer + time_this_part() + +I don't see a need for labels yet, but maybe some code will want like +metrics('foo', label1=one). Need histogram? Info? + +""" +from typing import Dict, Tuple, Callable, Type, TypeVar, cast +import cyclone.web +from prometheus_client import Counter, Gauge, Metric, Summary +from prometheus_client.exposition import generate_latest +from prometheus_client.registry import REGISTRY + +_created: Dict[str, Metric] = {} + +# _process=sys.argv[0] +# def setProcess(name: str): +# global _process +# _process = name + +MT = TypeVar("MT") + + +class _MetricsRequest: + + def __init__(self, name: str, **labels): + self.name = name + self.labels = labels + + def _ensure(self, cls: Type[MT]) -> MT: + if self.name not in _created: + _created[self.name] = cls(name=self.name, documentation=self.name, labelnames=self.labels.keys()) + m = _created[self.name] + if self.labels: + m = m.labels(**self.labels) + return m + + def __call__(self, fn) -> Callable: + return timed_fn + + def set(self, v: float): + self._ensure(Gauge).set(v) + + def inc(self): + self._ensure(Counter).inc() + + def offset(self, amount: float): + self._ensure(Gauge).inc(amount) + + def time(self): + return self._ensure(Summary).time() + + def observe(self, x: float): + return self._ensure(Summary).observe(x) + + def __enter__(self): + return self._ensure(Summary).__enter__() + + +def metrics(name: str, **labels): + return _MetricsRequest(name, **labels) + + +class _CycloneMetrics(cyclone.web.RequestHandler): + + def get(self): + self.add_header('content-type', 'text/plain') + self.write(generate_latest(REGISTRY)) + + +def metricsRoute() -> Tuple[str, Type[cyclone.web.RequestHandler]]: + return ('/metrics', _CycloneMetrics) + + +""" +stuff we used to have in greplin. Might be nice to get (client-side-computed) min/max/stddev back. + +class PmfStat(Stat): + A stat that stores min, max, mean, standard deviation, and some + percentiles for arbitrary floating-point data. This is potentially a + bit expensive, so its child values are only updated once every + twenty seconds. + + + +metrics consumer side can do this with the changing counts: + +class RecentFps(object): + def __init__(self, window=20): + self.window = window + self.recentTimes = [] + + def mark(self): + now = time.time() + self.recentTimes.append(now) + self.recentTimes = self.recentTimes[-self.window:] + + def rate(self): + def dec(innerFunc): + def f(*a, **kw): + self.mark() + return innerFunc(*a, **kw) + return f + return dec + + def __call__(self): + if len(self.recentTimes) < 2: + return {} + recents = sorted(round(1 / (b - a), 3) + for a, b in zip(self.recentTimes[:-1], + self.recentTimes[1:])) + avg = (len(self.recentTimes) - 1) / ( + self.recentTimes[-1] - self.recentTimes[0]) + return {'average': round(avg, 5), 'recents': recents} + + +i think prometheus covers this one: + +import psutil +def gatherProcessStats(): + procStats = scales.collection('/process', + scales.DoubleStat('time'), + scales.DoubleStat('cpuPercent'), + scales.DoubleStat('memMb'), + ) + proc = psutil.Process() + lastCpu = [0.] + def updateTimeStat(): + now = time.time() + procStats.time = round(now, 3) + if now - lastCpu[0] > 3: + procStats.cpuPercent = round(proc.cpu_percent(), 6) # (since last call) + lastCpu[0] = now + procStats.memMb = round(proc.memory_info().rss / 1024 / 1024, 6) + task.LoopingCall(updateTimeStat).start(.1) + +""" + + +class M: + + def __call__(self, name): + return