histogram.py 3.75 KB
Newer Older
1 2
#!/usr/bin/env python3

Tomas Krizek's avatar
Tomas Krizek committed
3 4 5 6
# NOTE: Due to a weird bug, numpy is detected as a 3rd party module, while lmdb
#       is not and pylint complains about wrong-import-order.
#       Since these checks have to be disabled for matplotlib imports anyway, they
#       were moved a bit higher up to avoid the issue.
7
# pylint: disable=wrong-import-order,wrong-import-position
8 9 10 11
import argparse
import logging
import math
from typing import Dict, List
12
import sys
13 14 15 16

import lmdb
import numpy as np

17
from respdiff import cfg, cli
18 19
from respdiff.database import DNSRepliesFactory, LMDB, MetaDatabase
from respdiff.typing import ResolverID
20

21 22 23 24 25 26
# Force matplotlib to use a different backend to handle machines without a display
import matplotlib
import matplotlib.ticker as mtick
matplotlib.use('Agg')
import matplotlib.pyplot as plt  # noqa

27

28 29 30 31 32
def load_data(
            txn: lmdb.Transaction,
            dnsreplies_factory: DNSRepliesFactory
        ) -> Dict[ResolverID, List[float]]:
    data = {}  # type: Dict[ResolverID, List[float]]
33 34
    cursor = txn.cursor()
    for value in cursor.iternext(keys=False, values=True):
35
        replies = dnsreplies_factory.parse(value)
36
        for resolver, reply in replies.items():
37
            data.setdefault(resolver, []).append(reply.time)
38 39 40
    return data


41
def plot_log_percentile_histogram(data: Dict[str, List[float]], config=None):
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
    """
    For graph explanation, see
    https://blog.powerdns.com/2017/11/02/dns-performance-metrics-the-logarithmic-percentile-histogram/
    """
    _, ax = plt.subplots(figsize=(8, 8))

    # Distribute sample points along logarithmic X axis
    percentiles = np.logspace(-3, 2, num=100)

    ax.set_xscale('log')
    ax.xaxis.set_major_formatter(mtick.FormatStrFormatter('%s'))
    ax.set_yscale('log')
    ax.yaxis.set_major_formatter(mtick.FormatStrFormatter('%s'))

    ax.grid(True, which='major')
    ax.grid(True, which='minor', linestyle='dotted', color='#DDDDDD')

    ax.set_xlabel('Slowest percentile')
    ax.set_ylabel('Response time [ms]')
    ax.set_title('Resolver Response Time')

    # plot data
64 65 66 67 68 69
    for server in sorted(data):
        try:
            color = config[server]['graph_color']
        except KeyError:
            color = None

70 71 72 73
        # convert to ms and sort
        values = sorted([1000 * x for x in data[server]], reverse=True)
        ax.plot(percentiles,
                [values[math.ceil(pctl * len(values) / 100) - 1] for pctl in percentiles],
74
                lw=2, label=server, color=color)
75 76 77 78 79

    plt.legend()


def main():
80
    cli.setup_logging()
81 82 83 84
    parser = argparse.ArgumentParser(
        description='Plot query response time histogram from answers stored '
                    'in LMDB')
    parser.add_argument('-o', '--output', type=str,
85 86
                        default='histogram.png',
                        help='output image file (default: histogram.png)')
87 88
    parser.add_argument('-c', '--config', default='respdiff.cfg', dest='cfgpath',
                        help='config file (default: respdiff.cfg)')
89 90 91
    parser.add_argument('envdir', type=str,
                        help='LMDB environment to read answers from')
    args = parser.parse_args()
92
    config = cfg.read_cfg(args.cfgpath)
93 94
    servers = config['servers']['names']
    dnsreplies_factory = DNSRepliesFactory(servers)
95 96 97

    with LMDB(args.envdir, readonly=True) as lmdb_:
        adb = lmdb_.open_db(LMDB.ANSWERS)
98 99 100 101 102 103 104

        try:
            MetaDatabase(lmdb_, servers, create=False)  # check version and servers
        except NotImplementedError as exc:
            logging.critical(exc)
            sys.exit(1)

105
        with lmdb_.env.begin(adb) as txn:
106
            data = load_data(txn, dnsreplies_factory)
107
    plot_log_percentile_histogram(data, config)
108 109 110 111 112 113 114

    # save to file
    plt.savefig(args.output, dpi=300)


if __name__ == '__main__':
    main()