Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-09 10:47:25

0001 #!/usr/bin/env python3
0002 """segfault-reproductions-reconcile.py — reproduction requests meet their runs.
0003 
0004 The step the canary agent runs after each probe collection
0005 (swf-epicprod docs/SEGFAULT_DIAGNOSIS.md, Reproduction;
0006 monitor_app/reproductions.py): every open reproduction request on a
0007 crash signature takes the identity and outcome of the canary run that
0008 answered it, a production run and a reference run that have both
0009 reported settle the signature's reproduction outcome, a reproduced
0010 crash with no trace on record has its trace read from the crashed
0011 run's payload report (the reference run first), and a signature
0012 settled as reproduced or site dependent with a trace on record has its
0013 diagnosis queued, once, unless a finding already names it or its frame,
0014 in which case the finding is its diagnosis and it is marked diagnosed
0015 by it, so nothing further is asked of it. The runs' evidence is what the collection
0016 already wrote to the canary store; the one PanDA read is the report of
0017 a reproduction that finished at the server, which sits in the metatable.
0018 
0019 Django-bootstrap standalone script, by hand::
0020 
0021     cd /data/wenauseic/github/swf-monitor/src
0022     ../../swf-testbed/.venv/bin/python ../scripts/segfault-reproductions-reconcile.py [--key KEY] [--dry-run]
0023 
0024 Prints one JSON line: the signatures visited and what changed on each.
0025 Exit status 1 when any signature's reconciliation raised.
0026 """
0027 import argparse
0028 import json
0029 import logging
0030 import os
0031 import sys
0032 import time
0033 
0034 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
0035 sys.path.insert(0, os.path.join(THIS_DIR, '..', 'src'))
0036 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'swf_monitor_project.settings')
0037 
0038 import django  # noqa: E402
0039 django.setup()
0040 
0041 from monitor_app.epicprod_logging import log_epicprod_action  # noqa: E402
0042 from monitor_app.reproductions import attempts, open_signature_keys, reconcile, withdraw_request  # noqa: E402
0043 from monitor_app.segfaults import queue_diagnosis  # noqa: E402
0044 
0045 log = logging.getLogger('segfault-reproductions-reconcile')
0046 
0047 
0048 def main():
0049     ap = argparse.ArgumentParser(description=__doc__.split('\n\n')[0])
0050     ap.add_argument('--key', help='one signature instead of every open one')
0051     ap.add_argument('--dry-run', action='store_true',
0052                     help='show the attempts of the open signatures, write nothing')
0053     ap.add_argument('--withdraw', metavar='REQUEST_ID',
0054                     help='record that the agent did not run this request of --key '
0055                          '(a duplicate it dropped, or with --failed a dispatch that '
0056                          'failed before a run existed), with --reason; nothing else runs')
0057     ap.add_argument('--failed', action='store_true',
0058                     help='--withdraw: the dispatch failed (reads as a failed submission)')
0059     ap.add_argument('--reason', default='',
0060                     help='--withdraw: why, as the runs page shows it')
0061     args = ap.parse_args()
0062     logging.basicConfig(level=logging.INFO, stream=sys.stderr,
0063                         format='%(asctime)s %(name)s %(levelname)s %(message)s')
0064     t0 = time.monotonic()
0065     if args.withdraw:
0066         if not args.key:
0067             ap.error('--withdraw needs --key')
0068         result = withdraw_request(args.key, args.withdraw, args.reason, failed=args.failed)
0069         log_epicprod_action(
0070             'canary-agent', 'segfault_reproduction_withdraw',
0071             outcome='ok' if result == 'withdrawn' else 'noop',
0072             sublevel='low', live_default=False,
0073             duration_ms=int((time.monotonic() - t0) * 1000),
0074             message=(f"reproduction request {args.withdraw} on {args.key} "
0075                      f"{'withdrawn' if result == 'withdrawn' else 'not withdrawn: ' + result}"
0076                      + (f" ({args.reason})" if args.reason else '')),
0077             key=args.key, request_id=args.withdraw, result=result, failed=args.failed)
0078         print(json.dumps({'key': args.key, 'request_id': args.withdraw, 'result': result}))
0079         return 0 if result in ('withdrawn', 'run exists', 'already ended') else 1
0080     keys = [args.key] if args.key else open_signature_keys()
0081     if args.dry_run:
0082         rows = attempts(keys=keys)
0083         print(json.dumps({'dry_run': True, 'signatures': keys,
0084                           'attempts': [{k: r[k] for k in ('signature', 'queue', 'phase', 'result',
0085                                                           'reason', 'jeditaskid', 'canary_pandaid',
0086                                                           'run_id', 'request_id')}
0087                                        for r in rows]}, default=str))
0088         return 0
0089     results = {}
0090     errors = 0
0091     for key in keys:
0092         try:
0093             results[key] = reconcile(key, queue_diagnosis=queue_diagnosis)
0094         except Exception as e:                                # noqa: BLE001
0095             log.exception('reconcile %s failed', key)
0096             results[key] = {'error': str(e)[:300]}
0097             errors += 1
0098     changed = {k: v for k, v in results.items()
0099                if v.get('entries') or v.get('outcome') or v.get('trace') or v.get('covered')
0100                or v.get('error')}
0101     if changed:
0102         settled = [k for k, v in changed.items() if v.get('outcome')]
0103         queued = [k for k, v in changed.items() if v.get('diagnosis')]
0104         covered = [f"{k} by {v['covered']}" for k, v in changed.items() if v.get('covered')]
0105         traced = [k for k, v in changed.items() if v.get('trace')]
0106         log_epicprod_action(
0107             'canary-agent', 'segfault_reproduction_reconcile',
0108             outcome='error' if errors else 'ok',
0109             sublevel='low', live_default=False,
0110             level=logging.ERROR if errors else logging.INFO,
0111             duration_ms=int((time.monotonic() - t0) * 1000),
0112             message=(f"reproduction reconcile: {len(changed)} signature(s) changed"
0113                      + (f", settled {', '.join(settled)}" if settled else '')
0114                      + (f", trace read for {', '.join(traced)}" if traced else '')
0115                      + (f", diagnosis queued for {', '.join(queued)}" if queued else '')
0116                      + (f", covered by a finding: {', '.join(covered)}" if covered else '')
0117                      + (f", {errors} error(s)" if errors else '')),
0118             visited=len(keys), changed=len(changed), settled=len(settled),
0119             diagnoses=len(queued), covered=len(covered), errors=errors)
0120     print(json.dumps({'visited': len(keys), 'changed': changed}, default=str))
0121     return 1 if errors else 0
0122 
0123 
0124 if __name__ == '__main__':
0125     sys.exit(main())