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)