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
30 avgr.label Average reverse time
34 avgr.info Moving 5 minute average time to perform reverse
36 max.label Slowest time to response (1/100)
40 max.info Slowest query in last 5 minutes (unit: 100s)"""
42 ENTRY_REGEX = re.compile(r'\[[^]]+\] (?P<dur>[0-9.]+) (?P<numres>\d+) (?P<type>[a-z]+) ')
43 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.]*\] ')
47 """ A query log file, unpacked. """
49 def __init__(self, filename):
50 self.fd = open(filename, encoding='utf-8', errors='replace')
51 self.len = os.path.getsize(filename)
56 def seek_next(self, abstime):
59 l = self.fd.readline()
60 e = TIME_REGEX.match(l)
64 return datetime(int(e['t_year']), int(e['t_month']), int(e['t_day']),
65 int(e['t_hour']), int(e['t_min']), int(e['t_sec']))
67 def seek_to_date(self, target):
68 # start position for binary search
70 fromdate = self.seek_next(0)
73 # end position for binary search
75 while -toseek < self.len:
76 todate = self.seek_next(self.len + toseek)
77 if todate is not None:
80 if todate is None or todate < target:
82 toseek = self.len + toseek
86 bps = (toseek - fromseek) / (todate - fromdate).total_seconds()
87 newseek = fromseek + int((target - fromdate).total_seconds() * bps)
88 newdate = self.seek_next(newseek)
91 error = abs((target - newdate).total_seconds())
97 oldfromseek = fromseek
98 fromseek = toseek - error * bps
100 if fromseek <= oldfromseek:
101 fromseek = oldfromseek
102 fromdate = self.seek_next(fromseek)
104 fromdate = self.seek_next(fromseek)
105 if fromdate < target:
108 fromseek -= error * bps
113 toseek = fromseek + error * bps
115 if toseek > oldtoseek:
117 todate = self.seek_next(toseek)
119 todate = self.seek_next(toseek)
123 toseek += error * bps
124 if toseek - fromseek < 500:
130 e = ENTRY_REGEX.match(l)
135 if __name__ == '__main__':
137 if len(sys.argv) > 1 and sys.argv[1] == 'config':
146 if 'NOMINATIM_QUERYLOG' in os.environ:
147 lf = LogFile(os.environ['NOMINATIM_QUERYLOG'])
148 if lf.seek_to_date(datetime.now() - timedelta(minutes=5)):
149 for l in lf.loglines():
150 dur = float(l['dur'])
151 if l['type'] == 'reverse':
154 elif l['type'] == 'search':
161 print('avgs.value', 0 if numsearch == 0 else sumsearch/numsearch)
162 print('avgr.value', 0 if numrev == 0 else sumrev/numrev)
163 print('max.value', maxres/100.0)