Skip to content

Commit

Permalink
Web Dashboard: improved worker queries display
Browse files Browse the repository at this point in the history
  • Loading branch information
iagaponenko committed Oct 27, 2023
1 parent b6564f5 commit bb5a505
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 47 deletions.
3 changes: 3 additions & 0 deletions src/www/qserv/css/QservMySQLConnections.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ table#fwk-qserv-mysql-connections > thead > tr > th.sticky {
top:80px;
z-index:2;
}
table#fwk-qserv-mysql-connections tbody > tr.display-worker-queries:hover {
cursor:pointer;
}
48 changes: 28 additions & 20 deletions src/www/qserv/js/QservMySQLConnections.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,32 +136,40 @@ function(CSSLoader,
* Display MySQL connections
*/
_display(data) {
const queryInspectTitle = "Click to see MySQL queries runing on the worker's MySQL server.";
let html = '';
for (let worker in data) {
if (!data[worker].success) {
html += `
<tr>
<th class="table-warning">${worker}</th>
<td class="table-secondary">&nbsp;</td>
<td class="table-secondary">&nbsp;</td>
<td class="table-secondary">&nbsp;</td>
<td class="table-secondary">&nbsp;</td>
<td class="table-secondary">&nbsp;</td>
</tr>`;
} else {
let totalCount = '';
let sqlScanConnCount = '';
let maxSqlScanConnections = '';
let sqlSharedConnCount = '';
let maxSqlSharedConnections = '';
if (data[worker].success) {
let sql_conn_mgr = data[worker].info.processor.sql_conn_mgr;
html += `
<tr>
totalCount = sql_conn_mgr.totalCount;
sqlScanConnCount = sql_conn_mgr.sqlScanConnCount;
maxSqlScanConnections = sql_conn_mgr.maxSqlScanConnections;
sqlSharedConnCount = sql_conn_mgr.sqlSharedConnCount;
maxSqlSharedConnections = sql_conn_mgr.maxSqlSharedConnections;
}
html += `
<tr worker="${worker}" class="display-worker-queries" title="${queryInspectTitle}">
<th>${worker}</th>
<td style="text-align:right;"><pre>${sql_conn_mgr.totalCount}</pre></td>
<td style="text-align:right;"><pre>${sql_conn_mgr.sqlScanConnCount}</pre></td>
<td style="text-align:right;"><pre>${sql_conn_mgr.maxSqlScanConnections}</pre></td>
<td style="text-align:right;"><pre>${sql_conn_mgr.sqlSharedConnCount}</pre></td>
<td style="text-align:right;"><pre>${sql_conn_mgr.maxSqlSharedConnections}</pre></td>
<td style="text-align:right;"><pre>${totalCount}</pre></td>
<td style="text-align:right;"><pre>${sqlScanConnCount}</pre></td>
<td style="text-align:right;"><pre>${maxSqlScanConnections}</pre></td>
<td style="text-align:right;"><pre>${sqlSharedConnCount}</pre></td>
<td style="text-align:right;"><pre>${maxSqlSharedConnections}</pre></td>
</tr>`;
}
}
this._table().children('tbody').html(html);
let tbody = this._table().children('tbody');
tbody.html(html);
let displayWorkerQueries = function(e) {
const worker = $(e.currentTarget).attr("worker");
Fwk.find("Workers", "MySQL Queries").set_worker(worker);
Fwk.show("Workers", "MySQL Queries");
};
tbody.find("tr.display-worker-queries").click(displayWorkerQueries);
}
}
return QservMySQLConnections;
Expand Down
169 changes: 142 additions & 27 deletions src/www/qserv/js/QservWorkerMySQLQueries.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ function(CSSLoader,

constructor(name) {
super(name);
this._queryId2Expanded = {}; // Store 'true' to allow persistent state for the expanded
// queries between updates.
this._id2query = {}; // Store query text for each identifier. The dictionary gets
// updated at each refresh of the page.
this._mySqlThreadId2Expanded = {}; // Store 'true' to allow persistent state for the expanded
// queries between updates.
this._mySqlThreaId2query = {}; // Store query text for each identifier. The dictionary gets
// updated at each refresh of the page.
}
fwk_app_on_show() {
console.log('show: ' + this.fwk_app_name);
Expand All @@ -43,6 +43,10 @@ function(CSSLoader,
}
}
}
set_worker(worker) {
this._init();
this._load(worker);
}
_init() {
if (this._initialized === undefined) this._initialized = false;
if (this._initialized) return;
Expand All @@ -51,11 +55,24 @@ function(CSSLoader,
<div class="row" id="fwk-worker-mysql-queries-controls">
<div class="col">
<div class="form-row">
<div class="form-group col-md-2">
<label for="num-queries"># displayed / total:</label>
<input type="text" id="num-queries" class="form-control" value="0 / 0" disabled>
</div>
<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="query-command">Command:</label>
<select id="query-command" class="form-control form-control-selector">
<option value="">&lt;any&gt;</option>
<option value="Query" selected>Query</option>
<option value="Sleep">Sleep</option>
</select>
</div>
<div class="form-group col-md-1">
${Common.html_update_ival('update-interval', 10)}
</div>
Expand All @@ -71,8 +88,31 @@ function(CSSLoader,
<table class="table table-sm table-hover table-bordered" id="fwk-worker-mysql-queries">
<thead class="thead-light">
<tr>
<th style="text-align:right;">Task</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th style="text-align:right;">MySQL</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
<tr>
<th class="sticky" style="text-align:right;">QID</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-info-circle-fill"></i></th>
<th class="sticky" style="text-align:right;">job</th>
<th class="sticky" style="text-align:right;">chunk</th>
<th class="sticky" style="text-align:right;">subchunk</th>
<th class="sticky" style="text-align:right;">templ</th>
<th class="sticky" style="text-align:right;">state</th>
<th class="sticky" style="text-align:right;">Id</th>
<th class="sticky" style="text-align:right;">Time</th>
<th class="sticky" style="text-align:right;">Command</th>
<th class="sticky" style="text-align:right;">State</th>
<th class="sticky" style="text-align:center;"><i class="bi bi-clipboard-fill"></i></th>
<th class="sticky">Query</th>
Expand All @@ -89,6 +129,7 @@ function(CSSLoader,
});
cont.find("button#reset-controls-form").click(() => {
this._set_update_interval_sec(10);
this._set_query_command('Query');
this._load();
});
}
Expand All @@ -101,6 +142,9 @@ function(CSSLoader,
}
_update_interval_sec() { return this._form_control('select', 'update-interval').val(); }
_set_update_interval_sec(val) { this._form_control('select', 'update-interval').val(val); }
_set_num_queries(total, displayed) { this._form_control('input', 'num-queries').val(displayed + ' / ' + total); }
_query_command() { return this._form_control('select', 'query-command').val(); }
_set_query_command(val) { this._form_control('select', 'query-command').val(val); }
_worker() { return this._form_control('select', 'worker').val(); }
_set_worker(val) { this._form_control('select', 'worker').val(val); }
_set_workers(workers) {
Expand All @@ -121,7 +165,7 @@ function(CSSLoader,
}
return this._table_obj;
}
_load() {
_load(worker = undefined) {
if (this._loading === undefined) this._loading = false;
if (this._loading) return;
this._loading = true;
Expand All @@ -135,6 +179,7 @@ function(CSSLoader,
workers.push(data.config.workers[i].name);
}
this._set_workers(workers);
if (!_.isUndefined(worker)) this._set_worker(worker);
this._load_queries();
},
(msg) => {
Expand All @@ -152,7 +197,7 @@ function(CSSLoader,
},
(data) => {
if (data.success) {
this._display(data.status.queries);
this._display(data.status);
Fwk.setLastUpdate(this._table().children('caption'));
} else {
console.log('request failed', this.fwk_app_name, data.error);
Expand All @@ -169,42 +214,99 @@ function(CSSLoader,
}
);
}
_display(queries) {
_display(status) {
const queryInspectTitle = "Click to see detailed info (progress, messages, etc.) on the query.";
const queryCopyTitle = "Click to copy the query text to the clipboard.";
const COL_Id = 0, COL_Command = 4, COL_Time = 5, COL_State = 6, COL_Info = 7;
const desiredQueryCommand = this._query_command();
let tbody = this._table().children('tbody');
if (_.isEmpty(queries.columns)) {
if (_.isEmpty(status.queries.columns)) {
tbody.html('');
return;
}
this._id2query = {};
this._mySqlThreaId2query = {};
let numQueriesTotal = 0;
let numQueriesDisplayed = 0;
let html = '';
for (let i in queries.rows) {
let row = queries.rows[i];
if (row[COL_Command] !== 'Query') continue;
let queryId = row[COL_Id];
for (let i in status.queries.rows) {
numQueriesTotal++;
// MySQL query context
let row = status.queries.rows[i];
const thisQueryCommand = row[COL_Command];
if ((desiredQueryCommand !== '') && (thisQueryCommand !== desiredQueryCommand)) continue;
let mySqlThreadId = row[COL_Id];
let query = row[COL_Info];
this._id2query[queryId] = query;
const expanded = (queryId in this._queryId2Expanded) && this._queryId2Expanded[queryId];
this._mySqlThreaId2query[mySqlThreadId] = query;
const expanded = (mySqlThreadId in this._mySqlThreadId2Expanded) && this._mySqlThreadId2Expanded[mySqlThreadId];
const queryToggleTitle = "Click to toggle query formatting.";
const queryStyle = "color:#4d4dff;";
// Task context (if any)
let queryId = '';
let jobId = '';
let chunkId = '';
let subChunkId = '';
let templateId = '';
let state = '';
if (_.has(status.mysql_thread_to_task, mySqlThreadId)) {
let task = status.mysql_thread_to_task[mySqlThreadId];
queryId = task['query_id'];
jobId = task['job_id'];
chunkId = task['chunk_id'];
subChunkId = task['subchunk_id'];
templateId = task['template_id'];
state = task['state'];
}
const rowClass = QservWorkerMySQLQueries._state2css(state);
html += `
<tr id="${queryId}">
<th style="text-align:right;"><pre>${queryId}</pre></th>
<tr mysql_thread_id="${mySqlThreadId}" query_id="${queryId}">
<th style="text-align:right;"><pre>${queryId}</pre></th>`;
if (queryId === '') {
html += `
<td style="text-align:center; padding-top:0; padding-bottom:0">&nbsp;</td>`;
} else {
html += `
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-info btn-sm inspect-query" style="height:20px; margin:0px;" title="${queryInspectTitle}"></button>
</td>`;
}
html += `
<td style="text-align:right;" class="${rowClass}"><pre>${jobId}</pre></td>
<td style="text-align:right;" class="${rowClass}"><pre>${chunkId}</pre></td>
<td style="text-align:right;" class="${rowClass}"><pre>${subChunkId}</pre></td>
<td style="text-align:right;" class="${rowClass}"><pre>${templateId}</pre></td>
<td style="text-align:right;" class="${rowClass}"><pre>${state}</pre></td>
<th style="text-align:right;"><pre>${mySqlThreadId}</pre></th>
<td style="text-align:right;"><pre>${row[COL_Time]}</pre></td>
<td style="text-align:right;"><pre>${row[COL_State]}</pre></td>
<td style="text-align:right;"><pre>${row[COL_Command]}</pre></td>
<td style="text-align:right;"><pre>${row[COL_State]}</pre></td>`;
if (query === '') {
html += `
<td style="text-align:center; padding-top:0; padding-bottom:0">&nbsp;</td>
<td>&nbsp;</td>`;

} else {
html += `
<td style="text-align:center; padding-top:0; padding-bottom:0">
<button class="btn btn-outline-dark btn-sm copy-query" style="height:20px; margin:0px;" title="${queryCopyTitle}"></button>
</td>
<td class="query_toggler" title="${queryToggleTitle}"><pre class="query" style="${queryStyle}">` + this._query2text(queryId, expanded) + `<pre></td>
<td class="query_toggler" title="${queryToggleTitle}"><pre class="query" style="${queryStyle}">` + this._query2text(mySqlThreadId, expanded) + `<pre></td>`;
}
html += `
</tr>`;
numQueriesDisplayed++;
}
tbody.html(html);
let that = this;
let displayQuery = function(e) {
let button = $(e.currentTarget);
let queryId = button.parent().parent().attr("query_id");
Fwk.find("Status", "Query Inspector").set_query_id(queryId);
Fwk.show("Status", "Query Inspector");
};
let copyQueryToClipboard = function(e) {
let button = $(e.currentTarget);
let queryId = button.parent().parent().attr("id");
let query = that._id2query[queryId];
let mySqlThreadId = button.parent().parent().attr("mysql_thread_id");
let query = that._mySqlThreaId2query[mySqlThreadId];
navigator.clipboard.writeText(query,
() => {},
() => { alert("Failed to write the query to the clipboard. Please copy the text manually: " + query); }
Expand All @@ -213,16 +315,29 @@ function(CSSLoader,
let toggleQueryDisplay = function(e) {
let td = $(e.currentTarget);
let pre = td.find("pre.query");
const queryId = td.parent().attr("id");
const expanded = !((queryId in that._queryId2Expanded) && that._queryId2Expanded[queryId]);
pre.text(that._query2text(queryId, expanded));
that._queryId2Expanded[queryId] = expanded;
const mySqlThreadId = td.parent().attr("mysql_thread_id");
const expanded = !((mySqlThreadId in that._mySqlThreadId2Expanded) && that._mySqlThreadId2Expanded[mySqlThreadId]);
pre.text(that._query2text(mySqlThreadId, expanded));
that._mySqlThreadId2Expanded[mySqlThreadId] = expanded;
};
tbody.find("button.inspect-query").click(displayQuery);
tbody.find("button.copy-query").click(copyQueryToClipboard);
tbody.find("td.query_toggler").click(toggleQueryDisplay);
this._set_num_queries(numQueriesTotal, numQueriesDisplayed);
}
_query2text(queryId, expanded) {
return Common.query2text(this._id2query[queryId], expanded);
_query2text(mySqlThreadId, expanded) {
return Common.query2text(this._mySqlThreaId2query[mySqlThreadId], expanded);
}
static _state2css(state) {
switch (state) {
case 'CREATED': return 'table-warning';
case 'QUEUED': return 'table-light';
case 'STARTED': return 'table-danger';
case 'EXECUTING_QUERY': return 'table-primary';
case 'READING_DATA': return 'table-info';
case 'FINISHED': return 'table-secondary';
default: return '';
}
}
}
return QservWorkerMySQLQueries;
Expand Down

0 comments on commit bb5a505

Please sign in to comment.