.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
__init__.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2# pylint: disable=missing-module-docstring
3
4import math
5import contextlib
6from timeit import default_timer
7
8from searx.engines import engines
9from searx.openmetrics import OpenMetricsFamily
10from .models import HistogramStorage, CounterStorage, VoidHistogram, VoidCounterStorage
11from .error_recorder import count_error, count_exception, errors_per_engines
12
13__all__ = [
14 "initialize",
15 "get_engines_stats",
16 "get_engine_errors",
17 "histogram",
18 "histogram_observe",
19 "histogram_observe_time",
20 "counter",
21 "counter_inc",
22 "counter_add",
23 "count_error",
24 "count_exception",
25]
26
27
28ENDPOINTS = {'search'}
29
30
31histogram_storage: HistogramStorage = None # type: ignore
32counter_storage: CounterStorage = None # type: ignore
33
34
35@contextlib.contextmanager
37 h = histogram_storage.get(*args)
38 before = default_timer()
39 yield before
40 duration = default_timer() - before
41 if h:
42 h.observe(duration)
43 else:
44 raise ValueError("histogram " + repr((*args,)) + " doesn't not exist")
45
46
47def histogram_observe(duration, *args):
48 histogram_storage.get(*args).observe(duration)
49
50
51def histogram(*args, raise_on_not_found=True):
52 h = histogram_storage.get(*args)
53 if raise_on_not_found and h is None:
54 raise ValueError("histogram " + repr((*args,)) + " doesn't not exist")
55 return h
56
57
58def counter_inc(*args: str):
59 counter_storage.add(1, *args)
60
61
62def counter_add(value: int, *args: str):
63 counter_storage.add(value, *args)
64
65
66def counter(*args):
67 return counter_storage.get(*args)
68
69
70def initialize(engine_names: list[str] | None = None, enabled: bool = True) -> None:
71 """
72 Initialize metrics
73 """
74 global counter_storage, histogram_storage # pylint: disable=global-statement
75
76 if enabled:
77 counter_storage = CounterStorage()
78 histogram_storage = HistogramStorage()
79 else:
80 counter_storage = VoidCounterStorage()
81 histogram_storage = HistogramStorage(histogram_class=VoidHistogram)
82
83 # max_timeout = max of all the engine.timeout
84 max_timeout = 2
85 for engine_name in engine_names or engines:
86 if engine_name in engines:
87 max_timeout = max(max_timeout, engines[engine_name].timeout)
88
89 # histogram configuration
90 histogram_width = 0.1
91 histogram_size = int(1.5 * max_timeout / histogram_width)
92
93 # engines
94 for engine_name in engine_names or engines:
95 # search count
96 counter_storage.configure('engine', engine_name, 'search', 'count', 'sent')
97 counter_storage.configure('engine', engine_name, 'search', 'count', 'successful')
98 # global counter of errors
99 counter_storage.configure('engine', engine_name, 'search', 'count', 'error')
100 # score of the engine
101 counter_storage.configure('engine', engine_name, 'score')
102 # result count per requests
103 histogram_storage.configure(1, 100, 'engine', engine_name, 'result', 'count')
104 # time doing HTTP requests
105 histogram_storage.configure(histogram_width, histogram_size, 'engine', engine_name, 'time', 'http')
106 # total time
107 # .time.request and ...response times may overlap .time.http time.
108 histogram_storage.configure(histogram_width, histogram_size, 'engine', engine_name, 'time', 'total')
109
110
111def get_engine_errors(engline_name_list):
112 result = {}
113 engine_names = list(errors_per_engines.keys())
114 engine_names.sort()
115 for engine_name in engine_names:
116 if engine_name not in engline_name_list:
117 continue
118
119 error_stats = errors_per_engines[engine_name]
120 sent_search_count = max(counter('engine', engine_name, 'search', 'count', 'sent'), 1)
121 sorted_context_count_list = sorted(error_stats.items(), key=lambda context_count: context_count[1])
122 r = []
123 for context, count in sorted_context_count_list:
124 percentage = round(20 * count / sent_search_count) * 5
125 r.append(
126 {
127 'filename': context.filename,
128 'function': context.function,
129 'line_no': context.line_no,
130 'code': context.code,
131 'exception_classname': context.exception_classname,
132 'log_message': context.log_message,
133 'log_parameters': context.log_parameters,
134 'secondary': context.secondary,
135 'percentage': percentage,
136 }
137 )
138 result[engine_name] = sorted(r, reverse=True, key=lambda d: d['percentage'])
139 return result
140
141
142def get_reliabilities(engline_name_list, checker_results):
143 reliabilities = {}
144
145 engine_errors = get_engine_errors(engline_name_list)
146
147 for engine_name in engline_name_list:
148 checker_result = checker_results.get(engine_name, {})
149 checker_success = checker_result.get('success', True)
150 errors = engine_errors.get(engine_name) or []
151 sent_count = counter('engine', engine_name, 'search', 'count', 'sent')
152
153 if sent_count == 0:
154 # no request
155 reliability = None
156 elif checker_success and not errors:
157 reliability = 100
158 elif 'simple' in checker_result.get('errors', {}):
159 # the basic (simple) test doesn't work: the engine is broken according to the checker
160 # even if there is no exception
161 reliability = 0
162 else:
163 # pylint: disable=consider-using-generator
164 reliability = 100 - sum([error['percentage'] for error in errors if not error.get('secondary')])
165
166 reliabilities[engine_name] = {
167 'reliability': reliability,
168 'sent_count': sent_count,
169 'errors': errors,
170 'checker': checker_result.get('errors', {}),
171 }
172 return reliabilities
173
174
175def get_engines_stats(engine_name_list: list[str]):
176 assert counter_storage is not None
177 assert histogram_storage is not None
178
179 list_time = []
180 max_time_total = max_result_count = None
181
182 for engine_name in engine_name_list:
183
184 sent_count = counter('engine', engine_name, 'search', 'count', 'sent')
185 if sent_count == 0:
186 continue
187
188 result_count = histogram('engine', engine_name, 'result', 'count').percentage(50)
189 result_count_sum = histogram('engine', engine_name, 'result', 'count').sum
190 successful_count = counter('engine', engine_name, 'search', 'count', 'successful')
191
192 time_total = histogram('engine', engine_name, 'time', 'total').percentage(50)
193 max_time_total = max(time_total or 0, max_time_total or 0)
194 max_result_count = max(result_count or 0, max_result_count or 0)
195
196 stats = {
197 'name': engine_name,
198 'total': None,
199 'total_p80': None,
200 'total_p95': None,
201 'http': None,
202 'http_p80': None,
203 'http_p95': None,
204 'processing': None,
205 'processing_p80': None,
206 'processing_p95': None,
207 'score': 0,
208 'score_per_result': 0,
209 'result_count': result_count,
210 }
211
212 if successful_count and result_count_sum:
213 score = counter('engine', engine_name, 'score')
214
215 stats['score'] = score
216 stats['score_per_result'] = score / float(result_count_sum)
217
218 time_http = histogram('engine', engine_name, 'time', 'http').percentage(50)
219 time_http_p80 = time_http_p95 = 0
220
221 if time_http is not None:
222
223 time_http_p80 = histogram('engine', engine_name, 'time', 'http').percentage(80)
224 time_http_p95 = histogram('engine', engine_name, 'time', 'http').percentage(95)
225
226 stats['http'] = round(time_http, 1)
227 stats['http_p80'] = round(time_http_p80, 1)
228 stats['http_p95'] = round(time_http_p95, 1)
229
230 if time_total is not None:
231
232 time_total_p80 = histogram('engine', engine_name, 'time', 'total').percentage(80)
233 time_total_p95 = histogram('engine', engine_name, 'time', 'total').percentage(95)
234
235 stats['total'] = round(time_total, 1)
236 stats['total_p80'] = round(time_total_p80, 1)
237 stats['total_p95'] = round(time_total_p95, 1)
238
239 stats['processing'] = round(time_total - (time_http or 0), 1)
240 stats['processing_p80'] = round(time_total_p80 - time_http_p80, 1)
241 stats['processing_p95'] = round(time_total_p95 - time_http_p95, 1)
242
243 list_time.append(stats)
244
245 return {
246 'time': list_time,
247 'max_time': math.ceil(max_time_total or 0),
248 'max_result_count': math.ceil(max_result_count or 0),
249 }
250
251
252def openmetrics(engine_stats, engine_reliabilities):
253 metrics = [
255 key="searxng_engines_response_time_total_seconds",
256 type_hint="gauge",
257 help_hint="The average total response time of the engine",
258 data_info=[{'engine_name': engine['name']} for engine in engine_stats['time']],
259 data=[engine['total'] or 0 for engine in engine_stats['time']],
260 ),
262 key="searxng_engines_response_time_processing_seconds",
263 type_hint="gauge",
264 help_hint="The average processing response time of the engine",
265 data_info=[{'engine_name': engine['name']} for engine in engine_stats['time']],
266 data=[engine['processing'] or 0 for engine in engine_stats['time']],
267 ),
269 key="searxng_engines_response_time_http_seconds",
270 type_hint="gauge",
271 help_hint="The average HTTP response time of the engine",
272 data_info=[{'engine_name': engine['name']} for engine in engine_stats['time']],
273 data=[engine['http'] or 0 for engine in engine_stats['time']],
274 ),
276 key="searxng_engines_result_count_total",
277 type_hint="counter",
278 help_hint="The total amount of results returned by the engine",
279 data_info=[{'engine_name': engine['name']} for engine in engine_stats['time']],
280 data=[engine['result_count'] or 0 for engine in engine_stats['time']],
281 ),
283 key="searxng_engines_request_count_total",
284 type_hint="counter",
285 help_hint="The total amount of user requests made to this engine",
286 data_info=[{'engine_name': engine['name']} for engine in engine_stats['time']],
287 data=[
288 engine_reliabilities.get(engine['name'], {}).get('sent_count', 0) or 0
289 for engine in engine_stats['time']
290 ],
291 ),
293 key="searxng_engines_reliability_total",
294 type_hint="counter",
295 help_hint="The overall reliability of the engine",
296 data_info=[{'engine_name': engine['name']} for engine in engine_stats['time']],
297 data=[
298 engine_reliabilities.get(engine['name'], {}).get('reliability', 0) or 0
299 for engine in engine_stats['time']
300 ],
301 ),
302 ]
303 return "".join([str(metric) for metric in metrics])
::1337x
Definition 1337x.py:1
get_engine_errors(engline_name_list)
Definition __init__.py:111
counter_inc(*str args)
Definition __init__.py:58
histogram(*args, raise_on_not_found=True)
Definition __init__.py:51
counter(*args)
Definition __init__.py:66
counter_add(int value, *str args)
Definition __init__.py:62
histogram_observe(duration, *args)
Definition __init__.py:47
None initialize(list[str]|None engine_names=None, bool enabled=True)
Definition __init__.py:70
get_reliabilities(engline_name_list, checker_results)
Definition __init__.py:142
get_engines_stats(list[str] engine_name_list)
Definition __init__.py:175
histogram_observe_time(*args)
Definition __init__.py:36