sidekiq_insight 0.1.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 +7 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +206 -0
- data/Rakefile +4 -0
- data/app/assets/javascripts/sidekiq_insight/sidekiq_insight.js +0 -0
- data/app/controllers/sidekiq_insight/base_controller.rb +6 -0
- data/app/controllers/sidekiq_insight/dashboard_controller.rb +19 -0
- data/app/controllers/sidekiq_insight/graphs_controller.rb +38 -0
- data/app/views/layouts/sidekiq_insight/sidekiq_insight.html.erb +48 -0
- data/app/views/sidekiq_insight/dashboard/index.html.erb +84 -0
- data/app/views/sidekiq_insight/dashboard/show.html.erb +87 -0
- data/app/views/sidekiq_insight/graphs/_chart.html.erb +40 -0
- data/app/views/sidekiq_insight/graphs/cpu.html.erb +33 -0
- data/app/views/sidekiq_insight/graphs/leaks.html.erb +38 -0
- data/app/views/sidekiq_insight/graphs/rss.html.erb +42 -0
- data/app/views/sidekiq_insight/graphs/wall.html.erb +31 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/routes.rb +11 -0
- data/lib/sidekiq_insight/configuration.rb +12 -0
- data/lib/sidekiq_insight/engine.rb +35 -0
- data/lib/sidekiq_insight/leak_detector.rb +24 -0
- data/lib/sidekiq_insight/metrics.rb +15 -0
- data/lib/sidekiq_insight/request_middleware.rb +38 -0
- data/lib/sidekiq_insight/server_middleware.rb +33 -0
- data/lib/sidekiq_insight/storage.rb +44 -0
- data/lib/sidekiq_insight/version.rb +5 -0
- data/lib/sidekiq_insight.rb +33 -0
- data/sig/sidekiq_insight.rbs +4 -0
- metadata +158 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a8c2db5cf7bb0e99fe1af7f752c3ad0999cc865c7e030d9b1c78b59c183e226c
|
|
4
|
+
data.tar.gz: 0c5594584fb8682227d72669cd1942ff0175051582efaddcc49af15511ff877e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5c1be3e9a2f1101c4ecd2fe32080d4c7f42dc47f7652fd27735e93e63a181ce5b12f75bb720f9b6e061e97f148e90cf3a6dd817c7b099e42721edc6dc61fb087
|
|
7
|
+
data.tar.gz: ed781d6af149092780b7051ebf7d4456b8444d84424245846407294a622daa4bd57a30230633b072b47de7d2ffed9024e10b3bfd43693e8a3f283674548a2016
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 mrmalvi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# SidekiqInsight
|
|
2
|
+
|
|
3
|
+
**SidekiqInsight** is a lightweight performance monitoring engine for Sidekiq.
|
|
4
|
+
It records CPU usage, wall time, memory (RSS), arguments, and detects memory-leak patterns — all displayed in a clean, Bootstrap-based dashboard.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🚀 Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem "sidekiq_insight"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## ⚙️ Configuration (Required)
|
|
25
|
+
|
|
26
|
+
Create the initializer:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
config/initializers/sidekiq_insight.rb
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Add:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
SidekiqInsight.configure do |config|
|
|
36
|
+
config.redis_url = "redis://127.0.0.1:6379/0"
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
SidekiqInsight uses Redis to store:
|
|
41
|
+
|
|
42
|
+
- job samples
|
|
43
|
+
- aggregated metrics
|
|
44
|
+
- leak alerts
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🛣 Mounting the Engine
|
|
49
|
+
|
|
50
|
+
Add this to your Rails **config/routes.rb**:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
mount SidekiqInsight::Engine => "/sidekiq_insight"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then visit:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
http://localhost:3000/sidekiq_insight
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📊 What SidekiqInsight Monitors
|
|
65
|
+
|
|
66
|
+
### For every Sidekiq job run, it records:
|
|
67
|
+
|
|
68
|
+
- `started_at`
|
|
69
|
+
- `wall_ms`
|
|
70
|
+
- `cpu_ms`
|
|
71
|
+
- `rss_kb`
|
|
72
|
+
- job arguments
|
|
73
|
+
- execution count
|
|
74
|
+
|
|
75
|
+
### Aggregated metrics:
|
|
76
|
+
|
|
77
|
+
- average CPU
|
|
78
|
+
- average memory usage
|
|
79
|
+
- execution counts
|
|
80
|
+
|
|
81
|
+
### Memory leak detection:
|
|
82
|
+
|
|
83
|
+
SidekiqInsight automatically analyzes RSS trends:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
SidekiqInsight::Metrics.detect_leak(samples)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Jobs with a positive trend appear under **Leak Alerts**.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 🖥 Dashboard Pages
|
|
94
|
+
|
|
95
|
+
### **Top Jobs Metrics**
|
|
96
|
+
- `/sidekiq_insight/graphs/cpu`
|
|
97
|
+
- `/sidekiq_insight/graphs/rss`
|
|
98
|
+
- `/sidekiq_insight/graphs/wall`
|
|
99
|
+
|
|
100
|
+
Each page shows sortable metrics and Chart.js graphs.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### **Leak Alerts**
|
|
105
|
+
Lists jobs where memory leak patterns are detected.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### **Job Details**
|
|
110
|
+
View every sample recorded for a job:
|
|
111
|
+
|
|
112
|
+
- CPU chart
|
|
113
|
+
- Memory (RSS) chart
|
|
114
|
+
- Wall time chart
|
|
115
|
+
- Raw arguments (JSON)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## ⚡ Adding Middleware (Highly Recommended)
|
|
120
|
+
|
|
121
|
+
Inside `config/initializers/sidekiq.rb` or `sidekiq.yml`:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
Sidekiq.configure_server do |config|
|
|
125
|
+
config.server_middleware do |chain|
|
|
126
|
+
chain.add SidekiqInsight::ServerMiddleware
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This ensures job metrics are captured.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🔧 Utility Methods
|
|
136
|
+
|
|
137
|
+
### Clear all stored metrics:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
SidekiqInsight.storage.clear_all
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Useful during development.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 📦 Directory Structure
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
sidekiq_insight/
|
|
151
|
+
app/
|
|
152
|
+
controllers/sidekiq_insight/
|
|
153
|
+
views/sidekiq_insight/
|
|
154
|
+
lib/
|
|
155
|
+
sidekiq_insight/
|
|
156
|
+
metrics.rb
|
|
157
|
+
storage.rb
|
|
158
|
+
server_middleware.rb
|
|
159
|
+
request_middleware.rb
|
|
160
|
+
version.rb
|
|
161
|
+
sidekiq_insight.rb
|
|
162
|
+
config/routes.rb
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 🎨 Frontend
|
|
168
|
+
|
|
169
|
+
The dashboard UI uses:
|
|
170
|
+
|
|
171
|
+
- **Bootstrap 5**
|
|
172
|
+
- **Chart.js graphs**
|
|
173
|
+
- **Responsive tables/cards**
|
|
174
|
+
- **Leak alerts highlighting**
|
|
175
|
+
|
|
176
|
+
No configuration needed out of the box.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 📝 Example Output
|
|
181
|
+
|
|
182
|
+
Metrics include data like:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"started_at": "2025-02-01T10:20:30Z",
|
|
187
|
+
"wall_ms": 52.0,
|
|
188
|
+
"cpu_ms": 13.5,
|
|
189
|
+
"rss_kb": 242.1,
|
|
190
|
+
"args": ["123", true]
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## ❤️ Contributing
|
|
197
|
+
|
|
198
|
+
Pull requests are welcome!
|
|
199
|
+
Please open an issue first to discuss changes.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 📜 License
|
|
204
|
+
|
|
205
|
+
MIT License
|
|
206
|
+
|
data/Rakefile
ADDED
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SidekiqInsight
|
|
2
|
+
class DashboardController < BaseController
|
|
3
|
+
def index
|
|
4
|
+
@jobs = SidekiqInsight.storage.top_jobs(50)
|
|
5
|
+
@alerts = SidekiqInsight.detector.recent_alerts(20)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def show
|
|
9
|
+
key = params[:job]
|
|
10
|
+
@samples = SidekiqInsight.storage.recent(key, 200)
|
|
11
|
+
@leak = SidekiqInsight::Metrics.detect_leak(@samples)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def clear
|
|
15
|
+
SidekiqInsight.storage.clear_all
|
|
16
|
+
redirect_to sidekiq_insight.root_path, notice: "Cleared profiler data"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module SidekiqInsight
|
|
2
|
+
class GraphsController < BaseController
|
|
3
|
+
def cpu
|
|
4
|
+
@jobs = SidekiqInsight.storage.top_jobs(50)
|
|
5
|
+
@cpu_chart = build_series(:cpu_ms)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def rss
|
|
9
|
+
@jobs = SidekiqInsight.storage.top_jobs(50)
|
|
10
|
+
@rss_chart = build_series(:rss_kb)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def wall
|
|
14
|
+
@jobs = SidekiqInsight.storage.top_jobs(50)
|
|
15
|
+
@wall_chart = build_series(:wall_ms)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def leaks
|
|
19
|
+
@jobs = SidekiqInsight.storage.top_jobs(50)
|
|
20
|
+
@leak_jobs = @jobs.select do |j|
|
|
21
|
+
samples = SidekiqInsight.storage.recent(j[:key], 200)
|
|
22
|
+
SidekiqInsight::Metrics.detect_leak(samples)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def build_series(metric_sym)
|
|
29
|
+
data = {}
|
|
30
|
+
SidekiqInsight.storage.top_jobs(20).each do |job|
|
|
31
|
+
samples = SidekiqInsight.storage.recent(job[:key], 200)
|
|
32
|
+
series = samples.reverse.map { |s| [s[:started_at], s[metric_sym].to_f] }
|
|
33
|
+
data[job[:key]] = series
|
|
34
|
+
end
|
|
35
|
+
data
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
+
<title>Sidekiq Insight</title>
|
|
6
|
+
|
|
7
|
+
<!-- Bootstrap CDN -->
|
|
8
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
|
9
|
+
|
|
10
|
+
<!-- Chart.js (v4) + date adapter BEFORE Chartkick -->
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js"></script>
|
|
12
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
13
|
+
|
|
14
|
+
<!-- Chartkick -->
|
|
15
|
+
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
body { background: #f8f9fa; font-family: -apple-system, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial; }
|
|
19
|
+
.container { margin-top: 20px; }
|
|
20
|
+
</style>
|
|
21
|
+
<script>
|
|
22
|
+
Chartkick.use(Chart);
|
|
23
|
+
</script>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
27
|
+
<div class="container-fluid">
|
|
28
|
+
<a class="navbar-brand" href="<%= sidekiq_insight.root_path %>">Sidekiq Insight</a>
|
|
29
|
+
<div class="collapse navbar-collapse">
|
|
30
|
+
<ul class="navbar-nav me-auto">
|
|
31
|
+
<li class="nav-item"><a class="nav-link" href="<%= sidekiq_insight.root_path %>">Overview</a></li>
|
|
32
|
+
<li class="nav-item"><a class="nav-link" href="<%= sidekiq_insight.cpu_path %>">CPU</a></li>
|
|
33
|
+
<li class="nav-item"><a class="nav-link" href="<%= sidekiq_insight.rss_path %>">Memory</a></li>
|
|
34
|
+
<li class="nav-item"><a class="nav-link" href="<%= sidekiq_insight.leaks_path %>">Leak Detector</a></li>
|
|
35
|
+
</ul>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</nav>
|
|
39
|
+
|
|
40
|
+
<div class="container">
|
|
41
|
+
<% if flash[:notice] %>
|
|
42
|
+
<div class="alert alert-success"><%= flash[:notice] %></div>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<%= yield %>
|
|
46
|
+
</div>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<div class="container py-4">
|
|
2
|
+
|
|
3
|
+
<div class="row g-4">
|
|
4
|
+
|
|
5
|
+
<!-- ================= TOP JOBS ================= -->
|
|
6
|
+
<div class="col-md-8">
|
|
7
|
+
<div class="card shadow-sm h-100">
|
|
8
|
+
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
|
9
|
+
<h4 class="mb-0">Top Jobs by Avg CPU</h4>
|
|
10
|
+
<span class="badge bg-light text-primary">
|
|
11
|
+
<%= @jobs.count %> jobs
|
|
12
|
+
</span>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="card-body p-0">
|
|
16
|
+
<table class="table table-hover table-striped mb-0">
|
|
17
|
+
<thead class="table-light">
|
|
18
|
+
<tr>
|
|
19
|
+
<th>Job</th>
|
|
20
|
+
<th>Count</th>
|
|
21
|
+
<th>Avg CPU (ms)</th>
|
|
22
|
+
<th>Avg Mem (KB)</th>
|
|
23
|
+
<th></th>
|
|
24
|
+
</tr>
|
|
25
|
+
</thead>
|
|
26
|
+
|
|
27
|
+
<tbody>
|
|
28
|
+
<% @jobs.each do |j| %>
|
|
29
|
+
<tr>
|
|
30
|
+
<td class="fw-semibold"><%= j[:key] %></td>
|
|
31
|
+
<td><%= j[:count] %></td>
|
|
32
|
+
<td>
|
|
33
|
+
<span class="badge bg-info text-dark">
|
|
34
|
+
<%= number_with_precision(j[:avg_cpu_ms], precision: 2) %>
|
|
35
|
+
</span>
|
|
36
|
+
</td>
|
|
37
|
+
<td>
|
|
38
|
+
<span class="badge bg-success">
|
|
39
|
+
<%= number_with_precision(j[:avg_mem_kb], precision: 2) %>
|
|
40
|
+
</span>
|
|
41
|
+
</td>
|
|
42
|
+
<td>
|
|
43
|
+
<a class="btn btn-sm btn-outline-primary"
|
|
44
|
+
href="<%= sidekiq_insight.job_path(job: j[:key]) %>">
|
|
45
|
+
View
|
|
46
|
+
</a>
|
|
47
|
+
</td>
|
|
48
|
+
</tr>
|
|
49
|
+
<% end %>
|
|
50
|
+
</tbody>
|
|
51
|
+
|
|
52
|
+
</table>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- ================= LEAK ALERTS ================= -->
|
|
58
|
+
<div class="col-md-4">
|
|
59
|
+
<div class="card shadow-sm h-100">
|
|
60
|
+
<div class="card-header bg-danger text-white">
|
|
61
|
+
<h4 class="mb-0">Leak Alerts</h4>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="card-body">
|
|
65
|
+
<% if @alerts.present? %>
|
|
66
|
+
<ul class="list-group">
|
|
67
|
+
<% @alerts.each do |a| %>
|
|
68
|
+
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
69
|
+
<strong><%= a[:job] %></strong>
|
|
70
|
+
<span class="badge bg-danger">Leak suspected</span>
|
|
71
|
+
</li>
|
|
72
|
+
<% end %>
|
|
73
|
+
</ul>
|
|
74
|
+
<% else %>
|
|
75
|
+
<div class="text-center text-muted py-3">
|
|
76
|
+
No recent leak alerts 🔍
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<div class="container py-4">
|
|
2
|
+
|
|
3
|
+
<!-- Page Header -->
|
|
4
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
5
|
+
<h2 class="fw-bold text-primary">
|
|
6
|
+
Samples for <%= params[:job] %>
|
|
7
|
+
</h2>
|
|
8
|
+
|
|
9
|
+
<% if @leak %>
|
|
10
|
+
<span class="badge bg-danger fs-6 px-3 py-2 shadow-sm">Memory Leak Detected</span>
|
|
11
|
+
<% else %>
|
|
12
|
+
<span class="badge bg-success fs-6 px-3 py-2 shadow-sm">No Leak</span>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Charts Section -->
|
|
17
|
+
<div class="row g-4">
|
|
18
|
+
|
|
19
|
+
<!-- WALL TIME -->
|
|
20
|
+
<div class="col-md-4">
|
|
21
|
+
<div class="card shadow-sm h-100">
|
|
22
|
+
<div class="card-header bg-primary text-white">Wall Time (ms)</div>
|
|
23
|
+
<div class="card-body">
|
|
24
|
+
<%= line_chart @samples.reverse.map { |s| [s[:started_at], s[:wall_ms].to_f] },
|
|
25
|
+
library: { maintainAspectRatio: false } %>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- CPU -->
|
|
31
|
+
<div class="col-md-4">
|
|
32
|
+
<div class="card shadow-sm h-100">
|
|
33
|
+
<div class="card-header bg-info text-white">CPU (ms)</div>
|
|
34
|
+
<div class="card-body">
|
|
35
|
+
<%= line_chart @samples.reverse.map { |s| [s[:started_at], s[:cpu_ms].to_f] } %>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- RSS MEMORY -->
|
|
41
|
+
<div class="col-md-4">
|
|
42
|
+
<div class="card shadow-sm h-100">
|
|
43
|
+
<div class="card-header bg-success text-white">RSS Delta (KB)</div>
|
|
44
|
+
<div class="card-body">
|
|
45
|
+
<%= line_chart @samples.reverse.map { |s| [s[:started_at], s[:rss_kb].to_f] } %>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- Raw Data Table -->
|
|
53
|
+
<div class="card mt-5 shadow-sm">
|
|
54
|
+
<div class="card-header bg-dark text-white">
|
|
55
|
+
Sample Details
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="card-body p-0">
|
|
59
|
+
<table class="table table-striped table-bordered mb-0">
|
|
60
|
+
<thead class="table-light">
|
|
61
|
+
<tr>
|
|
62
|
+
<th>Started</th>
|
|
63
|
+
<th>Wall (ms)</th>
|
|
64
|
+
<th>CPU (ms)</th>
|
|
65
|
+
<th>RSS (KB)</th>
|
|
66
|
+
<th>Args</th>
|
|
67
|
+
</tr>
|
|
68
|
+
</thead>
|
|
69
|
+
|
|
70
|
+
<tbody>
|
|
71
|
+
<% @samples.each do |s| %>
|
|
72
|
+
<tr>
|
|
73
|
+
<td class="text-nowrap"><%= s[:started_at] %></td>
|
|
74
|
+
<td><%= s[:wall_ms] %></td>
|
|
75
|
+
<td><%= s[:cpu_ms] %></td>
|
|
76
|
+
<td><%= s[:rss_kb] %></td>
|
|
77
|
+
<td style="max-width:300px;">
|
|
78
|
+
<pre class="small bg-light p-2 rounded"><%= JSON.pretty_generate(s[:args]) rescue s[:args] %></pre>
|
|
79
|
+
</td>
|
|
80
|
+
</tr>
|
|
81
|
+
<% end %>
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<div class="card mb-4 shadow-sm">
|
|
2
|
+
<div class="card-header bg-primary text-white">
|
|
3
|
+
<h5 class="mb-0"><%= title %></h5>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="card-body">
|
|
7
|
+
<canvas id="<%= dom_id %>" height="120"></canvas>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
13
|
+
const ctx = document.getElementById("<%= dom_id %>").getContext("2d");
|
|
14
|
+
|
|
15
|
+
const datasets = [
|
|
16
|
+
<% data.each do |job_key, series| %>
|
|
17
|
+
{
|
|
18
|
+
label: "<%= job_key %>",
|
|
19
|
+
data: <%= series.map { |t, v| { x: t, y: v } }.to_json %>,
|
|
20
|
+
borderWidth: 2,
|
|
21
|
+
tension: 0.3,
|
|
22
|
+
},
|
|
23
|
+
<% end %>
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
new Chart(ctx, {
|
|
27
|
+
type: 'line',
|
|
28
|
+
data: { datasets: datasets },
|
|
29
|
+
options: {
|
|
30
|
+
responsive: true,
|
|
31
|
+
parsing: false,
|
|
32
|
+
plugins: { legend: { position: "bottom" } },
|
|
33
|
+
scales: {
|
|
34
|
+
x: { type: 'time', time: { unit: 'minute' } },
|
|
35
|
+
y: { beginAtZero: true }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<div class="container py-4">
|
|
2
|
+
<h2 class="mb-4 text-primary fw-bold">CPU Usage — Top Jobs</h2>
|
|
3
|
+
|
|
4
|
+
<div class="card mb-4 shadow-sm">
|
|
5
|
+
<div class="card-header bg-dark text-white">
|
|
6
|
+
Job Statistics
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="card-body p-0">
|
|
10
|
+
<table class="table table-striped mb-0">
|
|
11
|
+
<thead class="table-light">
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Job</th>
|
|
14
|
+
<th>Average CPU (ms)</th>
|
|
15
|
+
<th>Runs</th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
<% @jobs.each do |job| %>
|
|
20
|
+
<tr>
|
|
21
|
+
<td><strong><%= job[:key] %></strong></td>
|
|
22
|
+
<td><%= job[:avg_cpu_ms].round(2) %></td>
|
|
23
|
+
<td><%= job[:count] %></td>
|
|
24
|
+
</tr>
|
|
25
|
+
<% end %>
|
|
26
|
+
</tbody>
|
|
27
|
+
</table>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<%= render partial: "chart",
|
|
32
|
+
locals: { title: "CPU Time (ms)", dom_id: "cpu_chart", data: @cpu_chart } %>
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div class="container py-4">
|
|
2
|
+
<h2 class="mb-4 text-danger fw-bold">Memory Leak Detector</h2>
|
|
3
|
+
|
|
4
|
+
<% if @leak_jobs.empty? %>
|
|
5
|
+
<div class="alert alert-success shadow-sm">
|
|
6
|
+
No leaks detected 🎉
|
|
7
|
+
</div>
|
|
8
|
+
<% else %>
|
|
9
|
+
<div class="card shadow-sm">
|
|
10
|
+
<div class="card-header bg-danger text-white">
|
|
11
|
+
Leaking Jobs
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="card-body p-0">
|
|
15
|
+
<table class="table table-striped mb-0">
|
|
16
|
+
<thead class="table-light">
|
|
17
|
+
<tr>
|
|
18
|
+
<th>Job</th>
|
|
19
|
+
<th>Avg RSS (KB)</th>
|
|
20
|
+
<th>Avg CPU (ms)</th>
|
|
21
|
+
<th>Runs</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
<% @leak_jobs.each do |job| %>
|
|
26
|
+
<tr class="table-danger">
|
|
27
|
+
<td><strong><%= job[:key] %></strong></td>
|
|
28
|
+
<td><%= job[:avg_mem_kb].round(2) %></td>
|
|
29
|
+
<td><%= job[:avg_cpu_ms].round(2) %></td>
|
|
30
|
+
<td><%= job[:count] %></td>
|
|
31
|
+
</tr>
|
|
32
|
+
<% end %>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<div class="container py-4">
|
|
2
|
+
|
|
3
|
+
<h2 class="mb-4 text-success fw-bold">
|
|
4
|
+
RSS Memory Usage — Top Jobs
|
|
5
|
+
</h2>
|
|
6
|
+
|
|
7
|
+
<!-- Jobs Table -->
|
|
8
|
+
<div class="card mb-4 shadow-sm">
|
|
9
|
+
<div class="card-header bg-success text-white">
|
|
10
|
+
Job Memory Summary
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="card-body p-0">
|
|
14
|
+
<table class="table table-hover table-striped mb-0">
|
|
15
|
+
<thead class="table-light">
|
|
16
|
+
<tr>
|
|
17
|
+
<th>Job</th>
|
|
18
|
+
<th>Average RSS (KB)</th>
|
|
19
|
+
<th>Runs</th>
|
|
20
|
+
</tr>
|
|
21
|
+
</thead>
|
|
22
|
+
|
|
23
|
+
<tbody>
|
|
24
|
+
<% @jobs.each do |job| %>
|
|
25
|
+
<tr>
|
|
26
|
+
<td><strong><%= job[:key] %></strong></td>
|
|
27
|
+
<td><%= job[:avg_mem_kb].round(2) %></td>
|
|
28
|
+
<td><%= job[:count] %></td>
|
|
29
|
+
</tr>
|
|
30
|
+
<% end %>
|
|
31
|
+
</tbody>
|
|
32
|
+
</table>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Chart -->
|
|
37
|
+
<%= render partial: "chart",
|
|
38
|
+
locals: { title: "RSS Memory (KB)",
|
|
39
|
+
dom_id: "rss_chart",
|
|
40
|
+
data: @rss_chart } %>
|
|
41
|
+
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<div class="container py-4">
|
|
2
|
+
<h2 class="mb-4 text-info fw-bold">Wall Time</h2>
|
|
3
|
+
|
|
4
|
+
<div class="card mb-3 shadow-sm">
|
|
5
|
+
<div class="card-header bg-info text-white">
|
|
6
|
+
Job Wall Time Summary
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="card-body p-0">
|
|
10
|
+
<table class="table table-bordered mb-0">
|
|
11
|
+
<thead class="table-light">
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Job</th>
|
|
14
|
+
<th>Runs</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody>
|
|
18
|
+
<% @jobs.each do |job| %>
|
|
19
|
+
<tr>
|
|
20
|
+
<td><strong><%= job[:key] %></strong></td>
|
|
21
|
+
<td><%= job[:count] %></td>
|
|
22
|
+
</tr>
|
|
23
|
+
<% end %>
|
|
24
|
+
</tbody>
|
|
25
|
+
</table>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<%= render partial: "chart",
|
|
30
|
+
locals: { title: "Wall Time (ms)", dom_id: "wall_chart", data: @wall_chart } %>
|
|
31
|
+
</div>
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "sidekiq_insight"
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require "irb"
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config/routes.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
SidekiqInsight::Engine.routes.draw do
|
|
2
|
+
root to: "dashboard#index"
|
|
3
|
+
get "/job", to: "dashboard#show", as: :job
|
|
4
|
+
post "/clear", to: "dashboard#clear", as: :clear
|
|
5
|
+
|
|
6
|
+
# Graphs
|
|
7
|
+
get "/cpu", to: "graphs#cpu", as: :cpu
|
|
8
|
+
get "/rss", to: "graphs#rss", as: :rss
|
|
9
|
+
get "/wall", to: "graphs#wall", as: :wall
|
|
10
|
+
get "/leaks", to: "graphs#leaks", as: :leaks
|
|
11
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'rails'
|
|
2
|
+
require "chartkick"
|
|
3
|
+
require "groupdate"
|
|
4
|
+
|
|
5
|
+
module SidekiqInsight
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
isolate_namespace SidekiqInsight
|
|
8
|
+
|
|
9
|
+
initializer 'sidekiq_insight.insert_middlewares' do |app|
|
|
10
|
+
# insert request profiling into Rails middleware stack
|
|
11
|
+
app.middleware.insert_before(0, SidekiqInsight::RequestMiddleware)
|
|
12
|
+
|
|
13
|
+
# configure Sidekiq server middleware
|
|
14
|
+
if defined?(Sidekiq)
|
|
15
|
+
Sidekiq.configure_server do |config|
|
|
16
|
+
config.server_middleware do |chain|
|
|
17
|
+
chain.add SidekiqInsight::ServerMiddleware
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
config.autoload_paths << root.join("app/helpers")
|
|
24
|
+
config.autoload_paths << root.join("lib/sidekiq_insight")
|
|
25
|
+
|
|
26
|
+
initializer "sidekiq_insight.assets" do |app|
|
|
27
|
+
app.config.assets.precompile += %w[
|
|
28
|
+
sidekiq_insight.js
|
|
29
|
+
chart.js
|
|
30
|
+
chartjs-adapter-date-fns.js
|
|
31
|
+
chartkick.js
|
|
32
|
+
]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SidekiqInsight
|
|
2
|
+
class LeakDetector
|
|
3
|
+
def initialize(storage:)
|
|
4
|
+
@storage = storage
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# check last N samples for rising memory trend
|
|
8
|
+
def detect(key, window = 20)
|
|
9
|
+
samples = @storage.recent(key, window)
|
|
10
|
+
SidekiqInsight::Metrics.detect_leak(samples)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def recent_alerts(limit = 20)
|
|
14
|
+
jobs = @storage.top_jobs(100)
|
|
15
|
+
alerts = []
|
|
16
|
+
jobs.each do |j|
|
|
17
|
+
leak = detect(j[:key], 50)
|
|
18
|
+
alerts << { job: j[:key], leak: leak } if leak
|
|
19
|
+
break if alerts.size >= limit
|
|
20
|
+
end
|
|
21
|
+
alerts
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module SidekiqInsight
|
|
2
|
+
module Metrics
|
|
3
|
+
def self.detect_leak(samples)
|
|
4
|
+
return false if samples.nil? || samples.size < 6
|
|
5
|
+
# use first half vs last half
|
|
6
|
+
half = samples.size / 2
|
|
7
|
+
first = samples[0, half].map { |s| s[:rss_kb].to_f }
|
|
8
|
+
last = samples[half, half].map { |s| s[:rss_kb].to_f }
|
|
9
|
+
return false if first.empty? || last.empty?
|
|
10
|
+
first_avg = first.sum / first.size
|
|
11
|
+
last_avg = last.sum / last.size
|
|
12
|
+
(last_avg - first_avg) > (first_avg * 0.2) # >20% increase indicates suspect
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "get_process_mem"
|
|
2
|
+
|
|
3
|
+
module SidekiqInsight
|
|
4
|
+
class RequestMiddleware
|
|
5
|
+
def initialize(app)
|
|
6
|
+
@app = app
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call(env)
|
|
10
|
+
pm_before = GetProcessMem.new
|
|
11
|
+
rss_before = pm_before.mb * 1024.0
|
|
12
|
+
cpu_before = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
|
|
13
|
+
t_before = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
14
|
+
|
|
15
|
+
status, headers, body = @app.call(env)
|
|
16
|
+
|
|
17
|
+
t_after = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
18
|
+
cpu_after = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
|
|
19
|
+
pm_after = GetProcessMem.new
|
|
20
|
+
rss_after = pm_after.mb * 1024.0
|
|
21
|
+
|
|
22
|
+
req = Rack::Request.new(env)
|
|
23
|
+
sample = {
|
|
24
|
+
path: req.path,
|
|
25
|
+
method: req.request_method,
|
|
26
|
+
status: status,
|
|
27
|
+
started_at: Time.now.utc.iso8601,
|
|
28
|
+
wall_ms: (t_after - t_before) * 1000.0,
|
|
29
|
+
cpu_ms: (cpu_after - cpu_before) * 1000.0,
|
|
30
|
+
rss_kb: (rss_after - rss_before)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
SidekiqInsight.storage.push_sample("__http__#{req.path}", sample)
|
|
34
|
+
|
|
35
|
+
[status, headers, body]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "get_process_mem"
|
|
2
|
+
|
|
3
|
+
module SidekiqInsight
|
|
4
|
+
class ServerMiddleware
|
|
5
|
+
def call(worker, job, queue)
|
|
6
|
+
pm_before = GetProcessMem.new
|
|
7
|
+
rss_before = pm_before.mb * 1024.0
|
|
8
|
+
cpu_before = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
|
|
9
|
+
t_before = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
10
|
+
|
|
11
|
+
yield
|
|
12
|
+
|
|
13
|
+
t_after = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
14
|
+
cpu_after = Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
|
|
15
|
+
pm_after = GetProcessMem.new
|
|
16
|
+
rss_after = pm_after.mb * 1024.0
|
|
17
|
+
|
|
18
|
+
sample = {
|
|
19
|
+
job_class: worker.class.name,
|
|
20
|
+
queue: queue,
|
|
21
|
+
args: job["args"],
|
|
22
|
+
started_at: Time.now.utc.iso8601,
|
|
23
|
+
wall_ms: (t_after - t_before) * 1000.0,
|
|
24
|
+
cpu_ms: (cpu_after - cpu_before) * 1000.0,
|
|
25
|
+
rss_kb: (rss_after - rss_before)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
SidekiqInsight.storage.push_sample(worker.class.name, sample)
|
|
29
|
+
rescue => e
|
|
30
|
+
raise
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module SidekiqInsight
|
|
4
|
+
class Storage
|
|
5
|
+
def initialize(redis:, prefix: "sidekiq_insight:")
|
|
6
|
+
@redis = redis
|
|
7
|
+
@prefix = prefix
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def push_sample(key, sample)
|
|
11
|
+
redis_key = namespaced("samples:#{key}")
|
|
12
|
+
@redis.lpush(redis_key, sample.to_json)
|
|
13
|
+
@redis.ltrim(redis_key, 0, 999)
|
|
14
|
+
@redis.hincrby(namespaced("counts"), key, 1)
|
|
15
|
+
@redis.hincrbyfloat(namespaced("cpu"), key, sample[:cpu_ms].to_f)
|
|
16
|
+
@redis.hincrbyfloat(namespaced("mem"), key, sample[:rss_kb].to_f)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def top_jobs(limit = 20)
|
|
20
|
+
counts = @redis.hgetall(namespaced("counts"))
|
|
21
|
+
return [] if counts.empty?
|
|
22
|
+
counts.map do |k,v|
|
|
23
|
+
c = v.to_i
|
|
24
|
+
{ key: k, count: c, avg_cpu_ms: @redis.hget(namespaced("cpu"), k).to_f / [c,1].max, avg_mem_kb: @redis.hget(namespaced("mem"), k).to_f / [c,1].max }
|
|
25
|
+
end.sort_by { |h| -h[:avg_cpu_ms] }[0, limit]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def recent(key, limit = 100)
|
|
29
|
+
arr = @redis.lrange(namespaced("samples:#{key}"), 0, limit-1)
|
|
30
|
+
arr.map { |j| JSON.parse(j, symbolize_names: true) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def clear_all
|
|
34
|
+
keys = @redis.keys("#{ @prefix }*")
|
|
35
|
+
@redis.del(*keys) if keys.any?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def namespaced(suffix)
|
|
41
|
+
"#{@prefix}#{suffix}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sidekiq_insight/version"
|
|
4
|
+
require "sidekiq_insight/metrics"
|
|
5
|
+
require "sidekiq_insight/configuration"
|
|
6
|
+
require "sidekiq_insight/storage"
|
|
7
|
+
require "sidekiq_insight/server_middleware"
|
|
8
|
+
require "sidekiq_insight/request_middleware"
|
|
9
|
+
require "sidekiq_insight/leak_detector"
|
|
10
|
+
require "sidekiq_insight/engine" if defined?(Rails)
|
|
11
|
+
|
|
12
|
+
module SidekiqInsight
|
|
13
|
+
class << self
|
|
14
|
+
attr_accessor :configuration
|
|
15
|
+
|
|
16
|
+
def configure
|
|
17
|
+
self.configuration ||= Configuration.new
|
|
18
|
+
yield(configuration) if block_given?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def redis
|
|
22
|
+
@redis ||= Redis.new(url: configuration&.redis_url || ENV['REDIS_URL'] || 'redis://127.0.0.1:6379/0')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def storage
|
|
26
|
+
@storage ||= Storage.new(redis: redis, prefix: configuration&.prefix || 'sidekiq_insight:')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def detector
|
|
30
|
+
@detector ||= LeakDetector.new(storage: storage)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sidekiq_insight
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- mrmalvi
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sidekiq
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: redis
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: get_process_mem
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: chartkick
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: groupdate
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rails
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
description: SidekiqInsight provides detailed CPU, memory, and wall-time profiling
|
|
97
|
+
for Sidekiq jobs and Rails requests, automatically detects memory leaks, stores
|
|
98
|
+
metrics in Redis, and displays everything in a clean, modern dashboard. Ideal for
|
|
99
|
+
monitoring production workload performance.
|
|
100
|
+
email:
|
|
101
|
+
- malviyak00@gmail.com
|
|
102
|
+
executables: []
|
|
103
|
+
extensions: []
|
|
104
|
+
extra_rdoc_files: []
|
|
105
|
+
files:
|
|
106
|
+
- Gemfile
|
|
107
|
+
- LICENSE.txt
|
|
108
|
+
- README.md
|
|
109
|
+
- Rakefile
|
|
110
|
+
- app/assets/javascripts/sidekiq_insight/sidekiq_insight.js
|
|
111
|
+
- app/controllers/sidekiq_insight/base_controller.rb
|
|
112
|
+
- app/controllers/sidekiq_insight/dashboard_controller.rb
|
|
113
|
+
- app/controllers/sidekiq_insight/graphs_controller.rb
|
|
114
|
+
- app/views/layouts/sidekiq_insight/sidekiq_insight.html.erb
|
|
115
|
+
- app/views/sidekiq_insight/dashboard/index.html.erb
|
|
116
|
+
- app/views/sidekiq_insight/dashboard/show.html.erb
|
|
117
|
+
- app/views/sidekiq_insight/graphs/_chart.html.erb
|
|
118
|
+
- app/views/sidekiq_insight/graphs/cpu.html.erb
|
|
119
|
+
- app/views/sidekiq_insight/graphs/leaks.html.erb
|
|
120
|
+
- app/views/sidekiq_insight/graphs/rss.html.erb
|
|
121
|
+
- app/views/sidekiq_insight/graphs/wall.html.erb
|
|
122
|
+
- bin/console
|
|
123
|
+
- bin/setup
|
|
124
|
+
- config/routes.rb
|
|
125
|
+
- lib/sidekiq_insight.rb
|
|
126
|
+
- lib/sidekiq_insight/configuration.rb
|
|
127
|
+
- lib/sidekiq_insight/engine.rb
|
|
128
|
+
- lib/sidekiq_insight/leak_detector.rb
|
|
129
|
+
- lib/sidekiq_insight/metrics.rb
|
|
130
|
+
- lib/sidekiq_insight/request_middleware.rb
|
|
131
|
+
- lib/sidekiq_insight/server_middleware.rb
|
|
132
|
+
- lib/sidekiq_insight/storage.rb
|
|
133
|
+
- lib/sidekiq_insight/version.rb
|
|
134
|
+
- sig/sidekiq_insight.rbs
|
|
135
|
+
homepage: https://github.com/mrmalvi/sidekiq_insight
|
|
136
|
+
licenses: []
|
|
137
|
+
metadata:
|
|
138
|
+
homepage_uri: https://github.com/mrmalvi/sidekiq_insight
|
|
139
|
+
allowed_push_host: https://rubygems.org
|
|
140
|
+
rdoc_options: []
|
|
141
|
+
require_paths:
|
|
142
|
+
- lib
|
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
|
+
requirements:
|
|
145
|
+
- - ">="
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
version: 2.6.0
|
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
requirements: []
|
|
154
|
+
rubygems_version: 3.6.9
|
|
155
|
+
specification_version: 4
|
|
156
|
+
summary: Lightweight Sidekiq & Rails performance profiler with CPU, memory, wall-time
|
|
157
|
+
tracking, leak detection, and a real-time dashboard.
|
|
158
|
+
test_files: []
|