| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 | # SPDX-License-Identifier: AGPL-3.0-or-laterimport decimalimport threadingfrom searx import logger__all__ = ["Histogram", "HistogramStorage", "CounterStorage"]logger = logger.getChild('searx.metrics')class Histogram:    _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:            """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            else:                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]            else:                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)                    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:    __slots__ = 'measures'    def __init__(self):        self.clear()    def clear(self):        self.measures = {}    def configure(self, width, size, *args):        measure = Histogram(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)        for k in ks:            logger.debug("- %-60s %s", '|'.join(k), self.measures[k])class CounterStorage:    __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)        logger.debug("Counters:")        for k in ks:            logger.debug("- %-60s %s", '|'.join(k), self.counters[k])
 |