sidekiq 6.5.1 → 6.5.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Changes.md +28 -0
- data/lib/sidekiq/api.rb +126 -22
- data/lib/sidekiq/cli.rb +1 -0
- data/lib/sidekiq/component.rb +2 -1
- data/lib/sidekiq/job_retry.rb +55 -35
- data/lib/sidekiq/launcher.rb +4 -2
- data/lib/sidekiq/metrics/deploy.rb +47 -0
- data/lib/sidekiq/metrics/query.rb +153 -0
- data/lib/sidekiq/metrics/shared.rb +94 -0
- data/lib/sidekiq/metrics/tracking.rb +134 -0
- data/lib/sidekiq/middleware/chain.rb +70 -35
- data/lib/sidekiq/middleware/current_attributes.rb +6 -4
- data/lib/sidekiq/processor.rb +9 -3
- data/lib/sidekiq/scheduled.rb +1 -7
- data/lib/sidekiq/version.rb +1 -1
- data/lib/sidekiq/web/action.rb +3 -3
- data/lib/sidekiq/web/application.rb +18 -5
- data/lib/sidekiq/web/helpers.rb +24 -1
- data/lib/sidekiq/web.rb +5 -1
- data/lib/sidekiq.rb +9 -1
- data/sidekiq.gemspec +1 -1
- data/web/assets/javascripts/application.js +1 -1
- data/web/assets/javascripts/chart.min.js +13 -0
- data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
- data/web/assets/javascripts/dashboard.js +0 -17
- data/web/assets/javascripts/graph.js +16 -0
- data/web/assets/javascripts/metrics.js +262 -0
- data/web/assets/stylesheets/application.css +44 -1
- data/web/locales/el.yml +43 -19
- data/web/locales/en.yml +7 -0
- data/web/views/_nav.erb +1 -1
- data/web/views/busy.erb +1 -1
- data/web/views/dashboard.erb +1 -0
- data/web/views/metrics.erb +69 -0
- data/web/views/metrics_for_job.erb +87 -0
- data/web/views/queue.erb +5 -1
- metadata +14 -5
- data/lib/sidekiq/.DS_Store +0 -0
@@ -0,0 +1,262 @@
|
|
1
|
+
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
2
|
+
Chart.defaults.borderColor = "#333"
|
3
|
+
Chart.defaults.color = "#aaa"
|
4
|
+
}
|
5
|
+
|
6
|
+
class BaseChart {
|
7
|
+
constructor(id, options) {
|
8
|
+
this.ctx = document.getElementById(id);
|
9
|
+
this.options = options
|
10
|
+
this.fallbackColor = "#999";
|
11
|
+
this.colors = [
|
12
|
+
// Colors taken from https://www.chartjs.org/docs/latest/samples/utils.html
|
13
|
+
"#537bc4",
|
14
|
+
"#4dc9f6",
|
15
|
+
"#f67019",
|
16
|
+
"#f53794",
|
17
|
+
"#acc236",
|
18
|
+
"#166a8f",
|
19
|
+
"#00a950",
|
20
|
+
"#58595b",
|
21
|
+
"#8549ba",
|
22
|
+
"#991b1b",
|
23
|
+
];
|
24
|
+
|
25
|
+
this.chart = new Chart(this.ctx, {
|
26
|
+
type: this.options.chartType,
|
27
|
+
data: { labels: this.options.labels, datasets: this.datasets },
|
28
|
+
options: this.chartOptions,
|
29
|
+
});
|
30
|
+
}
|
31
|
+
|
32
|
+
addMarksToChart() {
|
33
|
+
this.options.marks.forEach(([bucket, label], i) => {
|
34
|
+
this.chart.options.plugins.annotation.annotations[`deploy-${i}`] = {
|
35
|
+
type: "line",
|
36
|
+
xMin: bucket,
|
37
|
+
xMax: bucket,
|
38
|
+
borderColor: "rgba(220, 38, 38, 0.4)",
|
39
|
+
borderWidth: 2,
|
40
|
+
};
|
41
|
+
});
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
class JobMetricsOverviewChart extends BaseChart {
|
46
|
+
constructor(id, options) {
|
47
|
+
super(id, { ...options, chartType: "line" });
|
48
|
+
this.swatches = [];
|
49
|
+
|
50
|
+
this.addMarksToChart();
|
51
|
+
this.chart.update();
|
52
|
+
}
|
53
|
+
|
54
|
+
registerSwatch(id) {
|
55
|
+
const el = document.getElementById(id);
|
56
|
+
el.onchange = () => this.toggle(el.value, el.checked);
|
57
|
+
this.swatches[el.value] = el;
|
58
|
+
this.updateSwatch(el.value);
|
59
|
+
}
|
60
|
+
|
61
|
+
updateSwatch(kls) {
|
62
|
+
const el = this.swatches[kls];
|
63
|
+
const ds = this.chart.data.datasets.find((ds) => ds.label == kls);
|
64
|
+
el.checked = !!ds;
|
65
|
+
el.style.color = ds ? ds.borderColor : null;
|
66
|
+
}
|
67
|
+
|
68
|
+
toggle(kls, visible) {
|
69
|
+
if (visible) {
|
70
|
+
this.chart.data.datasets.push(this.dataset(kls));
|
71
|
+
} else {
|
72
|
+
const i = this.chart.data.datasets.findIndex((ds) => ds.label == kls);
|
73
|
+
this.colors.unshift(this.chart.data.datasets[i].borderColor);
|
74
|
+
this.chart.data.datasets.splice(i, 1);
|
75
|
+
}
|
76
|
+
|
77
|
+
this.updateSwatch(kls);
|
78
|
+
this.chart.update();
|
79
|
+
}
|
80
|
+
|
81
|
+
dataset(kls) {
|
82
|
+
const color = this.colors.shift() || this.fallbackColor;
|
83
|
+
|
84
|
+
return {
|
85
|
+
label: kls,
|
86
|
+
data: this.options.series[kls],
|
87
|
+
borderColor: color,
|
88
|
+
backgroundColor: color,
|
89
|
+
borderWidth: 2,
|
90
|
+
pointRadius: 2,
|
91
|
+
};
|
92
|
+
}
|
93
|
+
|
94
|
+
get datasets() {
|
95
|
+
return Object.entries(this.options.series)
|
96
|
+
.filter(([kls, _]) => this.options.visible.includes(kls))
|
97
|
+
.map(([kls, _]) => this.dataset(kls));
|
98
|
+
}
|
99
|
+
|
100
|
+
get chartOptions() {
|
101
|
+
return {
|
102
|
+
aspectRatio: 4,
|
103
|
+
scales: {
|
104
|
+
y: {
|
105
|
+
beginAtZero: true,
|
106
|
+
title: {
|
107
|
+
text: "Total execution time (sec)",
|
108
|
+
display: true,
|
109
|
+
},
|
110
|
+
},
|
111
|
+
},
|
112
|
+
interaction: {
|
113
|
+
mode: "x",
|
114
|
+
},
|
115
|
+
plugins: {
|
116
|
+
legend: {
|
117
|
+
display: false,
|
118
|
+
},
|
119
|
+
tooltip: {
|
120
|
+
callbacks: {
|
121
|
+
title: (items) => `${items[0].label} UTC`,
|
122
|
+
label: (item) =>
|
123
|
+
`${item.dataset.label}: ${item.parsed.y.toFixed(1)} seconds`,
|
124
|
+
footer: (items) => {
|
125
|
+
const bucket = items[0].label;
|
126
|
+
const marks = this.options.marks.filter(([b, _]) => b == bucket);
|
127
|
+
return marks.map(([b, msg]) => `Deploy: ${msg}`);
|
128
|
+
},
|
129
|
+
},
|
130
|
+
},
|
131
|
+
},
|
132
|
+
};
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
class HistTotalsChart extends BaseChart {
|
137
|
+
constructor(id, options) {
|
138
|
+
super(id, { ...options, chartType: "bar" });
|
139
|
+
}
|
140
|
+
|
141
|
+
get datasets() {
|
142
|
+
return [{
|
143
|
+
data: this.options.series,
|
144
|
+
backgroundColor: this.colors[0],
|
145
|
+
borderWidth: 0,
|
146
|
+
}];
|
147
|
+
}
|
148
|
+
|
149
|
+
get chartOptions() {
|
150
|
+
return {
|
151
|
+
aspectRatio: 6,
|
152
|
+
scales: {
|
153
|
+
y: {
|
154
|
+
beginAtZero: true,
|
155
|
+
title: {
|
156
|
+
text: "Total jobs",
|
157
|
+
display: true,
|
158
|
+
},
|
159
|
+
},
|
160
|
+
},
|
161
|
+
interaction: {
|
162
|
+
mode: "x",
|
163
|
+
},
|
164
|
+
plugins: {
|
165
|
+
legend: {
|
166
|
+
display: false,
|
167
|
+
},
|
168
|
+
tooltip: {
|
169
|
+
callbacks: {
|
170
|
+
label: (item) => `${item.parsed.y} jobs`,
|
171
|
+
},
|
172
|
+
},
|
173
|
+
},
|
174
|
+
};
|
175
|
+
}
|
176
|
+
}
|
177
|
+
|
178
|
+
class HistBubbleChart extends BaseChart {
|
179
|
+
constructor(id, options) {
|
180
|
+
super(id, { ...options, chartType: "bubble" });
|
181
|
+
|
182
|
+
this.addMarksToChart();
|
183
|
+
this.chart.update();
|
184
|
+
}
|
185
|
+
|
186
|
+
get datasets() {
|
187
|
+
const data = [];
|
188
|
+
let maxCount = 0;
|
189
|
+
|
190
|
+
Object.entries(this.options.hist).forEach(([bucket, hist]) => {
|
191
|
+
hist.forEach((count, histBucket) => {
|
192
|
+
if (count > 0) {
|
193
|
+
data.push({
|
194
|
+
x: bucket,
|
195
|
+
// histogram data is ordered fastest to slowest, but this.histIntervals is
|
196
|
+
// slowest to fastest (so it displays correctly on the chart).
|
197
|
+
y:
|
198
|
+
this.options.histIntervals[this.options.histIntervals.length - 1 - histBucket] /
|
199
|
+
1000,
|
200
|
+
count: count,
|
201
|
+
});
|
202
|
+
|
203
|
+
if (count > maxCount) maxCount = count;
|
204
|
+
}
|
205
|
+
});
|
206
|
+
});
|
207
|
+
|
208
|
+
// Chart.js will not calculate the bubble size. We have to do that.
|
209
|
+
const maxRadius = this.ctx.offsetWidth / this.options.labels.length;
|
210
|
+
const minRadius = 1
|
211
|
+
const multiplier = (maxRadius / maxCount) * 1.5;
|
212
|
+
data.forEach((entry) => {
|
213
|
+
entry.r = entry.count * multiplier + minRadius;
|
214
|
+
});
|
215
|
+
|
216
|
+
return [{
|
217
|
+
data: data,
|
218
|
+
backgroundColor: "#537bc4",
|
219
|
+
borderColor: "#537bc4",
|
220
|
+
}];
|
221
|
+
}
|
222
|
+
|
223
|
+
get chartOptions() {
|
224
|
+
return {
|
225
|
+
aspectRatio: 3,
|
226
|
+
scales: {
|
227
|
+
x: {
|
228
|
+
type: "category",
|
229
|
+
labels: this.options.labels,
|
230
|
+
},
|
231
|
+
y: {
|
232
|
+
title: {
|
233
|
+
text: "Execution time (sec)",
|
234
|
+
display: true,
|
235
|
+
},
|
236
|
+
},
|
237
|
+
},
|
238
|
+
interaction: {
|
239
|
+
mode: "x",
|
240
|
+
},
|
241
|
+
plugins: {
|
242
|
+
legend: {
|
243
|
+
display: false,
|
244
|
+
},
|
245
|
+
tooltip: {
|
246
|
+
callbacks: {
|
247
|
+
title: (items) => `${items[0].raw.x} UTC`,
|
248
|
+
label: (item) =>
|
249
|
+
`${item.parsed.y} seconds: ${item.raw.count} job${
|
250
|
+
item.raw.count == 1 ? "" : "s"
|
251
|
+
}`,
|
252
|
+
footer: (items) => {
|
253
|
+
const bucket = items[0].raw.x;
|
254
|
+
const marks = this.options.marks.filter(([b, _]) => b == bucket);
|
255
|
+
return marks.map(([b, msg]) => `Deploy: ${msg}`);
|
256
|
+
},
|
257
|
+
},
|
258
|
+
},
|
259
|
+
},
|
260
|
+
};
|
261
|
+
}
|
262
|
+
}
|
@@ -67,10 +67,15 @@ body {
|
|
67
67
|
padding: 0 20px;
|
68
68
|
}
|
69
69
|
|
70
|
-
h3 {
|
70
|
+
h1, h2, h3 {
|
71
|
+
font-size: 24px;
|
71
72
|
line-height: 45px;
|
72
73
|
}
|
73
74
|
|
75
|
+
.header-with-subheader h2 {
|
76
|
+
margin-top: -18px;
|
77
|
+
}
|
78
|
+
|
74
79
|
.centered {
|
75
80
|
text-align: center;
|
76
81
|
}
|
@@ -954,3 +959,41 @@ div.interval-slider input {
|
|
954
959
|
padding: 3px 7px;
|
955
960
|
margin-left: 5px;
|
956
961
|
}
|
962
|
+
|
963
|
+
.metrics-swatch-wrapper {
|
964
|
+
display: flex;
|
965
|
+
align-items: center;
|
966
|
+
gap: 6px;
|
967
|
+
}
|
968
|
+
|
969
|
+
.metrics-swatch[type=checkbox] {
|
970
|
+
display: inline-block;
|
971
|
+
width: 16px;
|
972
|
+
height: 16px;
|
973
|
+
margin: 0;
|
974
|
+
border-radius: 2px;
|
975
|
+
appearance: none;
|
976
|
+
-webkit-appearance: none;
|
977
|
+
-moz-appearance: none;
|
978
|
+
border: 1px solid #bbb;
|
979
|
+
color: white;
|
980
|
+
background-color: currentColor;
|
981
|
+
}
|
982
|
+
|
983
|
+
/* We need to add the checkmark since we've taken over the appearance */
|
984
|
+
.metrics-swatch[type=checkbox]:checked {
|
985
|
+
border-color: currentColor;
|
986
|
+
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
|
987
|
+
background-size: 100% 100%;
|
988
|
+
background-position: center;
|
989
|
+
background-repeat: no-repeat;
|
990
|
+
}
|
991
|
+
|
992
|
+
.metrics-swatch[type=checkbox]:focus {
|
993
|
+
outline: 1px solid #888;
|
994
|
+
outline-offset: 2px;
|
995
|
+
}
|
996
|
+
|
997
|
+
canvas {
|
998
|
+
margin: 20px 0 30px;
|
999
|
+
}
|
data/web/locales/el.yml
CHANGED
@@ -6,11 +6,12 @@ el: # <---- change this to your locale code
|
|
6
6
|
Namespace: Namespace
|
7
7
|
Realtime: Τρέχουσα Κατάσταση
|
8
8
|
History: Ιστορικό
|
9
|
-
Busy:
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
Busy: Υπό επεξεργασία
|
10
|
+
Utilization: Σε χρήση
|
11
|
+
Processed: Επεξεργάστηκαν
|
12
|
+
Failed: Απέτυχαν
|
13
|
+
Scheduled: Προγραμματισμένα
|
14
|
+
Retries: Επαναλήψεις
|
14
15
|
Enqueued: Μπήκαν στην στοίβα
|
15
16
|
Worker: Εργάτης
|
16
17
|
LivePoll: Τρέχουσα Κατάσταση
|
@@ -20,40 +21,42 @@ el: # <---- change this to your locale code
|
|
20
21
|
Job: Εργασία
|
21
22
|
Arguments: Ορίσματα
|
22
23
|
Extras: Extras
|
23
|
-
Started:
|
24
|
+
Started: Ξεκίνησε
|
24
25
|
ShowAll: Εμφάνιση Όλων
|
25
26
|
CurrentMessagesInQueue: Τρέχουσες εργασίες <span class='title'>%{queue}</span>
|
26
27
|
Delete: Διαγραφή
|
27
28
|
AddToQueue: Προσθήκη στην στοίβα
|
28
|
-
AreYouSureDeleteJob: Θέλετε να διαγράψετε την
|
29
|
-
AreYouSureDeleteQueue: Θέλετε να διαγράψετε την %{queue}
|
29
|
+
AreYouSureDeleteJob: Θέλετε να διαγράψετε αυτή την εργασία;
|
30
|
+
AreYouSureDeleteQueue: Θέλετε να διαγράψετε την στοίβα %{queue}; Αυτό θα διαγράψει όλες τις εργασίες εντός της στοίβας, θα εμφανιστεί ξανά εάν προωθήσετε περισσότερες εργασίες σε αυτήν στο μέλλον.
|
30
31
|
Queues: Στοίβες
|
31
32
|
Size: Μέγεθος
|
32
33
|
Actions: Ενέργειες
|
33
|
-
NextRetry: Επόμενη
|
34
|
-
RetryCount: Αριθμός
|
35
|
-
RetryNow:
|
36
|
-
|
34
|
+
NextRetry: Επόμενη Προσπάθεια
|
35
|
+
RetryCount: Αριθμός Προσπαθειών
|
36
|
+
RetryNow: Επανάληψη Τώρα
|
37
|
+
# Kill: Kill
|
38
|
+
LastRetry: Τελευταία Προσπάθεια
|
37
39
|
OriginallyFailed: Αρχικές Αποτυχίες
|
38
|
-
AreYouSure: Είστε
|
39
|
-
DeleteAll: Διαγραφή
|
40
|
+
AreYouSure: Είστε σίγουρος;
|
41
|
+
DeleteAll: Διαγραφή Όλων
|
40
42
|
RetryAll: Επανάληψη Όλων
|
41
|
-
|
43
|
+
# KillAll: Kill All
|
44
|
+
NoRetriesFound: Δεν βρέθηκαν εργασίες προς επαναλήψη
|
42
45
|
Error: Σφάλμα
|
43
46
|
ErrorClass: Κλάση σφάλματος
|
44
47
|
ErrorMessage: Μήνυμα Σφάλματος
|
45
|
-
ErrorBacktrace:
|
48
|
+
ErrorBacktrace: Backtrace Σφάλματος
|
46
49
|
GoBack: ← Πίσω
|
47
50
|
NoScheduledFound: Δεν βρέθηκαν προγραμματισμένες εργασίες
|
48
51
|
When: Πότε
|
49
52
|
ScheduledJobs: Προγραμματισμένες Εργασίες
|
50
|
-
idle:
|
51
|
-
active:
|
53
|
+
idle: αδρανές
|
54
|
+
active: ενεργό
|
52
55
|
Version: Έκδοση
|
53
56
|
Connections: Συνδέσεις
|
54
57
|
MemoryUsage: Χρήση Μνήμης
|
55
58
|
PeakMemoryUsage: Μέγιστη Χρήση Μνήμης
|
56
|
-
Uptime:
|
59
|
+
Uptime: Ημέρες Λειτουργίας
|
57
60
|
OneWeek: 1 εβδομάδα
|
58
61
|
OneMonth: 1 μήνας
|
59
62
|
ThreeMonths: 3 μήνες
|
@@ -62,7 +65,28 @@ el: # <---- change this to your locale code
|
|
62
65
|
DeadJobs: Αδρανείς Εργασίες
|
63
66
|
NoDeadJobsFound: Δεν βρέθηκαν αδρανείς εργασίες
|
64
67
|
Dead: Αδρανείς
|
68
|
+
Process: Διεργασία
|
65
69
|
Processes: Διεργασίες
|
70
|
+
Name: Όνομα
|
66
71
|
Thread: Νήμα
|
67
72
|
Threads: Νήματα
|
68
73
|
Jobs: Εργασίες
|
74
|
+
Paused: Σε παύση
|
75
|
+
Stop: Διακοπή
|
76
|
+
Quiet: Σίγαση
|
77
|
+
StopAll: Διακοπή Όλων
|
78
|
+
QuietAll: Σίγαση Όλων
|
79
|
+
PollingInterval: Συχνότητα Ανανέωσης
|
80
|
+
Plugins: Πρόσθετα
|
81
|
+
NotYetEnqueued: Δεν προστέθηκε στην στοίβα ακόμη
|
82
|
+
CreatedAt: Δημιουργήθηκε στις
|
83
|
+
BackToApp: Πίσω στην Εφαρμογή
|
84
|
+
Latency: Καθυστέρηση
|
85
|
+
Pause: Παύση
|
86
|
+
Unpause: Κατάργηση Παύσης
|
87
|
+
Metrics: Μετρήσεις
|
88
|
+
NoDataFound: Δεν βρέθηκαν δεδομένα
|
89
|
+
ExecutionTime: Συνολικός Χρόνος Εκτέλεσης
|
90
|
+
AvgExecutionTime: Μέσος Χρόνος Εκτέλεσης
|
91
|
+
# Context: Context
|
92
|
+
|
data/web/locales/en.yml
CHANGED
@@ -84,3 +84,10 @@ en: # <---- change this to your locale code
|
|
84
84
|
Latency: Latency
|
85
85
|
Pause: Pause
|
86
86
|
Unpause: Unpause
|
87
|
+
Metrics: Metrics
|
88
|
+
NoDataFound: No data found
|
89
|
+
ExecutionTime: Total Execution Time
|
90
|
+
AvgExecutionTime: Average Execution Time
|
91
|
+
Context: Context
|
92
|
+
Bucket: Bucket
|
93
|
+
NoJobMetricsFound: No recent job metrics were found
|
data/web/views/_nav.erb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<div class="navbar navbar-default navbar-fixed-top">
|
2
2
|
<div class="container-fluid">
|
3
3
|
<div class="navbar-header" data-navbar="static">
|
4
|
-
<button type="button" class="navbar-toggle collapsed" data-toggle="
|
4
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="navbar-menu" data-target="#navbar-menu">
|
5
5
|
<span class="icon-bar"></span>
|
6
6
|
<span class="icon-bar"></span>
|
7
7
|
<span class="icon-bar"></span>
|
data/web/views/busy.erb
CHANGED
data/web/views/dashboard.erb
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
<script type="text/javascript" src="<%= root_path %>javascripts/chart.min.js"></script>
|
2
|
+
<script type="text/javascript" src="<%= root_path %>javascripts/chartjs-plugin-annotation.min.js"></script>
|
3
|
+
<script type="text/javascript" src="<%= root_path %>javascripts/metrics.js"></script>
|
4
|
+
|
5
|
+
<h2>Total execution time</h2>
|
6
|
+
|
7
|
+
<%
|
8
|
+
table_limit = 20
|
9
|
+
chart_limit = 5
|
10
|
+
job_results = @query_result.job_results.sort_by { |(kls, jr)| jr.totals["s"] }.reverse.first(table_limit)
|
11
|
+
visible_kls = job_results.first(chart_limit).map(&:first)
|
12
|
+
%>
|
13
|
+
|
14
|
+
<canvas id="job-metrics-overview-chart"></canvas>
|
15
|
+
|
16
|
+
<script>
|
17
|
+
window.jobMetricsChart = new JobMetricsOverviewChart(
|
18
|
+
"job-metrics-overview-chart",
|
19
|
+
<%= Sidekiq.dump_json({
|
20
|
+
series: job_results.map { |(kls, jr)| [kls, jr.dig("series", "s")] }.to_h,
|
21
|
+
marks: @query_result.marks.map { |m| [m.bucket, m.label] },
|
22
|
+
visible: visible_kls,
|
23
|
+
labels: @query_result.buckets,
|
24
|
+
}) %>
|
25
|
+
)
|
26
|
+
</script>
|
27
|
+
|
28
|
+
<h2>Most Time-Consuming Jobs</h2>
|
29
|
+
|
30
|
+
<div class="table_container">
|
31
|
+
<table class="table table-bordered table-striped table-hover">
|
32
|
+
<tbody>
|
33
|
+
<tr>
|
34
|
+
<th><%= t('Name') %></th>
|
35
|
+
<th><%= t('Processed') %></th>
|
36
|
+
<th><%= t('Failed') %></th>
|
37
|
+
<th><%= t('ExecutionTime') %></th>
|
38
|
+
<th><%= t('AvgExecutionTime') %></th>
|
39
|
+
</tr>
|
40
|
+
<% if job_results.any? %>
|
41
|
+
<% job_results.each_with_index do |(kls, jr), i| %>
|
42
|
+
<tr>
|
43
|
+
<td>
|
44
|
+
<div class="metrics-swatch-wrapper">
|
45
|
+
<% id = "metrics-swatch-#{kls}" %>
|
46
|
+
<input
|
47
|
+
type="checkbox"
|
48
|
+
id="<%= id %>"
|
49
|
+
class="metrics-swatch"
|
50
|
+
value="<%= kls %>"
|
51
|
+
/>
|
52
|
+
<code><a href="<%= root_path %>metrics/<%= kls %>"><%= kls %></a></code>
|
53
|
+
</div>
|
54
|
+
<script>jobMetricsChart.registerSwatch("<%= id %>")</script>
|
55
|
+
</td>
|
56
|
+
<td><%= jr.dig("totals", "p") %></td>
|
57
|
+
<td><%= jr.dig("totals", "f") %></td>
|
58
|
+
<td><%= jr.dig("totals", "s").round(2) %> seconds</td>
|
59
|
+
<td><%= jr.total_avg("s").round(2) %> seconds</td>
|
60
|
+
</tr>
|
61
|
+
<% end %>
|
62
|
+
<% else %>
|
63
|
+
<tr><td colspan=5><%= t("NoDataFound") %></td></tr>
|
64
|
+
<% end %>
|
65
|
+
</tbody>
|
66
|
+
</table>
|
67
|
+
</div>
|
68
|
+
|
69
|
+
<p><small>Data from <%= @query_result.starts_at %> to <%= @query_result.ends_at %></small></p>
|
@@ -0,0 +1,87 @@
|
|
1
|
+
<script type="text/javascript" src="<%= root_path %>javascripts/chart.min.js"></script>
|
2
|
+
<script type="text/javascript" src="<%= root_path %>javascripts/chartjs-plugin-annotation.min.js"></script>
|
3
|
+
<script type="text/javascript" src="<%= root_path %>javascripts/metrics.js"></script>
|
4
|
+
|
5
|
+
<%
|
6
|
+
job_result = @query_result.job_results[@name]
|
7
|
+
hist_totals = job_result.hist.values.first.zip(*job_result.hist.values[1..-1]).map(&:sum)
|
8
|
+
bucket_labels =Sidekiq::Metrics::Histogram::LABELS
|
9
|
+
bucket_intervals =Sidekiq::Metrics::Histogram::BUCKET_INTERVALS.reverse
|
10
|
+
|
11
|
+
# Replace INFINITY since it can't be represented as JSON
|
12
|
+
bucket_intervals[0] = bucket_intervals[1] * 2
|
13
|
+
%>
|
14
|
+
|
15
|
+
<% if job_result.totals["s"] > 0 %>
|
16
|
+
<div class="header-with-subheader">
|
17
|
+
<h1>
|
18
|
+
<a href="<%= root_path %>/metrics"><%= t(:metrics).to_s.titleize %></a> /
|
19
|
+
<%= h @name %>
|
20
|
+
</h1>
|
21
|
+
<h2>Histogram summary</h2>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<canvas id="hist-totals-chart"></canvas>
|
25
|
+
|
26
|
+
<script>
|
27
|
+
window.histTotalsChart = new HistTotalsChart(
|
28
|
+
"hist-totals-chart",
|
29
|
+
<%= Sidekiq.dump_json({
|
30
|
+
series: hist_totals,
|
31
|
+
labels: bucket_labels,
|
32
|
+
}) %>
|
33
|
+
)
|
34
|
+
</script>
|
35
|
+
|
36
|
+
<h2>Performance over time</h2>
|
37
|
+
|
38
|
+
<canvas id="hist-bubble-chart"></canvas>
|
39
|
+
|
40
|
+
<script>
|
41
|
+
window.histBubbleChart = new HistBubbleChart(
|
42
|
+
"hist-bubble-chart",
|
43
|
+
<%= Sidekiq.dump_json({
|
44
|
+
hist: job_result.hist,
|
45
|
+
marks: @query_result.marks.map { |m| [m.bucket, m.label] },
|
46
|
+
labels: @query_result.buckets,
|
47
|
+
histIntervals: bucket_intervals,
|
48
|
+
}) %>
|
49
|
+
)
|
50
|
+
</script>
|
51
|
+
|
52
|
+
<div class="table_container">
|
53
|
+
<table class="table table-bordered table-striped table-hover">
|
54
|
+
<tbody>
|
55
|
+
<tr>
|
56
|
+
<th><%= t('Time') %></th>
|
57
|
+
<th><%= t('Processed') %></th>
|
58
|
+
<th><%= t('Failed') %></th>
|
59
|
+
<th><%= t('ExecutionTime') %></th>
|
60
|
+
<th><%= t('AvgExecutionTime') %></th>
|
61
|
+
</tr>
|
62
|
+
<% @query_result.buckets.reverse.each do |bucket| %>
|
63
|
+
<tr>
|
64
|
+
<td><%= bucket %></td>
|
65
|
+
<td><%= job_result.series.dig("p", bucket) %></td>
|
66
|
+
<td><%= job_result.series.dig("f", bucket) %></td>
|
67
|
+
<% if (total_sec = job_result.series.dig("s", bucket)) > 0 %>
|
68
|
+
<td><%= total_sec.round(2) %> seconds</td>
|
69
|
+
<td><%= job_result.series_avg("s")[bucket].round(2) %> seconds</td>
|
70
|
+
<% else %>
|
71
|
+
<td>—</td>
|
72
|
+
<td>—</td>
|
73
|
+
<% end %>
|
74
|
+
</tr>
|
75
|
+
<% end %>
|
76
|
+
</tbody>
|
77
|
+
</table>
|
78
|
+
</div>
|
79
|
+
<p><small>Data from <%= @query_result.starts_at %> to <%= @query_result.ends_at %></small></p>
|
80
|
+
<% else %>
|
81
|
+
<h1>
|
82
|
+
<a href="<%= root_path %>/metrics"><%= t(:metrics).to_s.titleize %></a> /
|
83
|
+
<%= h @name %>
|
84
|
+
</h1>
|
85
|
+
|
86
|
+
<div class="alert alert-success"><%= t('NoJobMetricsFound') %></div>
|
87
|
+
<% end %>
|
data/web/views/queue.erb
CHANGED
@@ -18,6 +18,7 @@
|
|
18
18
|
<th><a href="<%= url %>?direction=<%= params[:direction] == 'asc' ? 'desc' : 'asc' %>"># <%= sort_direction_label %></a></th>
|
19
19
|
<th><%= t('Job') %></th>
|
20
20
|
<th><%= t('Arguments') %></th>
|
21
|
+
<th><%= t('Context') %></th>
|
21
22
|
<th></th>
|
22
23
|
</thead>
|
23
24
|
<% @jobs.each_with_index do |job, index| %>
|
@@ -35,12 +36,15 @@
|
|
35
36
|
<% a = job.display_args %>
|
36
37
|
<% if a.inspect.size > 100 %>
|
37
38
|
<span id="job_<%= index %>"><%= h(a.inspect[0..100]) + "... " %></span>
|
38
|
-
<button data-toggle="job_<%= index %>" class="btn btn-default btn-xs"><%= t('ShowAll') %></button>
|
39
|
+
<button data-toggle="job_<%= index %>_full" class="btn btn-default btn-xs"><%= t('ShowAll') %></button>
|
39
40
|
<div class="toggle" id="job_<%= index %>_full"><%= display_args(a) %></div>
|
40
41
|
<% else %>
|
41
42
|
<%= display_args(job.display_args) %>
|
42
43
|
<% end %>
|
43
44
|
</td>
|
45
|
+
<td>
|
46
|
+
<%= h(job["cattr"].inspect) if job["cattr"]&.any? %>
|
47
|
+
</td>
|
44
48
|
<td>
|
45
49
|
<form action="<%= root_path %>queues/<%= CGI.escape(@name) %>/delete" method="post">
|
46
50
|
<%= csrf_tag %>
|