123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- # SPDX-License-Identifier: AGPL-3.0-or-later
- # pylint: disable=missing-module-docstring
- import decimal
- import threading
- from searx import logger
- __all__ = ["Histogram", "HistogramStorage", "CounterStorage"]
- logger = logger.getChild('searx.metrics')
- class Histogram: # pylint: disable=missing-class-docstring
- _slots__ = '_lock', '_size', '_sum', '_quartiles', '_count', '_width'
- def __init__(self, width=10, size=200):
- self._lock = threading.Lock()
- self._width = width
- self._size = size
- self._quartiles = [0] * size
- self._count = 0
- self._sum = 0
- def observe(self, value):
- q = int(value / self._width)
- if q < 0: # pylint: disable=consider-using-max-builtin
- # Value below zero is ignored
- q = 0
- if q >= self._size:
- # Value above the maximum is replaced by the maximum
- q = self._size - 1
- with self._lock:
- self._quartiles[q] += 1
- self._count += 1
- self._sum += value
- @property
- def quartiles(self):
- return list(self._quartiles)
- @property
- def count(self):
- return self._count
- @property
- def sum(self):
- return self._sum
- @property
- def average(self):
- with self._lock:
- if self._count != 0:
- return self._sum / self._count
- return 0
- @property
- def quartile_percentage(self):
- '''Quartile in percentage'''
- with self._lock:
- if self._count > 0:
- return [int(q * 100 / self._count) for q in self._quartiles]
- return self._quartiles
- @property
- def quartile_percentage_map(self):
- result = {}
- # use Decimal to avoid rounding errors
- x = decimal.Decimal(0)
- width = decimal.Decimal(self._width)
- width_exponent = -width.as_tuple().exponent
- with self._lock:
- if self._count > 0:
- for y in self._quartiles:
- yp = int(y * 100 / self._count) # pylint: disable=invalid-name
- if yp != 0:
- result[round(float(x), width_exponent)] = yp
- x += width
- return result
- def percentage(self, percentage):
- # use Decimal to avoid rounding errors
- x = decimal.Decimal(0)
- width = decimal.Decimal(self._width)
- stop_at_value = decimal.Decimal(self._count) / 100 * percentage
- sum_value = 0
- with self._lock:
- if self._count > 0:
- for y in self._quartiles:
- sum_value += y
- if sum_value >= stop_at_value:
- return x
- x += width
- return None
- def __repr__(self):
- return "Histogram<avg: " + str(self.average) + ", count: " + str(self._count) + ">"
- class HistogramStorage: # pylint: disable=missing-class-docstring
- __slots__ = 'measures', 'histogram_class'
- def __init__(self, histogram_class=Histogram):
- self.clear()
- self.histogram_class = histogram_class
- def clear(self):
- self.measures = {}
- def configure(self, width, size, *args):
- measure = self.histogram_class(width, size)
- self.measures[args] = measure
- return measure
- def get(self, *args):
- return self.measures.get(args, None)
- def dump(self):
- logger.debug("Histograms:")
- ks = sorted(self.measures.keys(), key='/'.join) # pylint: disable=invalid-name
- for k in ks:
- logger.debug("- %-60s %s", '|'.join(k), self.measures[k])
- class CounterStorage: # pylint: disable=missing-class-docstring
- __slots__ = 'counters', 'lock'
- def __init__(self):
- self.lock = threading.Lock()
- self.clear()
- def clear(self):
- with self.lock:
- self.counters = {}
- def configure(self, *args):
- with self.lock:
- self.counters[args] = 0
- def get(self, *args):
- return self.counters[args]
- def add(self, value, *args):
- with self.lock:
- self.counters[args] += value
- def dump(self):
- with self.lock:
- ks = sorted(self.counters.keys(), key='/'.join) # pylint: disable=invalid-name
- logger.debug("Counters:")
- for k in ks:
- logger.debug("- %-60s %s", '|'.join(k), self.counters[k])
- class VoidHistogram(Histogram): # pylint: disable=missing-class-docstring
- def observe(self, value):
- pass
- class VoidCounterStorage(CounterStorage): # pylint: disable=missing-class-docstring
- def add(self, value, *args):
- pass
|