Skip to content

Commit

Permalink
Web Dashboard: added a page for displaying MySQL queries at workers
Browse files Browse the repository at this point in the history
  • Loading branch information
iagaponenko committed Aug 9, 2023
1 parent 5671055 commit 8956832
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 1 deletion.
24 changes: 24 additions & 0 deletions src/www/qserv/css/QservWorkerMySQLQueries.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#fwk-worker-mysql-queries-controls label {
font-weight: bold;
}
table#fwk-worker-mysql-queries caption {
caption-side: top;
text-align: right;
padding-top: 0;
}
table#fwk-worker-mysql-queries > thead > tr > th.sticky {
position:sticky;
top:80px;
z-index:2;
}
table#fwk-worker-mysql-queries tbody th,
table#fwk-worker-mysql-queries tbody td {
vertical-align:middle;
}
table#fwk-worker-mysql-queries pre {
padding: 0;
margin: 0;
}
table#fwk-worker-mysql-queries caption.updating {
background-color: #ffeeba;
}
2 changes: 1 addition & 1 deletion src/www/qserv/js/Common.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ define([

function(sqlFormatter) {
class Common {
static RestAPIVersion = 23;
static RestAPIVersion = 24;
static query2text(query, expanded) {
if (expanded) {
return sqlFormatter.format(query, Common._sqlFormatterConfig);
Expand Down
3 changes: 3 additions & 0 deletions src/www/qserv/js/QservMonitoringDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require([
'qserv/StatusUserQueries',
'qserv/QservCss',
'qserv/QservMySQLConnections',
'qserv/QservWorkerMySQLQueries',
'qserv/QservWorkerQueries',
'qserv/QservWorkerSchedulers',
'qserv/QservWorkerSchedulerHist',
Expand Down Expand Up @@ -81,6 +82,7 @@ function(CSSLoader,
StatusUserQueries,
QservCss,
QservMySQLConnections,
QservWorkerMySQLQueries,
QservWorkerQueries,
QservWorkerSchedulers,
QservWorkerSchedulerHist,
Expand Down Expand Up @@ -169,6 +171,7 @@ function(CSSLoader,
{ name: 'Workers',
apps: [
new QservMySQLConnections('MySQL Connections'),
new QservWorkerMySQLQueries('MySQL Queries'),
new QservWorkerQueries('Queries in Worker Queues'),
new QservWorkerSchedulers('Schedulers'),
new QservWorkerSchedulerHist('Scheduler Histograms'),
Expand Down
214 changes: 214 additions & 0 deletions src/www/qserv/js/QservWorkerMySQLQueries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
define([
'webfwk/CSSLoader',
'webfwk/Fwk',
'webfwk/FwkApplication',
'qserv/Common',
'underscore'],

function(CSSLoader,
Fwk,
FwkApplication,
Common,
_) {

CSSLoader.load('qserv/css/QservWorkerMySQLQueries.css');

class QservWorkerMySQLQueries extends FwkApplication {

constructor(name) {
super(name);
}
fwk_app_on_show() {
console.log('show: ' + this.fwk_app_name);
this.fwk_app_on_update();
}
fwk_app_on_hide() {
console.log('hide: ' + this.fwk_app_name);
}
fwk_app_on_update() {
if (this.fwk_app_visible) {
this._init();
if (this._prev_update_sec === undefined) {
this._prev_update_sec = 0;
}
let now_sec = Fwk.now().sec;
if (now_sec - this._prev_update_sec > this._update_interval_sec()) {
this._prev_update_sec = now_sec;
this._init();
this._load();
}
}
}
_init() {
if (this._initialized === undefined) this._initialized = false;
if (this._initialized) return;
this._initialized = true;
let html = `
<div class="row" id="fwk-worker-mysql-queries-controls">
<div class="col">
<div class="form-row">
<div class="form-group col-md-3">
<label for="worker">Worker:</label>
<select id="worker" class="form-control form-control-selector">
</select>
</div>
<div class="form-group col-md-1">
<label for="update-interval"><i class="bi bi-arrow-repeat"></i> interval:</label>
<select id="update-interval" class="form-control form-control-selector">
<option value="5">5 sec</option>
<option value="10" selected>10 sec</option>
<option value="20">20 sec</option>
<option value="30">30 sec</option>
<option value="60">1 min</option>
<option value="120">2 min</option>
<option value="300">5 min</option>
</select>
</div>
<div class="form-group col-md-1">
<label for="reset-controls-form">&nbsp;</label>
<button id="reset-controls-form" class="btn btn-primary form-control">Reset</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table class="table table-sm table-hover table-bordered" id="fwk-worker-mysql-queries">
<thead class="thead-light">
<tr>
<th class="sticky" style="text-align:right;">Id</th>
<th class="sticky" style="text-align:right;">Time</th>
<th class="sticky">State</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-clipboard-fill"></i></th>
<th class="sticky" style="text-align:center;"><i class="bi bi-info-circle-fill"></i></th>
<th class="sticky">Query</th>
</tr>
</thead>
<caption class="updating">Loading...</caption>
<tbody></tbody>
</table>
</div>
</div>`;
let cont = this.fwk_app_container.html(html);
cont.find(".form-control-selector").change(() => {
this._load();
});
cont.find("button#reset-controls-form").click(() => {
this._set_update_interval_sec(10);
this._load();
});
}
_form_control(elem_type, id) {
if (this._form_control_obj === undefined) this._form_control_obj = {};
if (!_.has(this._form_control_obj, id)) {
this._form_control_obj[id] = this.fwk_app_container.find(elem_type + '#' + id);
}
return this._form_control_obj[id];
}
_update_interval_sec() { return this._form_control('select', 'update-interval').val(); }
_set_update_interval_sec(val) { this._form_control('select', 'update-interval').val(val); }
_worker() { return this._form_control('select', 'worker').val(); }
_set_worker(val) { this._form_control('select', 'worker').val(val); }
_set_workers(workers) {
const prev_worker = this._worker();
let html = '';
for (let i in workers) {
const worker = workers[i];
const selected = (_.isEmpty(prev_worker) && (i === 0)) ||
(!_.isEmpty(prev_worker) && (prev_worker === worker));
html += `
<option value="${worker}" ${selected ? "selected" : ""}>${worker}</option>`;
}
this._form_control('select', 'worker').html(html);
}
_table() {
if (this._table_obj === undefined) {
this._table_obj = this.fwk_app_container.find('table#fwk-worker-mysql-queries');
}
return this._table_obj;
}
_load() {
if (this._loading === undefined) this._loading = false;
if (this._loading) return;
this._loading = true;
this._table().children('caption').addClass('updating');
Fwk.web_service_GET(
"/replication/config",
{version: Common.RestAPIVersion},
(data) => {
let workers = [];
for (let i in data.config.workers) {
workers.push(data.config.workers[i].name);
}
this._set_workers(workers);
this._load_queries();
},
(msg) => {
console.log('request failed', this.fwk_app_name, msg);
this._table().children('caption').html('<span style="color:maroon">No Response</span>');
this._table().children('caption').removeClass('updating');
this._loading = false;
}
);
}
_load_queries() {
Fwk.web_service_GET(
"/replication/qserv/worker/db/" + this._worker(),
{ timeout_sec: 2, version: Common.RestAPIVersion
},
(data) => {
if (data.success) {
this._display(data.status.queries);
Fwk.setLastUpdate(this._table().children('caption'));
} else {
console.log('request failed', this.fwk_app_name, data.error);
this._table().children('caption').html('<span style="color:maroon">' + data.error + '</span>');
}
this._table().children('caption').removeClass('updating');
this._loading = false;
},
(msg) => {
console.log('request failed', this.fwk_app_name, msg);
this._table().children('caption').html('<span style="color:maroon">No Response</span>');
this._table().children('caption').removeClass('updating');
this._loading = false;
}
);
}
_display(queries) {
const queryInspectTitle = "Click to see detailed info (progress, messages, etc.) on the query.";
const COL_Id = 0, COL_Command = 4, COL_Time = 5, COL_State = 6, COL_Info = 7;
let thead = this._table().children('thead');
let tbody = this._table().children('tbody');
if (_.isEmpty(queries.columns)) {
thead.html('');
tbody.html('');
return;
}
let html = '<tr>';
for (let i in queries.columns) {
html += `<td>${queries.columns[i]}</td>`;
}
html += '</tr>';
thead.html(html);
html = '';
for (let i in queries.rows) {
let row = queries.rows[i];
html = '<tr>';
for (let j in row) {
html += `<td style="text-align:left;"><pre>${row[j]}</pre></td>`;
}
html += '</tr>';
}
tbody.html(html);
let displayQuery = function(e) {
let button = $(e.currentTarget);
let queryId = button.parent().parent().attr("id");
Fwk.find("Status", "Query Inspector").set_query_id(queryId);
Fwk.show("Status", "Query Inspector");
};
tbody.find("button.inspect-query").click(displayQuery);
}
}
return QservWorkerMySQLQueries;
});

0 comments on commit 8956832

Please sign in to comment.