Back to home page

EIC code displayed by LXR

 
 

    


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

0001 """The core occupancy plot of an Event Service job: one lane per core the
0002 job works (one single-threaded payload instance each)
0003 along the job's own clock. Measured initialization, event processing and
0004 setup/output handling have distinct segments; legacy aggregate time stays
0005 unseparated. Processing carries its output outcome (green saved, red failed; after a sudden end, light
0006 yellow the processing cut off and dark yellow the units whose output
0007 never left the node), each core's processed events at its right and their total on the closes
0008 lane, the closes on their own lane, the harness's span, the pilot's head and
0009 tail as the bare lanes before the first unit and after the last. What
0010 the job's cores were doing every minute of its life, and what they were not
0011 (swf-epicprod NODE_EVENT_DISPATCHER.md, The record). Inline SVG, no
0012 script, legible in both themes.
0013 """
0014 from django.utils.html import escape
0015 from django.utils.safestring import mark_safe
0016 
0017 LEFT = 70          # the lane labels
0018 RIGHT = 90         # each core's events
0019 WIDTH = 1000
0020 LANE_H = 20
0021 AXIS_H = 34
0022 TOP = 8
0023 
0024 DONE = '#2e8b57'
0025 FAILED = '#c0392b'
0026 CLOSE = '#3a7bd5'
0027 HARNESS = '#8e6bbf'
0028 INTERRUPTED = '#f5d76e'   # processing cut off by a sudden end (preemption)
0029 UNSHIPPED = '#d49a00'     # finished, its output never left the node
0030 RUNNING = '#7fc8a9'       # an allocation's job still running
0031 UNIT_COLOR = {'done': DONE, 'failed': FAILED, 'interrupted': INTERRUPTED, 'unshipped': UNSHIPPED,
0032               'running': RUNNING, 'awaiting': '#879d66'}
0033 IDLE = 'rgba(128,128,128,0.18)'
0034 INITIALIZATION = '#d97706'
0035 OVERHEAD = '#64748b'
0036 UNSEPARATED = '#8a7f91'
0037 
0038 
0039 def _tick_step(wall_s):
0040     """The minute step of the axis ticks for a wall of wall_s seconds."""
0041     minutes = wall_s / 60.0
0042     for step in (1, 2, 5, 10, 15, 30, 60, 120, 240):
0043         if minutes / step <= 12:
0044             return step
0045     return 480
0046 
0047 
0048 def slot_plot_svg(tl):
0049     """The SVG for a timeline from queries.es_slot_timeline, or ''.
0050 
0051     queries.allocation_timeline gives the same shape for a batch
0052     allocation's jobs, with optional keys: ``lane_label`` (default
0053     'core'), ``closes_lane`` False to omit the closes lane, a row's
0054     ``right_text`` and the timeline's ``total_text`` in place of the
0055     events counts, and a unit's ``title`` in place of the default hover
0056     text; ``compact_lanes`` narrows the lanes and labels every eighth."""
0057     if not tl or not tl.get('wall_s'):
0058         return ''
0059     wall = float(tl['wall_s'])
0060     rows = list(tl.get('rows') or [])
0061     closes_lane = tl.get('closes_lane', True)
0062     lane_label = tl.get('lane_label') or 'core'
0063     lanes = len(rows) + (1 if closes_lane else 0)
0064     compact = bool(tl.get('compact_lanes'))
0065     LANE_H = 8 if compact else 20
0066     label_every = 8 if compact else 1
0067     height = TOP + lanes * LANE_H + AXIS_H
0068     plot_w = WIDTH - LEFT - RIGHT
0069     pad = 1 if compact else 3
0070     label_dy = LANE_H / 2 + 5 if compact else LANE_H * 0.72
0071 
0072     def x(t):
0073         return LEFT + max(0.0, min(1.0, t / wall)) * plot_w
0074 
0075     parts = [f'<svg viewBox="0 0 {WIDTH} {height}" width="100%" height="{height}" '
0076              f'role="img" aria-label="core occupancy" style="font-family:inherit;font-size:14px;max-width:100%">']
0077     # Lanes: the idle band the whole job long, then the units.
0078     for i, row in enumerate(rows):
0079         y = TOP + i * LANE_H
0080         # Compact lanes are labelled every eighth, the label centred on its lane.
0081         if i % label_every == 0:
0082             # A pool member's lane carries its own label (reco N).
0083             label = row.get('label') or f"{lane_label} {row['index']}"
0084             parts.append(f'<text x="{LEFT - 8}" y="{y + label_dy:.1f}" text-anchor="end" fill="currentColor">'
0085                          f'{escape(label)}</text>')
0086         parts.append(f'<rect x="{LEFT}" y="{y + pad}" width="{plot_w:.1f}" height="{LANE_H - 2 * pad}" fill="{IDLE}"/>')
0087         for u in row['units']:
0088             x0, x1 = x(u['start_s']), x(u['end_s'])
0089             color = UNIT_COLOR.get(u['status'], FAILED)
0090             title = escape(u.get('title') or (
0091                 f"{u.get('unit_id') or 'unit'}: {u['status']}, {u.get('events') or 0} events, "
0092                 f"{(u['end_s'] - u['start_s']) / 60:.1f} min from {u['start_s'] / 60:.1f} min"
0093                 + (f"; output shipped in close {u['close']}" if u.get('close') else '')))
0094             segments = u.get('segments')
0095             if segments:
0096                 for segment in segments:
0097                     phase = segment['phase']
0098                     a, b = x(segment['start_s']), x(segment['end_s'])
0099                     fill = (INITIALIZATION if phase == 'initialization' else
0100                             OVERHEAD if phase == 'overhead' else
0101                             UNSEPARATED if phase == 'unseparated' else color)
0102                     label = {'initialization': 'initialization', 'overhead': 'setup / output handling',
0103                              'unseparated': 'unit time; initialization and processing not separately measured',
0104                              'simulation': 'simulation events', 'reconstruction': 'reconstruction events'}[phase]
0105                     detail = escape(f"{label}: {segment['end_s'] - segment['start_s']:.2f} s; ")
0106                     parts.append(f'<rect x="{a:.1f}" y="{y + pad}" width="{max(.2, b - a):.1f}" '
0107                                  f'height="{LANE_H - 2 * pad}" fill="{fill}"><title>{detail}{title}</title></rect>')
0108             else:
0109                 parts.append(f'<rect x="{x0:.1f}" y="{y + pad}" width="{max(1.5, x1 - x0):.1f}" height="{LANE_H - 2 * pad}" '
0110                              f'fill="{color}"><title>{title}</title></rect>')
0111             # A slim white line where the unit starts: consecutive units
0112             # on a core read as one bar without it.
0113             if x1 - x0 >= 3:
0114                 parts.append(f'<line x1="{x0:.1f}" y1="{y + pad}" x2="{x0:.1f}" y2="{y + LANE_H - pad}" '
0115                              f'stroke="#fff" stroke-width="1"/>')
0116         # The events the core processed: unequal counts show the stream
0117         # filling each core by its own pace.
0118         if 'right_text' in row:
0119             right = row['right_text']
0120         else:
0121             n = sum(int(u.get('events') or 0) for u in row['units'] if u['status'] == 'done')
0122             right = f'{n:,} ev'
0123         if right and i % label_every == 0:
0124             parts.append(f'<text x="{WIDTH - 4}" y="{y + label_dy:.1f}" text-anchor="end" fill="currentColor">'
0125                          f'{escape(right)}</text>')
0126     if closes_lane:
0127         # The closes lane.
0128         y = TOP + len(rows) * LANE_H
0129         total = sum(int(u.get('events') or 0) for row in rows for u in row['units'] if u['status'] == 'done')
0130         parts.append(f'<text x="{WIDTH - 4}" y="{y + LANE_H * 0.72:.1f}" text-anchor="end" fill="currentColor" '
0131                      f'font-weight="bold">{total:,} ev</text>')
0132         parts.append(f'<text x="{LEFT - 8}" y="{y + LANE_H * 0.72:.1f}" text-anchor="end" fill="currentColor">closes</text>')
0133         parts.append(f'<rect x="{LEFT}" y="{y + 3}" width="{plot_w:.1f}" height="{LANE_H - 6}" fill="{IDLE}"/>')
0134         for c in tl.get('closes') or []:
0135             x0, x1 = x(c['start_s']), x(c['end_s'])
0136             outcome = 'running' if c.get('running') else 'registered' if c.get('ok') else 'failed'
0137             title = escape(f"close {c.get('index')}: {outcome}, "
0138                            f"{(c['end_s'] - c['start_s']):.0f} s from {c['start_s'] / 60:.1f} min")
0139             parts.append(f'<rect x="{x0:.1f}" y="{y + 3}" width="{max(2.0, x1 - x0):.1f}" height="{LANE_H - 6}" '
0140                          f'fill="{CLOSE if c.get("ok") or c.get("running") else FAILED}"><title>{title}</title></rect>')
0141     # The cut of a preemption: a red line through the lanes.
0142     if tl.get('cut_s') is not None:
0143         xc = x(tl['cut_s'])
0144         label = 'preempted' if tl.get('cut_real') else 'cut (test)'
0145         parts.append(f'<line x1="{xc:.1f}" y1="{TOP}" x2="{xc:.1f}" y2="{TOP + lanes * LANE_H}" '
0146                      f'stroke="{FAILED}" stroke-width="2"><title>{label} at {tl["cut_s"] / 60:.1f} min</title></line>')
0147     # The harness's span, a thin line under the lanes.
0148     h = tl.get('harness')
0149     y_axis = TOP + lanes * LANE_H
0150     if h and h.get('start_s') is not None:
0151         x0 = x(h['start_s'])
0152         x1 = x(h['end_s']) if h.get('end_s') is not None else x(wall)
0153         parts.append(f'<rect x="{x0:.1f}" y="{y_axis + 2}" width="{max(1.0, x1 - x0):.1f}" height="3" fill="{HARNESS}">'
0154                      f'<title>harness running: {h["start_s"] / 60:.1f} to {(h["end_s"] or wall) / 60:.1f} min</title></rect>')
0155     # The axis: minutes from the pilot's start of the job.
0156     step = _tick_step(wall)
0157     parts.append(f'<line x1="{LEFT}" y1="{y_axis + 8}" x2="{WIDTH - RIGHT}" y2="{y_axis + 8}" stroke="currentColor" stroke-opacity="0.5"/>')
0158     m = 0
0159     while m * 60 <= wall + 1e-6:
0160         xt = x(m * 60)
0161         parts.append(f'<line x1="{xt:.1f}" y1="{y_axis + 8}" x2="{xt:.1f}" y2="{y_axis + 13}" stroke="currentColor" stroke-opacity="0.5"/>')
0162         parts.append(f'<text x="{xt:.1f}" y="{y_axis + 27}" text-anchor="middle" fill="currentColor">{m}</text>')
0163         m += step
0164     parts.append(f'<text x="{WIDTH - 4}" y="{y_axis + 27}" text-anchor="end" fill="currentColor" fill-opacity="0.7">min</text>')
0165     parts.append('</svg>')
0166     return mark_safe(''.join(parts))