#!/usr/bin/python3 # # Plugin to monitor the types of requsts made to the API # # Uses the query log. # # Parameters: # # config (required) # autoconf (optional - used by munin-config) # import re import os import sys from datetime import datetime, timedelta CONFIG="""graph_title Requests by API call graph_args --base 1000 -l 0 graph_vlabel requests per minute graph_category nominatim z1.label reverse z1.draw AREA z1.type GAUGE z2.label search (successful) z2.draw STACK z2.type GAUGE z3.label search (no result) z3.draw STACK z3.type GAUGE z4.label lookup z4.draw STACK z4.type GAUGE z4.label details z4.draw STACK z4.type GAUGE""" ENTRY_REGEX = re.compile(r'\[[^]]+\] (?P[0-9.]+) (?P\d+) (?P[a-z]+) ') TIME_REGEX = re.compile(r'\[(?P\d\d\d\d)-(?P\d\d)-(?P\d\d) (?P\d\d):(?P\d\d):(?P\d\d)[0-9.]*\] ') class LogFile: """ A query log file, unpacked. """ def __init__(self, filename): self.fd = open(filename, encoding='utf-8', errors='replace') self.len = os.path.getsize(filename) def __del__(self): self.fd.close() def seek_next(self, abstime): self.fd.seek(abstime) self.fd.readline() l = self.fd.readline() e = TIME_REGEX.match(l) if e is None: return None e = e.groupdict() return datetime(int(e['t_year']), int(e['t_month']), int(e['t_day']), int(e['t_hour']), int(e['t_min']), int(e['t_sec'])) def seek_to_date(self, target): # start position for binary search fromseek = 0 fromdate = self.seek_next(0) if fromdate > target: return True # end position for binary search toseek = -100 while -toseek < self.len: todate = self.seek_next(self.len + toseek) if todate is not None: break toseek -= 100 if todate is None or todate < target: return False toseek = self.len + toseek while True: bps = (toseek - fromseek) / (todate - fromdate).total_seconds() newseek = fromseek + int((target - fromdate).total_seconds() * bps) newdate = self.seek_next(newseek) if newdate is None: return False; error = abs((target - newdate).total_seconds()) if error < 1: return True if newdate > target: toseek = newseek todate = newdate oldfromseek = fromseek fromseek = toseek - error * bps while True: if fromseek <= oldfromseek: fromseek = oldfromseek fromdate = self.seek_next(fromseek) break fromdate = self.seek_next(fromseek) if fromdate < target: break; bps *=2 fromseek -= error * bps else: fromseek = newseek fromdate = newdate oldtoseek = toseek toseek = fromseek + error * bps while True: if toseek > oldtoseek: toseek = oldtoseek todate = self.seek_next(toseek) break todate = self.seek_next(toseek) if todate > target: break bps *=2 toseek += error * bps if toseek - fromseek < 500: return True def loglines(self): for l in self.fd: e = ENTRY_REGEX.match(l) if e is not None: yield e.groupdict() if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'config': print(CONFIG) sys.exit(0) reverse = 0 searchy = 0 searchn = 0 lookup = 0 details = 0 if 'NOMINATIM_QUERYLOG' in os.environ: lf = LogFile(os.environ['NOMINATIM_QUERYLOG']) if lf.seek_to_date(datetime.now() - timedelta(minutes=5)): for l in lf.loglines(): if l['type'] == 'reverse': reverse += 1 elif l['type'] == 'search': if l['numres'] == '0': searchn += 1 else: searchy += 1 elif l['type'] == 'place': lookup +=1 else: details += 1 print('z1.value', reverse/5) print('z2.value', searchy/5) print('z3.value', searchn/5) print('z4.value', lookup/5) print('z4.value', details/5)