Changeset - 9d6c7cab31b0
[Not reviewed]
default
0 1 1
drewp@bigasterisk.com - 20 months ago 2023-05-24 06:44:13
drewp@bigasterisk.com
refactor, though i think i want to remove this since it's redundant with metrics
2 files changed with 30 insertions and 29 deletions:
0 comments (0 inline, 0 general)
light9/metrics.py
Show inline comments
 
"""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:
 
  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
light9/recentfps.py
Show inline comments
 
new file 100644
 
# server side version of what the metrics consumer does with changing counts
 
import time
 

	
 
class RecentFps:
 
  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}
0 comments (0 inline, 0 general)