Skip to content

Commit 9b28348

Browse files
authored
Add filters to Running Compactions table in monitor (apache#4986)
* Add filters to Running Compactions table in monitor
1 parent 4e6aa7d commit 9b28348

File tree

2 files changed

+195
-6
lines changed
  • server/monitor/src/main/resources/org/apache/accumulo/monitor

2 files changed

+195
-6
lines changed

server/monitor/src/main/resources/org/apache/accumulo/monitor/resources/js/ec.js

+144-4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ $(document).ready(function () {
6969
]
7070
});
7171

72+
const hostnameColumnName = 'hostname';
73+
const queueNameColumnName = 'queueName';
74+
const tableIdColumnName = 'tableId';
75+
const durationColumnName = 'duration';
76+
7277
// Create a table for running compactors
7378
runningTable = $('#runningTable').DataTable({
7479
"ajax": {
@@ -94,7 +99,8 @@ $(document).ready(function () {
9499
}
95100
],
96101
"columns": [{
97-
"data": "server"
102+
"data": "server",
103+
"name": hostnameColumnName
98104
},
99105
{
100106
"data": "kind"
@@ -103,10 +109,12 @@ $(document).ready(function () {
103109
"data": "status"
104110
},
105111
{
106-
"data": "queueName"
112+
"data": "queueName",
113+
"name": queueNameColumnName
107114
},
108115
{
109-
"data": "tableId"
116+
"data": "tableId",
117+
"name": tableIdColumnName
110118
},
111119
{
112120
"data": "numFiles"
@@ -132,7 +140,8 @@ $(document).ready(function () {
132140
"data": "lastUpdate"
133141
},
134142
{
135-
"data": "duration"
143+
"data": "duration",
144+
"name": durationColumnName
136145
},
137146
{ // more column settings
138147
"class": "details-control",
@@ -143,6 +152,127 @@ $(document).ready(function () {
143152
]
144153
});
145154

155+
function handleFilterKeyup(input, feedbackElement, columnName) {
156+
if (isValidRegex(input) || input === '') { // if valid, apply the filter
157+
feedbackElement.hide();
158+
$(this).removeClass('is-invalid');
159+
const isRegex = true;
160+
const smartEnabled = false;
161+
runningTable
162+
.column(`${columnName}:name`)
163+
.search(input, isRegex, smartEnabled)
164+
.draw();
165+
} else { // if invalid, show the warning
166+
feedbackElement.show();
167+
$(this).addClass('is-invalid');
168+
}
169+
}
170+
171+
$('#hostname-filter').on('keyup', function () {
172+
handleFilterKeyup.call(this, this.value, $('#hostname-feedback'), hostnameColumnName);
173+
});
174+
175+
$('#queue-filter').on('keyup', function () {
176+
handleFilterKeyup.call(this, this.value, $('#queue-feedback'), queueNameColumnName);
177+
});
178+
179+
$('#tableid-filter').on('keyup', function () {
180+
handleFilterKeyup.call(this, this.value, $('#tableid-feedback'), tableIdColumnName);
181+
});
182+
183+
$('#duration-filter').on('keyup', function () {
184+
runningTable.draw();
185+
});
186+
187+
// Clear Filters button handler
188+
$('#clear-filters').on('click', function () {
189+
$(this).prop('disabled', true); // disable the clear button
190+
191+
// set the filter inputs to empty and trigger the keyup event to clear the filters
192+
$('#hostname-filter').val('').trigger('keyup');
193+
$('#queue-filter').val('').trigger('keyup');
194+
$('#tableid-filter').val('').trigger('keyup');
195+
$('#duration-filter').val('').trigger('keyup');
196+
197+
$(this).prop('disabled', false); // re-enable the clear
198+
});
199+
200+
// Custom filter function for duration
201+
$.fn.dataTable.ext.search.push(function (settings, data, dataIndex) {
202+
if (settings.nTable.id !== 'runningTable') {
203+
return true;
204+
}
205+
206+
const durationColIndex = runningTable.column(`${durationColumnName}:name`).index();
207+
const durationStr = data[durationColIndex];
208+
const durationSeconds = parseDuration(durationStr);
209+
210+
const input = $('#duration-filter').val().trim();
211+
if (input === '') {
212+
$('#duration-feedback').hide();
213+
return true;
214+
}
215+
216+
const match = validateDurationInput(input);
217+
if (!match) {
218+
$('#duration-feedback').show();
219+
return false;
220+
}
221+
222+
$('#duration-feedback').hide();
223+
const operator = match[1];
224+
const value = parseInt(match[2]);
225+
const unit = match[3];
226+
const filterSeconds = convertToSeconds(value, unit);
227+
228+
switch (operator) {
229+
case '>':
230+
return durationSeconds > filterSeconds;
231+
case '>=':
232+
return durationSeconds >= filterSeconds;
233+
case '<':
234+
return durationSeconds < filterSeconds;
235+
case '<=':
236+
return durationSeconds <= filterSeconds;
237+
default:
238+
console.error(`Unexpected operator "${operator}" encountered in duration filter.`);
239+
return true;
240+
}
241+
});
242+
243+
// Helper function to convert duration strings to seconds
244+
function convertToSeconds(value, unit) {
245+
switch (unit.toLowerCase()) {
246+
case 's':
247+
return value;
248+
case 'm':
249+
return value * 60;
250+
case 'h':
251+
return value * 3600;
252+
case 'd':
253+
return value * 86400;
254+
default:
255+
console.error(`Unexpected unit "${unit}" encountered in duration filter. Defaulting to seconds.`);
256+
return value;
257+
}
258+
}
259+
260+
// Helper function to validate duration input. Makes sure that the input is in the format of '<operator> <value> <unit>'
261+
function validateDurationInput(input) {
262+
return input.match(/^([<>]=?)\s*(\d+)([smhd])$/i);
263+
}
264+
265+
/**
266+
* @param {number} durationStr duration in milliseconds
267+
* @returns duration in seconds
268+
*/
269+
function parseDuration(durationStr) {
270+
// Assuming durationStr is in milliseconds
271+
const milliseconds = parseInt(durationStr, 10);
272+
const seconds = milliseconds / 1000;
273+
return seconds;
274+
}
275+
146276
// Create a table for compaction coordinator
147277
coordinatorTable = $('#coordinatorTable').DataTable({
148278
"ajax": {
@@ -344,3 +474,13 @@ function refreshRunning() {
344474
// user paging is not reset on reload
345475
ajaxReloadTable(runningTable);
346476
}
477+
478+
// Helper function to validate regex
479+
function isValidRegex(input) {
480+
try {
481+
new RegExp(input);
482+
return true;
483+
} catch (e) {
484+
return false;
485+
}
486+
}

server/monitor/src/main/resources/org/apache/accumulo/monitor/templates/ec.ftl

+51-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,57 @@
6060
<div class="row">
6161
<div class="col-xs-12">
6262
<table id="runningTable" class="table caption-top table-bordered table-striped table-condensed">
63-
<caption><span class="table-caption">Running Compactions</span>&nbsp;&nbsp;
64-
<a href="javascript:refreshRunning();"><span style="font-size: 1.5em; color: black;" class="bi bi-arrow-repeat"></span></a>
63+
<caption>
64+
<div class="d-flex justify-content-between align-items-center mb-3">
65+
<div>
66+
<span class="table-caption">Running Compactions</span>&nbsp;&nbsp;
67+
<a href="javascript:refreshRunning();">
68+
<span style="font-size: 1.5em; color: black;" class="bi bi-arrow-repeat"></span>
69+
</a>
70+
</div>
71+
</div>
72+
<div class="accordion" id="filterAccordion">
73+
<div class="accordion-item">
74+
<h2 class="accordion-header" id="filterHeading">
75+
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#filterCollapse" aria-expanded="false" aria-controls="filterCollapse">
76+
Filters
77+
</button>
78+
</h2>
79+
<div id="filterCollapse" class="accordion-collapse collapse" aria-labelledby="filterHeading" data-bs-parent="#filterAccordion">
80+
<div class="accordion-body">
81+
<!-- Hostname Filter -->
82+
<div class="mb-3">
83+
<label for="hostname-filter" class="form-label">Hostname Filter</label>
84+
<input type="text" id="hostname-filter" class="form-control" placeholder="Enter hostname regex">
85+
<small id="hostname-feedback" class="form-text text-danger" style="display:none;">Invalid regex pattern</small>
86+
</div>
87+
<!-- Queue Filter -->
88+
<div class="mb-3">
89+
<label for="queue-filter" class="form-label">Queue Filter</label>
90+
<input type="text" id="queue-filter" class="form-control" placeholder="Enter queue regex">
91+
<small id="queue-feedback" class="form-text text-danger" style="display:none;">Invalid regex pattern</small>
92+
</div>
93+
<!-- Table ID Filter -->
94+
<div class="mb-3">
95+
<label for="tableid-filter" class="form-label">Table ID Filter</label>
96+
<input type="text" id="tableid-filter" class="form-control" placeholder="Enter table ID regex">
97+
<small id="tableid-feedback" class="form-text text-danger" style="display:none;">Invalid regex pattern</small>
98+
</div>
99+
<!-- Duration Filter -->
100+
<div class="mb-3">
101+
<label for="duration-filter" class="form-label">Duration Filter</label>
102+
<input type="text" id="duration-filter" class="form-control" placeholder="Enter duration (e.g., &gt;10m, &lt;1h, &gt;=5s, &lt;=2d)">
103+
<small id="duration-feedback" class="form-text text-danger" style="display:none;">Invalid duration format</small>
104+
<small class="form-text text-muted">Valid formats: &gt;10m, &lt;1h, &gt;=5s, &lt;=2d</small>
105+
</div>
106+
<!-- Clear Filters Button -->
107+
<div class="mb-3">
108+
<button id="clear-filters" class="btn btn-secondary">Clear Filters</button>
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
</div>
65114
</caption>
66115
<thead>
67116
<tr>

0 commit comments

Comments
 (0)