models.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. import decimal
  3. import threading
  4. from searx import logger
  5. __all__ = ["Histogram", "HistogramStorage", "CounterStorage"]
  6. logger = logger.getChild('searx.metrics')
  7. class Histogram:
  8. _slots__ = '_lock', '_size', '_sum', '_quartiles', '_count', '_width'
  9. def __init__(self, width=10, size=200):
  10. self._lock = threading.Lock()
  11. self._width = width
  12. self._size = size
  13. self._quartiles = [0] * size
  14. self._count = 0
  15. self._sum = 0
  16. def observe(self, value):
  17. q = int(value / self._width)
  18. if q < 0:
  19. """Value below zero is ignored"""
  20. q = 0
  21. if q >= self._size:
  22. """Value above the maximum is replaced by the maximum"""
  23. q = self._size - 1
  24. with self._lock:
  25. self._quartiles[q] += 1
  26. self._count += 1
  27. self._sum += value
  28. @property
  29. def quartiles(self):
  30. return list(self._quartiles)
  31. @property
  32. def count(self):
  33. return self._count
  34. @property
  35. def sum(self):
  36. return self._sum
  37. @property
  38. def average(self):
  39. with self._lock:
  40. if self._count != 0:
  41. return self._sum / self._count
  42. else:
  43. return 0
  44. @property
  45. def quartile_percentage(self):
  46. ''' Quartile in percentage '''
  47. with self._lock:
  48. if self._count > 0:
  49. return [int(q * 100 / self._count) for q in self._quartiles]
  50. else:
  51. return self._quartiles
  52. @property
  53. def quartile_percentage_map(self):
  54. result = {}
  55. # use Decimal to avoid rounding errors
  56. x = decimal.Decimal(0)
  57. width = decimal.Decimal(self._width)
  58. width_exponent = -width.as_tuple().exponent
  59. with self._lock:
  60. if self._count > 0:
  61. for y in self._quartiles:
  62. yp = int(y * 100 / self._count)
  63. if yp != 0:
  64. result[round(float(x), width_exponent)] = yp
  65. x += width
  66. return result
  67. def percentage(self, percentage):
  68. # use Decimal to avoid rounding errors
  69. x = decimal.Decimal(0)
  70. width = decimal.Decimal(self._width)
  71. stop_at_value = decimal.Decimal(self._count) / 100 * percentage
  72. sum_value = 0
  73. with self._lock:
  74. if self._count > 0:
  75. for y in self._quartiles:
  76. sum_value += y
  77. if sum_value >= stop_at_value:
  78. return x
  79. x += width
  80. return None
  81. def __repr__(self):
  82. return "Histogram<avg: " + str(self.average) + ", count: " + str(self._count) + ">"
  83. class HistogramStorage:
  84. __slots__ = 'measures'
  85. def __init__(self):
  86. self.clear()
  87. def clear(self):
  88. self.measures = {}
  89. def configure(self, width, size, *args):
  90. measure = Histogram(width, size)
  91. self.measures[args] = measure
  92. return measure
  93. def get(self, *args):
  94. return self.measures.get(args, None)
  95. def dump(self):
  96. logger.debug("Histograms:")
  97. ks = sorted(self.measures.keys(), key='/'.join)
  98. for k in ks:
  99. logger.debug("- %-60s %s", '|'.join(k), self.measures[k])
  100. class CounterStorage:
  101. __slots__ = 'counters', 'lock'
  102. def __init__(self):
  103. self.lock = threading.Lock()
  104. self.clear()
  105. def clear(self):
  106. with self.lock:
  107. self.counters = {}
  108. def configure(self, *args):
  109. with self.lock:
  110. self.counters[args] = 0
  111. def get(self, *args):
  112. return self.counters[args]
  113. def add(self, value, *args):
  114. with self.lock:
  115. self.counters[args] += value
  116. def dump(self):
  117. with self.lock:
  118. ks = sorted(self.counters.keys(), key='/'.join)
  119. logger.debug("Counters:")
  120. for k in ks:
  121. logger.debug("- %-60s %s", '|'.join(k), self.counters[k])