Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-22 09:42:05

0001 """Human-facing Snapper report and instrument views."""
0002 
0003 import json
0004 
0005 from django.http import Http404
0006 from django.shortcuts import get_object_or_404, redirect, render
0007 
0008 from snapper_ai.models import CaptureCursor, CurrentComponent, SystemSnap
0009 
0010 from ..models import SysConfig, SystemStatus
0011 
0012 
0013 SCOPES = ('epicprod', 'testbed')
0014 RECENT_SNAP_LIMIT = 100
0015 
0016 
0017 def _validated_scope(scope):
0018     if scope not in SCOPES:
0019         raise Http404(f'Unknown Snapper scope {scope!r}')
0020     return scope
0021 
0022 
0023 def _scope_options(scope):
0024     return [
0025         {
0026             'name': name,
0027             'label': 'epicprod' if name == 'epicprod' else 'Testbed',
0028             'active': name == scope,
0029         }
0030         for name in SCOPES
0031     ]
0032 
0033 
0034 def _dict(value):
0035     return value if isinstance(value, dict) else {}
0036 
0037 
0038 def _json(value):
0039     return json.dumps(value, indent=2, sort_keys=True, default=str)
0040 
0041 
0042 def _value_at(data, path):
0043     value = data
0044     for part in str(path or '').split('.'):
0045         if not part or not isinstance(value, dict) or part not in value:
0046             return None
0047         value = value[part]
0048     return value
0049 
0050 
0051 def _quantity_values(registration, data):
0052     rows = []
0053     for name, definition in sorted(
0054             _dict(registration.get('quantities')).items()):
0055         definition = _dict(definition)
0056         value = _value_at(data, definition.get('path', name))
0057         is_complex = isinstance(value, (dict, list))
0058         if is_complex:
0059             display = json.dumps(value, indent=2, sort_keys=True)
0060         elif value is None:
0061             display = '—'
0062         else:
0063             display = str(value)
0064         rows.append({
0065             'name': name,
0066             'description': definition.get('description', ''),
0067             'value': display,
0068             'is_complex': is_complex,
0069         })
0070     return rows
0071 
0072 
0073 def _present_snap_component(name, payload):
0074     payload = _dict(payload)
0075     registration = _dict(payload.get('registration'))
0076     data = _dict(payload.get('data'))
0077     health = None
0078     if name == 'health':
0079         overall = _dict(data.get('overall'))
0080         checks = []
0081         for check_name, check in sorted(_dict(data.get('checks')).items()):
0082             check = _dict(check)
0083             checks.append({
0084                 'name': check_name,
0085                 'category': check.get('category', ''),
0086                 'status': check.get('status', 'unknown'),
0087                 'summary': check.get('summary', ''),
0088             })
0089         health = {
0090             'status': overall.get('status', 'unknown'),
0091             'reason': overall.get('reason', ''),
0092             'counts': _dict(overall.get('counts')),
0093             'checks': checks,
0094         }
0095     return {
0096         'name': name,
0097         'title': registration.get('title') or name,
0098         'description': registration.get('description', ''),
0099         'revision': payload.get('revision'),
0100         'registration_version': payload.get('registration_version'),
0101         'assessed_at': payload.get('assessed_at'),
0102         'source_as_of': payload.get('source_as_of'),
0103         'accepted_at': payload.get('accepted_at'),
0104         'publisher_identity': payload.get('publisher_identity', ''),
0105         'health': health,
0106         'quantities': _quantity_values(registration, data),
0107         'payload_json': _json(payload),
0108     }
0109 
0110 
0111 def _snap_row(snap):
0112     return {
0113         'id': snap.id,
0114         'snap_time': snap.snap_time,
0115         'observed_at': snap.observed_at,
0116         'reasons': ', '.join(snap.reasons or []) or '—',
0117         'changed_components': (
0118             ', '.join(snap.changed_components or []) or '—'),
0119         'capture_policy': snap.capture_policy,
0120         'encoding': snap.encoding,
0121         'component_count': len(snap.component_revisions or {}),
0122     }
0123 
0124 
0125 def snapper_root(request):
0126     return redirect('monitor_app:snapper_report', scope='epicprod')
0127 
0128 
0129 def snapper_report(request, scope, snap_id=None):
0130     scope = _validated_scope(scope)
0131     snaps = SystemSnap.objects.filter(scope=scope).order_by('-snap_time')
0132     latest_snap = snaps.first()
0133     if snap_id is None:
0134         selected_snap = latest_snap
0135     else:
0136         selected_snap = get_object_or_404(snaps, id=snap_id)
0137 
0138     components = []
0139     observation_delay = None
0140     if selected_snap is not None:
0141         state = _dict(selected_snap.state)
0142         components = [
0143             _present_snap_component(name, payload)
0144             for name, payload in sorted(
0145                 _dict(state.get('components')).items())
0146         ]
0147         observation_delay = (
0148             selected_snap.observed_at - selected_snap.snap_time
0149         ).total_seconds()
0150 
0151     recent = list(snaps[:RECENT_SNAP_LIMIT])
0152     return render(request, 'monitor_app/snapper.html', {
0153         'active_tab': 'report',
0154         'scope': scope,
0155         'scope_label': 'epicprod' if scope == 'epicprod' else 'Testbed',
0156         'scope_options': _scope_options(scope),
0157         'selected_snap': selected_snap,
0158         'latest_snap': latest_snap,
0159         'observation_delay': observation_delay,
0160         'components': components,
0161         'selected_snap_json': (
0162             _json(selected_snap.state) if selected_snap is not None else ''),
0163         'snap_rows': [_snap_row(snap) for snap in recent],
0164         'snap_count': snaps.count(),
0165         'recent_snap_limit': RECENT_SNAP_LIMIT,
0166     })
0167 
0168 
0169 def _positive_int(config, key, default, minimum):
0170     raw = config.get(key, default)
0171     try:
0172         value = int(raw)
0173         if value < minimum:
0174             raise ValueError
0175     except (TypeError, ValueError):
0176         return raw, None
0177     return raw, value
0178 
0179 
0180 def _duration(seconds):
0181     if seconds is None:
0182         return 'unavailable'
0183     minutes, remainder = divmod(seconds, 60)
0184     if minutes and remainder:
0185         return f'{minutes}m {remainder}s'
0186     if minutes:
0187         return f'{minutes}m'
0188     return f'{remainder}s'
0189 
0190 
0191 def _component_registration(component):
0192     registration = _dict(component.registration)
0193     quantities = []
0194     for name, definition in sorted(
0195             _dict(registration.get('quantities')).items()):
0196         definition = _dict(definition)
0197         limits = []
0198         for key, label in (
0199                 ('max_items', 'max items'),
0200                 ('max_length', 'max length'),
0201                 ('minimum', 'minimum'),
0202                 ('maximum', 'maximum')):
0203             if key in definition:
0204                 limits.append(f'{label}: {definition[key]}')
0205         if definition.get('enum'):
0206             limits.append('values: ' + ', '.join(
0207                 str(value) for value in definition['enum']))
0208         quantities.append({
0209             'name': name,
0210             'path': definition.get('path', name),
0211             'kind': definition.get('kind', ''),
0212             'type': definition.get('type', ''),
0213             'required': bool(definition.get('required')),
0214             'limits': '; '.join(limits) or '—',
0215             'description': definition.get('description', ''),
0216         })
0217     event_sources = []
0218     for source in registration.get('event_sources') or []:
0219         source = _dict(source)
0220         event_sources.append({
0221             'name': source.get('name', ''),
0222             'kind': source.get('event_kind', ''),
0223             'resolver': source.get('resolver', ''),
0224             'owner': source.get('owner', ''),
0225             'visibility': source.get('visibility', ''),
0226         })
0227     return {
0228         'record': component,
0229         'title': registration.get('title') or component.name,
0230         'description': registration.get('description', ''),
0231         'owning_subsystem': registration.get('owning_subsystem', ''),
0232         'visibility': registration.get('visibility', ''),
0233         'assessment_policy': registration.get('assessment_policy', ''),
0234         'max_serialized_bytes': registration.get('max_serialized_bytes'),
0235         'quantities': quantities,
0236         'event_sources': event_sources,
0237         'registration_json': _json(registration),
0238     }
0239 
0240 
0241 def snapper_system(request, scope):
0242     scope = _validated_scope(scope)
0243     config = SysConfig.get_config()
0244     opportunity_key = f'snapper_opportunity_seconds_{scope}'
0245     baseline_key = f'snapper_baseline_every_{scope}'
0246     policy_key = f'snapper_capture_policy_{scope}'
0247     opportunity_raw, opportunity = _positive_int(
0248         config, opportunity_key, 10, 10)
0249     baseline_raw, baseline = _positive_int(config, baseline_key, 10, 1)
0250     lock_raw, lock_timeout = _positive_int(
0251         config, 'snapper_lock_timeout_ms', 5000, 1)
0252     max_quiet = (
0253         opportunity * baseline
0254         if opportunity is not None and baseline is not None else None)
0255 
0256     cursor = (
0257         CaptureCursor.objects.select_related('latest_snap')
0258         .filter(scope=scope).first()
0259     )
0260     scheduler_status = SystemStatus.objects.filter(
0261         name=f'snapper-{scope}-scheduler').first()
0262     components = [
0263         _component_registration(component)
0264         for component in CurrentComponent.objects.filter(scope=scope)
0265         .order_by('-active', 'name')
0266     ]
0267     policy_rows = [
0268         {
0269             'setting': 'Snap opportunity',
0270             'value': f'{opportunity}s' if opportunity is not None else opportunity_raw,
0271             'key': opportunity_key,
0272             'valid': opportunity is not None,
0273         },
0274         {
0275             'setting': 'Periodic baseline',
0276             'value': (
0277                 f'every {baseline} opportunities'
0278                 if baseline is not None else baseline_raw),
0279             'key': baseline_key,
0280             'valid': baseline is not None,
0281         },
0282         {
0283             'setting': 'Maximum quiet interval',
0284             'value': _duration(max_quiet),
0285             'key': 'derived from opportunity × baseline',
0286             'valid': max_quiet is not None,
0287         },
0288         {
0289             'setting': 'Capture policy',
0290             'value': config.get(policy_key, f'{scope}-v1'),
0291             'key': policy_key,
0292             'valid': bool(config.get(policy_key, f'{scope}-v1')),
0293         },
0294         {
0295             'setting': 'Database lock timeout',
0296             'value': (
0297                 f'{lock_timeout} ms' if lock_timeout is not None else lock_raw),
0298             'key': 'snapper_lock_timeout_ms',
0299             'valid': lock_timeout is not None,
0300         },
0301     ]
0302     return render(request, 'monitor_app/snapper.html', {
0303         'active_tab': 'system',
0304         'scope': scope,
0305         'scope_label': 'epicprod' if scope == 'epicprod' else 'Testbed',
0306         'scope_options': _scope_options(scope),
0307         'cursor': cursor,
0308         'scheduler_status': scheduler_status,
0309         'policy_rows': policy_rows,
0310         'components': components,
0311         'baseline_every': baseline,
0312     })