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.
|
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 | } |