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