#!/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 Total Nominatim response time graph_vlabel Time to response graph_category Nominatim graph_period minute graph_args --base 1000 avgs.label Average search time avgs.draw LINE avgs.type GAUGE avgs.min 0 avgs.info Moving 5 minute average time to perform search avgr.label Average reverse time avgr.draw LINE avgr.type GAUGE avgr.min 0 avgr.info Moving 5 minute average time to perform reverse max.label Slowest time to response (1/100) max.draw LINE max.type GAUGE max.min 0 max.info Slowest query in last 5 minutes (unit: 100s)""" 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) sumrev = 0 numrev = 0 sumsearch = 0 numsearch = 0 maxres = 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(): dur = float(l['dur']) if l['type'] == 'reverse': numrev += 1 sumrev += dur elif l['type'] == 'search': numsearch += 1 sumsearch += dur if dur > maxres: maxres = dur print('avgs.value', 0 if numsearch == 0 else sumsearch/numsearch) print('avgr.value', 0 if numrev == 0 else sumrev/numrev) print('max.value', maxres/100.0)