Back to home page

EIC code displayed by LXR

 
 

    


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

0001 """The Event Service reading of one queue: what the node harness reported
0002 across the queue's Event Service jobs over a window, from the job
0003 reports (swf-epicprod docs/NODE_EVENT_DISPATCHER.md, The record), as a
0004 cached product on the uniform mechanism (docs/CACHED_PRODUCTS.md): the
0005 queue detail page serves the stored reading and rebuilds behind the
0006 response.
0007 
0008 The reading: jobs with a harness report and their slots, units done and
0009 failed with their events, the unit wall's middle and tail and the
0010 seconds an event costs on this queue, the closes with their events and
0011 wall, the jobs that stopped taking ranges at the deadline, and the
0012 events done per hour of job wall on the queue's Event Service jobs.
0013 """
0014 import json
0015 import logging
0016 
0017 from django.db import connections
0018 
0019 from .cached_product import get_product
0020 from .panda.constants import PANDA_SCHEMA
0021 
0022 logger = logging.getLogger(__name__)
0023 
0024 TTL_S = 300
0025 WINDOW_DAYS = 7
0026 JOB_CAP = 3000
0027 
0028 
0029 def _median(values):
0030     vals = sorted(v for v in values if v is not None)
0031     if not vals:
0032         return None
0033     mid = len(vals) // 2
0034     return round(vals[mid] if len(vals) % 2 else (vals[mid - 1] + vals[mid]) / 2, 1)
0035 
0036 
0037 def _reading(queue_name, days=WINDOW_DAYS):
0038     """The queue's Event Service reading over ``days``; pure database."""
0039     with connections['panda'].cursor() as cursor:
0040         cursor.execute(
0041             f'SELECT j."pandaid", j."jobstatus", j."jobsubstatus", '
0042             f'EXTRACT(EPOCH FROM (j."endtime" - j."starttime")), m."metadata" '
0043             f'FROM "{PANDA_SCHEMA}"."jobsarchived4" j '
0044             f'LEFT JOIN "{PANDA_SCHEMA}"."metatable" m ON m."pandaid" = j."pandaid" '
0045             f'WHERE j."computingsite" = %s AND j."eventservice" IN (1, 6) '
0046             f'AND j."modificationtime" > NOW() - INTERVAL %s '
0047             f'ORDER BY j."pandaid" DESC LIMIT %s',
0048             [queue_name, f'{int(days)} days', JOB_CAP])
0049         rows = cursor.fetchall()
0050     out = {'queue': queue_name, 'window_days': int(days), 'es_jobs': len(rows),
0051            'capped': len(rows) >= JOB_CAP, 'by_status': {}, 'by_substatus': {}}
0052     reports = 0
0053     slots = []
0054     unit_walls, unit_events, spe = [], [], []
0055     done = failed = events_done = 0
0056     closes = close_failed = close_events = 0
0057     close_walls = []
0058     deadline_marks = 0
0059     job_wall_s = 0.0
0060     for pandaid, status, substatus, wall, raw in rows:
0061         out['by_status'][status] = out['by_status'].get(status, 0) + 1
0062         if substatus:
0063             out['by_substatus'][substatus] = out['by_substatus'].get(substatus, 0) + 1
0064         if wall is not None:
0065             job_wall_s += float(wall)
0066         if not raw:
0067             continue
0068         try:
0069             metadata = json.loads(raw) if isinstance(raw, str) else raw
0070         except (ValueError, TypeError):
0071             continue
0072         es = metadata.get('es') if isinstance(metadata, dict) else None
0073         if not isinstance(es, dict):
0074             continue
0075         reports += 1
0076         if es.get('slots'):
0077             slots.append(int(es['slots']))
0078         if es.get('untaken_at_deadline'):
0079             deadline_marks += 1
0080         for u in es.get('ranges_done') or []:
0081             if not isinstance(u, dict):
0082                 continue
0083             done += 1
0084             ev = int(u.get('events_reconstructed') or u.get('events') or 0)
0085             events_done += ev
0086             w = u.get('wall_s')
0087             if w is not None:
0088                 unit_walls.append(float(w))
0089                 unit_events.append(ev)
0090                 if ev:
0091                     spe.append(float(w) / ev)
0092         failed += len(es.get('ranges_failed') or [])
0093         for c in es.get('closes') or []:
0094             if not isinstance(c, dict):
0095                 continue
0096             closes += 1
0097             if c.get('ok'):
0098                 close_events += int(c.get('events') or 0)
0099             else:
0100                 close_failed += 1
0101             if c.get('wall_s') is not None:
0102                 close_walls.append(float(c['wall_s']))
0103     walls = sorted(unit_walls)
0104     out.update({
0105         'reports': reports,
0106         'slots_min': min(slots) if slots else None, 'slots_max': max(slots) if slots else None,
0107         'units_done': done, 'units_failed': failed, 'events_done': events_done,
0108         'unit_events_median': _median(unit_events),
0109         'unit_wall_median_s': _median(unit_walls),
0110         'unit_wall_p90_s': round(walls[min(len(walls) - 1, int(0.9 * len(walls)))], 1) if walls else None,
0111         's_per_event_median': round(_median(spe), 2) if spe else None,
0112         'closes': closes, 'closes_failed': close_failed, 'close_events': close_events,
0113         'close_wall_median_s': _median(close_walls),
0114         'deadline_marks': deadline_marks,
0115         'job_wall_h': round(job_wall_s / 3600.0, 2),
0116         'events_per_job_hour': round(events_done / (job_wall_s / 3600.0), 1) if job_wall_s else None,
0117     })
0118     return out
0119 
0120 
0121 def es_reading(queue_name, refresh=False):
0122     """One queue's Event Service reading with its build time; None when
0123     the queue ran no Event Service job in the window; an error record
0124     when the read fails."""
0125     key = 'es_reading:v1:{}'.format(queue_name)
0126     try:
0127         product = get_product(key, lambda: _reading(queue_name),
0128                               ttl_seconds=TTL_S, refresh=refresh)
0129     except Exception as e:                                    # noqa: BLE001
0130         logger.error('event service reading failed for %s: %s', queue_name, e)
0131         return {'queue': queue_name, 'error': str(e)}
0132     value = dict((product or {}).get('value') or {})
0133     if not value.get('es_jobs'):
0134         return None
0135     value['built_at'] = (product or {}).get('built_at')
0136     value['age_seconds'] = (product or {}).get('age_seconds')
0137     return value