forked from zaclys/searxng
		
	[enh] Add Server-Timing header (#1637)
Server Timing specification: https://www.w3.org/TR/server-timing/ In the browser Dev Tools, focus on the main request, there are the responses per engine in the Timing tab.
This commit is contained in:
		
							parent
							
								
									cfcbc3a5c3
								
							
						
					
					
						commit
						554a21e1d0
					
				
					 4 changed files with 70 additions and 15 deletions
				
			
		|  | @ -136,6 +136,7 @@ class ResultContainer(object): | ||||||
|         self._ordered = False |         self._ordered = False | ||||||
|         self.paging = False |         self.paging = False | ||||||
|         self.unresponsive_engines = set() |         self.unresponsive_engines = set() | ||||||
|  |         self.timings = [] | ||||||
| 
 | 
 | ||||||
|     def extend(self, engine_name, results): |     def extend(self, engine_name, results): | ||||||
|         for result in list(results): |         for result in list(results): | ||||||
|  | @ -319,3 +320,13 @@ class ResultContainer(object): | ||||||
| 
 | 
 | ||||||
|     def add_unresponsive_engine(self, engine_error): |     def add_unresponsive_engine(self, engine_error): | ||||||
|         self.unresponsive_engines.add(engine_error) |         self.unresponsive_engines.add(engine_error) | ||||||
|  | 
 | ||||||
|  |     def add_timing(self, engine_name, engine_time, page_load_time): | ||||||
|  |         self.timings.append({ | ||||||
|  |             'engine': engines[engine_name].shortcut, | ||||||
|  |             'total': engine_time, | ||||||
|  |             'load': page_load_time | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |     def get_timings(self): | ||||||
|  |         return self.timings | ||||||
|  |  | ||||||
|  | @ -74,10 +74,10 @@ def search_one_request(engine, query, request_params): | ||||||
| 
 | 
 | ||||||
|     # ignoring empty urls |     # ignoring empty urls | ||||||
|     if request_params['url'] is None: |     if request_params['url'] is None: | ||||||
|         return [] |         return None | ||||||
| 
 | 
 | ||||||
|     if not request_params['url']: |     if not request_params['url']: | ||||||
|         return [] |         return None | ||||||
| 
 | 
 | ||||||
|     # send request |     # send request | ||||||
|     response = send_http_request(engine, request_params) |     response = send_http_request(engine, request_params) | ||||||
|  | @ -103,20 +103,29 @@ def search_one_request_safe(engine_name, query, request_params, result_container | ||||||
|         # send requests and parse the results |         # send requests and parse the results | ||||||
|         search_results = search_one_request(engine, query, request_params) |         search_results = search_one_request(engine, query, request_params) | ||||||
| 
 | 
 | ||||||
|         # add results |         # check if the engine accepted the request | ||||||
|  |         if search_results is not None: | ||||||
|  |             # yes, so add results | ||||||
|             result_container.extend(engine_name, search_results) |             result_container.extend(engine_name, search_results) | ||||||
| 
 | 
 | ||||||
|             # update engine time when there is no exception |             # update engine time when there is no exception | ||||||
|  |             engine_time = time() - start_time | ||||||
|  |             page_load_time = requests_lib.get_time_for_thread() | ||||||
|  |             result_container.add_timing(engine_name, engine_time, page_load_time) | ||||||
|             with threading.RLock(): |             with threading.RLock(): | ||||||
|             engine.stats['engine_time'] += time() - start_time |                 engine.stats['engine_time'] += engine_time | ||||||
|                 engine.stats['engine_time_count'] += 1 |                 engine.stats['engine_time_count'] += 1 | ||||||
|                 # update stats with the total HTTP time |                 # update stats with the total HTTP time | ||||||
|             engine.stats['page_load_time'] += requests_lib.get_time_for_thread() |                 engine.stats['page_load_time'] += page_load_time | ||||||
|                 engine.stats['page_load_count'] += 1 |                 engine.stats['page_load_count'] += 1 | ||||||
| 
 | 
 | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         search_duration = time() - start_time |         # Timing | ||||||
|  |         engine_time = time() - start_time | ||||||
|  |         page_load_time = requests_lib.get_time_for_thread() | ||||||
|  |         result_container.add_timing(engine_name, engine_time, page_load_time) | ||||||
| 
 | 
 | ||||||
|  |         # Record the errors | ||||||
|         with threading.RLock(): |         with threading.RLock(): | ||||||
|             engine.stats['errors'] += 1 |             engine.stats['errors'] += 1 | ||||||
| 
 | 
 | ||||||
|  | @ -125,14 +134,14 @@ def search_one_request_safe(engine_name, query, request_params, result_container | ||||||
|             # requests timeout (connect or read) |             # requests timeout (connect or read) | ||||||
|             logger.error("engine {0} : HTTP requests timeout" |             logger.error("engine {0} : HTTP requests timeout" | ||||||
|                          "(search duration : {1} s, timeout: {2} s) : {3}" |                          "(search duration : {1} s, timeout: {2} s) : {3}" | ||||||
|                          .format(engine_name, search_duration, timeout_limit, e.__class__.__name__)) |                          .format(engine_name, engine_time, timeout_limit, e.__class__.__name__)) | ||||||
|             requests_exception = True |             requests_exception = True | ||||||
|         elif (issubclass(e.__class__, requests.exceptions.RequestException)): |         elif (issubclass(e.__class__, requests.exceptions.RequestException)): | ||||||
|             result_container.add_unresponsive_engine((engine_name, gettext('request exception'))) |             result_container.add_unresponsive_engine((engine_name, gettext('request exception'))) | ||||||
|             # other requests exception |             # other requests exception | ||||||
|             logger.exception("engine {0} : requests exception" |             logger.exception("engine {0} : requests exception" | ||||||
|                              "(search duration : {1} s, timeout: {2} s) : {3}" |                              "(search duration : {1} s, timeout: {2} s) : {3}" | ||||||
|                              .format(engine_name, search_duration, timeout_limit, e)) |                              .format(engine_name, engine_time, timeout_limit, e)) | ||||||
|             requests_exception = True |             requests_exception = True | ||||||
|         else: |         else: | ||||||
|             result_container.add_unresponsive_engine(( |             result_container.add_unresponsive_engine(( | ||||||
|  |  | ||||||
|  | @ -43,6 +43,7 @@ except: | ||||||
|     exit(1) |     exit(1) | ||||||
| from cgi import escape | from cgi import escape | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
|  | from time import time | ||||||
| from werkzeug.contrib.fixers import ProxyFix | from werkzeug.contrib.fixers import ProxyFix | ||||||
| from flask import ( | from flask import ( | ||||||
|     Flask, request, render_template, url_for, Response, make_response, |     Flask, request, render_template, url_for, Response, make_response, | ||||||
|  | @ -402,6 +403,8 @@ def render(template_name, override_theme=None, **kwargs): | ||||||
| 
 | 
 | ||||||
| @app.before_request | @app.before_request | ||||||
| def pre_request(): | def pre_request(): | ||||||
|  |     request.start_time = time() | ||||||
|  |     request.timings = [] | ||||||
|     request.errors = [] |     request.errors = [] | ||||||
| 
 | 
 | ||||||
|     preferences = Preferences(themes, list(categories.keys()), engines, plugins) |     preferences = Preferences(themes, list(categories.keys()), engines, plugins) | ||||||
|  | @ -437,6 +440,21 @@ def pre_request(): | ||||||
|             request.user_plugins.append(plugin) |             request.user_plugins.append(plugin) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @app.after_request | ||||||
|  | def post_request(response): | ||||||
|  |     total_time = time() - request.start_time | ||||||
|  |     timings_all = ['total;dur=' + str(round(total_time * 1000, 3))] | ||||||
|  |     if len(request.timings) > 0: | ||||||
|  |         timings = sorted(request.timings, key=lambda v: v['total']) | ||||||
|  |         timings_total = ['total_' + str(i) + '_' + v['engine'] + | ||||||
|  |                          ';dur=' + str(round(v['total'] * 1000, 3)) for i, v in enumerate(timings)] | ||||||
|  |         timings_load = ['load_' + str(i) + '_' + v['engine'] + | ||||||
|  |                         ';dur=' + str(round(v['load'] * 1000, 3)) for i, v in enumerate(timings)] | ||||||
|  |         timings_all = timings_all + timings_total + timings_load | ||||||
|  |     response.headers.add('Server-Timing', ', '.join(timings_all)) | ||||||
|  |     return response | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def index_error(output_format, error_message): | def index_error(output_format, error_message): | ||||||
|     if output_format == 'json': |     if output_format == 'json': | ||||||
|         return Response(json.dumps({'error': error_message}), |         return Response(json.dumps({'error': error_message}), | ||||||
|  | @ -515,6 +533,9 @@ def index(): | ||||||
|     # UI |     # UI | ||||||
|     advanced_search = request.form.get('advanced_search', None) |     advanced_search = request.form.get('advanced_search', None) | ||||||
| 
 | 
 | ||||||
|  |     # Server-Timing header | ||||||
|  |     request.timings = result_container.get_timings() | ||||||
|  | 
 | ||||||
|     # output |     # output | ||||||
|     for result in results: |     for result in results: | ||||||
|         if output_format == 'html': |         if output_format == 'html': | ||||||
|  |  | ||||||
|  | @ -33,6 +33,19 @@ class ViewsTestCase(SearxTestCase): | ||||||
|             }, |             }, | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|  |         timings = [ | ||||||
|  |             { | ||||||
|  |                 'engine': 'startpage', | ||||||
|  |                 'total': 0.8, | ||||||
|  |                 'load': 0.7 | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 'engine': 'youtube', | ||||||
|  |                 'total': 0.9, | ||||||
|  |                 'load': 0.6 | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|         def search_mock(search_self, *args): |         def search_mock(search_self, *args): | ||||||
|             search_self.result_container = Mock(get_ordered_results=lambda: self.test_results, |             search_self.result_container = Mock(get_ordered_results=lambda: self.test_results, | ||||||
|                                                 answers=set(), |                                                 answers=set(), | ||||||
|  | @ -42,7 +55,8 @@ class ViewsTestCase(SearxTestCase): | ||||||
|                                                 unresponsive_engines=set(), |                                                 unresponsive_engines=set(), | ||||||
|                                                 results=self.test_results, |                                                 results=self.test_results, | ||||||
|                                                 results_number=lambda: 3, |                                                 results_number=lambda: 3, | ||||||
|                                                 results_length=lambda: len(self.test_results)) |                                                 results_length=lambda: len(self.test_results), | ||||||
|  |                                                 get_timings=lambda: timings) | ||||||
| 
 | 
 | ||||||
|         Search.search = search_mock |         Search.search = search_mock | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Alexandre Flament
						Alexandre Flament