From 166a21f02e1ca1d71f2e55732f24aa34f03474e8 Mon Sep 17 00:00:00 2001 From: Igor Gaponenko Date: Fri, 18 Aug 2023 18:34:58 +0000 Subject: [PATCH] Web Dashboard: added a page for displaying Czar status --- src/www/dashboard.html | 2 +- src/www/qserv/css/QservCzarStatistics.css | 27 +++ src/www/qserv/js/Common.js | 2 +- src/www/qserv/js/QservCzarStatistics.js | 240 +++++++++++++++++++ src/www/qserv/js/QservMonitoringDashboard.js | 3 + 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 src/www/qserv/css/QservCzarStatistics.css create mode 100644 src/www/qserv/js/QservCzarStatistics.js diff --git a/src/www/dashboard.html b/src/www/dashboard.html index c8b950a6c..154f4288d 100644 --- a/src/www/dashboard.html +++ b/src/www/dashboard.html @@ -4,7 +4,7 @@ Qserv monitoring dashboard - + diff --git a/src/www/qserv/css/QservCzarStatistics.css b/src/www/qserv/css/QservCzarStatistics.css new file mode 100644 index 000000000..36f1a042e --- /dev/null +++ b/src/www/qserv/css/QservCzarStatistics.css @@ -0,0 +1,27 @@ +#fwk-qserv-czar-stats-controls label { + font-weight: bold; +} +table#fwk-qserv-czar-stats-status { + margin:0; +} +table.fwk-qserv-czar-stats caption { + caption-side: top; + text-align: right; + padding-top: 0; +} +table.fwk-qserv-czar-stats caption.updating { + background-color: #ffeeba; +} +table.fwk-qserv-czar-stats > thead > tr > th.sticky { + position:sticky; + top:80px; + z-index:2; +} +table.fwk-qserv-czar-stats tbody th, +table.fwk-qserv-czar-stats tbody td { + vertical-align:middle; +} +table.fwk-qserv-czar-stats pre { + padding: 0; + margin: 0; +} diff --git a/src/www/qserv/js/Common.js b/src/www/qserv/js/Common.js index 8fc5bdd0f..2d959d737 100644 --- a/src/www/qserv/js/Common.js +++ b/src/www/qserv/js/Common.js @@ -6,7 +6,7 @@ function(sqlFormatter, _) { class Common { - static RestAPIVersion = 24; + static RestAPIVersion = 25; static query2text(query, expanded) { if (expanded) { return sqlFormatter.format(query, Common._sqlFormatterConfig); diff --git a/src/www/qserv/js/QservCzarStatistics.js b/src/www/qserv/js/QservCzarStatistics.js new file mode 100644 index 000000000..60dd6097b --- /dev/null +++ b/src/www/qserv/js/QservCzarStatistics.js @@ -0,0 +1,240 @@ +define([ + 'webfwk/CSSLoader', + 'webfwk/Fwk', + 'webfwk/FwkApplication', + 'qserv/Common', + 'underscore'], + +function(CSSLoader, + Fwk, + FwkApplication, + Common, + _) { + + CSSLoader.load('qserv/css/QservCzarStatistics.css'); + + class QservCzarStatistics 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(); + } + } + } + static _counters = ['queryRespConcurrentSetupCount', + 'queryRespConcurrentWaitCount', + 'queryRespConcurrentProcessingCount', + 'numQueries', + 'numJobs']; + static _qdisppool_columns = ['priority', 'running', 'size']; + + _init() { + if (this._initialized === undefined) this._initialized = false; + if (this._initialized) return; + this._initialized = true; + let html = ` +
+
+
+
+ ${Common.html_update_ival('update-interval', 10)} +
+
+ + +
+
+
+
+
+
+ + +
Loading...
+
+
+
+
+

Counters

+ + ` + _.reduce(QservCzarStatistics._counters, function(html, counter) { return html + ` + + + + `; }, '') + ` + +
${counter}
Loading...
+
+
+

QdispPool

+ + + ` + _.reduce(QservCzarStatistics._qdisppool_columns, function(html, column) { return html + ` + `; }, '') + ` + + + +
${column}
+
+
+
+
+

Timing Histograms

+
+
+
+
+
+

Data Rates Histograms

+
+
+
+`; + let cont = this.fwk_app_container.html(html); + cont.find(".form-control-selector").change(() => { + this._load(); + }); + cont.find("button#reset-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); } + _table(name) { + if (_.isUndefined(this._table_obj)) this._table_obj = {}; + if (!_.has(this._table_obj, name)) { + this._table_obj[name] = this.fwk_app_container.find('table#fwk-qserv-czar-stats-' + name); + } + return this._table_obj[name]; + } + _status() { + if (_.isUndefined(this._status_obj)) { + this._status_obj = this._table('status').children('caption'); + } + return this._status_obj; + } + _set_counter(name, val) { + if (_.isUndefined(this._counters_obj)) this._counters_obj = {}; + if (!_.has(this._counters_obj, name)) { + this._counters_obj[name] = this._table('counters').children('tbody').find('#' + name); + } + this._counters_obj[name].text(val); + } + _load() { + if (this._loading === undefined) this._loading = false; + if (this._loading) return; + this._loading = true; + this._status().addClass('updating'); + Fwk.web_service_GET( + "/replication/qserv/master/status", + {version: Common.RestAPIVersion}, + (data) => { + if (data.success) { + this._display(data.status); + Fwk.setLastUpdate(this._status()); + } else { + console.log('request failed', this.fwk_app_name, data.error); + this._status().html('' + data.error + ''); + } + this._status().removeClass('updating'); + this._loading = false; + }, + (msg) => { + console.log('request failed', this.fwk_app_name, msg); + this._status().html('No Response'); + this._status().removeClass('updating'); + this._loading = false; + } + ); + } + _display(data) { + let tbody = this._table('qdisppool').children('tbody'); + if (_.isEmpty(data) || _.isEmpty(data.qdisp_stats) || _.isEmpty(data.qdisp_stats.QdispPool)) { + tbody.html(''); + return; + } + let that = this; + _.each(QservCzarStatistics._counters, function(counter) { + that._set_counter(counter, data.qdisp_stats[counter]); + }); + let html = ''; + _.each(data.qdisp_stats.QdispPool, function (row) { + html += ` +` + _.reduce(QservCzarStatistics._qdisppool_columns, function (html, column) { return html + ` +
${row[column]}
`; }, '') + ` +`; + }); + this._table('qdisppool').children('tbody').html(html); + + // Locate and display histograms nested in the top-level objects + this._table('timing').html(this._htmlgen_histograms( + _.reduce(data.qdisp_stats, function (histograms, e) { + if (_.isObject(e) && _.has(e, 'HistogramId')) histograms.push(e); + return histograms; + }, []) + )); + this._table('data').html(this._htmlgen_histograms( + _.reduce(data.transmit_stats, function (histograms, e) { + if (_.isObject(e) && _.has(e, 'HistogramId')) histograms.push(e); + return histograms; + }, []) + )); + } + _htmlgen_histograms(histograms) { + return _.reduce(histograms, function (html, histogram) { + if (html == '') { + let idx = 0; + html = ` + + + id + total + totalCount + avg` + _.reduce(histogram.buckets, function (html, bucket) { return html + ` + ${(idx++) == 0 ? "≤ " : ""}${bucket.maxVal}`; }, '') + ` + + +`; + + } + html += ` + + ${histogram.HistogramId} +
${histogram.total.toFixed(3)}
+
${histogram.totalCount}
+
${histogram.avg.toFixed(3)}
` + _.reduce(histogram.buckets, function (html, bucket) { return html + ` +
${bucket.count}
`; }, '') + ` + `; + return html; + }, '') + ` +`; + } + } + return QservCzarStatistics; +}); diff --git a/src/www/qserv/js/QservMonitoringDashboard.js b/src/www/qserv/js/QservMonitoringDashboard.js index 502e207f5..3230fec23 100644 --- a/src/www/qserv/js/QservMonitoringDashboard.js +++ b/src/www/qserv/js/QservMonitoringDashboard.js @@ -42,6 +42,7 @@ require([ 'qserv/StatusWorkers', 'qserv/StatusUserQueries', 'qserv/QservCzarMySQLQueries', + 'qserv/QservCzarStatistics', 'qserv/QservCss', 'qserv/QservMySQLConnections', 'qserv/QservWorkerMySQLQueries', @@ -82,6 +83,7 @@ function(CSSLoader, StatusWorkers, StatusUserQueries, QservCzarMySQLQueries, + QservCzarStatistics, QservCss, QservMySQLConnections, QservWorkerMySQLQueries, @@ -168,6 +170,7 @@ function(CSSLoader, { name: 'Czar', apps: [ new QservCzarMySQLQueries('MySQL Queries'), + new QservCzarStatistics('Statistics'), new QservCss('CSS') ] },