#!/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<dur>[0-9.]+) (?P<numres>\d+) (?P<type>[a-z]+) ')
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.]*\] ')


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)