File indexing completed on 2026-11-09 10:47:25
0001
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
0039 django.setup()
0040
0041 from monitor_app.epicprod_logging import log_epicprod_action
0042 from monitor_app.reproductions import attempts, open_signature_keys, reconcile, withdraw_request
0043 from monitor_app.segfaults import queue_diagnosis
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:
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())