3 # Plugin to monitor the types of requsts made to the API
10 # autoconf (optional - used by munin-config)
16 from datetime import datetime, timedelta
18 CONFIG="""graph_title Total Nominatim response time
19 graph_vlabel Time to response
20 graph_category Nominatim
22 graph_args --base 1000
24 avgs.label Average search time
28 avgs.info Moving 5 minute average time to perform search
29 avgs.label Average time to response
31 avgr.label Average reverse time
35 avgr.info Moving 5 minute average time to perform search
36 avgr.label Average time to response
38 max.label Slowest time to response
42 max.info Slowest query in last 5 minutes"""
44 ENTRY_REGEX = re.compile(r'\[[^]]+\] (?P<dur>[0-9.]+) (?P<numres>\d+) (?P<type>[a-z]+) ')
45 TIME_REGEX = re.compile(r'\[(?P<t_year>\d\d\d\d)-(?P<t_month>\d\d)-(?P<t_day>\d\d) (?P<t_hour>\d\d):(?P<t_min>\d\d):(?P<t_sec>\d\d)[0-9.]*\] ')
49 """ A query log file, unpacked. """
51 def __init__(self, filename):
52 self.fd = open(filename, encoding='utf-8')
53 self.len = os.path.getsize(filename)
58 def seek_next(self, abstime):
61 l = self.fd.readline()
62 e = TIME_REGEX.match(l)
66 return datetime(int(e['t_year']), int(e['t_month']), int(e['t_day']),
67 int(e['t_hour']), int(e['t_min']), int(e['t_sec']))
69 def seek_to_date(self, target):
70 # start position for binary search
72 fromdate = self.seek_next(0)
75 # end position for binary search
77 while -toseek < self.len:
78 todate = self.seek_next(self.len + toseek)
79 if todate is not None:
82 if todate is None or todate < target:
84 toseek = self.len + toseek
88 bps = (toseek - fromseek) / (todate - fromdate).total_seconds()
89 newseek = fromseek + int((target - fromdate).total_seconds() * bps)
90 newdate = self.seek_next(newseek)
93 error = abs((target - newdate).total_seconds())
99 oldfromseek = fromseek
100 fromseek = toseek - error * bps
102 if fromseek <= oldfromseek:
103 fromseek = oldfromseek
104 fromdate = self.seek_next(fromseek)
106 fromdate = self.seek_next(fromseek)
107 if fromdate < target:
110 fromseek -= error * bps
115 toseek = fromseek + error * bps
117 if toseek > oldtoseek:
119 todate = self.seek_next(toseek)
121 todate = self.seek_next(toseek)
125 toseek += error * bps
126 if toseek - fromseek < 500:
132 e = ENTRY_REGEX.match(l)
134 raise ValueError("Invalid log line:", l)
138 if __name__ == '__main__':
140 if len(sys.argv) > 1 and sys.argv[1] == 'config':
149 if 'NOMINATIM_QUERYLOG' in os.environ:
150 lf = LogFile(os.environ['NOMINATIM_QUERYLOG'])
151 if lf.seek_to_date(datetime.now() - timedelta(minutes=5)):
152 for l in lf.loglines():
153 dur = float(l['dur'])
154 if l['type'] == 'reverse':
157 elif l['type'] == 'search':
164 print('avgs.value', 0 if numsearch == 0 else sumsearch/numsearch)
165 print('avgr.value', 0 if numrev == 0 else sumrev/numrev)
166 print('max.value', maxres)