introduce parameter for saving query statistics

This commit is contained in:
Sarah Hoffmann
2025-09-10 10:24:20 +02:00
parent 355cbcc7b8
commit 0b7bde2500
2 changed files with 105 additions and 45 deletions

View File

@@ -217,7 +217,9 @@ class NominatimAPIAsync:
""" """
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.LookupDetails.from_kwargs(params) details = ntyp.LookupDetails.from_kwargs(params)
with details.query_stats as qs:
async with self.begin(abs_timeout=timeout.abs) as conn: async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout) conn.set_query_timeout(self.query_timeout)
if details.keywords: if details.keywords:
await nsearch.make_query_analyzer(conn) await nsearch.make_query_analyzer(conn)
@@ -230,7 +232,9 @@ class NominatimAPIAsync:
""" """
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.LookupDetails.from_kwargs(params) details = ntyp.LookupDetails.from_kwargs(params)
with details.query_stats as qs:
async with self.begin(abs_timeout=timeout.abs) as conn: async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout) conn.set_query_timeout(self.query_timeout)
if details.keywords: if details.keywords:
await nsearch.make_query_analyzer(conn) await nsearch.make_query_analyzer(conn)
@@ -249,7 +253,9 @@ class NominatimAPIAsync:
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
details = ntyp.ReverseDetails.from_kwargs(params) details = ntyp.ReverseDetails.from_kwargs(params)
with details.query_stats as qs:
async with self.begin(abs_timeout=timeout.abs) as conn: async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout) conn.set_query_timeout(self.query_timeout)
if details.keywords: if details.keywords:
await nsearch.make_query_analyzer(conn) await nsearch.make_query_analyzer(conn)
@@ -260,15 +266,17 @@ class NominatimAPIAsync:
async def search(self, query: str, **params: Any) -> SearchResults: async def search(self, query: str, **params: Any) -> SearchResults:
""" Find a place by free-text search. Also known as forward geocoding. """ Find a place by free-text search. Also known as forward geocoding.
""" """
timeout = Timeout(self.request_timeout)
details = ntyp.SearchDetails.from_kwargs(params)
with details.query_stats as qs:
query = query.strip() query = query.strip()
if not query: if not query:
raise UsageError('Nothing to search for.') raise UsageError('Nothing to search for.')
timeout = Timeout(self.request_timeout)
async with self.begin(abs_timeout=timeout.abs) as conn: async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout) conn.set_query_timeout(self.query_timeout)
geocoder = nsearch.ForwardGeocoder(conn, ntyp.SearchDetails.from_kwargs(params), geocoder = nsearch.ForwardGeocoder(conn, details, timeout)
timeout)
phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p.strip()) for p in query.split(',')] phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p.strip()) for p in query.split(',')]
return await geocoder.lookup(phrases) return await geocoder.lookup(phrases)
@@ -283,10 +291,8 @@ class NominatimAPIAsync:
""" Find an address using structured search. """ Find an address using structured search.
""" """
timeout = Timeout(self.request_timeout) timeout = Timeout(self.request_timeout)
async with self.begin(abs_timeout=timeout.abs) as conn:
conn.set_query_timeout(self.query_timeout)
details = ntyp.SearchDetails.from_kwargs(params) details = ntyp.SearchDetails.from_kwargs(params)
with details.query_stats as qs:
phrases: List[nsearch.Phrase] = [] phrases: List[nsearch.Phrase] = []
if amenity: if amenity:
@@ -325,6 +331,9 @@ class NominatimAPIAsync:
if amenity: if amenity:
details.layers |= ntyp.DataLayer.POI details.layers |= ntyp.DataLayer.POI
async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout)
geocoder = nsearch.ForwardGeocoder(conn, details, timeout) geocoder = nsearch.ForwardGeocoder(conn, details, timeout)
return await geocoder.lookup(phrases) return await geocoder.lookup(phrases)
@@ -335,12 +344,14 @@ class NominatimAPIAsync:
The near place may either be given as an unstructured search The near place may either be given as an unstructured search
query in itself or as coordinates. query in itself or as coordinates.
""" """
timeout = Timeout(self.request_timeout)
details = ntyp.SearchDetails.from_kwargs(params)
with details.query_stats as qs:
if not categories: if not categories:
return SearchResults() return SearchResults()
timeout = Timeout(self.request_timeout)
details = ntyp.SearchDetails.from_kwargs(params)
async with self.begin(abs_timeout=timeout.abs) as conn: async with self.begin(abs_timeout=timeout.abs) as conn:
qs.log_time('start_query')
conn.set_query_timeout(self.query_timeout) conn.set_query_timeout(self.query_timeout)
if near_query: if near_query:
phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p) for p in near_query.split(',')] phrases = [nsearch.Phrase(nsearch.PHRASE_ANY, p) for p in near_query.split(',')]

View File

@@ -2,7 +2,7 @@
# #
# This file is part of Nominatim. (https://nominatim.org) # This file is part of Nominatim. (https://nominatim.org)
# #
# Copyright (C) 2024 by the Nominatim developer community. # Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log. # For a full list of authors see the git log.
""" """
Complex datatypes used by the Nominatim API. Complex datatypes used by the Nominatim API.
@@ -11,6 +11,7 @@ from typing import Optional, Union, Tuple, NamedTuple, TypeVar, Type, Dict, \
Any, List, Sequence Any, List, Sequence
from collections import abc from collections import abc
import dataclasses import dataclasses
import datetime as dt
import enum import enum
import math import math
from struct import unpack from struct import unpack
@@ -334,6 +335,49 @@ class DataLayer(enum.Flag):
""" """
class QueryStatistics(dict[str, Any]):
""" A specialised dictionary for collecting query statistics.
"""
def __enter__(self) -> 'QueryStatistics':
self.log_time('start_function')
return self
def __exit__(self, *_: Any) -> None:
self.log_time('end_function')
self['total_time'] = (self['end_function'] - self['start_function']) \
/ dt.timedelta(microseconds=1)
if 'start_query' in self:
self['wait_time'] = (self['start_query'] - self['start_function']) \
/ dt.timedelta(microseconds=1)
else:
self['wait_time'] = 0
self['query_time'] = self['total_time'] - self['wait_time']
def __missing__(self, key: str) -> str:
return ''
def log_time(self, key: str) -> None:
self[key] = dt.datetime.now(tz=dt.timezone.utc)
class NoQueryStats:
""" Null object to use, when no query statistics are requested.
"""
def __enter__(self) -> 'NoQueryStats':
return self
def __exit__(self, *_: Any) -> None:
pass
def __setitem__(self, key: str, value: Any) -> None:
pass
def log_time(self, key: str) -> None:
pass
def format_country(cc: Any) -> List[str]: def format_country(cc: Any) -> List[str]:
""" Extract a list of country codes from the input which may be either """ Extract a list of country codes from the input which may be either
a string or list of strings. Filters out all values that are not a string or list of strings. Filters out all values that are not
@@ -412,6 +456,11 @@ class LookupDetails:
0.0 means the original geometry is kept. The higher the value, the 0.0 means the original geometry is kept. The higher the value, the
more the geometry gets simplified. more the geometry gets simplified.
""" """
query_stats: Union[QueryStatistics, NoQueryStats] = \
dataclasses.field(default_factory=NoQueryStats)
""" Optional QueryStatistics object collecting information about
runtime behaviour of the call.
"""
@classmethod @classmethod
def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam: def from_kwargs(cls: Type[TParam], kwargs: Dict[str, Any]) -> TParam: