dispatch_policy 0.2.0 → 0.3.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/MIT-LICENSE +16 -17
- data/README.md +433 -388
- data/app/assets/stylesheets/dispatch_policy/application.css +157 -0
- data/app/controllers/dispatch_policy/application_controller.rb +45 -1
- data/app/controllers/dispatch_policy/dashboard_controller.rb +91 -0
- data/app/controllers/dispatch_policy/partitions_controller.rb +122 -0
- data/app/controllers/dispatch_policy/policies_controller.rb +94 -267
- data/app/controllers/dispatch_policy/staged_jobs_controller.rb +9 -0
- data/app/models/dispatch_policy/adaptive_concurrency_stats.rb +11 -81
- data/app/models/dispatch_policy/inflight_job.rb +12 -0
- data/app/models/dispatch_policy/partition.rb +21 -0
- data/app/models/dispatch_policy/staged_job.rb +4 -97
- data/app/models/dispatch_policy/tick_sample.rb +11 -0
- data/app/views/dispatch_policy/dashboard/index.html.erb +109 -0
- data/app/views/dispatch_policy/partitions/index.html.erb +63 -0
- data/app/views/dispatch_policy/partitions/show.html.erb +106 -0
- data/app/views/dispatch_policy/policies/index.html.erb +15 -37
- data/app/views/dispatch_policy/policies/show.html.erb +139 -223
- data/app/views/dispatch_policy/shared/_capacity.html.erb +67 -0
- data/app/views/dispatch_policy/shared/_hints.html.erb +13 -0
- data/app/views/dispatch_policy/shared/_partition_row.html.erb +12 -0
- data/app/views/dispatch_policy/staged_jobs/show.html.erb +31 -0
- data/app/views/layouts/dispatch_policy/application.html.erb +95 -238
- data/config/routes.rb +18 -2
- data/db/migrate/20260501000001_create_dispatch_policy_tables.rb +103 -0
- data/lib/dispatch_policy/bypass.rb +23 -0
- data/lib/dispatch_policy/config.rb +85 -0
- data/lib/dispatch_policy/context.rb +50 -0
- data/lib/dispatch_policy/cursor_pagination.rb +121 -0
- data/lib/dispatch_policy/decision.rb +22 -0
- data/lib/dispatch_policy/engine.rb +4 -27
- data/lib/dispatch_policy/forwarder.rb +63 -0
- data/lib/dispatch_policy/gate.rb +10 -38
- data/lib/dispatch_policy/gates/adaptive_concurrency.rb +99 -97
- data/lib/dispatch_policy/gates/concurrency.rb +45 -26
- data/lib/dispatch_policy/gates/throttle.rb +65 -41
- data/lib/dispatch_policy/inflight_tracker.rb +174 -0
- data/lib/dispatch_policy/job_extension.rb +155 -0
- data/lib/dispatch_policy/operator_hints.rb +126 -0
- data/lib/dispatch_policy/pipeline.rb +48 -0
- data/lib/dispatch_policy/policy.rb +61 -59
- data/lib/dispatch_policy/policy_dsl.rb +120 -0
- data/lib/dispatch_policy/railtie.rb +35 -0
- data/lib/dispatch_policy/registry.rb +46 -0
- data/lib/dispatch_policy/repository.rb +723 -0
- data/lib/dispatch_policy/serializer.rb +36 -0
- data/lib/dispatch_policy/tick.rb +260 -256
- data/lib/dispatch_policy/tick_loop.rb +59 -26
- data/lib/dispatch_policy/version.rb +1 -1
- data/lib/dispatch_policy.rb +71 -52
- data/lib/generators/dispatch_policy/install/install_generator.rb +70 -0
- data/lib/generators/dispatch_policy/install/templates/create_dispatch_policy_tables.rb.tt +95 -0
- data/lib/generators/dispatch_policy/install/templates/dispatch_tick_loop_job.rb.tt +53 -0
- data/lib/generators/dispatch_policy/install/templates/initializer.rb.tt +11 -0
- metadata +101 -43
- data/CHANGELOG.md +0 -43
- data/app/models/dispatch_policy/partition_inflight_count.rb +0 -42
- data/app/models/dispatch_policy/partition_observation.rb +0 -76
- data/app/models/dispatch_policy/throttle_bucket.rb +0 -41
- data/db/migrate/20260424000001_create_dispatch_policy_tables.rb +0 -80
- data/db/migrate/20260424000002_create_adaptive_concurrency_stats.rb +0 -22
- data/db/migrate/20260424000003_create_adaptive_concurrency_samples.rb +0 -25
- data/db/migrate/20260424000004_rename_samples_to_partition_observations.rb +0 -32
- data/db/migrate/20260425000001_add_duration_to_partition_observations.rb +0 -8
- data/lib/dispatch_policy/active_job_perform_all_later_patch.rb +0 -32
- data/lib/dispatch_policy/dispatch_context.rb +0 -53
- data/lib/dispatch_policy/dispatchable.rb +0 -123
- data/lib/dispatch_policy/gates/fair_interleave.rb +0 -32
- data/lib/dispatch_policy/gates/global_cap.rb +0 -26
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<section class="dp-stats">
|
|
2
|
+
<div class="dp-stat"><span class="dp-stat-label">Staged</span><span class="dp-stat-value"><%= format_count(@totals[:staged]) %></span></div>
|
|
3
|
+
<div class="dp-stat"><span class="dp-stat-label">Partitions</span><span class="dp-stat-value"><%= format_count(@totals[:partitions]) %></span></div>
|
|
4
|
+
<div class="dp-stat"><span class="dp-stat-label">Active</span><span class="dp-stat-value"><%= format_count(@totals[:active_parts]) %></span></div>
|
|
5
|
+
<div class="dp-stat"><span class="dp-stat-label">Paused</span><span class="dp-stat-value <%= "dp-warn" if @totals[:paused_parts].positive? %>"><%= format_count(@totals[:paused_parts]) %></span></div>
|
|
6
|
+
<div class="dp-stat"><span class="dp-stat-label">In flight</span><span class="dp-stat-value"><%= format_count(@totals[:in_flight]) %></span></div>
|
|
7
|
+
</section>
|
|
8
|
+
|
|
9
|
+
<%= render "dispatch_policy/shared/hints", hints: @hints %>
|
|
10
|
+
<%= render "dispatch_policy/shared/capacity",
|
|
11
|
+
capacity: @capacity,
|
|
12
|
+
pending_trend: @pending_trend,
|
|
13
|
+
pending_buckets: @pending_buckets %>
|
|
14
|
+
|
|
15
|
+
<section class="dp-section">
|
|
16
|
+
<h2>Throughput</h2>
|
|
17
|
+
<table class="dp-table">
|
|
18
|
+
<thead>
|
|
19
|
+
<tr><th>Window</th><th class="dp-num">Jobs admitted</th><th class="dp-num">Ticks</th><th class="dp-num">Avg tick</th><th class="dp-num">Max tick</th><th class="dp-num">Forward fail</th></tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
<% @windows.each do |label, m| %>
|
|
23
|
+
<% fail_pct = m[:jobs_admitted].positive? ? (m[:forward_failures].to_f / m[:jobs_admitted] * 100) : 0.0 %>
|
|
24
|
+
<tr>
|
|
25
|
+
<td><strong><%= label %></strong></td>
|
|
26
|
+
<td class="dp-num"><%= format_count(m[:jobs_admitted]) %></td>
|
|
27
|
+
<td class="dp-num"><%= format_count(m[:ticks]) %></td>
|
|
28
|
+
<td class="dp-num"><%= format_duration_ms(m[:avg_duration_ms]) %></td>
|
|
29
|
+
<td class="dp-num"><%= format_duration_ms(m[:max_duration_ms]) %></td>
|
|
30
|
+
<td class="dp-num <%= "dp-warn" if fail_pct >= 1.0 %>">
|
|
31
|
+
<%= format_count(m[:forward_failures]) %>
|
|
32
|
+
<% if m[:jobs_admitted].positive? && m[:forward_failures].positive? %>
|
|
33
|
+
<span style="color:#9ca3af; font-size:11px;">(<%= format("%.2f%%", fail_pct) %>)</span>
|
|
34
|
+
<% end %>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</section>
|
|
41
|
+
|
|
42
|
+
<section class="dp-section">
|
|
43
|
+
<h2>Round-trip across active partitions</h2>
|
|
44
|
+
<% if @round_trip[:active_partitions].zero? %>
|
|
45
|
+
<p class="dp-empty">No active partitions with pending jobs.</p>
|
|
46
|
+
<% else %>
|
|
47
|
+
<div class="dp-stats">
|
|
48
|
+
<div class="dp-stat"><span class="dp-stat-label">Active w/ pending</span><span class="dp-stat-value"><%= format_count(@round_trip[:active_partitions]) %></span></div>
|
|
49
|
+
<div class="dp-stat"><span class="dp-stat-label">Never checked</span><span class="dp-stat-value <%= "dp-warn" if @round_trip[:never_checked].positive? %>"><%= format_count(@round_trip[:never_checked]) %></span></div>
|
|
50
|
+
<div class="dp-stat"><span class="dp-stat-label">In backoff</span><span class="dp-stat-value"><%= format_count(@round_trip[:in_backoff]) %></span></div>
|
|
51
|
+
<div class="dp-stat"><span class="dp-stat-label">Oldest age</span><span class="dp-stat-value"><%= format_duration_seconds(@round_trip[:oldest_age_seconds]) %></span></div>
|
|
52
|
+
<div class="dp-stat"><span class="dp-stat-label">P50 age</span><span class="dp-stat-value"><%= format_duration_seconds(@round_trip[:p50_age_seconds]) %></span></div>
|
|
53
|
+
<div class="dp-stat"><span class="dp-stat-label">P95 age</span><span class="dp-stat-value"><%= format_duration_seconds(@round_trip[:p95_age_seconds]) %></span></div>
|
|
54
|
+
</div>
|
|
55
|
+
<p class="dp-hint">
|
|
56
|
+
If <strong>oldest age</strong> is bigger than your tolerable latency for the slowest partition, increase
|
|
57
|
+
<code>partition_batch_size</code> or shard ticks per <code>policy_name</code>.
|
|
58
|
+
If <strong>never checked</strong> stays positive across refreshes, the tick is not keeping up — same remedy.
|
|
59
|
+
</p>
|
|
60
|
+
<% end %>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section class="dp-section">
|
|
64
|
+
<h2>Policies</h2>
|
|
65
|
+
<% if @policies.empty? %>
|
|
66
|
+
<p class="dp-empty">No policies have produced staged jobs yet.</p>
|
|
67
|
+
<% else %>
|
|
68
|
+
<table class="dp-table">
|
|
69
|
+
<thead>
|
|
70
|
+
<tr>
|
|
71
|
+
<th>Name</th>
|
|
72
|
+
<th class="dp-num">Pending</th>
|
|
73
|
+
<th class="dp-num">In flight</th>
|
|
74
|
+
<th class="dp-num">Admit/min (1m)</th>
|
|
75
|
+
<th class="dp-num">Ticks (1m)</th>
|
|
76
|
+
<th class="dp-num">Avg tick</th>
|
|
77
|
+
<th class="dp-num">P95 age</th>
|
|
78
|
+
<th class="dp-num">Backoff</th>
|
|
79
|
+
<th>Top denial</th>
|
|
80
|
+
<th>Last admit</th>
|
|
81
|
+
<th></th>
|
|
82
|
+
</tr>
|
|
83
|
+
</thead>
|
|
84
|
+
<tbody>
|
|
85
|
+
<% @policies.each do |p| %>
|
|
86
|
+
<tr>
|
|
87
|
+
<td><%= link_to p[:name], policy_path(p[:name]), class: "dp-link" %></td>
|
|
88
|
+
<td class="dp-num"><%= format_count(p[:pending]) %></td>
|
|
89
|
+
<td class="dp-num"><%= format_count(p[:in_flight]) %></td>
|
|
90
|
+
<td class="dp-num"><%= format_count(p[:admitted_1m]) %></td>
|
|
91
|
+
<td class="dp-num"><%= format_count(p[:ticks_1m]) %></td>
|
|
92
|
+
<td class="dp-num"><%= format_duration_ms(p[:avg_tick_ms_1m]) %></td>
|
|
93
|
+
<td class="dp-num"><%= format_duration_seconds(p[:p95_age_seconds]) %></td>
|
|
94
|
+
<td class="dp-num <%= "dp-warn" if p[:in_backoff].to_i.positive? %>"><%= format_count(p[:in_backoff]) %></td>
|
|
95
|
+
<td>
|
|
96
|
+
<% if p[:top_denial_reason] %>
|
|
97
|
+
<code><%= p[:top_denial_reason] %></code> ×<%= p[:top_denial_count] %>
|
|
98
|
+
<% else %>
|
|
99
|
+
—
|
|
100
|
+
<% end %>
|
|
101
|
+
</td>
|
|
102
|
+
<td><%= format_time(p[:last_admit_at]) %></td>
|
|
103
|
+
<td><%= link_to "Partitions →", partitions_path(policy: p[:name]), class: "dp-link" %></td>
|
|
104
|
+
</tr>
|
|
105
|
+
<% end %>
|
|
106
|
+
</tbody>
|
|
107
|
+
</table>
|
|
108
|
+
<% end %>
|
|
109
|
+
</section>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<h1>
|
|
2
|
+
Partitions<% if @policy %> · <code><%= @policy %></code><% end %><% if @shard %> · shard <code><%= @shard %></code><% end %>
|
|
3
|
+
<small style="font-weight:400; color:#6b7280; font-size:14px;">
|
|
4
|
+
<%= format_count(@total) %> match<%= @total == 1 ? "" : "es" %>
|
|
5
|
+
</small>
|
|
6
|
+
</h1>
|
|
7
|
+
|
|
8
|
+
<%= form_with url: partitions_path, method: :get, local: true, class: "dp-search-form" do |f| %>
|
|
9
|
+
<%= f.hidden_field :policy, value: @policy if @policy.present? %>
|
|
10
|
+
<% if @shards.size > 1 %>
|
|
11
|
+
<%= f.select :shard, [["all shards", ""]] + @shards.map { |s| [s, s] }, { selected: @shard }, class: "dp-input" %>
|
|
12
|
+
<% elsif @shards.size == 1 %>
|
|
13
|
+
<%= f.hidden_field :shard, value: @shard if @shard.present? %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= f.text_field :q, value: @query, placeholder: "search partition_key…", class: "dp-input" %>
|
|
16
|
+
<%= f.select :sort,
|
|
17
|
+
DispatchPolicy::CursorPagination::SORTS.map { |k, defn| ["sort: #{defn[:label]}", k] },
|
|
18
|
+
{ selected: @sort },
|
|
19
|
+
class: "dp-input" %>
|
|
20
|
+
<label style="font-size:12.5px; margin: 0 6px;">
|
|
21
|
+
<%= f.check_box :only_pending, { checked: @only_pending }, "1", "0" %>
|
|
22
|
+
only pending>0
|
|
23
|
+
</label>
|
|
24
|
+
<%= f.submit "Apply", class: "dp-btn" %>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<% if @partitions.any? %>
|
|
28
|
+
<table class="dp-table">
|
|
29
|
+
<thead>
|
|
30
|
+
<tr>
|
|
31
|
+
<th>Policy</th><th>Shard</th><th>Queue</th><th>Partition key</th><th>Status</th>
|
|
32
|
+
<th class="dp-num">Pending</th><th class="dp-num">Lifetime</th>
|
|
33
|
+
<th>Next eligible</th><th>Last admit</th><th>Last enq</th>
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody>
|
|
37
|
+
<% @partitions.each do |p| %>
|
|
38
|
+
<%= render "dispatch_policy/shared/partition_row", partition: p %>
|
|
39
|
+
<% end %>
|
|
40
|
+
</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
|
|
43
|
+
<div class="dp-pagination">
|
|
44
|
+
<span class="dp-pagination-info">
|
|
45
|
+
<%= format_count(@partitions.size) %> shown
|
|
46
|
+
<% if @cursor %> · paged via cursor — use the browser back button to go back <% end %>
|
|
47
|
+
</span>
|
|
48
|
+
<span class="dp-pagination-nav">
|
|
49
|
+
<% if @cursor %>
|
|
50
|
+
<%= link_to "« first", partitions_path(pagination_params(cursor: nil)), class: "dp-btn" %>
|
|
51
|
+
<% else %>
|
|
52
|
+
<span class="dp-btn dp-btn-disabled">« first</span>
|
|
53
|
+
<% end %>
|
|
54
|
+
<% if @next_cursor %>
|
|
55
|
+
<%= link_to "next ›", partitions_path(pagination_params(cursor: @next_cursor)), class: "dp-btn" %>
|
|
56
|
+
<% else %>
|
|
57
|
+
<span class="dp-btn dp-btn-disabled">next ›</span>
|
|
58
|
+
<% end %>
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
<% else %>
|
|
62
|
+
<p class="dp-empty">No partitions match.</p>
|
|
63
|
+
<% end %>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<h1>
|
|
2
|
+
<%= link_to "← all partitions", partitions_path(policy: @partition.policy_name), class: "dp-link" %>
|
|
3
|
+
/
|
|
4
|
+
<code><%= @partition.partition_key %></code>
|
|
5
|
+
</h1>
|
|
6
|
+
|
|
7
|
+
<% in_backoff = @partition.next_eligible_at && @partition.next_eligible_at > Time.current %>
|
|
8
|
+
<% age_seconds = @partition.last_checked_at && (Time.current - @partition.last_checked_at) %>
|
|
9
|
+
<% half_life = (DispatchPolicy.registry.fetch(@partition.policy_name)&.fairness_half_life_seconds || DispatchPolicy.config.fairness_half_life_seconds).to_f %>
|
|
10
|
+
<%
|
|
11
|
+
decayed_now =
|
|
12
|
+
if @partition.decayed_admits_at && half_life.positive?
|
|
13
|
+
tau = half_life / Math.log(2)
|
|
14
|
+
elapsed = [Time.current.to_f - @partition.decayed_admits_at.to_f, 0.0].max
|
|
15
|
+
@partition.decayed_admits.to_f * Math.exp(-elapsed / tau)
|
|
16
|
+
else
|
|
17
|
+
@partition.decayed_admits.to_f
|
|
18
|
+
end
|
|
19
|
+
admits_per_min_estimate = decayed_now * (Math.log(2) / half_life) * 60.0 if half_life.positive?
|
|
20
|
+
%>
|
|
21
|
+
|
|
22
|
+
<section class="dp-stats">
|
|
23
|
+
<div class="dp-stat"><span class="dp-stat-label">Policy</span><span class="dp-stat-value"><%= @partition.policy_name %></span></div>
|
|
24
|
+
<div class="dp-stat"><span class="dp-stat-label">Shard</span><span class="dp-stat-value"><code><%= @partition.shard %></code></span></div>
|
|
25
|
+
<div class="dp-stat"><span class="dp-stat-label">Queue</span><span class="dp-stat-value"><%= @partition.queue_name || "—" %></span></div>
|
|
26
|
+
<div class="dp-stat"><span class="dp-stat-label">Status</span><span class="dp-stat-value <%= "dp-warn" if @partition.paused? %>"><%= @partition.status %></span></div>
|
|
27
|
+
<div class="dp-stat"><span class="dp-stat-label">Pending</span><span class="dp-stat-value"><%= format_count(@partition.pending_count) %></span></div>
|
|
28
|
+
<div class="dp-stat"><span class="dp-stat-label">Lifetime admitted</span><span class="dp-stat-value"><%= format_count(@partition.total_admitted) %></span></div>
|
|
29
|
+
<div class="dp-stat"><span class="dp-stat-label">Round-trip age</span><span class="dp-stat-value"><%= age_seconds ? format_duration_seconds(age_seconds) : "never" %></span></div>
|
|
30
|
+
<div class="dp-stat"><span class="dp-stat-label">Backoff</span><span class="dp-stat-value <%= "dp-warn" if in_backoff %>"><%= in_backoff ? "until #{format_time(@partition.next_eligible_at)}" : "—" %></span></div>
|
|
31
|
+
<div class="dp-stat">
|
|
32
|
+
<span class="dp-stat-label">Recent admits (EWMA)</span>
|
|
33
|
+
<span class="dp-stat-value"><%= format("%.2f", decayed_now) %></span>
|
|
34
|
+
</div>
|
|
35
|
+
<% if admits_per_min_estimate %>
|
|
36
|
+
<div class="dp-stat">
|
|
37
|
+
<span class="dp-stat-label">≈ admits/min</span>
|
|
38
|
+
<span class="dp-stat-value"><%= format("%.1f", admits_per_min_estimate) %></span>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
<section class="dp-hint">
|
|
44
|
+
<strong>Recent admits (EWMA)</strong> is an exponentially weighted count of admissions for this partition,
|
|
45
|
+
with a half-life of <%= half_life.to_i %>s. The Tick reorders claimed partitions by this value ASC, so the
|
|
46
|
+
least-recently-active ones get first crack at the tick budget. The "≈ admits/min" line reads it as the
|
|
47
|
+
steady-state admission rate.
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
<section class="dp-section">
|
|
51
|
+
<h2>Timing</h2>
|
|
52
|
+
<ul class="dp-list">
|
|
53
|
+
<li>Last checked: <%= format_time(@partition.last_checked_at) %></li>
|
|
54
|
+
<li>Last admit: <%= format_time(@partition.last_admit_at) %></li>
|
|
55
|
+
<li>Last enqueued: <%= format_time(@partition.last_enqueued_at) %></li>
|
|
56
|
+
<li>Next eligible: <%= format_time(@partition.next_eligible_at) %></li>
|
|
57
|
+
<li>Context updated: <%= format_time(@partition.context_updated_at) %></li>
|
|
58
|
+
</ul>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section class="dp-section">
|
|
62
|
+
<h2>Context (refreshed on each enqueue)</h2>
|
|
63
|
+
<pre class="dp-json"><%= JSON.pretty_generate(@partition.context || {}) %></pre>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
<section class="dp-section">
|
|
67
|
+
<h2>Gate state</h2>
|
|
68
|
+
<pre class="dp-json"><%= JSON.pretty_generate(@partition.gate_state || {}) %></pre>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<section class="dp-section">
|
|
72
|
+
<h2>Recent staged jobs (max 50)</h2>
|
|
73
|
+
<% if @recent_jobs.any? %>
|
|
74
|
+
<table class="dp-table">
|
|
75
|
+
<thead><tr><th>id</th><th>Job class</th><th>Scheduled</th><th>Enqueued</th><th></th></tr></thead>
|
|
76
|
+
<tbody>
|
|
77
|
+
<% @recent_jobs.each do |j| %>
|
|
78
|
+
<tr>
|
|
79
|
+
<td><%= j.id %></td>
|
|
80
|
+
<td><code><%= j.job_class %></code></td>
|
|
81
|
+
<td><%= format_time(j.scheduled_at) %></td>
|
|
82
|
+
<td><%= format_time(j.enqueued_at) %></td>
|
|
83
|
+
<td><%= link_to "inspect", staged_job_path(j), class: "dp-link" %></td>
|
|
84
|
+
</tr>
|
|
85
|
+
<% end %>
|
|
86
|
+
</tbody>
|
|
87
|
+
</table>
|
|
88
|
+
<% else %>
|
|
89
|
+
<p class="dp-empty">No staged jobs in this partition.</p>
|
|
90
|
+
<% end %>
|
|
91
|
+
</section>
|
|
92
|
+
|
|
93
|
+
<section class="dp-section">
|
|
94
|
+
<h2>Actions</h2>
|
|
95
|
+
<%= button_to "Force admit 1", admit_partition_path(@partition, count: 1), class: "dp-btn", method: :post, form: { class: "dp-form-inline" } %>
|
|
96
|
+
<%= button_to "Force admit 10", admit_partition_path(@partition, count: 10), class: "dp-btn", method: :post, form: { class: "dp-form-inline" } %>
|
|
97
|
+
<%= button_to "Drain partition", drain_partition_path(@partition),
|
|
98
|
+
class: "dp-btn dp-btn-warn",
|
|
99
|
+
method: :post,
|
|
100
|
+
form: { class: "dp-form-inline",
|
|
101
|
+
onsubmit: "return confirm('Force-admit every staged job in this partition, bypassing all gates? This sends them to the real adapter immediately.');" } %>
|
|
102
|
+
<p class="dp-hint">
|
|
103
|
+
<strong>Drain</strong> empties the partition by sending every staged job to the real adapter without consulting throttle/concurrency gates.
|
|
104
|
+
Capped at 10,000 jobs per click — click again for more.
|
|
105
|
+
</p>
|
|
106
|
+
</section>
|
|
@@ -1,49 +1,27 @@
|
|
|
1
|
-
<h1>
|
|
2
|
-
<p class="muted">Admission-control policies registered in this app.</p>
|
|
1
|
+
<h1>Policies</h1>
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
<div class="label">Active partitions</div>
|
|
7
|
-
<div class="value"><%= @active_partitions %></div>
|
|
8
|
-
</div>
|
|
9
|
-
<div class="summary-item">
|
|
10
|
-
<div class="label">Expired leases</div>
|
|
11
|
-
<div class="value"><%= @expired_leases %></div>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
|
|
15
|
-
<h2>Policies</h2>
|
|
16
|
-
<% if @policies.empty? %>
|
|
17
|
-
<p class="muted">No policies registered yet.</p>
|
|
3
|
+
<% if @rows.empty? %>
|
|
4
|
+
<p class="dp-empty">No policies registered or observed.</p>
|
|
18
5
|
<% else %>
|
|
19
|
-
<table>
|
|
6
|
+
<table class="dp-table">
|
|
20
7
|
<thead>
|
|
21
8
|
<tr>
|
|
22
|
-
<th>
|
|
23
|
-
<th>
|
|
24
|
-
<th>Pending</th>
|
|
25
|
-
<th>Admitted</th>
|
|
26
|
-
<th>Completed (24h)</th>
|
|
27
|
-
<th>Oldest pending</th>
|
|
9
|
+
<th>Name</th><th class="dp-num">Pending</th><th class="dp-num">In flight</th>
|
|
10
|
+
<th class="dp-num">Partitions</th><th class="dp-num">Paused</th><th>Registered</th><th></th>
|
|
28
11
|
</tr>
|
|
29
12
|
</thead>
|
|
30
13
|
<tbody>
|
|
31
|
-
<% @
|
|
14
|
+
<% @rows.each do |row| %>
|
|
32
15
|
<tr>
|
|
33
|
-
<td><%= link_to
|
|
34
|
-
<td
|
|
35
|
-
<td><%=
|
|
36
|
-
<td><%=
|
|
37
|
-
<td><%=
|
|
16
|
+
<td><%= link_to row[:name], policy_path(row[:name]), class: "dp-link" %></td>
|
|
17
|
+
<td class="dp-num"><%= format_count(row[:pending]) %></td>
|
|
18
|
+
<td class="dp-num"><%= format_count(row[:in_flight]) %></td>
|
|
19
|
+
<td class="dp-num"><%= format_count(row[:partitions]) %></td>
|
|
20
|
+
<td class="dp-num"><%= row[:paused_count].positive? ? content_tag(:span, format_count(row[:paused_count]), class: "dp-warn") : 0 %></td>
|
|
21
|
+
<td><%= row[:registered] ? "yes" : content_tag(:span, "no (orphan)", class: "dp-warn") %></td>
|
|
38
22
|
<td>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<span class="<%= 'stale' if stale %>">
|
|
42
|
-
<%= time_ago_in_words(p[:oldest_pending]) %> ago
|
|
43
|
-
</span>
|
|
44
|
-
<% else %>
|
|
45
|
-
<span class="muted">—</span>
|
|
46
|
-
<% end %>
|
|
23
|
+
<%= button_to "Pause", pause_policy_path(row[:name]), class: "dp-btn", method: :post, form: { class: "dp-form-inline" } %>
|
|
24
|
+
<%= button_to "Resume", resume_policy_path(row[:name]), class: "dp-btn dp-btn-ok", method: :post, form: { class: "dp-form-inline" } %>
|
|
47
25
|
</td>
|
|
48
26
|
</tr>
|
|
49
27
|
<% end %>
|