Skip to content

Commit

Permalink
Web Dashboard: added a page for displaying Czar status
Browse files Browse the repository at this point in the history
  • Loading branch information
iagaponenko committed Aug 23, 2023
1 parent 7170258 commit 166a21f
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/www/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<title>Qserv monitoring dashboard</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script data-main="qserv/js/QservMonitoringDashboard.js?bust=68" src="https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
<script data-main="qserv/js/QservMonitoringDashboard.js?bust=69" src="https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
</head>
<body></body>
</html>
27 changes: 27 additions & 0 deletions src/www/qserv/css/QservCzarStatistics.css
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion src/www/qserv/js/Common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
240 changes: 240 additions & 0 deletions src/www/qserv/js/QservCzarStatistics.js
Original file line number Diff line number Diff line change
@@ -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 = `
<div class="row" id="fwk-qserv-czar-stats-controls">
<div class="col">
<div class="form-row">
<div class="form-group col-md-1">
${Common.html_update_ival('update-interval', 10)}
</div>
<div class="form-group col-md-1">
<label for="reset-form">&nbsp;</label>
<button id="reset-form" class="btn btn-primary form-control">Reset</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table class="table table-sm fwk-qserv-czar-stats" id="fwk-qserv-czar-stats-status">
<caption class="updating">Loading...</caption>
</table>
</div>
</div>
<div class="row">
<div class="col">
<h4>Counters</h4>
<table class="table table-sm table-hover fwk-qserv-czar-stats" id="fwk-qserv-czar-stats-counters">
<tbody>` + _.reduce(QservCzarStatistics._counters, function(html, counter) { return html + `
<tr>
<td style="text-align:left" scope="row"><pre>${counter}</pre></td>
<td style="text-align:left"><pre id="${counter}">Loading...</pre></td>
</tr>`; }, '') + `
</tbody>
</table>
</div>
<div class="col">
<h4>QdispPool</h4>
<table class="table table-sm table-hover table-bordered fwk-qserv-czar-stats" id="fwk-qserv-czar-stats-qdisppool">
<thead class="thead-light">
<tr>` + _.reduce(QservCzarStatistics._qdisppool_columns, function(html, column) { return html + `
<th class="sticky" style="text-align:right;">${column}</th>`; }, '') + `
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="row">
<div class="col">
<h4>Timing Histograms</h4>
<table class="table table-sm table-hover table-bordered fwk-qserv-czar-stats" id="fwk-qserv-czar-stats-timing"></table>
</div>
</div>
<div class="row">
<div class="col">
<h4>Data Rates Histograms</h4>
<table class="table table-sm table-hover table-bordered fwk-qserv-czar-stats" id="fwk-qserv-czar-stats-data"></table>
</div>
</div>
`;
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('<span style="color:maroon">' + data.error + '</span>');
}
this._status().removeClass('updating');
this._loading = false;
},
(msg) => {
console.log('request failed', this.fwk_app_name, msg);
this._status().html('<span style="color:maroon">No Response</span>');
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 += `
<tr>` + _.reduce(QservCzarStatistics._qdisppool_columns, function (html, column) { return html + `
<td style="text-align:right;"><pre>${row[column]}</pre></td>`; }, '') + `
</tr>`;
});
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 = `
<thead class="thead-light">
<tr>
<th class="sticky" style="text-align:left;">id</th>
<th class="sticky" style="text-align:right;">total</th>
<th class="sticky" style="text-align:right;">totalCount</th>
<th class="sticky" style="text-align:right;">avg</th>` + _.reduce(histogram.buckets, function (html, bucket) { return html + `
<th class="sticky" style="text-align:right;">${(idx++) == 0 ? "&le;&nbsp;" : ""}${bucket.maxVal}</th>`; }, '') + `
</tr>
</thead>
<tbody>`;

}
html += `
<tr>
<th style="text-align:left;">${histogram.HistogramId}</th>
<td style="text-align:right;"><pre>${histogram.total.toFixed(3)}</pre></td>
<td style="text-align:right;"><pre>${histogram.totalCount}</pre></td>
<td style="text-align:right;"><pre>${histogram.avg.toFixed(3)}</pre></td>` + _.reduce(histogram.buckets, function (html, bucket) { return html + `
<td style="text-align:right;"><pre>${bucket.count}</pre></td>`; }, '') + `
</tr>`;
return html;
}, '') + `
</tbody>`;
}
}
return QservCzarStatistics;
});
3 changes: 3 additions & 0 deletions src/www/qserv/js/QservMonitoringDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ require([
'qserv/StatusWorkers',
'qserv/StatusUserQueries',
'qserv/QservCzarMySQLQueries',
'qserv/QservCzarStatistics',
'qserv/QservCss',
'qserv/QservMySQLConnections',
'qserv/QservWorkerMySQLQueries',
Expand Down Expand Up @@ -82,6 +83,7 @@ function(CSSLoader,
StatusWorkers,
StatusUserQueries,
QservCzarMySQLQueries,
QservCzarStatistics,
QservCss,
QservMySQLConnections,
QservWorkerMySQLQueries,
Expand Down Expand Up @@ -168,6 +170,7 @@ function(CSSLoader,
{ name: 'Czar',
apps: [
new QservCzarMySQLQueries('MySQL Queries'),
new QservCzarStatistics('Statistics'),
new QservCss('CSS')
]
},
Expand Down

0 comments on commit 166a21f

Please sign in to comment.