def main(): cli.setup_logging() parser = argparse.ArgumentParser(description=( "Plot and compare reports against statistical data. " "Returns non-zero exit code if any threshold is exceeded.")) cli.add_arg_stats(parser) cli.add_arg_report(parser) cli.add_arg_config(parser) parser.add_argument( '-l', '--label', default='fields_overview', help='Set plot label. It is also used for the filename.') args = parser.parse_args() sumstats = args.stats field_weights = args.cfg['report']['field_weights'] try: summaries = cli.load_summaries(args.report) except ValueError: sys.exit(1) logging.info('Start Comparison: %s', args.label) passed = plot_overview(sumstats, field_weights, summaries, args.label) if not passed: sys.exit(3) else: sys.exit(0)
def main(): cli.setup_logging() parser = argparse.ArgumentParser( description='Check if new samples belong to reference ' 'distribution. Ends with exitcode 0 if belong, ' '1 if not,') parser.add_argument('-r', '--reference', type=cli.read_stats, help='json statistics file with reference data') cli.add_arg_report_filename(parser) parser.add_argument( '-c', '--coef', type=float, default=DEFAULT_COEF, help=( 'coeficient for comparation (new belongs to refference if ' 'its median is closer than COEF * standart deviation of reference ' 'from reference mean) (default: {})'.format(DEFAULT_COEF))) args = parser.parse_args() reports = cli.get_reports_from_filenames(args) try: newstats = SummaryStatistics(reports) except ValueError as exc: logging.critical(exc) sys.exit(2) if not belongs_to_all(args.reference, newstats, args.coef): sys.exit(1) sys.exit(0)
def main(): cli.setup_logging() parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description='Convert queries data from standard input and store ' 'wire format into LMDB "queries" DB.') cli.add_arg_envdir(parser) parser.add_argument( '-f', '--in-format', type=str, choices=['text', 'pcap'], default='text', help='define format for input data, default value is text\n' 'Expected input for "text" is: "<qname> <RR type>", ' 'one query per line.\n' 'Expected input for "pcap" is content of the pcap file.') parser.add_argument('--pcap-file', type=argparse.FileType('rb')) args = parser.parse_args() if args.in_format == 'text' and args.pcap_file: logging.critical( "Argument --pcap-file can be use only in combination with -f pcap") sys.exit(1) if args.in_format == 'pcap' and not args.pcap_file: logging.critical("Missing path to pcap file, use argument --pcap-file") sys.exit(1) with LMDB(args.envdir) as lmdb: qdb = lmdb.open_db(LMDB.QUERIES, create=True, check_notexists=True) txn = lmdb.env.begin(qdb, write=True) try: with pool.Pool(initializer=lambda: signal.signal( signal.SIGINT, signal.SIG_IGN)) as workers: if args.in_format == 'text': data_stream = read_lines(sys.stdin) method = wrk_process_line elif args.in_format == 'pcap': data_stream = parse_pcap(args.pcap_file) method = wrk_process_frame else: logging.error('unknown in-format, use "text" or "pcap"') sys.exit(1) for qid, wire in workers.imap(method, data_stream, chunksize=1000): if qid is not None: key = qid2key(qid) txn.put(key, wire) except KeyboardInterrupt: logging.info('SIGINT received, exiting...') sys.exit(130) except RuntimeError as err: logging.error(err) sys.exit(1) finally: # attempt to preserve data if something went wrong (or not) logging.debug('Comitting LMDB transaction...') txn.commit()
def main(): cli.setup_logging() parser = argparse.ArgumentParser(description="export queries from reports' summaries") cli.add_arg_report_filename(parser, nargs='+') parser.add_argument('--envdir', type=str, help="LMDB environment (required when output format isn't 'qid')") parser.add_argument('-f', '--format', type=str, choices=['query', 'qid', 'domain', 'base64url'], default='domain', help="output data format") parser.add_argument('-o', '--output', type=str, help='output file') parser.add_argument('--failing', action='store_true', help="get target disagreements") parser.add_argument('--unstable', action='store_true', help="get upstream unstable") parser.add_argument('--qidlist', type=str, help='path to file with list of QIDs to export') args = parser.parse_args() if args.format != 'qid' and not args.envdir: logging.critical("--envdir required when output format isn't 'qid'") sys.exit(1) if not args.failing and not args.unstable and not args.qidlist: logging.critical('No filter selected!') sys.exit(1) reports = cli.get_reports_from_filenames(args) if not reports: logging.critical('No reports found!') sys.exit(1) try: qids = get_qids_to_export(args, reports) except ValueError as exc: logging.critical(str(exc)) sys.exit(1) with cli.smart_open(args.output) as fh: if args.format == 'qid': export_qids(qids, fh) else: with LMDB(args.envdir, readonly=True) as lmdb: lmdb.open_db(LMDB.QUERIES) if args.format == 'query': export_qids_to_qname_qtype(qids, lmdb, fh) elif args.format == 'domain': export_qids_to_qname(qids, lmdb, fh) elif args.format == 'base64url': export_qids_to_base64url(qids, lmdb, fh) else: raise ValueError('unsupported output format')
def main(): cli.setup_logging() parser = argparse.ArgumentParser( description='read queries from LMDB, send them in parallel to servers ' 'listed in configuration file, and record answers into LMDB') cli.add_arg_envdir(parser) cli.add_arg_config(parser) parser.add_argument( '--ignore-timeout', action="store_true", help='continue despite consecutive timeouts from resolvers') args = parser.parse_args() sendrecv.module_init(args) with LMDB(args.envdir) as lmdb: meta = MetaDatabase(lmdb, args.cfg['servers']['names'], create=True) meta.write_version() meta.write_start_time() lmdb.open_db(LMDB.QUERIES) adb = lmdb.open_db(LMDB.ANSWERS, create=True, check_notexists=True) qstream = lmdb.key_value_stream(LMDB.QUERIES) txn = lmdb.env.begin(adb, write=True) try: # process queries in parallel with pool.Pool(processes=args.cfg['sendrecv']['jobs'], initializer=sendrecv.worker_init) as p: i = 0 for qkey, blob in p.imap(sendrecv.worker_perform_query, qstream, chunksize=100): i += 1 if i % 10000 == 0: logging.info('Received {:d} answers'.format(i)) txn.put(qkey, blob) except KeyboardInterrupt: logging.info('SIGINT received, exiting...') sys.exit(130) except RuntimeError as err: logging.error(err) sys.exit(1) finally: # attempt to preserve data if something went wrong (or not) logging.debug('Comitting LMDB transaction...') txn.commit() meta.write_end_time()
def main(): cli.setup_logging() parser = argparse.ArgumentParser( description='attempt to reproduce original diffs from JSON report') cli.add_arg_envdir(parser) cli.add_arg_config(parser) cli.add_arg_datafile(parser) parser.add_argument('--sequential', action='store_true', default=False, help='send one query at a time (slower, but more reliable)') args = parser.parse_args() sendrecv.module_init(args) datafile = cli.get_datafile(args) report = DiffReport.from_json(datafile) restart_scripts = repro.get_restart_scripts(args.cfg) servers = args.cfg['servers']['names'] dnsreplies_factory = DNSRepliesFactory(servers) if args.sequential: nproc = 1 else: nproc = args.cfg['sendrecv']['jobs'] if report.reprodata is None: report.reprodata = ReproData() with LMDB(args.envdir, readonly=True) as lmdb: lmdb.open_db(LMDB.QUERIES) cli.check_metadb_servers_version(lmdb, servers) dstream = repro.query_stream_from_disagreements(lmdb, report) try: repro.reproduce_queries( dstream, report, dnsreplies_factory, args.cfg['diff']['criteria'], args.cfg['diff']['target'], restart_scripts, nproc) finally: # make sure data is saved in case of interrupt report.export_json(datafile)
def main(): global lmdb cli.setup_logging() parser = argparse.ArgumentParser( description= 'compute diff from answers stored in LMDB and write diffs to LMDB') cli.add_arg_envdir(parser) cli.add_arg_config(parser) cli.add_arg_datafile(parser) args = parser.parse_args() datafile = cli.get_datafile(args, check_exists=False) criteria = args.cfg['diff']['criteria'] target = args.cfg['diff']['target'] servers = args.cfg['servers']['names'] with LMDB(args.envdir) as lmdb_: # NOTE: To avoid an lmdb.BadRslotError, probably caused by weird # interaction when using multiple transaction / processes, open a separate # environment. Also, any dbs have to be opened before using MetaDatabase(). report = prepare_report(lmdb_, servers) cli.check_metadb_servers_version(lmdb_, servers) with LMDB(args.envdir, fast=True) as lmdb_: lmdb = lmdb_ lmdb.open_db(LMDB.ANSWERS) lmdb.open_db(LMDB.DIFFS, create=True, drop=True) qid_stream = lmdb.key_stream(LMDB.ANSWERS) dnsreplies_factory = DNSRepliesFactory(servers) compare_func = partial(compare_lmdb_wrapper, criteria, target, dnsreplies_factory) with pool.Pool() as p: for _ in p.imap_unordered(compare_func, qid_stream, chunksize=10): pass export_json(datafile, report)
def main(): cli.setup_logging() parser = argparse.ArgumentParser( description= "use dnsviz to categorize domains (perfect, warnings, errors)") cli.add_arg_config(parser) cli.add_arg_dnsviz(parser) parser.add_argument('input', type=str, help='input file with domains (one qname per line)') args = parser.parse_args() njobs = args.cfg['sendrecv']['jobs'] try: probe = subprocess.run([ 'dnsviz', 'probe', '-A', '-R', respdiff.dnsviz.TYPES, '-f', args.input, '-t', str(njobs) ], check=True, stdout=subprocess.PIPE) except subprocess.CalledProcessError as exc: logging.critical("dnsviz probe failed: %s", exc) sys.exit(1) except FileNotFoundError: logging.critical("'dnsviz' tool is not installed!") sys.exit(1) try: subprocess.run(['dnsviz', 'grok', '-o', args.dnsviz], input=probe.stdout, check=True, stdout=subprocess.PIPE) except subprocess.CalledProcessError as exc: logging.critical("dnsviz grok failed: %s", exc) sys.exit(1)
def main(): cli.setup_logging() parser = argparse.ArgumentParser(description='compare two diff summaries') cli.add_arg_config(parser) parser.add_argument('old_datafile', type=str, help='report to compare against') parser.add_argument('new_datafile', type=str, help='report to compare evaluate') cli.add_arg_envdir( parser) # TODO remove when we no longer need to read queries from lmdb cli.add_arg_limit(parser) args = parser.parse_args() report = DiffReport.from_json(cli.get_datafile(args, key='new_datafile')) field_weights = args.cfg['report']['field_weights'] ref_report = DiffReport.from_json( cli.get_datafile(args, key='old_datafile')) check_report_summary(report) check_report_summary(ref_report) check_usable_answers(report, ref_report) cli.print_global_stats(report, ref_report) cli.print_differences_stats(report, ref_report) if report.summary or ref_report.summary: # when there are any differences to report field_counters = report.summary.get_field_counters() ref_field_counters = ref_report.summary.get_field_counters() # make sure "disappeared" fields show up as well for field in ref_field_counters: if field not in field_counters: field_counters[field] = Counter() cli.print_fields_overview(field_counters, len(report.summary), ref_field_counters) for field in field_weights: if field in field_counters: counter = field_counters[field] ref_counter = ref_field_counters.get(field, Counter()) # make sure "disappeared" mismatches show up as well for mismatch in ref_counter: if mismatch not in counter: counter[mismatch] = 0 cli.print_field_mismatch_stats(field, counter, len(report.summary), ref_counter) # query details with LMDB(args.envdir, readonly=True) as lmdb: lmdb.open_db(LMDB.QUERIES) queries_all = convert_queries( get_query_iterator(lmdb, report.summary.keys())) ref_queries_all = convert_queries( get_query_iterator(lmdb, ref_report.summary.keys())) for field in field_weights: if field in field_counters: # ensure "disappeared" mismatches are shown field_mismatches = dict( report.summary.get_field_mismatches(field)) ref_field_mismatches = dict( ref_report.summary.get_field_mismatches(field)) mismatches = set(field_mismatches.keys()) mismatches.update(ref_field_mismatches.keys()) for mismatch in mismatches: qids = field_mismatches.get(mismatch, set()) queries = convert_queries( get_query_iterator(lmdb, qids)) ref_queries = convert_queries( get_query_iterator( lmdb, ref_field_mismatches.get(mismatch, set()))) cli.print_mismatch_queries( field, mismatch, get_printable_queries_format( queries, queries_all, ref_queries, ref_queries_all), args.limit)
def main(): cli.setup_logging() args = parse_args() datafile = cli.get_datafile(args) report = DiffReport.from_json(datafile) field_weights = args.cfg['report']['field_weights'] check_args(args, report) ignore_qids = set() if args.without_ref_unstable or args.without_ref_failing: try: stats = cli.read_stats(args.stats_filename) except ValueError as exc: logging.critical(str(exc)) sys.exit(1) if args.without_ref_unstable: ignore_qids.update(stats.queries.unstable) if args.without_ref_failing: ignore_qids.update(stats.queries.failing) report = DiffReport.from_json(datafile) report.summary = Summary.from_report( report, field_weights, without_diffrepro=args.without_diffrepro, ignore_qids=ignore_qids) # dnsviz filter: by domain -> need to iterate over disagreements to get QIDs if args.without_dnsviz_errors: try: dnsviz_grok = DnsvizGrok.from_json(args.dnsviz) except (FileNotFoundError, RuntimeError) as exc: logging.critical('Failed to load dnsviz data: %s', exc) sys.exit(1) error_domains = dnsviz_grok.error_domains() with LMDB(args.envdir, readonly=True) as lmdb: lmdb.open_db(LMDB.QUERIES) # match domain, add QID to ignore for qid, wire in get_query_iterator(lmdb, report.summary.keys()): msg = dns.message.from_wire(wire) if msg.question: if any(msg.question[0].name.is_subdomain(name) for name in error_domains): ignore_qids.add(qid) report.summary = Summary.from_report( report, field_weights, without_diffrepro=args.without_diffrepro, ignore_qids=ignore_qids) cli.print_global_stats(report) cli.print_differences_stats(report) if report.summary: # when there are any differences to report field_counters = report.summary.get_field_counters() cli.print_fields_overview(field_counters, len(report.summary)) for field in field_weights: if field in report.summary.field_labels: cli.print_field_mismatch_stats(field, field_counters[field], len(report.summary)) # query details with LMDB(args.envdir, readonly=True) as lmdb: lmdb.open_db(LMDB.QUERIES) for field in field_weights: if field in report.summary.field_labels: for mismatch, qids in report.summary.get_field_mismatches( field): queries = convert_queries( get_query_iterator(lmdb, qids)) cli.print_mismatch_queries( field, mismatch, get_printable_queries_format(queries), args.limit) report.export_json(datafile)
def main(): cli.setup_logging() parser = argparse.ArgumentParser( description='Plot query response time histogram from answers stored ' 'in LMDB') parser.add_argument( '-o', '--output', type=str, default='histogram', help='output directory for image files (default: histogram)') parser.add_argument('-f', '--format', type=str, default='png', help='output image format (default: png)') parser.add_argument('-c', '--config', default='respdiff.cfg', dest='cfgpath', help='config file (default: respdiff.cfg)') parser.add_argument('envdir', type=str, help='LMDB environment to read answers from') args = parser.parse_args() config = cfg.read_cfg(args.cfgpath) servers = config['servers']['names'] dnsreplies_factory = DNSRepliesFactory(servers) with LMDB(args.envdir, readonly=True) as lmdb_: adb = lmdb_.open_db(LMDB.ANSWERS) try: MetaDatabase(lmdb_, servers, create=False) # check version and servers except NotImplementedError as exc: logging.critical(exc) sys.exit(1) with lmdb_.env.begin(adb) as txn: data = load_data(txn, dnsreplies_factory) def get_filepath(filename) -> str: return os.path.join(args.output, filename + '.' + args.format) if not os.path.exists(args.output): os.makedirs(args.output) create_histogram({k: [tup[0] for tup in d] for (k, d) in data.items()}, get_filepath('all'), 'all', config) # rcode-specific queries with pool.Pool() as p: fargs = [] for rcode in range(HISTOGRAM_RCODE_MAX + 1): rcode_text = dns.rcode.to_text(rcode) filepath = get_filepath(rcode_text) fargs.append((data, filepath, rcode_text, config, rcode)) p.starmap(histogram_by_rcode, fargs) filepath = get_filepath('unparsed') histogram_by_rcode(data, filepath, 'unparsed queries', config, None)