solid_queue_guard 0.5.0 → 1.0.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/.gitignore +12 -0
- data/.rubocop.yml +44 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +81 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +308 -0
- data/README.md +66 -9
- data/app/controllers/solid_queue_guard/health_controller.rb +1 -4
- data/lib/generators/solid_queue_guard/install/USAGE.ci +9 -0
- data/lib/generators/solid_queue_guard/install/ci_generator.rb +19 -0
- data/lib/generators/solid_queue_guard/install/templates/solid_queue_guard.rb +6 -0
- data/lib/generators/solid_queue_guard/install/templates/solid_queue_guard.yml +25 -0
- data/lib/solid_queue_guard/checks/base.rb +16 -0
- data/lib/solid_queue_guard/checks/config/async_supervisor_config_check.rb +25 -0
- data/lib/solid_queue_guard/checks/config/puma_colocated_check.rb +5 -11
- data/lib/solid_queue_guard/checks/registry.rb +8 -2
- data/lib/solid_queue_guard/checks/runtime/database_support.rb +5 -2
- data/lib/solid_queue_guard/checks/runtime/dispatcher_check.rb +4 -2
- data/lib/solid_queue_guard/checks/runtime/failed_jobs_check.rb +1 -1
- data/lib/solid_queue_guard/checks/runtime/puma_plugin_runtime_check.rb +39 -0
- data/lib/solid_queue_guard/checks/runtime/recurring_stale_check.rb +1 -1
- data/lib/solid_queue_guard/checks/runtime/scheduled_backlog_check.rb +1 -1
- data/lib/solid_queue_guard/checks/runtime/stale_process_check.rb +1 -1
- data/lib/solid_queue_guard/configuration.rb +34 -1
- data/lib/solid_queue_guard/http_status_policy.rb +35 -0
- data/lib/solid_queue_guard/puma_plugin_support.rb +35 -0
- data/lib/solid_queue_guard/recommendations/topology.rb +13 -0
- data/lib/solid_queue_guard/runner.rb +10 -1
- data/lib/solid_queue_guard/version.rb +1 -1
- data/lib/solid_queue_guard.rb +1 -1
- data/solid_queue_guard.gemspec +54 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 690433fe81ac90880ec7fdaa046b7638330eb879776e19343066732fd1f0f3e7
|
|
4
|
+
data.tar.gz: 6028bf63b80b0919158a10fb5771b8cdec74518be47b0882cfd39f893bbf10f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2d9bf3c79fbe0871ed9456eec1cefb22baf52f4949b128cc2a1f8d30dde4bb468bbece87752a33ea06a0e53d12ec25db65e1c1295205ac140643d9cf5dbfc769
|
|
7
|
+
data.tar.gz: 0d40a0805708376a56e243977772274a61f6e4a799ca3dfc64dfb362a46217e30258ba1758e1dda7bc3956f63e13895cde6814f446551b4bf271a07b24477e57
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rails
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
NewCops: enable
|
|
6
|
+
TargetRubyVersion: 3.1
|
|
7
|
+
SuggestExtensions: false
|
|
8
|
+
Exclude:
|
|
9
|
+
- "gemfiles/**/*"
|
|
10
|
+
- "test/dummy/**/*"
|
|
11
|
+
- "vendor/**/*"
|
|
12
|
+
- "tmp/**/*"
|
|
13
|
+
|
|
14
|
+
Style/Documentation:
|
|
15
|
+
Enabled: false
|
|
16
|
+
|
|
17
|
+
Metrics/BlockLength:
|
|
18
|
+
Exclude:
|
|
19
|
+
- "Rakefile"
|
|
20
|
+
- "*.gemspec"
|
|
21
|
+
- "lib/solid_queue_guard/tasks.rb"
|
|
22
|
+
|
|
23
|
+
Metrics/MethodLength:
|
|
24
|
+
Max: 25
|
|
25
|
+
|
|
26
|
+
Metrics/AbcSize:
|
|
27
|
+
Max: 25
|
|
28
|
+
Exclude:
|
|
29
|
+
- lib/solid_queue_guard/checks/runtime/**/*
|
|
30
|
+
|
|
31
|
+
Gemspec/DevelopmentDependencies:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Rails/Exit:
|
|
35
|
+
Exclude:
|
|
36
|
+
- "lib/solid_queue_guard/cli.rb"
|
|
37
|
+
|
|
38
|
+
Naming/PredicateMethod:
|
|
39
|
+
Exclude:
|
|
40
|
+
- "lib/solid_queue_guard/tasks.rb"
|
|
41
|
+
|
|
42
|
+
Style/MultilineBlockChain:
|
|
43
|
+
Exclude:
|
|
44
|
+
- "lib/solid_queue_guard/checks/runtime/**/*"
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.9
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-07-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Stable public API guarantee documented in README
|
|
13
|
+
- Gemspec packaging via `git ls-files` for reproducible releases
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Deprecations now target removal in `2.0` per semver policy
|
|
18
|
+
|
|
19
|
+
## [0.8.0] - 2026-07-09
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Configurable HTTP status policy via `degraded_http_status` and `unhealthy_http_status`
|
|
24
|
+
- `solid_queue_guard:install:ci` generator for GitHub Actions doctor workflow
|
|
25
|
+
|
|
26
|
+
## [0.7.0] - 2026-07-09
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `AsyncSupervisorConfigCheck` for async supervisor mode
|
|
31
|
+
- `PumaPluginRuntimeCheck` for co-located Puma plugin health
|
|
32
|
+
- Async-aware topology pool recommendations
|
|
33
|
+
|
|
34
|
+
## [0.6.0] - 2026-07-09
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Per-check configuration via `disabled_checks` and `config.checks`
|
|
39
|
+
- Per-check threshold overrides for queue lag, failed jobs, stale processes, and scheduled backlog
|
|
40
|
+
|
|
41
|
+
## [0.5.0] - 2026-07-09
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `TopologyRecommendationCheck` for automatic `queue.yml` and pool recommendations
|
|
46
|
+
- `SolidQueueGuard::Recommendations::Topology` analyzer
|
|
47
|
+
|
|
48
|
+
## [0.4.0] - 2026-07-09
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- Metrics export via `config.metrics_backends` (`:statsd`, `:prometheus`, `:opentelemetry`)
|
|
53
|
+
- `SolidQueueGuard::Metrics::Exporter` and per-backend exporters
|
|
54
|
+
|
|
55
|
+
## [0.3.0] - 2026-07-09
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- Notification adapters: `:rails_logger`, `:slack`, `:datadog`, `:webhook`
|
|
60
|
+
- Automatic notification delivery on degraded/unhealthy CLI reports via `config.notify_with`
|
|
61
|
+
|
|
62
|
+
## [0.2.0] - 2026-07-09
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- Runtime checks: queue lag, stale processes, dispatcher health, scheduled backlog, blocked jobs, orphaned claims, failed jobs, recurring staleness, paused queue lag, pidfile, finished jobs growth
|
|
67
|
+
- HTTP `/solid_queue_guard/health` endpoint with optional token auth and response caching
|
|
68
|
+
- `health` rake task now runs full runtime scope
|
|
69
|
+
- Terminal report hides skipped runtime checks
|
|
70
|
+
- Optional Rails `/up` integration via `integrate_rails_health`
|
|
71
|
+
|
|
72
|
+
## [0.1.0] - 2026-07-09
|
|
73
|
+
|
|
74
|
+
### Added
|
|
75
|
+
|
|
76
|
+
- `solid_queue_guard:doctor` rake task for configuration readiness checks
|
|
77
|
+
- `solid_queue_guard:report` rake task for full diagnostic output
|
|
78
|
+
- `solid_queue_guard:install` generator for the initializer
|
|
79
|
+
- JSON output via `SOLID_QUEUE_GUARD_FORMAT=json` or `doctor[json]`
|
|
80
|
+
- Strict CI mode via `SOLID_QUEUE_GUARD_STRICT=1` or `--strict`
|
|
81
|
+
- Configuration checks: adapter, queue database, `connects_to`, queue schema, thread pool, worker coverage, scheduler, environment flags, process heartbeat thresholds, and Puma co-location
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
solid_queue_guard (1.0.0)
|
|
5
|
+
actionpack (>= 7.1, < 9.0)
|
|
6
|
+
activejob (>= 7.1, < 9.0)
|
|
7
|
+
activerecord (>= 7.1, < 9.0)
|
|
8
|
+
activesupport (>= 7.1, < 9.0)
|
|
9
|
+
railties (>= 7.1, < 9.0)
|
|
10
|
+
solid_queue (>= 1.0, < 2.0)
|
|
11
|
+
|
|
12
|
+
GEM
|
|
13
|
+
remote: https://rubygems.org/
|
|
14
|
+
specs:
|
|
15
|
+
action_text-trix (2.1.19)
|
|
16
|
+
railties
|
|
17
|
+
actioncable (8.1.3)
|
|
18
|
+
actionpack (= 8.1.3)
|
|
19
|
+
activesupport (= 8.1.3)
|
|
20
|
+
nio4r (~> 2.0)
|
|
21
|
+
websocket-driver (>= 0.6.1)
|
|
22
|
+
zeitwerk (~> 2.6)
|
|
23
|
+
actionmailbox (8.1.3)
|
|
24
|
+
actionpack (= 8.1.3)
|
|
25
|
+
activejob (= 8.1.3)
|
|
26
|
+
activerecord (= 8.1.3)
|
|
27
|
+
activestorage (= 8.1.3)
|
|
28
|
+
activesupport (= 8.1.3)
|
|
29
|
+
mail (>= 2.8.0)
|
|
30
|
+
actionmailer (8.1.3)
|
|
31
|
+
actionpack (= 8.1.3)
|
|
32
|
+
actionview (= 8.1.3)
|
|
33
|
+
activejob (= 8.1.3)
|
|
34
|
+
activesupport (= 8.1.3)
|
|
35
|
+
mail (>= 2.8.0)
|
|
36
|
+
rails-dom-testing (~> 2.2)
|
|
37
|
+
actionpack (8.1.3)
|
|
38
|
+
actionview (= 8.1.3)
|
|
39
|
+
activesupport (= 8.1.3)
|
|
40
|
+
nokogiri (>= 1.8.5)
|
|
41
|
+
rack (>= 2.2.4)
|
|
42
|
+
rack-session (>= 1.0.1)
|
|
43
|
+
rack-test (>= 0.6.3)
|
|
44
|
+
rails-dom-testing (~> 2.2)
|
|
45
|
+
rails-html-sanitizer (~> 1.6)
|
|
46
|
+
useragent (~> 0.16)
|
|
47
|
+
actiontext (8.1.3)
|
|
48
|
+
action_text-trix (~> 2.1.15)
|
|
49
|
+
actionpack (= 8.1.3)
|
|
50
|
+
activerecord (= 8.1.3)
|
|
51
|
+
activestorage (= 8.1.3)
|
|
52
|
+
activesupport (= 8.1.3)
|
|
53
|
+
globalid (>= 0.6.0)
|
|
54
|
+
nokogiri (>= 1.8.5)
|
|
55
|
+
actionview (8.1.3)
|
|
56
|
+
activesupport (= 8.1.3)
|
|
57
|
+
builder (~> 3.1)
|
|
58
|
+
erubi (~> 1.11)
|
|
59
|
+
rails-dom-testing (~> 2.2)
|
|
60
|
+
rails-html-sanitizer (~> 1.6)
|
|
61
|
+
activejob (8.1.3)
|
|
62
|
+
activesupport (= 8.1.3)
|
|
63
|
+
globalid (>= 0.3.6)
|
|
64
|
+
activemodel (8.1.3)
|
|
65
|
+
activesupport (= 8.1.3)
|
|
66
|
+
activerecord (8.1.3)
|
|
67
|
+
activemodel (= 8.1.3)
|
|
68
|
+
activesupport (= 8.1.3)
|
|
69
|
+
timeout (>= 0.4.0)
|
|
70
|
+
activestorage (8.1.3)
|
|
71
|
+
actionpack (= 8.1.3)
|
|
72
|
+
activejob (= 8.1.3)
|
|
73
|
+
activerecord (= 8.1.3)
|
|
74
|
+
activesupport (= 8.1.3)
|
|
75
|
+
marcel (~> 1.0)
|
|
76
|
+
activesupport (8.1.3)
|
|
77
|
+
base64
|
|
78
|
+
bigdecimal
|
|
79
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
80
|
+
connection_pool (>= 2.2.5)
|
|
81
|
+
drb
|
|
82
|
+
i18n (>= 1.6, < 2)
|
|
83
|
+
json
|
|
84
|
+
logger (>= 1.4.2)
|
|
85
|
+
minitest (>= 5.1)
|
|
86
|
+
securerandom (>= 0.3)
|
|
87
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
88
|
+
uri (>= 0.13.1)
|
|
89
|
+
appraisal (2.5.0)
|
|
90
|
+
bundler
|
|
91
|
+
rake
|
|
92
|
+
thor (>= 0.14.0)
|
|
93
|
+
ast (2.4.3)
|
|
94
|
+
base64 (0.3.0)
|
|
95
|
+
bigdecimal (4.1.2)
|
|
96
|
+
builder (3.3.0)
|
|
97
|
+
concurrent-ruby (1.3.7)
|
|
98
|
+
connection_pool (3.0.2)
|
|
99
|
+
crass (1.0.7)
|
|
100
|
+
date (3.5.1)
|
|
101
|
+
debug (1.11.1)
|
|
102
|
+
irb (~> 1.10)
|
|
103
|
+
reline (>= 0.3.8)
|
|
104
|
+
drb (2.2.3)
|
|
105
|
+
erb (6.0.4)
|
|
106
|
+
erubi (1.13.1)
|
|
107
|
+
et-orbi (1.4.0)
|
|
108
|
+
tzinfo
|
|
109
|
+
fugit (1.12.3)
|
|
110
|
+
et-orbi (~> 1.4)
|
|
111
|
+
raabro (~> 1.4)
|
|
112
|
+
globalid (1.4.0)
|
|
113
|
+
activesupport (>= 6.1)
|
|
114
|
+
i18n (1.15.2)
|
|
115
|
+
concurrent-ruby (~> 1.0)
|
|
116
|
+
io-console (0.8.2)
|
|
117
|
+
irb (1.18.0)
|
|
118
|
+
pp (>= 0.6.0)
|
|
119
|
+
prism (>= 1.3.0)
|
|
120
|
+
rdoc (>= 4.0.0)
|
|
121
|
+
reline (>= 0.4.2)
|
|
122
|
+
json (2.20.0)
|
|
123
|
+
language_server-protocol (3.17.0.6)
|
|
124
|
+
lint_roller (1.1.0)
|
|
125
|
+
logger (1.7.0)
|
|
126
|
+
loofah (2.25.1)
|
|
127
|
+
crass (~> 1.0.2)
|
|
128
|
+
nokogiri (>= 1.12.0)
|
|
129
|
+
mail (2.9.1)
|
|
130
|
+
logger
|
|
131
|
+
mini_mime (>= 0.1.1)
|
|
132
|
+
net-imap
|
|
133
|
+
net-pop
|
|
134
|
+
net-smtp
|
|
135
|
+
marcel (1.2.1)
|
|
136
|
+
mini_mime (1.1.5)
|
|
137
|
+
minitest (5.27.0)
|
|
138
|
+
mocha (3.1.0)
|
|
139
|
+
ruby2_keywords (>= 0.0.5)
|
|
140
|
+
net-imap (0.6.4.1)
|
|
141
|
+
date
|
|
142
|
+
net-protocol
|
|
143
|
+
net-pop (0.1.2)
|
|
144
|
+
net-protocol
|
|
145
|
+
net-protocol (0.2.2)
|
|
146
|
+
timeout
|
|
147
|
+
net-smtp (0.5.1)
|
|
148
|
+
net-protocol
|
|
149
|
+
nio4r (2.7.5)
|
|
150
|
+
nokogiri (1.19.4-aarch64-linux-gnu)
|
|
151
|
+
racc (~> 1.4)
|
|
152
|
+
nokogiri (1.19.4-aarch64-linux-musl)
|
|
153
|
+
racc (~> 1.4)
|
|
154
|
+
nokogiri (1.19.4-arm-linux-gnu)
|
|
155
|
+
racc (~> 1.4)
|
|
156
|
+
nokogiri (1.19.4-arm-linux-musl)
|
|
157
|
+
racc (~> 1.4)
|
|
158
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
159
|
+
racc (~> 1.4)
|
|
160
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
161
|
+
racc (~> 1.4)
|
|
162
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
163
|
+
racc (~> 1.4)
|
|
164
|
+
nokogiri (1.19.4-x86_64-linux-musl)
|
|
165
|
+
racc (~> 1.4)
|
|
166
|
+
parallel (1.28.0)
|
|
167
|
+
parser (3.3.11.1)
|
|
168
|
+
ast (~> 2.4.1)
|
|
169
|
+
racc
|
|
170
|
+
pp (0.6.4)
|
|
171
|
+
prettyprint
|
|
172
|
+
prettyprint (0.2.0)
|
|
173
|
+
prism (1.9.0)
|
|
174
|
+
puma (8.0.2)
|
|
175
|
+
nio4r (~> 2.0)
|
|
176
|
+
raabro (1.4.0)
|
|
177
|
+
racc (1.8.1)
|
|
178
|
+
rack (3.2.6)
|
|
179
|
+
rack-session (2.1.2)
|
|
180
|
+
base64 (>= 0.1.0)
|
|
181
|
+
rack (>= 3.0.0)
|
|
182
|
+
rack-test (2.2.0)
|
|
183
|
+
rack (>= 1.3)
|
|
184
|
+
rackup (2.3.1)
|
|
185
|
+
rack (>= 3)
|
|
186
|
+
rails (8.1.3)
|
|
187
|
+
actioncable (= 8.1.3)
|
|
188
|
+
actionmailbox (= 8.1.3)
|
|
189
|
+
actionmailer (= 8.1.3)
|
|
190
|
+
actionpack (= 8.1.3)
|
|
191
|
+
actiontext (= 8.1.3)
|
|
192
|
+
actionview (= 8.1.3)
|
|
193
|
+
activejob (= 8.1.3)
|
|
194
|
+
activemodel (= 8.1.3)
|
|
195
|
+
activerecord (= 8.1.3)
|
|
196
|
+
activestorage (= 8.1.3)
|
|
197
|
+
activesupport (= 8.1.3)
|
|
198
|
+
bundler (>= 1.15.0)
|
|
199
|
+
railties (= 8.1.3)
|
|
200
|
+
rails-dom-testing (2.3.0)
|
|
201
|
+
activesupport (>= 5.0.0)
|
|
202
|
+
minitest
|
|
203
|
+
nokogiri (>= 1.6)
|
|
204
|
+
rails-html-sanitizer (1.7.0)
|
|
205
|
+
loofah (~> 2.25)
|
|
206
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
207
|
+
railties (8.1.3)
|
|
208
|
+
actionpack (= 8.1.3)
|
|
209
|
+
activesupport (= 8.1.3)
|
|
210
|
+
irb (~> 1.13)
|
|
211
|
+
rackup (>= 1.0.0)
|
|
212
|
+
rake (>= 12.2)
|
|
213
|
+
thor (~> 1.0, >= 1.2.2)
|
|
214
|
+
tsort (>= 0.2)
|
|
215
|
+
zeitwerk (~> 2.6)
|
|
216
|
+
rainbow (3.1.1)
|
|
217
|
+
rake (13.4.2)
|
|
218
|
+
rbs (4.0.3)
|
|
219
|
+
logger
|
|
220
|
+
prism (>= 1.6.0)
|
|
221
|
+
tsort
|
|
222
|
+
rdoc (8.0.0)
|
|
223
|
+
erb
|
|
224
|
+
prism (>= 1.6.0)
|
|
225
|
+
rbs (>= 4.0.0)
|
|
226
|
+
tsort
|
|
227
|
+
regexp_parser (2.12.0)
|
|
228
|
+
reline (0.6.3)
|
|
229
|
+
io-console (~> 0.5)
|
|
230
|
+
rubocop (1.88.2)
|
|
231
|
+
json (~> 2.3)
|
|
232
|
+
language_server-protocol (~> 3.17.0.2)
|
|
233
|
+
lint_roller (~> 1.1.0)
|
|
234
|
+
parallel (>= 1.10)
|
|
235
|
+
parser (>= 3.3.0.2)
|
|
236
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
237
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
238
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
239
|
+
ruby-progressbar (~> 1.7)
|
|
240
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
241
|
+
rubocop-ast (1.50.0)
|
|
242
|
+
parser (>= 3.3.7.2)
|
|
243
|
+
prism (~> 1.7)
|
|
244
|
+
rubocop-rails (2.35.5)
|
|
245
|
+
activesupport (>= 4.2.0)
|
|
246
|
+
lint_roller (~> 1.1)
|
|
247
|
+
rack (>= 1.1)
|
|
248
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
249
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
|
250
|
+
ruby-progressbar (1.13.0)
|
|
251
|
+
ruby2_keywords (0.0.5)
|
|
252
|
+
securerandom (0.4.1)
|
|
253
|
+
solid_queue (1.4.0)
|
|
254
|
+
activejob (>= 7.1)
|
|
255
|
+
activerecord (>= 7.1)
|
|
256
|
+
concurrent-ruby (>= 1.3.1)
|
|
257
|
+
fugit (~> 1.11)
|
|
258
|
+
railties (>= 7.1)
|
|
259
|
+
thor (>= 1.3.1)
|
|
260
|
+
sqlite3 (2.9.5-aarch64-linux-gnu)
|
|
261
|
+
sqlite3 (2.9.5-aarch64-linux-musl)
|
|
262
|
+
sqlite3 (2.9.5-arm-linux-gnu)
|
|
263
|
+
sqlite3 (2.9.5-arm-linux-musl)
|
|
264
|
+
sqlite3 (2.9.5-arm64-darwin)
|
|
265
|
+
sqlite3 (2.9.5-x86_64-darwin)
|
|
266
|
+
sqlite3 (2.9.5-x86_64-linux-gnu)
|
|
267
|
+
sqlite3 (2.9.5-x86_64-linux-musl)
|
|
268
|
+
thor (1.5.0)
|
|
269
|
+
timeout (0.6.1)
|
|
270
|
+
tsort (0.2.0)
|
|
271
|
+
tzinfo (2.0.6)
|
|
272
|
+
concurrent-ruby (~> 1.0)
|
|
273
|
+
unicode-display_width (3.2.0)
|
|
274
|
+
unicode-emoji (~> 4.1)
|
|
275
|
+
unicode-emoji (4.2.0)
|
|
276
|
+
uri (1.1.1)
|
|
277
|
+
useragent (0.16.11)
|
|
278
|
+
websocket-driver (0.8.2)
|
|
279
|
+
base64
|
|
280
|
+
websocket-extensions (>= 0.1.0)
|
|
281
|
+
websocket-extensions (0.1.5)
|
|
282
|
+
zeitwerk (2.8.2)
|
|
283
|
+
|
|
284
|
+
PLATFORMS
|
|
285
|
+
aarch64-linux-gnu
|
|
286
|
+
aarch64-linux-musl
|
|
287
|
+
arm-linux-gnu
|
|
288
|
+
arm-linux-musl
|
|
289
|
+
arm64-darwin
|
|
290
|
+
x86_64-darwin
|
|
291
|
+
x86_64-linux-gnu
|
|
292
|
+
x86_64-linux-musl
|
|
293
|
+
|
|
294
|
+
DEPENDENCIES
|
|
295
|
+
appraisal (~> 2.5)
|
|
296
|
+
debug
|
|
297
|
+
minitest (~> 5.0)
|
|
298
|
+
mocha (>= 2.1)
|
|
299
|
+
parallel (~> 1.26)
|
|
300
|
+
puma (>= 6.0)
|
|
301
|
+
rails (>= 7.1, < 9.0)
|
|
302
|
+
rubocop (~> 1.75)
|
|
303
|
+
rubocop-rails (~> 2.30)
|
|
304
|
+
solid_queue_guard!
|
|
305
|
+
sqlite3 (~> 2.1)
|
|
306
|
+
|
|
307
|
+
BUNDLED WITH
|
|
308
|
+
2.7.2
|
data/README.md
CHANGED
|
@@ -67,7 +67,7 @@ One command. Actionable output. No Datadog required to get started.
|
|
|
67
67
|
|
|
68
68
|
## What it checks
|
|
69
69
|
|
|
70
|
-
###
|
|
70
|
+
### Configuration doctor
|
|
71
71
|
|
|
72
72
|
Runs locally, in CI, or pre-deploy. **No extra infrastructure.**
|
|
73
73
|
|
|
@@ -83,8 +83,10 @@ Runs locally, in CI, or pre-deploy. **No extra infrastructure.**
|
|
|
83
83
|
| **Env flags** | `SOLID_QUEUE_SKIP_RECURRING=true` in production |
|
|
84
84
|
| **Heartbeats** | Default thresholds that may not fit your deploy |
|
|
85
85
|
| **Puma plugin** | Solid Queue co-located with web in production |
|
|
86
|
+
| **Async supervisor** | `async` mode with thread/pool sizing risks |
|
|
87
|
+
| **Topology** | `queue.yml` worker and pool recommendations |
|
|
86
88
|
|
|
87
|
-
###
|
|
89
|
+
### Runtime guards
|
|
88
90
|
|
|
89
91
|
| Check | Catches |
|
|
90
92
|
| ----- | ------- |
|
|
@@ -93,6 +95,7 @@ Runs locally, in CI, or pre-deploy. **No extra infrastructure.**
|
|
|
93
95
|
| **Dispatcher health** | Scheduled jobs never becoming ready |
|
|
94
96
|
| **Blocked jobs** | Concurrency control silently holding jobs |
|
|
95
97
|
| **Recurring staleness** | Cron tasks that stopped firing |
|
|
98
|
+
| **Puma plugin runtime** | Plugin enabled but no active Solid Queue processes |
|
|
96
99
|
| **HTTP `/health`** | Kamal, ECS, K8s, UptimeRobot integration |
|
|
97
100
|
|
|
98
101
|
---
|
|
@@ -104,6 +107,7 @@ Runs locally, in CI, or pre-deploy. **No extra infrastructure.**
|
|
|
104
107
|
```bash
|
|
105
108
|
bundle add solid_queue_guard
|
|
106
109
|
bin/rails solid_queue_guard:install
|
|
110
|
+
bin/rails generate solid_queue_guard:install:ci # optional GitHub Actions workflow
|
|
107
111
|
```
|
|
108
112
|
|
|
109
113
|
### Run the doctor
|
|
@@ -138,6 +142,12 @@ Perfect for deploy pipelines:
|
|
|
138
142
|
run: SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctor
|
|
139
143
|
```
|
|
140
144
|
|
|
145
|
+
Or generate a workflow:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
bin/rails generate solid_queue_guard:install:ci
|
|
149
|
+
```
|
|
150
|
+
|
|
141
151
|
### HTTP health
|
|
142
152
|
|
|
143
153
|
```ruby
|
|
@@ -157,6 +167,13 @@ config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
|
|
|
157
167
|
# curl -H "X-Solid-Queue-Guard-Token: $TOKEN" ...
|
|
158
168
|
```
|
|
159
169
|
|
|
170
|
+
HTTP status policy (for Kamal, ECS, load balancers):
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
config.degraded_http_status = 207 # or :ok (200), 503, etc.
|
|
174
|
+
config.unhealthy_http_status = 503 # default
|
|
175
|
+
```
|
|
176
|
+
|
|
160
177
|
Works with **Kamal**, **Heroku**, **Fly.io**, **ECS/Fargate**, **Kubernetes**, **Better Stack**, **UptimeRobot**.
|
|
161
178
|
|
|
162
179
|
---
|
|
@@ -177,13 +194,46 @@ SolidQueueGuard.configure do |config|
|
|
|
177
194
|
config.failed_jobs_threshold = 20
|
|
178
195
|
config.stale_process_threshold = 5.minutes
|
|
179
196
|
|
|
180
|
-
#
|
|
181
|
-
|
|
197
|
+
# Per-check overrides (v0.6+)
|
|
198
|
+
config.disabled_checks = [:pidfile]
|
|
199
|
+
config.checks.queue_lag = { threshold: 10.minutes }
|
|
200
|
+
config.checks.failed_jobs = { threshold: 5, enabled: true }
|
|
201
|
+
|
|
202
|
+
# HTTP status policy (v0.8+)
|
|
203
|
+
# config.degraded_http_status = 207
|
|
204
|
+
# config.unhealthy_http_status = 503
|
|
205
|
+
|
|
206
|
+
# config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
|
|
207
|
+
# config.integrate_rails_health = true
|
|
208
|
+
# config.notify_with = [:rails_logger, :slack, :datadog, :webhook]
|
|
209
|
+
# config.metrics_backends = [:statsd, :prometheus, :opentelemetry]
|
|
182
210
|
end
|
|
183
211
|
```
|
|
184
212
|
|
|
185
213
|
---
|
|
186
214
|
|
|
215
|
+
## Public API (v1.0+)
|
|
216
|
+
|
|
217
|
+
The following surface is **stable** until `2.0` and follows [semantic versioning](https://semver.org/):
|
|
218
|
+
|
|
219
|
+
| API | Description |
|
|
220
|
+
| --- | ----------- |
|
|
221
|
+
| `SolidQueueGuard.configure` | Block-style configuration |
|
|
222
|
+
| `SolidQueueGuard.config` | Current configuration object |
|
|
223
|
+
| `SolidQueueGuard.enabled?` | Whether checks run |
|
|
224
|
+
| `solid_queue_guard:doctor` | Config readiness report |
|
|
225
|
+
| `solid_queue_guard:health` | Runtime health report |
|
|
226
|
+
| `solid_queue_guard:report` | Full diagnostic report |
|
|
227
|
+
| `solid_queue_guard:install` | Initializer generator |
|
|
228
|
+
| `solid_queue_guard:install:ci` | GitHub Actions workflow generator |
|
|
229
|
+
| `mount SolidQueueGuard::Engine` | HTTP health endpoint |
|
|
230
|
+
|
|
231
|
+
Configuration attributes, rake tasks, and health JSON shape are public. Internal check classes and registry are `@api private`.
|
|
232
|
+
|
|
233
|
+
Breaking changes ship only in major versions (`2.0+`). Deprecations warn one minor version ahead.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
187
237
|
## solid_queue_guard vs Mission Control
|
|
188
238
|
|
|
189
239
|
| | [Mission Control — Jobs](https://github.com/rails/mission_control-jobs) | solid_queue_guard |
|
|
@@ -192,9 +242,9 @@ end
|
|
|
192
242
|
| **UI** | Dashboard | CLI + JSON + health endpoint |
|
|
193
243
|
| **Retry / discard** | Yes | No (use Mission Control) |
|
|
194
244
|
| **Config doctor** | No | Yes |
|
|
195
|
-
| **Queue lag alerts** | No | Yes
|
|
245
|
+
| **Queue lag alerts** | No | Yes |
|
|
196
246
|
| **Pre-deploy checks** | No | Yes |
|
|
197
|
-
| **Recurring job guard** | Manual inspection | Automatic
|
|
247
|
+
| **Recurring job guard** | Manual inspection | Automatic |
|
|
198
248
|
|
|
199
249
|
**Use both.** They solve different problems.
|
|
200
250
|
|
|
@@ -202,13 +252,17 @@ end
|
|
|
202
252
|
|
|
203
253
|
## Roadmap
|
|
204
254
|
|
|
205
|
-
| Version | Ships |
|
|
206
|
-
| ------- | ----- |
|
|
255
|
+
| Version | Ships | Status |
|
|
256
|
+
| ------- | ----- | ------ |
|
|
207
257
|
| **v0.1** | `doctor` — config checks, CI integration, install generator | ✅ Released |
|
|
208
258
|
| **v0.2** | Runtime health, queue lag, dispatcher/blocked jobs, HTTP endpoint | ✅ Released |
|
|
209
259
|
| **v0.3** | Slack, Datadog, webhook notifications | ✅ Released |
|
|
210
260
|
| **v0.4** | StatsD, Prometheus, OpenTelemetry metrics | ✅ Released |
|
|
211
261
|
| **v0.5** | Auto-recommendations for `queue.yml` topology | ✅ Released |
|
|
262
|
+
| **v0.6** | Per-check configuration (`disabled_checks`, `config.checks`) | ✅ Released |
|
|
263
|
+
| **v0.7** | Puma plugin runtime check, async supervisor awareness | ✅ Released |
|
|
264
|
+
| **v0.8** | HTTP status policy, `install:ci` generator | ✅ Released |
|
|
265
|
+
| **v1.0** | Stable public API, strict semver | ✅ Released |
|
|
212
266
|
|
|
213
267
|
---
|
|
214
268
|
|
|
@@ -216,8 +270,9 @@ end
|
|
|
216
270
|
|
|
217
271
|
| Gem version | Ruby | Rails |
|
|
218
272
|
| ----------- | ---- | ----- |
|
|
219
|
-
| 0.
|
|
273
|
+
| 1.0.x | 3.1+ | 7.1, 7.2, 8.0 |
|
|
220
274
|
| 0.5.x | 3.1+ | 7.1, 7.2, 8.0 |
|
|
275
|
+
| 0.1.x | 3.1+ | 7.1, 7.2, 8.0 |
|
|
221
276
|
|
|
222
277
|
## Requirements
|
|
223
278
|
|
|
@@ -240,6 +295,8 @@ bundle exec appraisal install
|
|
|
240
295
|
bundle exec appraisal rake test
|
|
241
296
|
```
|
|
242
297
|
|
|
298
|
+
Release a new version by pushing a `v*` tag after CI passes (Trusted Publishing on RubyGems).
|
|
299
|
+
|
|
243
300
|
---
|
|
244
301
|
|
|
245
302
|
## Contributing
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators/base'
|
|
4
|
+
|
|
5
|
+
module SolidQueueGuard
|
|
6
|
+
module Generators
|
|
7
|
+
module Install
|
|
8
|
+
class CiGenerator < Rails::Generators::Base
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
desc 'Adds a GitHub Actions workflow that runs solid_queue_guard:doctor in CI'
|
|
12
|
+
|
|
13
|
+
def copy_workflow
|
|
14
|
+
template 'solid_queue_guard.yml', '.github/workflows/solid_queue_guard.yml'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -14,6 +14,12 @@ SolidQueueGuard.configure do |config|
|
|
|
14
14
|
config.health_cache_ttl = 15.seconds
|
|
15
15
|
config.scheduled_backlog_threshold = 100
|
|
16
16
|
|
|
17
|
+
# config.disabled_checks = [:pidfile]
|
|
18
|
+
# config.checks.queue_lag = { threshold: 10.minutes }
|
|
19
|
+
# config.checks.failed_jobs = { threshold: 5, enabled: true }
|
|
20
|
+
# config.degraded_http_status = 207
|
|
21
|
+
# config.unhealthy_http_status = 503
|
|
22
|
+
|
|
17
23
|
# config.health_token = ENV["SOLID_QUEUE_GUARD_TOKEN"]
|
|
18
24
|
# config.integrate_rails_health = true
|
|
19
25
|
# config.notify_with = [:rails_logger, :slack, :datadog, :webhook]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Solid Queue Guard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
doctor:
|
|
10
|
+
name: Solid Queue production readiness
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: .ruby-version
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Prepare test database
|
|
22
|
+
run: bin/rails db:test:prepare
|
|
23
|
+
|
|
24
|
+
- name: Run Solid Queue Guard doctor
|
|
25
|
+
run: SOLID_QUEUE_GUARD_STRICT=1 bin/rails solid_queue_guard:doctor
|
|
@@ -8,6 +8,10 @@ module SolidQueueGuard
|
|
|
8
8
|
new(**options).call
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def self.check_id
|
|
12
|
+
name.demodulize.underscore.delete_suffix('_check')
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def initialize(**options)
|
|
12
16
|
@options = options
|
|
13
17
|
end
|
|
@@ -47,6 +51,18 @@ module SolidQueueGuard
|
|
|
47
51
|
def solid_queue_configuration
|
|
48
52
|
@solid_queue_configuration ||= SolidQueue::Configuration.new
|
|
49
53
|
end
|
|
54
|
+
|
|
55
|
+
def check_id
|
|
56
|
+
self.class.check_id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def guard_config
|
|
60
|
+
SolidQueueGuard.config
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def check_setting(key, default = nil)
|
|
64
|
+
guard_config.check_setting(check_id, key, default)
|
|
65
|
+
end
|
|
50
66
|
end
|
|
51
67
|
end
|
|
52
68
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Config
|
|
6
|
+
class AsyncSupervisorConfigCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
if PumaPluginSupport.async_supervisor_mode?
|
|
9
|
+
warn(
|
|
10
|
+
check_id,
|
|
11
|
+
'Solid Queue supervisor is running in async mode',
|
|
12
|
+
suggestion: [
|
|
13
|
+
'Review thread and database pool sizing;',
|
|
14
|
+
'the processes option in queue.yml is ignored in async mode'
|
|
15
|
+
].join(' '),
|
|
16
|
+
metadata: { supervisor_mode: 'async' }
|
|
17
|
+
)
|
|
18
|
+
else
|
|
19
|
+
pass(check_id, 'Solid Queue supervisor is running in fork mode')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -4,27 +4,21 @@ module SolidQueueGuard
|
|
|
4
4
|
module Checks
|
|
5
5
|
module Config
|
|
6
6
|
class PumaColocatedCheck < Base
|
|
7
|
-
PUMA_PLUGIN_PATTERN = /plugin\s+:?solid_queue/
|
|
8
|
-
|
|
9
7
|
def call
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return pass('puma_colocated', 'No config/puma.rb found') unless puma_config.exist?
|
|
13
|
-
|
|
14
|
-
content = puma_config.read
|
|
8
|
+
return pass(check_id, 'No config/puma.rb found') unless PumaPluginSupport.puma_config_path.exist?
|
|
15
9
|
|
|
16
|
-
if
|
|
10
|
+
if PumaPluginSupport.puma_plugin_enabled?
|
|
17
11
|
if Rails.env.production?
|
|
18
12
|
warn(
|
|
19
|
-
|
|
13
|
+
check_id,
|
|
20
14
|
'Solid Queue Puma plugin is enabled in production',
|
|
21
15
|
suggestion: 'Run Solid Queue in a dedicated job process for better isolation and memory management'
|
|
22
16
|
)
|
|
23
17
|
else
|
|
24
|
-
pass(
|
|
18
|
+
pass(check_id, 'Solid Queue Puma plugin detected (non-production environment)')
|
|
25
19
|
end
|
|
26
20
|
else
|
|
27
|
-
pass(
|
|
21
|
+
pass(check_id, 'Solid Queue is not co-located with Puma')
|
|
28
22
|
end
|
|
29
23
|
end
|
|
30
24
|
end
|
|
@@ -15,7 +15,8 @@ module SolidQueueGuard
|
|
|
15
15
|
Config::EnvFlagsCheck,
|
|
16
16
|
Config::ProcessHeartbeatConfigCheck,
|
|
17
17
|
Config::PumaColocatedCheck,
|
|
18
|
-
Config::TopologyRecommendationCheck
|
|
18
|
+
Config::TopologyRecommendationCheck,
|
|
19
|
+
Config::AsyncSupervisorConfigCheck
|
|
19
20
|
].freeze
|
|
20
21
|
|
|
21
22
|
RUNTIME_CHECKS = [
|
|
@@ -30,10 +31,15 @@ module SolidQueueGuard
|
|
|
30
31
|
Runtime::RecurringStaleCheck,
|
|
31
32
|
Runtime::PausedQueueLagCheck,
|
|
32
33
|
Runtime::PidfileCheck,
|
|
33
|
-
Runtime::FinishedJobsGrowthCheck
|
|
34
|
+
Runtime::FinishedJobsGrowthCheck,
|
|
35
|
+
Runtime::PumaPluginRuntimeCheck
|
|
34
36
|
].freeze
|
|
35
37
|
|
|
36
38
|
class << self
|
|
39
|
+
def check_id_for(check_class)
|
|
40
|
+
check_class.check_id
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
def for(scope)
|
|
38
44
|
case scope.to_sym
|
|
39
45
|
when :config then CONFIG_CHECKS
|
|
@@ -28,8 +28,11 @@ module SolidQueueGuard
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def lag_threshold_for(queue_name)
|
|
31
|
-
thresholds = config.queue_lag_thresholds
|
|
32
|
-
thresholds[queue_name.to_sym] || thresholds[queue_name]
|
|
31
|
+
thresholds = config.check_setting(:queue_lag, :thresholds, config.queue_lag_thresholds)
|
|
32
|
+
per_queue = thresholds[queue_name.to_sym] || thresholds[queue_name]
|
|
33
|
+
return per_queue if per_queue
|
|
34
|
+
|
|
35
|
+
config.check_setting(:queue_lag, :threshold, thresholds[:default])
|
|
33
36
|
end
|
|
34
37
|
end
|
|
35
38
|
end
|
|
@@ -9,16 +9,18 @@ module SolidQueueGuard
|
|
|
9
9
|
dispatchers = SolidQueue::Process.where(kind: 'Dispatcher')
|
|
10
10
|
due_count = SolidQueue::ScheduledExecution.due.count
|
|
11
11
|
|
|
12
|
+
threshold = config.check_setting(:scheduled_backlog, :threshold, config.scheduled_backlog_threshold)
|
|
13
|
+
|
|
12
14
|
if dispatchers.none? && due_count.positive?
|
|
13
15
|
failure(
|
|
14
16
|
check_id,
|
|
15
17
|
"#{due_count} scheduled job(s) are due but no dispatcher is running",
|
|
16
18
|
suggestion: 'Start a dispatcher process or verify bin/jobs is running'
|
|
17
19
|
)
|
|
18
|
-
elsif due_count >
|
|
20
|
+
elsif due_count > threshold
|
|
19
21
|
warn(
|
|
20
22
|
check_id,
|
|
21
|
-
"#{due_count} scheduled executions are due (threshold: #{
|
|
23
|
+
"#{due_count} scheduled executions are due (threshold: #{threshold})",
|
|
22
24
|
suggestion: 'Verify the dispatcher is keeping up with scheduled work'
|
|
23
25
|
)
|
|
24
26
|
else
|
|
@@ -9,7 +9,7 @@ module SolidQueueGuard
|
|
|
9
9
|
def call
|
|
10
10
|
with_queue_database do
|
|
11
11
|
count = SolidQueue::FailedExecution.where(created_at: WINDOW.ago..).count
|
|
12
|
-
threshold = config.failed_jobs_threshold
|
|
12
|
+
threshold = config.check_setting(:failed_jobs, :threshold, config.failed_jobs_threshold)
|
|
13
13
|
|
|
14
14
|
if count > threshold
|
|
15
15
|
warn(
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
module Checks
|
|
5
|
+
module Runtime
|
|
6
|
+
class PumaPluginRuntimeCheck < Base
|
|
7
|
+
def call
|
|
8
|
+
return skip(check_id, 'Solid Queue Puma plugin is not enabled') unless PumaPluginSupport.puma_plugin_enabled?
|
|
9
|
+
|
|
10
|
+
with_queue_database do
|
|
11
|
+
threshold = config.check_setting(:puma_plugin_runtime, :threshold, config.stale_process_threshold)
|
|
12
|
+
active = SolidQueue::Process.where(last_heartbeat_at: threshold.ago..)
|
|
13
|
+
|
|
14
|
+
if active.any?
|
|
15
|
+
pass(
|
|
16
|
+
check_id,
|
|
17
|
+
"Solid Queue processes are active via Puma plugin (#{active.count} process(es))",
|
|
18
|
+
metadata: { active_processes: active.count, supervisor_mode: supervisor_mode_label }
|
|
19
|
+
)
|
|
20
|
+
else
|
|
21
|
+
failure(
|
|
22
|
+
check_id,
|
|
23
|
+
'Solid Queue Puma plugin is enabled but no active processes were found',
|
|
24
|
+
suggestion: 'Verify the Puma plugin booted Solid Queue and workers are processing jobs',
|
|
25
|
+
metadata: { supervisor_mode: supervisor_mode_label }
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def supervisor_mode_label
|
|
34
|
+
PumaPluginSupport.async_supervisor_mode? ? 'async' : 'fork'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -7,7 +7,7 @@ module SolidQueueGuard
|
|
|
7
7
|
def call
|
|
8
8
|
with_queue_database do
|
|
9
9
|
due_count = SolidQueue::ScheduledExecution.due.count
|
|
10
|
-
threshold = config.scheduled_backlog_threshold
|
|
10
|
+
threshold = config.check_setting(:scheduled_backlog, :threshold, config.scheduled_backlog_threshold)
|
|
11
11
|
|
|
12
12
|
if due_count > threshold
|
|
13
13
|
warn(
|
|
@@ -6,7 +6,7 @@ module SolidQueueGuard
|
|
|
6
6
|
class StaleProcessCheck < Base
|
|
7
7
|
def call
|
|
8
8
|
with_queue_database do
|
|
9
|
-
threshold = config.stale_process_threshold
|
|
9
|
+
threshold = config.check_setting(:stale_process, :threshold, config.stale_process_threshold)
|
|
10
10
|
stale = SolidQueue::Process.where(last_heartbeat_at: ...threshold.ago)
|
|
11
11
|
|
|
12
12
|
if stale.none?
|
|
@@ -12,7 +12,11 @@ module SolidQueueGuard
|
|
|
12
12
|
:scheduled_backlog_threshold,
|
|
13
13
|
:integrate_rails_health,
|
|
14
14
|
:notify_with,
|
|
15
|
-
:metrics_backends
|
|
15
|
+
:metrics_backends,
|
|
16
|
+
:disabled_checks,
|
|
17
|
+
:checks,
|
|
18
|
+
:degraded_http_status,
|
|
19
|
+
:unhealthy_http_status
|
|
16
20
|
|
|
17
21
|
def initialize
|
|
18
22
|
@enabled = true
|
|
@@ -26,10 +30,39 @@ module SolidQueueGuard
|
|
|
26
30
|
@integrate_rails_health = false
|
|
27
31
|
@notify_with = [:rails_logger]
|
|
28
32
|
@metrics_backends = []
|
|
33
|
+
@disabled_checks = []
|
|
34
|
+
@checks = ActiveSupport::OrderedOptions.new
|
|
35
|
+
@degraded_http_status = :ok
|
|
36
|
+
@unhealthy_http_status = :service_unavailable
|
|
29
37
|
end
|
|
30
38
|
|
|
31
39
|
def strict?
|
|
32
40
|
strict_mode || ActiveModel::Type::Boolean.new.cast(ENV.fetch('SOLID_QUEUE_GUARD_STRICT', nil))
|
|
33
41
|
end
|
|
42
|
+
|
|
43
|
+
def check_enabled?(check_id)
|
|
44
|
+
id = check_id.to_sym
|
|
45
|
+
return false if disabled_checks.map(&:to_sym).include?(id)
|
|
46
|
+
|
|
47
|
+
settings = check_settings_for(id)
|
|
48
|
+
return true if settings.nil?
|
|
49
|
+
|
|
50
|
+
enabled_value = settings[:enabled]
|
|
51
|
+
enabled_value = settings['enabled'] if enabled_value.nil?
|
|
52
|
+
enabled_value != false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def check_settings_for(check_id)
|
|
56
|
+
checks[check_id.to_sym] || checks[check_id.to_s]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def check_setting(check_id, key, default = nil)
|
|
60
|
+
settings = check_settings_for(check_id)
|
|
61
|
+
return default unless settings
|
|
62
|
+
|
|
63
|
+
value = settings[key]
|
|
64
|
+
value = settings[key.to_sym] if value.nil?
|
|
65
|
+
value.nil? ? default : value
|
|
66
|
+
end
|
|
34
67
|
end
|
|
35
68
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
# @api private
|
|
5
|
+
module HttpStatusPolicy
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
STATUS_MAP = {
|
|
9
|
+
ok: :ok,
|
|
10
|
+
success: :ok,
|
|
11
|
+
200 => :ok,
|
|
12
|
+
multi_status: :multi_status,
|
|
13
|
+
207 => :multi_status,
|
|
14
|
+
service_unavailable: :service_unavailable,
|
|
15
|
+
unavailable: :service_unavailable,
|
|
16
|
+
503 => :service_unavailable
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def resolve(status)
|
|
20
|
+
key = status.is_a?(Integer) ? status : status.to_sym
|
|
21
|
+
STATUS_MAP.fetch(key, :ok)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def for_report_status(report_status, config: SolidQueueGuard.config)
|
|
25
|
+
case report_status.to_s
|
|
26
|
+
when 'unhealthy'
|
|
27
|
+
resolve(config.unhealthy_http_status)
|
|
28
|
+
when 'degraded'
|
|
29
|
+
resolve(config.degraded_http_status)
|
|
30
|
+
else
|
|
31
|
+
:ok
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueGuard
|
|
4
|
+
# @api private
|
|
5
|
+
module PumaPluginSupport
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
PUMA_PLUGIN_PATTERN = /plugin\s+:?solid_queue/
|
|
9
|
+
ASYNC_MODE_PATTERN = /solid_queue_mode\s+:?async/
|
|
10
|
+
|
|
11
|
+
def puma_config_path
|
|
12
|
+
Rails.root.join('config/puma.rb')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def puma_config_content
|
|
16
|
+
return unless puma_config_path.exist?
|
|
17
|
+
|
|
18
|
+
puma_config_path.read
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def puma_plugin_enabled?
|
|
22
|
+
content = puma_config_content
|
|
23
|
+
content.present? && content.match?(PUMA_PLUGIN_PATTERN)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def puma_async_mode?
|
|
27
|
+
content = puma_config_content
|
|
28
|
+
content.present? && content.match?(ASYNC_MODE_PATTERN)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def async_supervisor_mode?
|
|
32
|
+
ENV.fetch('SOLID_QUEUE_SUPERVISOR_MODE', 'fork').casecmp('async').zero? || puma_async_mode?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -31,6 +31,8 @@ module SolidQueueGuard
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def pool_recommendations
|
|
34
|
+
return async_pool_recommendation if PumaPluginSupport.async_supervisor_mode?
|
|
35
|
+
|
|
34
36
|
required_threads = configuration.estimated_number_of_threads
|
|
35
37
|
pool_size = SolidQueue::Record.connection_pool&.size
|
|
36
38
|
return [] if pool_size.nil? || required_threads <= pool_size
|
|
@@ -58,6 +60,17 @@ module SolidQueueGuard
|
|
|
58
60
|
rescue StandardError
|
|
59
61
|
false
|
|
60
62
|
end
|
|
63
|
+
|
|
64
|
+
def async_pool_recommendation
|
|
65
|
+
required_threads = configuration.estimated_number_of_threads
|
|
66
|
+
pool_size = SolidQueue::Record.connection_pool&.size
|
|
67
|
+
return [] if pool_size.nil? || required_threads <= pool_size
|
|
68
|
+
|
|
69
|
+
["Increase queue DB pool to at least #{required_threads + 2} " \
|
|
70
|
+
"for async supervisor mode (currently #{pool_size})"]
|
|
71
|
+
rescue StandardError
|
|
72
|
+
[]
|
|
73
|
+
end
|
|
61
74
|
end
|
|
62
75
|
end
|
|
63
76
|
end
|
|
@@ -27,10 +27,19 @@ module SolidQueueGuard
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def run_check(check_class)
|
|
30
|
+
check_id = Checks::Registry.check_id_for(check_class)
|
|
31
|
+
unless SolidQueueGuard.config.check_enabled?(check_id)
|
|
32
|
+
return Check::Result.new(
|
|
33
|
+
id: check_id,
|
|
34
|
+
status: :skip,
|
|
35
|
+
message: 'Check disabled via configuration'
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
30
39
|
check_class.call
|
|
31
40
|
rescue StandardError => e
|
|
32
41
|
Check::Result.new(
|
|
33
|
-
id:
|
|
42
|
+
id: check_id,
|
|
34
43
|
status: :fail,
|
|
35
44
|
message: "Check raised an error: #{e.class}: #{e.message}"
|
|
36
45
|
)
|
data/lib/solid_queue_guard.rb
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/solid_queue_guard/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'solid_queue_guard'
|
|
7
|
+
spec.version = SolidQueueGuard::VERSION
|
|
8
|
+
spec.authors = ['Rafael Pissardo']
|
|
9
|
+
spec.email = ['rpissardo@users.noreply.github.com']
|
|
10
|
+
spec.homepage = 'https://github.com/rafael-pissardo/solid_queue_guard'
|
|
11
|
+
spec.summary = 'Production readiness checks and runtime guards for Rails Solid Queue.'
|
|
12
|
+
spec.description = [
|
|
13
|
+
'Detect queue lag, dead workers, unsafe thread/pool configuration,',
|
|
14
|
+
'and broken recurring jobs before they become incidents.'
|
|
15
|
+
].join(' ')
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
|
|
18
|
+
spec.metadata = {
|
|
19
|
+
'homepage_uri' => spec.homepage,
|
|
20
|
+
'source_code_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard',
|
|
21
|
+
'changelog_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard/blob/main/CHANGELOG.md',
|
|
22
|
+
'bug_tracker_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard/issues',
|
|
23
|
+
'documentation_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard#readme',
|
|
24
|
+
'rubygems_mfa_required' => 'true'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
28
|
+
`git ls-files -z`.split("\x0").reject do |file|
|
|
29
|
+
file.start_with?('test/', '.github/', 'docs/', 'script/', 'gemfiles/', 'Appraisals')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
spec.require_paths = ['lib']
|
|
34
|
+
spec.required_ruby_version = '>= 3.1'
|
|
35
|
+
|
|
36
|
+
rails_version = '>= 7.1', '< 9.0'
|
|
37
|
+
|
|
38
|
+
spec.add_dependency 'actionpack', rails_version
|
|
39
|
+
spec.add_dependency 'activejob', rails_version
|
|
40
|
+
spec.add_dependency 'activerecord', rails_version
|
|
41
|
+
spec.add_dependency 'activesupport', rails_version
|
|
42
|
+
spec.add_dependency 'railties', rails_version
|
|
43
|
+
spec.add_dependency 'solid_queue', '>= 1.0', '< 2.0'
|
|
44
|
+
|
|
45
|
+
spec.add_development_dependency 'appraisal', '~> 2.5'
|
|
46
|
+
spec.add_development_dependency 'debug'
|
|
47
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
|
48
|
+
spec.add_development_dependency 'mocha', '>= 2.1'
|
|
49
|
+
spec.add_development_dependency 'puma', '>= 6.0'
|
|
50
|
+
spec.add_development_dependency 'rails', rails_version
|
|
51
|
+
spec.add_development_dependency 'rubocop', '~> 1.75'
|
|
52
|
+
spec.add_development_dependency 'rubocop-rails', '~> 2.30'
|
|
53
|
+
spec.add_development_dependency 'sqlite3', '~> 2.1'
|
|
54
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_queue_guard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rafael Pissardo
|
|
@@ -269,6 +269,12 @@ executables: []
|
|
|
269
269
|
extensions: []
|
|
270
270
|
extra_rdoc_files: []
|
|
271
271
|
files:
|
|
272
|
+
- ".gitignore"
|
|
273
|
+
- ".rubocop.yml"
|
|
274
|
+
- ".ruby-version"
|
|
275
|
+
- CHANGELOG.md
|
|
276
|
+
- Gemfile
|
|
277
|
+
- Gemfile.lock
|
|
272
278
|
- MIT-LICENSE
|
|
273
279
|
- README.md
|
|
274
280
|
- Rakefile
|
|
@@ -276,12 +282,16 @@ files:
|
|
|
276
282
|
- app/controllers/solid_queue_guard/health_controller.rb
|
|
277
283
|
- config/routes.rb
|
|
278
284
|
- lib/generators/solid_queue_guard/install/USAGE
|
|
285
|
+
- lib/generators/solid_queue_guard/install/USAGE.ci
|
|
286
|
+
- lib/generators/solid_queue_guard/install/ci_generator.rb
|
|
279
287
|
- lib/generators/solid_queue_guard/install/install_generator.rb
|
|
280
288
|
- lib/generators/solid_queue_guard/install/templates/solid_queue_guard.rb
|
|
289
|
+
- lib/generators/solid_queue_guard/install/templates/solid_queue_guard.yml
|
|
281
290
|
- lib/solid_queue_guard.rb
|
|
282
291
|
- lib/solid_queue_guard/check/result.rb
|
|
283
292
|
- lib/solid_queue_guard/checks/base.rb
|
|
284
293
|
- lib/solid_queue_guard/checks/config/adapter_check.rb
|
|
294
|
+
- lib/solid_queue_guard/checks/config/async_supervisor_config_check.rb
|
|
285
295
|
- lib/solid_queue_guard/checks/config/connects_to_check.rb
|
|
286
296
|
- lib/solid_queue_guard/checks/config/env_flags_check.rb
|
|
287
297
|
- lib/solid_queue_guard/checks/config/process_heartbeat_config_check.rb
|
|
@@ -303,6 +313,7 @@ files:
|
|
|
303
313
|
- lib/solid_queue_guard/checks/runtime/paused_queue_lag_check.rb
|
|
304
314
|
- lib/solid_queue_guard/checks/runtime/pidfile_check.rb
|
|
305
315
|
- lib/solid_queue_guard/checks/runtime/process_topology_check.rb
|
|
316
|
+
- lib/solid_queue_guard/checks/runtime/puma_plugin_runtime_check.rb
|
|
306
317
|
- lib/solid_queue_guard/checks/runtime/queue_lag_check.rb
|
|
307
318
|
- lib/solid_queue_guard/checks/runtime/recurring_stale_check.rb
|
|
308
319
|
- lib/solid_queue_guard/checks/runtime/scheduled_backlog_check.rb
|
|
@@ -314,18 +325,21 @@ files:
|
|
|
314
325
|
- lib/solid_queue_guard/formatters/terminal.rb
|
|
315
326
|
- lib/solid_queue_guard/health/cache.rb
|
|
316
327
|
- lib/solid_queue_guard/health/payload.rb
|
|
328
|
+
- lib/solid_queue_guard/http_status_policy.rb
|
|
317
329
|
- lib/solid_queue_guard/metrics/exporter.rb
|
|
318
330
|
- lib/solid_queue_guard/metrics/open_telemetry.rb
|
|
319
331
|
- lib/solid_queue_guard/metrics/prometheus.rb
|
|
320
332
|
- lib/solid_queue_guard/metrics/statsd.rb
|
|
321
333
|
- lib/solid_queue_guard/notifier.rb
|
|
322
334
|
- lib/solid_queue_guard/notifiers/base.rb
|
|
335
|
+
- lib/solid_queue_guard/puma_plugin_support.rb
|
|
323
336
|
- lib/solid_queue_guard/queue_coverage.rb
|
|
324
337
|
- lib/solid_queue_guard/recommendations/topology.rb
|
|
325
338
|
- lib/solid_queue_guard/report.rb
|
|
326
339
|
- lib/solid_queue_guard/runner.rb
|
|
327
340
|
- lib/solid_queue_guard/tasks.rb
|
|
328
341
|
- lib/solid_queue_guard/version.rb
|
|
342
|
+
- solid_queue_guard.gemspec
|
|
329
343
|
homepage: https://github.com/rafael-pissardo/solid_queue_guard
|
|
330
344
|
licenses:
|
|
331
345
|
- MIT
|