Files @ 0dd05e9d4ae7
Branch filter:

Location: light9/light9/metrics.py

drewp@bigasterisk.com
ran into this line- maybe it is not NotImplemented after all?
"""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