Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Blame · server/web/runners.go

Line attribution on main · 101 line(s)

ac433531
Keenan Nemetz
16d ago
1 package web
2
43e681d8
Keenan Nemetz
4d ago
3 // Runner presence and queue depth for the Admin page's Runners tab.
ac433531
Keenan Nemetz
16d ago
4 //
5 // A runner is not an entity the forge stores: it is an SSH channel that leases jobs,
4506a4d3
Keenan Nemetz
13d ago
6 // authenticated by a key. So this is built from two derived facts in the queue — the
7 // presence rows a session writes while it is connected, and the work it is holding —
8 // plus the queue depth per repository. Nothing here is authoritative; it answers "is
9 // my build waiting because nobody is listening", which is otherwise a question about
10 // a process on somebody else's machine.
ac433531
Keenan Nemetz
16d ago
11
12 import (
13 "sort"
6c03e461
Keenan Nemetz
16d ago
14 "strings"
ac433531
Keenan Nemetz
16d ago
15 "time"
16 )
17
18 // presenceWindow is how long a silent runner is still shown. A resident runner
19 // refreshes presence at least once per lease wait (25s) or heartbeat (a third of its
20 // lease), so two minutes is a runner that died rather than one that is thinking.
21 const presenceWindow = 2 * time.Minute
22
23 type runnerView struct {
24 Account string
25 Repo string
26 RunID string
27 Job string
28 Seen time.Time
29 Idle bool
6c03e461
Keenan Nemetz
16d ago
30 // Labels are what the runner says it is, joined for display. They matter here
31 // because a job waiting for a machine that is not connected is invisible from the
32 // queue side: the depth says seven waiting, and only this says that nobody
33 // carrying the label the job asks for has ever connected.
34 Labels string
ac433531
Keenan Nemetz
16d ago
35 }
36
37 type queueView struct {
38 Name string
39 Queued int
40 Running int
8d336083
Keenan Nemetz
16d ago
41 // Cap is the per-repository bound on running jobs, 0 when there is none. It is
42 // shown beside the depth because a queue that is full and not draining is the
43 // question this page exists to answer, and "running 4 of 4" answers it in one
44 // line where "4 running, 7 waiting" leaves the reader guessing.
45 Cap int
ac433531
Keenan Nemetz
16d ago
46 }
47
4506a4d3
Keenan Nemetz
13d ago
48 // runnerStatus reads the queue for the status page. The status page is an
49 // instance-admin surface, so every repository is visible, including private ones.
50 // Problems are returned alongside whatever could still be read.
51 func (w *Web) runnerStatus() (sessions []runnerView, queue []queueView, problems []string) {
52 if w.Runs == nil {
53 return nil, nil, nil
54 }
ac433531
Keenan Nemetz
16d ago
55 now := time.Now().UTC()
56
4506a4d3
Keenan Nemetz
13d ago
57 rows, err := w.Runs.Sessions(now.Add(-presenceWindow))
58 if err != nil {
59 problems = append(problems, "sessions: "+err.Error())
60 } else {
ac433531
Keenan Nemetz
16d ago
61 for _, s := range rows {
6c03e461
Keenan Nemetz
16d ago
62 v := runnerView{
62576fa8
Keenan Nemetz
14d ago
63 Account: s.Name, Repo: s.Repo, RunID: s.RunID, Seen: s.LastSeen,
6c03e461
Keenan Nemetz
16d ago
64 Idle: s.RunID == "", Labels: strings.Join(s.Labels, ", "),
65 }
ac433531
Keenan Nemetz
16d ago
66 if s.Repo != "" {
4506a4d3
Keenan Nemetz
13d ago
67 if w.Repos == nil {
68 continue
69 }
70 if _, err := w.Repos.Open(s.Repo); err != nil {
71 continue // a stale row for a deleted repository
ac433531
Keenan Nemetz
16d ago
72 }
73 if run, err := w.Runs.Get(s.RunID); err == nil && run != nil {
74 v.Job = run.Job
75 }
76 }
77 sessions = append(sessions, v)
78 }
4506a4d3
Keenan Nemetz
13d ago
79 }
ac433531
Keenan Nemetz
16d ago
80
4506a4d3
Keenan Nemetz
13d ago
81 depths, err := w.Runs.QueueByRepo()
82 if err != nil {
83 problems = append(problems, "queue: "+err.Error())
84 return sessions, nil, problems
85 }
86 perRepoCap := 0
87 if w.Cfg != nil {
88 perRepoCap = w.Cfg.MaxJobsPerRepo
89 }
90 for name, d := range depths {
91 if w.Repos == nil {
92 continue
ac433531
Keenan Nemetz
16d ago
93 }
4506a4d3
Keenan Nemetz
13d ago
94 if _, err := w.Repos.Open(name); err != nil {
95 continue // a queue row whose repository is gone
8d336083
Keenan Nemetz
16d ago
96 }
4506a4d3
Keenan Nemetz
13d ago
97 queue = append(queue, queueView{Name: name, Queued: d.Queued, Running: d.Running, Cap: perRepoCap})
ac433531
Keenan Nemetz
16d ago
98 }
4506a4d3
Keenan Nemetz
13d ago
99 sort.Slice(queue, func(i, j int) bool { return queue[i].Name < queue[j].Name })
100 return sessions, queue, problems
ac433531
Keenan Nemetz
16d ago
101 }