roundhouse_ui 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +7 -1
- data/app/controllers/roundhouse_ui/busy_controller.rb +13 -3
- data/app/controllers/roundhouse_ui/capsules_controller.rb +4 -0
- data/app/views/layouts/roundhouse_ui/application.html.erb +38 -10
- data/app/views/roundhouse_ui/dashboard/show.html.erb +1 -1
- data/app/views/roundhouse_ui/queues/index.html.erb +7 -5
- data/app/views/roundhouse_ui/workers/index.html.erb +11 -9
- data/lib/roundhouse_ui/version.rb +1 -1
- data/lib/roundhouse_ui.rb +8 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b76009aa85c003ad62a2e6bacf8efbdf69767bda6e1b9b382de8a606ec3acb8b
|
|
4
|
+
data.tar.gz: 4ae59af49806c3536b6f709f02146338ad69e716257d2223f1fbf5061995390a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7df7cbc7dbdab13f4821500f1c5e3d8d9ea8357416530662d4e6cd0a38edd427f11eec13d2a7d22ff923b289a5f5dd10edc3ba405217967563bea6e55a7432d4
|
|
7
|
+
data.tar.gz: e9ebfd3e8edf616ed69ae517f92ca2bdda61dd3065e83624d524b4f5d133bd7d395ef7e4cba7d6d477c92dd1547d5495c83fd68822d58a5136db279a64963268
|
data/README.md
CHANGED
|
@@ -83,6 +83,11 @@ RoundhouseUi.configure do |c|
|
|
|
83
83
|
# Fold sidekiq-failures' `failed` set into the Errors view (see below).
|
|
84
84
|
# No-op unless the sidekiq-failures gem is loaded. Default: off.
|
|
85
85
|
c.show_sidekiq_failures = true
|
|
86
|
+
|
|
87
|
+
# Set false to hide queue pause/resume controls and the "not enforced" warning,
|
|
88
|
+
# e.g. when you run reliable fetch (super_fetch) instead of RoundhouseUi::Fetch.
|
|
89
|
+
# Default: true.
|
|
90
|
+
# c.pause_enabled = false
|
|
86
91
|
end
|
|
87
92
|
```
|
|
88
93
|
|
|
@@ -110,7 +115,8 @@ Roundhouse detects whether a fetcher has reported in).
|
|
|
110
115
|
> ⚠️ **Sidekiq Pro/Enterprise users:** super_fetch / reliable fetch sets its own
|
|
111
116
|
> `fetch_class`, and Sidekiq allows only one. Installing `RoundhouseUi::Fetch` would
|
|
112
117
|
> **replace reliable fetch and lose its crash-recovery guarantees** — don't. On those
|
|
113
|
-
> tiers, leave the fetch strategy out
|
|
118
|
+
> tiers, leave the fetch strategy out and set `RoundhouseUi.pause_enabled = false` to
|
|
119
|
+
> hide the pause controls and the (otherwise permanent) "not enforced" warning.
|
|
114
120
|
|
|
115
121
|
## Surfacing sidekiq-failures
|
|
116
122
|
|
|
@@ -9,9 +9,7 @@ module RoundhouseUi
|
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
11
|
@threshold = LONG_RUNNING
|
|
12
|
-
@work = Sidekiq::WorkSet.new.map
|
|
13
|
-
{ process: process_id, tid: tid, queue: work.queue, run_at: work.run_at, job: work.job }
|
|
14
|
-
end
|
|
12
|
+
@work = Sidekiq::WorkSet.new.map { |process_id, tid, work| normalize(process_id, tid, work) }
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
def cancel
|
|
@@ -21,6 +19,18 @@ module RoundhouseUi
|
|
|
21
19
|
|
|
22
20
|
private
|
|
23
21
|
|
|
22
|
+
# Sidekiq 7+ yields a Sidekiq::Work (queue/run_at/job methods); Sidekiq 6.x
|
|
23
|
+
# yields a plain Hash (string keys, an epoch run_at, a JSON payload). Normalize
|
|
24
|
+
# both to the same shape the view expects (run_at as a Time, job as a JobRecord).
|
|
25
|
+
def normalize(process_id, tid, work)
|
|
26
|
+
if work.respond_to?(:queue)
|
|
27
|
+
{ process: process_id, tid: tid, queue: work.queue, run_at: work.run_at, job: work.job }
|
|
28
|
+
else
|
|
29
|
+
{ process: process_id, tid: tid, queue: work["queue"],
|
|
30
|
+
run_at: Time.at(work["run_at"]), job: Sidekiq::JobRecord.new(work["payload"]) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
24
34
|
def require_writable!
|
|
25
35
|
return unless RoundhouseUi.read_only
|
|
26
36
|
redirect_to busy_path, alert: "Roundhouse is in read-only mode — cancellation is disabled."
|
|
@@ -13,6 +13,10 @@ module RoundhouseUi
|
|
|
13
13
|
capsules = Hash.new { |h, k| h[k] = { name: k, concurrency: 0, processes: 0, queues: {} } }
|
|
14
14
|
|
|
15
15
|
Sidekiq::ProcessSet.new.each do |process|
|
|
16
|
+
# `capsules` is a Sidekiq 8.0.8+ Process attribute; absent on 6.x/early 7,
|
|
17
|
+
# where calling it would raise NoMethodError. Skip → view shows empty state.
|
|
18
|
+
next unless process.respond_to?(:capsules)
|
|
19
|
+
|
|
16
20
|
(process.capsules || {}).each do |name, data|
|
|
17
21
|
cap = capsules[name]
|
|
18
22
|
cap[:concurrency] += data["concurrency"].to_i
|
|
@@ -166,15 +166,29 @@
|
|
|
166
166
|
return "~" + (s / 86400).toFixed(1) + "d";
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// Points to display, from the window picker (24 pts ≈ 1m at the 2.5s poll).
|
|
170
|
+
function chartWindow() { var s = document.getElementById("rh-chart-window"); return s ? parseInt(s.value, 10) : 24; }
|
|
171
|
+
// Moving average sized to the window — flattens the per-poll spikes into a
|
|
172
|
+
// sustained line instead of a noisy sawtooth.
|
|
173
|
+
function smooth(arr) {
|
|
174
|
+
var k = Math.max(2, Math.round(arr.length / 12)), out = [];
|
|
175
|
+
for (var i = 0; i < arr.length; i++) {
|
|
176
|
+
var a = Math.max(0, i - k + 1), sum = 0, c = 0;
|
|
177
|
+
for (var j = a; j <= i; j++) { sum += arr[j]; c++; }
|
|
178
|
+
out.push(sum / c);
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
169
182
|
function draw() {
|
|
170
183
|
var cv = document.getElementById("rh-chart"); if (!cv) return;
|
|
171
|
-
var ctx = cv.getContext("2d"), w = cv.width, h = cv.height, pad = 6
|
|
172
|
-
ctx.clearRect(0, 0, w, h);
|
|
173
|
-
var
|
|
184
|
+
var ctx = cv.getContext("2d"), w = cv.width, h = cv.height, pad = 6;
|
|
185
|
+
ctx.clearRect(0, 0, w, h);
|
|
186
|
+
var pts = smooth(series.slice(-chartWindow())), n = pts.length; if (n < 2) return;
|
|
187
|
+
var max = Math.max.apply(null, pts) * 1.25 || 1;
|
|
174
188
|
var x = function (i) { return i / (n - 1) * w; }, y = function (v) { return h - pad - v / max * (h - pad * 2); };
|
|
175
189
|
var g = ctx.createLinearGradient(0, 0, 0, h); g.addColorStop(0, "rgba(110,139,255,.30)"); g.addColorStop(1, "rgba(110,139,255,0)");
|
|
176
|
-
ctx.beginPath(); ctx.moveTo(0, h);
|
|
177
|
-
ctx.beginPath();
|
|
190
|
+
ctx.beginPath(); ctx.moveTo(0, h); pts.forEach(function (v, i) { ctx.lineTo(x(i), y(v)); }); ctx.lineTo(w, h); ctx.closePath(); ctx.fillStyle = g; ctx.fill();
|
|
191
|
+
ctx.beginPath(); pts.forEach(function (v, i) { i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v)); }); ctx.strokeStyle = "#6E8BFF"; ctx.lineWidth = 2; ctx.lineJoin = "round"; ctx.stroke();
|
|
178
192
|
}
|
|
179
193
|
function apply(d) {
|
|
180
194
|
Object.keys(d).forEach(function (k) {
|
|
@@ -196,7 +210,7 @@
|
|
|
196
210
|
var rate = dt > 0 ? Math.max(0, dp / dt) : 0;
|
|
197
211
|
var re = document.querySelector('[data-stat="rate"]'); if (re) re.textContent = fmt(Math.round(rate * 60));
|
|
198
212
|
var ne = document.getElementById("rh-chart-now"); if (ne) ne.textContent = Math.round(rate);
|
|
199
|
-
series.push(rate); if (series.length >
|
|
213
|
+
series.push(rate); if (series.length > 360) series.shift(); draw();
|
|
200
214
|
|
|
201
215
|
// Metrics tab live rates (these elements exist only on that page).
|
|
202
216
|
setText("rh-m-throughput", Math.round(rate) + "/s");
|
|
@@ -255,7 +269,7 @@
|
|
|
255
269
|
{ label: "Retries", path: "<%= retries_path %>", icon: "↻" },
|
|
256
270
|
{ label: "Errors", path: "<%= errors_path %>", icon: "⚠" },
|
|
257
271
|
{ label: "Dead", path: "<%= dead_set_path %>", icon: "✕" },
|
|
258
|
-
{ label: "Capsules", path: "<%= capsules_path %>", icon: "⊞" }
|
|
272
|
+
<% if defined?(Sidekiq::Capsule) %>{ label: "Capsules", path: "<%= capsules_path %>", icon: "⊞" },<% end %>
|
|
259
273
|
{ label: "Redis", path: "<%= redis_info_path %>", icon: "◈" },
|
|
260
274
|
{ label: "Snapshots", path: "<%= snapshots_path %>", icon: "⛁" },
|
|
261
275
|
{ label: "Audit log", path: "<%= audit_log_path %>", icon: "☷" },
|
|
@@ -303,6 +317,17 @@
|
|
|
303
317
|
if (e.target && e.target.id === "rh-palette") { palClose(); return; }
|
|
304
318
|
if (e.target.closest && e.target.closest("#rh-palette-open")) { e.preventDefault(); palOpen(); }
|
|
305
319
|
});
|
|
320
|
+
// Throughput window picker — persist the choice and redraw the smoothed line.
|
|
321
|
+
function restoreChartWindow() {
|
|
322
|
+
var s = document.getElementById("rh-chart-window"); if (!s) return;
|
|
323
|
+
try { var v = localStorage.getItem("rh-chart-win"); if (v) s.value = v; } catch (_) {}
|
|
324
|
+
}
|
|
325
|
+
document.addEventListener("change", function (e) {
|
|
326
|
+
if (!e.target || e.target.id !== "rh-chart-window") return;
|
|
327
|
+
try { localStorage.setItem("rh-chart-win", e.target.value); } catch (_) {}
|
|
328
|
+
draw();
|
|
329
|
+
});
|
|
330
|
+
|
|
306
331
|
document.addEventListener("keydown", function (e) {
|
|
307
332
|
if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); var p = palEl(); (p && p.hidden) ? palOpen() : palClose(); return; }
|
|
308
333
|
var p = palEl(); if (!p || p.hidden) return;
|
|
@@ -312,8 +337,8 @@
|
|
|
312
337
|
else if (e.key === "Enter") { e.preventDefault(); if (palFiltered[palSel]) palRun(palFiltered[palSel]); }
|
|
313
338
|
});
|
|
314
339
|
|
|
315
|
-
document.addEventListener("turbo:load", function () { startOnce(); syncTheme(); syncWidth(); setActiveNav(); draw(); });
|
|
316
|
-
document.addEventListener("DOMContentLoaded", function () { startOnce(); syncTheme(); syncWidth(); setActiveNav(); });
|
|
340
|
+
document.addEventListener("turbo:load", function () { startOnce(); syncTheme(); syncWidth(); setActiveNav(); restoreChartWindow(); draw(); });
|
|
341
|
+
document.addEventListener("DOMContentLoaded", function () { startOnce(); syncTheme(); syncWidth(); setActiveNav(); restoreChartWindow(); });
|
|
317
342
|
document.addEventListener("visibilitychange", function () { if (!document.hidden) poll(); });
|
|
318
343
|
})();
|
|
319
344
|
</script>
|
|
@@ -343,7 +368,10 @@
|
|
|
343
368
|
</nav>
|
|
344
369
|
<nav class="rh-grp">
|
|
345
370
|
<div class="rh-grp-label">Infrastructure</div>
|
|
346
|
-
|
|
371
|
+
<%# Capsules are a Sidekiq 7+ concept — hide the nav entirely on 6.x. %>
|
|
372
|
+
<% if defined?(Sidekiq::Capsule) %>
|
|
373
|
+
<%= nav_link "Capsules", capsules_path, icon: "⊞" %>
|
|
374
|
+
<% end %>
|
|
347
375
|
<%= nav_link "Redis", redis_info_path, icon: "◈" %>
|
|
348
376
|
<%= nav_link "Snapshots", snapshots_path, icon: "⛁" %>
|
|
349
377
|
<%= nav_link "Audit log", audit_log_path, icon: "☷" %>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
</div>
|
|
43
43
|
|
|
44
44
|
<div class="rh-chart-wrap">
|
|
45
|
-
<div class="top"><h3>Throughput</h3><span class="rh-sub">jobs / sec · live</span><span class="now"><span id="rh-chart-now">—</span>/s</span></div>
|
|
45
|
+
<div class="top"><h3>Throughput</h3><span class="rh-sub">jobs / sec · live</span><span class="now"><span id="rh-chart-now">—</span>/s</span><select id="rh-chart-window" aria-label="Throughput window" style="margin-left:12px;background:var(--panel-2);color:var(--muted);border:1px solid var(--line);border-radius:7px;padding:3px 6px;font:12px var(--sans)"><option value="24">1m</option><option value="120">5m</option><option value="360">15m</option></select></div>
|
|
46
46
|
<canvas id="rh-chart" width="1100" height="180"></canvas>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<div class="rh-toolbar"><%= link_to "+ Enqueue job", new_job_path, class: "rh-btn rh-btn-primary" %></div>
|
|
5
5
|
<% end %>
|
|
6
6
|
|
|
7
|
-
<%
|
|
7
|
+
<% if RoundhouseUi.pause_enabled && !@fetch_installed %>
|
|
8
8
|
<div class="rh-warn">
|
|
9
9
|
Pausing is <strong>recorded but not enforced</strong> — no Roundhouse fetcher has reported in.
|
|
10
10
|
Add it to your Sidekiq config: <code>config[:fetch_class] = RoundhouseUi::Fetch</code>
|
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
<td class="r"><%= number_with_delimiter q.size %></td>
|
|
27
27
|
<td class="r <%= "rh-lat-warn" if q.latency > 60 %>"><%= q.latency.round(2) %>s</td>
|
|
28
28
|
<td class="r">
|
|
29
|
-
<% if
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
<% if RoundhouseUi.pause_enabled %>
|
|
30
|
+
<% if paused %>
|
|
31
|
+
<%= button_to "Resume", resume_queue_path(q.name), method: :post, class: "rh-btn", form_class: "rh-inline" %>
|
|
32
|
+
<% else %>
|
|
33
|
+
<%= button_to "Pause", pause_queue_path(q.name), method: :post, class: "rh-btn", form_class: "rh-inline" %>
|
|
34
|
+
<% end %>
|
|
33
35
|
<% end %>
|
|
34
36
|
<%= button_to "Snapshot", snapshot_queue_path(q.name), method: :post, class: "rh-btn", form_class: "rh-inline" %>
|
|
35
37
|
<%= button_to "Purge", purge_queue_path(q.name),
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
<% content_for :title, "Workers" %>
|
|
2
2
|
|
|
3
3
|
<h2 class="rh-h2">Workers · <%= @processes.size %> <%= "process".pluralize(@processes.size) %></h2>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
</
|
|
4
|
+
<% if RoundhouseUi.pause_enabled %>
|
|
5
|
+
<p class="rh-sub" style="margin:-4px 0 16px">
|
|
6
|
+
Fetch strategy:
|
|
7
|
+
<% if @fetch_active %>
|
|
8
|
+
<span class="rh-st rh-st-ok">RoundhouseUi::Fetch — pause-aware</span>
|
|
9
|
+
<% else %>
|
|
10
|
+
<span class="rh-st rh-st-paused">default — pause not enforced</span>
|
|
11
|
+
<% end %>
|
|
12
|
+
<span class="rh-sub" style="margin-left:6px">crash-reliable fetch (super_fetch) is Sidekiq Pro</span>
|
|
13
|
+
</p>
|
|
14
|
+
<% end %>
|
|
13
15
|
<table class="rh-table">
|
|
14
16
|
<thead><tr><th>Process</th><th class="r">Threads</th><th>Queues</th><th class="r">Started</th><th class="r">Heartbeat</th><th class="r">Actions</th></tr></thead>
|
|
15
17
|
<tbody>
|
data/lib/roundhouse_ui.rb
CHANGED
|
@@ -57,6 +57,13 @@ module RoundhouseUi
|
|
|
57
57
|
# Sidekiq's retry/dead sets, so this is the only way to surface them here.
|
|
58
58
|
attr_accessor :show_sidekiq_failures
|
|
59
59
|
|
|
60
|
+
# Queue pause/resume is only enforced when RoundhouseUi::Fetch is installed
|
|
61
|
+
# as the server's fetch strategy. If you run reliable fetch (Sidekiq
|
|
62
|
+
# Pro/Enterprise super_fetch) you can't also run our fetcher, so pause can't
|
|
63
|
+
# be enforced — set this to false to hide the pause controls and the
|
|
64
|
+
# "not enforced" warning entirely. Default: true.
|
|
65
|
+
attr_accessor :pause_enabled
|
|
66
|
+
|
|
60
67
|
# Configure in an initializer:
|
|
61
68
|
#
|
|
62
69
|
# RoundhouseUi.configure do |c|
|
|
@@ -77,4 +84,5 @@ module RoundhouseUi
|
|
|
77
84
|
self.allow_job_editing = false
|
|
78
85
|
self.redact_args = []
|
|
79
86
|
self.show_sidekiq_failures = false
|
|
87
|
+
self.pause_enabled = true
|
|
80
88
|
end
|