From bb5a505243cbadc66c6a99e28942efeb679bd950 Mon Sep 17 00:00:00 2001 From: Igor Gaponenko Date: Thu, 26 Oct 2023 21:00:22 -0700 Subject: [PATCH] Web Dashboard: improved worker queries display --- src/www/qserv/css/QservMySQLConnections.css | 3 + src/www/qserv/js/QservMySQLConnections.js | 48 +++--- src/www/qserv/js/QservWorkerMySQLQueries.js | 169 ++++++++++++++++---- 3 files changed, 173 insertions(+), 47 deletions(-) diff --git a/src/www/qserv/css/QservMySQLConnections.css b/src/www/qserv/css/QservMySQLConnections.css index 50b66acb8a..ed42965da2 100644 --- a/src/www/qserv/css/QservMySQLConnections.css +++ b/src/www/qserv/css/QservMySQLConnections.css @@ -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; +} \ No newline at end of file diff --git a/src/www/qserv/js/QservMySQLConnections.js b/src/www/qserv/js/QservMySQLConnections.js index 176e951f3b..3c440da2d2 100644 --- a/src/www/qserv/js/QservMySQLConnections.js +++ b/src/www/qserv/js/QservMySQLConnections.js @@ -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 += ` - - ${worker} -   -   -   -   -   -`; - } 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 += ` - + 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 += ` + ${worker} -
${sql_conn_mgr.totalCount}
-
${sql_conn_mgr.sqlScanConnCount}
-
${sql_conn_mgr.maxSqlScanConnections}
-
${sql_conn_mgr.sqlSharedConnCount}
-
${sql_conn_mgr.maxSqlSharedConnections}
+
${totalCount}
+
${sqlScanConnCount}
+
${maxSqlScanConnections}
+
${sqlSharedConnCount}
+
${maxSqlSharedConnections}
`; - } } - 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; diff --git a/src/www/qserv/js/QservWorkerMySQLQueries.js b/src/www/qserv/js/QservWorkerMySQLQueries.js index 4a85d4372a..2c89cb4f4d 100644 --- a/src/www/qserv/js/QservWorkerMySQLQueries.js +++ b/src/www/qserv/js/QservWorkerMySQLQueries.js @@ -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); @@ -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; @@ -51,11 +55,24 @@ function(CSSLoader,
+ +
+ + +
+
+ + +
${Common.html_update_ival('update-interval', 10)}
@@ -71,8 +88,31 @@ function(CSSLoader, + + + + + + + + + + + + + + + + + + + + + + + @@ -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(); }); } @@ -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) { @@ -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; @@ -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) => { @@ -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); @@ -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 += ` - - + + `; + if (queryId === '') { + html += ` + `; + } else { + html += ` + `; + } + html += ` + + + + + + - + + `; + if (query === '') { + html += ` + + `; + + } else { + html += ` - + `; + } + html += ` `; + 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); } @@ -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;
Task      MySQL     
QIDjobchunksubchunktemplstate Id TimeCommand State Query
${queryId}
${queryId}
  + +
${jobId}
${chunkId}
${subChunkId}
${templateId}
${state}
${mySqlThreadId}
${row[COL_Time]}
${row[COL_State]}
${row[COL_Command]}
${row[COL_State]}
  
` + this._query2text(queryId, expanded) + `
` + this._query2text(mySqlThreadId, expanded) + `