#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-only # # This file is part of Nominatim. # Copyright (C) 2020 Sarah Hoffmann """ Script for analysing the indexing process. The script enables detailed logging for nested statements and then runs the indexing process for teh given object. Detailed 'EXPLAIN ANALYSE' information is printed for each executed query in the trigger. The transaction is then rolled back, so that no actual changes to the database happen. It also disables logging into the system log, so that the log files are not cluttered. """ from argparse import ArgumentParser, RawDescriptionHelpFormatter, ArgumentTypeError import psycopg2 import getpass import re class Analyser(object): def __init__(self, options): password = None if options.password_prompt: password = getpass.getpass("Database password: ") self.options = options self.conn = psycopg2.connect(dbname=options.dbname, user=options.user, password=password, host=options.host, port=options.port) def run(self): c = self.conn.cursor() if self.options.placeid: place_id = self.options.placeid else: if self.options.rank: c.execute(f"""select place_id from placex where rank_address = {self.options.rank} and linked_place_id is null limit 1""") objinfo = f"rank {self.options.rank}" if self.options.osmid: osm_type = self.options.osmid[0].upper() if osm_type not in ('N', 'W', 'R'): raise RuntimeError("OSM ID must be of form ") try: osm_id = int(self.options.osmid[1:]) except ValueError: raise RuntimeError("OSM ID must be of form ") c.execute(f"""SELECT place_id FROM placex WHERE osm_type = '{osm_type}' AND osm_id = {osm_id}""") objinfo = f"OSM object {self.options.osmid}" if c.rowcount < 1: raise RuntimeError(f"Cannot find a place for {objinfo}.") place_id = c.fetchone()[0] c.execute(f"""update placex set indexed_status = 2 where place_id = {place_id}""") c.execute("""SET auto_explain.log_min_duration = '0'; SET auto_explain.log_analyze = 'true'; SET auto_explain.log_nested_statements = 'true'; LOAD 'auto_explain'; SET client_min_messages = LOG; SET log_min_messages = FATAL"""); c.execute(f"""update placex set indexed_status = 0 where place_id = {place_id}""") c.close() # automatic rollback for l in self.conn.notices: print(l) if __name__ == '__main__': def h(s): return re.sub("\s\s+" , " ", s) p = ArgumentParser(description=__doc__, formatter_class=RawDescriptionHelpFormatter) group = p.add_mutually_exclusive_group(required=True) group.add_argument('--rank', dest='rank', type=int, help='Analyse indexing of the given address rank') group.add_argument('--osm-id', dest='osmid', type=str, help='Analyse indexing of the given OSM object') group.add_argument('--place-id', dest='placeid', type=int, help='Analyse indexing of the given Nominatim object') p.add_argument('-d', '--database', dest='dbname', action='store', default='nominatim', help='Name of the PostgreSQL database to connect to.') p.add_argument('-U', '--username', dest='user', action='store', help='PostgreSQL user name.') p.add_argument('-W', '--password', dest='password_prompt', action='store_true', help='Force password prompt.') p.add_argument('-H', '--host', dest='host', action='store', help='PostgreSQL server hostname or socket location.') p.add_argument('-P', '--port', dest='port', action='store', help='PostgreSQL server port') Analyser(p.parse_args()).run()