OXYGENHUB
# 858eca0d03c4 oxygenhub / templates / insights.html
6.7 KiB · HTML · last changed Insights: OxygenFlow's standard charts, for the site, an owner and a repository 18 days ago source blame history raw
e97500b7 Andy McCall ↶ 1
{% extends "base.html" %}
2
{% import "repo_tabs.html" as m %}
3
{% block title %}Insights · {{ scope }}{% endblock %}
4
{% block bodyattrs %}{% if wall %} class="wall"{% endif %}{% endblock %}
5
{% block tabs %}{% if !wall %}{% if let Some(r) = repo %}{% call m::tabs(r, "insights") %}{% endif %}{% endif %}{% endblock %}
6
{% block content %}
7
<div class="insights" data-refresh="30" data-fragment="insights">
8
<div class="head"><h1>Insights <small>{{ scope }} · OxygenFlow, the last {% if range_on("1h") %}hour{% else if range_on("7d") %}7 days{% else %}24 hours{% endif %}</small></h1></div>
9
<div class="bar insights-bar">
10
  <span class="modes">
11
    <a href="{{ base }}?range=1h{{ keep }}"{% if range_on("1h") %} class="on" aria-current="page"{% endif %}>1 hour</a>
12
    <a href="{{ base }}?range=24h{{ keep }}"{% if range_on("24h") %} class="on" aria-current="page"{% endif %}>24 hours</a>
13
    <a href="{{ base }}?range=7d{{ keep }}"{% if range_on("7d") %} class="on" aria-current="page"{% endif %}>7 days</a>
14
  </span>
15
  {% if !repo_choices.is_empty() || !runner_choices.is_empty() %}
16
  <form class="filters" method="get" action="{{ base }}">
17
    <input type="hidden" name="range" value="{{ i.range.key() }}">{% if wall %}<input type="hidden" name="wall" value="1">{% endif %}
18
    <select name="repo" aria-label="Repository or owner"><option value="">every repository</option>{% for r in repo_choices %}<option{% if *r == repo_filter %} selected{% endif %}>{{ r }}</option>{% endfor %}</select>
19
    <select name="runner" aria-label="Runner"><option value="">every runner</option>{% for r in runner_choices %}<option{% if *r == runner_filter %} selected{% endif %}>{{ r }}</option>{% endfor %}</select>
20
    <button>filter</button>
21
  </form>
22
  {% endif %}
23
  <span class="insights-links small">
24
    {% if let Some((label, url)) = wider %}<a href="{{ url }}?range={{ i.range.key() }}">{{ label }}</a>{% endif %}
25
    {% if wall %}<a href="{{ base }}?range={{ i.range.key() }}{% if !repo_filter.is_empty() %}&repo={{ repo_filter|urlencode }}{% endif %}{% if !runner_filter.is_empty() %}&runner={{ runner_filter|urlencode }}{% endif %}">leave the wall view</a>{% else %}<a href="{{ base }}?range={{ i.range.key() }}{{ keep }}&wall=1" target="_blank" rel="noopener" title="For a second monitor: large, no menus, refreshing itself">wall view ↗</a>{% endif %}
26
    {% if !grafana.is_empty() %}<a href="{{ grafana }}" target="_blank" rel="noopener">open in Grafana ↗</a>{% endif %}
27
  </span>
28
</div>
29
30
<div class="kpis">
31
  <div class="kpi"><span class="kpi-v">{{ secs(i.pickup_median) }}</span><span class="kpi-l">time to pickup, median</span><span class="kpi-s">95th percentile {{ secs(i.pickup_p95) }}</span></div>
32
  <div class="kpi"><span class="kpi-v">{{ i.ended }}</span><span class="kpi-l">jobs finished</span><span class="kpi-s">{{ pass_rate() }} passed of those that passed or failed</span></div>
33
  <div class="kpi"><span class="kpi-v">{{ secs(i.duration_median) }}</span><span class="kpi-l">job duration, median</span><span class="kpi-s">started to finished</span></div>
34
  <div class="kpi{% if i.queued_now > 0 && i.slots_total > 0 && i.slots_busy >= i.slots_total %} kpi-warn{% endif %}"><span class="kpi-v">{{ i.queued_now }}</span><span class="kpi-l">queued now</span><span class="kpi-s">{{ i.running_now }} running</span></div>
35
  <div class="kpi{% if i.runners_total > 0 && i.runners_online == 0 %} kpi-bad{% endif %}"><span class="kpi-v">{{ i.runners_online }}/{{ i.runners_total }}</span><span class="kpi-l">runners online</span><span class="kpi-s">{{ i.slots_busy }} of {{ i.slots_total }} slots busy</span></div>
36
</div>
37
38
<div class="chart-grid">
39
  <figure class="chart-card"><figcaption>Time to pickup <span class="mute small">ready to started</span></figcaption>{{ charts.pickup|safe }}<div class="legend">{% for s in i.pickup %}<span class="key {{ s.class }}">{{ s.name }}</span>{% endfor %}</div></figure>
40
  <figure class="chart-card"><figcaption>Jobs finished <span class="mute small">per {% if range_on("1h") %}minute{% else if range_on("7d") %}3 hours{% else %}30 minutes{% endif %}</span></figcaption>{{ charts.results|safe }}<div class="legend">{% for s in i.results %}<span class="key {{ s.class }}">{{ s.name }}</span>{% endfor %}</div></figure>
41
  <figure class="chart-card"><figcaption>Queued and running</figcaption>{{ charts.load|safe }}<div class="legend">{% for s in i.load %}<span class="key {{ s.class }}">{{ s.name }}</span>{% endfor %}</div></figure>
42
  <figure class="chart-card"><figcaption>Job duration <span class="mute small">started to finished</span></figcaption>{{ charts.durations|safe }}<div class="legend">{% for s in i.durations %}<span class="key {{ s.class }}">{{ s.name }}</span>{% endfor %}</div></figure>
43
  <figure class="chart-card"><figcaption>Runner utilisation <span class="mute small">running jobs over the slots of the runners online now</span></figcaption>{{ charts.utilisation|safe }}<div class="legend">{% for s in i.utilisation %}<span class="key {{ s.class }}">{{ s.name }}</span>{% endfor %}</div></figure>
44
</div>
45
46
{% if !i.runners.is_empty() %}
47
<section class="panel"><h2>Runners</h2>
48
<div class="table"><table>
49
<tr><th>Runner</th><th>Status</th><th>Busy</th><th>Jobs</th><th>Failed</th><th>Pickup, median</th><th>Duration, median</th><th>Last heard from</th></tr>
50
{% for r in i.runners %}<tr><td><strong>{{ r.name }}</strong></td><td><span class="runner-status st-{{ r.status }}">{{ r.status }}</span></td><td>{{ r.running }}/{{ r.max_jobs }}</td><td>{{ r.jobs }}</td><td>{% if r.failed > 0 %}{{ r.failed }}{% else %}<span class="mute">0</span>{% endif %}</td><td>{{ secs(r.pickup_median) }}</td><td>{{ secs(r.duration_median) }}</td><td class="small">{% if let Some(t) = r.last_seen_ms %}<time title="{{ t|when_i }}">{{ t|ago_i }}</time>{% else %}<span class="mute">never</span>{% endif %}</td></tr>{% endfor %}
51
</table></div>
52
</section>
53
{% endif %}
54
55
{% if i.repos.len() > 1 %}
56
<section class="panel"><h2>Repositories <small class="mute">busiest first</small></h2>
57
<div class="table"><table>
58
<tr><th>Repository</th><th>Jobs</th><th>Failed</th><th>Queued now</th><th>Pickup, median</th><th>Pickup, 95th percentile</th></tr>
59
{% for r in i.repos %}<tr><td><a href="/{{ r.slug }}/insights?range={{ i.range.key() }}">{{ r.slug }}</a></td><td>{{ r.jobs }}</td><td>{% if r.failed > 0 %}{{ r.failed }}{% else %}<span class="mute">0</span>{% endif %}</td><td>{{ r.queued }}</td><td>{{ secs(r.pickup_median) }}</td><td>{{ secs(r.pickup_p95) }}</td></tr>{% endfor %}
60
</table></div>
61
</section>
62
{% endif %}
63
<p class="small mute">Updated <time title="{{ i.now_ms|when_i }}">{{ i.now_ms|ago_i }}</time>; this page refreshes itself every 30 seconds. Pickup is from when a job could run (queued, its dependencies passed, approved) to when a runner took it.</p>
64
</div>
65
{% endblock %}