diff --git a/src/www/qserv/css/QservWorkerFiles.css b/src/www/qserv/css/QservWorkerFiles.css new file mode 100644 index 000000000..6d00c1f38 --- /dev/null +++ b/src/www/qserv/css/QservWorkerFiles.css @@ -0,0 +1,24 @@ +#fwk-qserv-files-controls label { + font-weight: bold; +} +table#fwk-qserv-files caption { + caption-side: top; + text-align: right; + padding-top: 0; +} +table#fwk-qserv-files > thead > tr > th.sticky { + position:sticky; + top:80px; + z-index:2; +} +table#fwk-qserv-files tbody th, +table#fwk-qserv-files tbody td { + vertical-align:middle; +} +table#fwk-qserv-files pre { + padding: 0; + margin: 0; +} +table#fwk-qserv-files caption.updating { + background-color: #ffeeba; +} diff --git a/src/www/qserv/css/QservWorkerResultsFilesystem.css b/src/www/qserv/css/QservWorkerResultsFilesystem.css index 8f1a2669b..37b02f93b 100644 --- a/src/www/qserv/css/QservWorkerResultsFilesystem.css +++ b/src/www/qserv/css/QservWorkerResultsFilesystem.css @@ -22,3 +22,6 @@ table#fwk-qserv-results-filesystem > thead > tr > th.sticky { top:80px; z-index:2; } +table#fwk-qserv-results-filesystem tbody > tr.display-worker-files:hover { + cursor:pointer; +} \ No newline at end of file diff --git a/src/www/qserv/js/Common.js b/src/www/qserv/js/Common.js index cda8f0505..202fb7c8c 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 = 27; + static RestAPIVersion = 28; static query2text(query, expanded) { if (expanded) { return sqlFormatter.format(query, Common._sqlFormatterConfig); @@ -37,6 +37,16 @@ function(sqlFormatter, }, '') + ` `; } + static KB = 1000; + static MB = 1000 * 1000; + static GB = 1000 * 1000 * 1000; + static format_data_rate(v) { + if (v == 0) return v + ""; // as string + else if (v < Common.KB * 10) return v.toFixed(0); + else if (v < Common.MB * 10) return (v / Common.KB).toFixed(0) + " KB"; + else if (v < Common.GB * 10) return (v / Common.MB).toFixed(0) + " MB"; + else return (v / Common.GB).toFixed(0) + " GB"; + } } return Common; }); diff --git a/src/www/qserv/js/QservCzarStatistics.js b/src/www/qserv/js/QservCzarStatistics.js index f61022946..3455f26c6 100644 --- a/src/www/qserv/js/QservCzarStatistics.js +++ b/src/www/qserv/js/QservCzarStatistics.js @@ -269,7 +269,7 @@ function(CSSLoader, if (runTimeSec > 0) { const perf = data.qdisp_stats[counter] / runTimeSec; if (QservCzarStatistics._totals_data_rate.has(counter)) { - that._set_counter_perf('totals', counter, QservCzarStatistics._format_data_rate(perf), '_sum'); + that._set_counter_perf('totals', counter, Common.format_data_rate(perf), '_sum'); } else { that._set_counter_perf('totals', counter, perf.toFixed(0), '_sum'); } @@ -281,7 +281,7 @@ function(CSSLoader, if (deltaT > 0) { const perf = deltaVal / deltaT; if (QservCzarStatistics._totals_data_rate.has(counter)) { - that._set_counter_perf('totals', counter, QservCzarStatistics._format_data_rate(perf)); + that._set_counter_perf('totals', counter, Common.format_data_rate(perf)); } else { that._set_counter_perf('totals', counter, perf.toFixed(0)); } @@ -366,26 +366,16 @@ function(CSSLoader, }, '') + ` `; } - static _KB = 1000; - static _MB = 1000 * 1000; - static _GB = 1000 * 1000 * 1000; static _format_bucket_limit(v, data_rate=false) { if (isNaN(v)) return v; if (data_rate) { - if (v < QservCzarStatistics._KB) return v + " B/s"; - else if (v < QservCzarStatistics._MB) return (v / QservCzarStatistics._KB).toFixed(0) + " KB/s"; - else if (v < QservCzarStatistics._GB) return (v / QservCzarStatistics._MB).toFixed(0) + " MB/s"; - return (v / QservCzarStatistics._GB).toFixed(0) + " GB/s"; + if (v < Common.KB) return v + " B/s"; + else if (v < Common.MB) return (v / Common.KB).toFixed(0) + " KB/s"; + else if (v < Common.GB) return (v / Common.MB).toFixed(0) + " MB/s"; + return (v / Common.GB).toFixed(0) + " GB/s"; } return v.toLocaleString(); } - static _format_data_rate(v) { - if (v == 0) return v + ""; // as string - else if (v < QservCzarStatistics._KB * 10) return v.toFixed(0); - else if (v < QservCzarStatistics._MB * 10) return (v / QservCzarStatistics._KB).toFixed(0) + " KB"; - else if (v < QservCzarStatistics._GB * 10) return (v / QservCzarStatistics._MB).toFixed(0) + " MB"; - else return (v / QservCzarStatistics._GB).toFixed(0) + " GB"; - } /** * @param {Number} seconds diff --git a/src/www/qserv/js/QservMonitoringDashboard.js b/src/www/qserv/js/QservMonitoringDashboard.js index 8e3e3ff03..f8de34858 100644 --- a/src/www/qserv/js/QservMonitoringDashboard.js +++ b/src/www/qserv/js/QservMonitoringDashboard.js @@ -56,6 +56,7 @@ require([ 'qserv/QservWorkerTasks', 'qserv/QservWorkerTaskHist', 'qserv/QservWorkerResultsFilesystem', + 'qserv/QservWorkerFiles', 'qserv/QservWorkerConfig', 'qserv/ReplicationController', 'qserv/ReplicationTools', @@ -101,6 +102,7 @@ function(CSSLoader, QservWorkerTasks, QservWorkerTaskHist, QservWorkerResultsFilesystem, + QservWorkerFiles, QservWorkerConfig, ReplicationController, ReplicationTools, @@ -175,6 +177,7 @@ function(CSSLoader, new QservWorkerTasks('Tasks'), new QservWorkerTaskHist('Task Histograms'), new QservWorkerResultsFilesystem('Results Filesystem'), + new QservWorkerFiles('Files'), new QservWorkerConfig('Config') ] }, diff --git a/src/www/qserv/js/QservWorkerFiles.js b/src/www/qserv/js/QservWorkerFiles.js new file mode 100644 index 000000000..bf8825fcd --- /dev/null +++ b/src/www/qserv/js/QservWorkerFiles.js @@ -0,0 +1,302 @@ +define([ + 'webfwk/CSSLoader', + 'webfwk/Fwk', + 'webfwk/FwkApplication', + 'qserv/Common', + 'underscore'], + +function(CSSLoader, + Fwk, + FwkApplication, + Common, + _) { + + CSSLoader.load('qserv/css/QservWorkerFiles.css'); + + class QservWorkerFiles 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(); + } + } + } + set_worker(worker) { + this._init(); + this._load(worker); + } + _init() { + if (this._initialized === undefined) this._initialized = false; + if (this._initialized) return; + this._initialized = true; + let html = ` +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ ${Common.html_update_ival('update-interval', 10)} +
+
+ + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
QIDchunkjobattemptfilenamesizes-1createdmodifiedinspected
Loading...
+
+
`; + let cont = this.fwk_app_container.html(html); + cont.find(".form-control-selector").change(() => { + this._load(); + }); + cont.find("button#reset-files-form").click(() => { + this._set_query(''); + this._set_max_files(200); + 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); } + _query() { return this._form_control('select', 'query').val(); } + _set_query(val) { this._form_control('select', 'query').val(val); } + _set_queries(queries) { + const prev_query = this._query(); + console.log("prev_query", prev_query, "queries", queries); + let html = ''; + for (let i in queries) { + const query = queries[i]; + const selected = (!_.isEmpty(prev_query) && (prev_query == query)); + html += ` +`; + } + this._form_control('select', 'query').html(html); + } + _max_files() { return this._form_control('select', 'max-files').val(); } + _set_max_files(val) { this._form_control('select', 'max-files').val(val); } + _set_num_files(total, selected, displayed) { this._form_control('input', 'num-files').val(displayed + ' / ' + selected + ' / ' + total); } + _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 += ` + `; + } + this._form_control('select', 'worker').html(html); + } + + /** + * Table for displaying files that exist at the worker. + */ + _table() { + if (this._table_obj === undefined) { + this._table_obj = this.fwk_app_container.find('table#fwk-qserv-files'); + } + return this._table_obj; + } + _load(worker = undefined) { + 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); + if (!_.isUndefined(worker)) this._set_worker(worker); + this._load_files(); + }, + (msg) => { + console.log('request failed', this.fwk_app_name, msg); + this._table().children('caption').html('No Response'); + this._table().children('caption').removeClass('updating'); + this._loading = false; + } + ); + } + _load_files() { + Fwk.web_service_GET( + "/replication/qserv/worker/files/" + this._worker(), + { timeout_sec: 2, version: Common.RestAPIVersion, + query_ids: this._query(), + max_files: this._max_files() + }, + (data) => { + if (data.success) { + this._display(data.status); + Fwk.setLastUpdate(this._table().children('caption')); + } else { + console.log('request failed', this.fwk_app_name, data.error); + this._table().children('caption').html('' + data.error + ''); + } + this._table().children('caption').removeClass('updating'); + this._loading = false; + }, + (msg) => { + console.log('request failed', this.fwk_app_name, msg); + this._table().children('caption').html('No Response'); + this._table().children('caption').removeClass('updating'); + this._loading = false; + } + ); + } + _display(status) { + // Update a collection of queries in the selector. + const query_ids = _.uniq(_.map(status.files, function(file) { return file.task.query_id; })); + query_ids.sort(); + this._set_queries(query_ids); + // Turn the collection of files into a dictionary + const files = _.groupBy(status.files, function(file) { return file.task.query_id; }); + console.log(files); + const queryInspectTitle = "Click to see detailed info (progress, messages, etc.) on the query."; + + // Display files + let html = ''; + for (let queryId in files) { + const queryFiles = _.sortBy(files[queryId], function(file) { return file.task.chunk_id; }); + console.log(queryFiles); + let rowspan = 1; + let htmlFiles = ''; + for (let i in queryFiles) { + const file = queryFiles[i]; + const createTime_msec = file.ctime * 1000; + const modifyTime_msec = file.mtime * 1000; + const snapshotTime_msec = file.current_time_ms; + htmlFiles += ` + +
${file.task.chunk_id}
+
${file.task.job_id}
+
${file.task.attemptcount}
+
${file.filename}
+
${file.size}
+
${QservWorkerFiles._io_performance(file.size, file.ctime, file.mtime)}
+
${(new Date(createTime_msec)).toISOString()}
+
${QservWorkerFiles._timestamps_diff2str(createTime_msec, modifyTime_msec)}
+
${QservWorkerFiles._timestamp2hhmmss(modifyTime_msec)}
+
${QservWorkerFiles._timestamps_diff2str(modifyTime_msec, snapshotTime_msec)}
+
${QservWorkerFiles._timestamp2hhmmss(snapshotTime_msec)}
+`; + rowspan++; + } + html += ` + +
${queryId}
+ + + +` + htmlFiles; + + } + let tbody = this._table().children('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); + this._set_num_files(status.num_total, status.num_selected, status.files.length); + } + static _timestamps_diff2str(begin, end, snapshot) { + return ((end - begin) / 1000.0).toFixed(1); + } + static _timestamp2hhmmss(ts) { + return (new Date(ts)).toISOString().substring(11, 19); + } + static _io_performance(size, begin_time_sec, end_time_sec) { + let bytes_per_sec = size; + let interval = end_time_sec - begin_time_sec; + if (interval > 0) bytes_per_sec = size / interval; + return Common.format_data_rate(bytes_per_sec); + } + } + return QservWorkerFiles; +}); diff --git a/src/www/qserv/js/QservWorkerResultsFilesystem.js b/src/www/qserv/js/QservWorkerResultsFilesystem.js index 560feb8e2..712b422a5 100644 --- a/src/www/qserv/js/QservWorkerResultsFilesystem.js +++ b/src/www/qserv/js/QservWorkerResultsFilesystem.js @@ -139,6 +139,7 @@ function(CSSLoader, * Display MySQL connections */ _display(data) { + const workerFilesInspectTitle = "Click to see files existing on the worker."; let html = ''; for (let worker in data) { if (!data[worker].success) { @@ -160,7 +161,7 @@ function(CSSLoader, (100.0 * (filesystem.capacity_bytes - filesystem.available_bytes) / filesystem.capacity_bytes).toFixed(1) : -1; html += ` - + ${worker} ${filesystem.protocol} ${filesystem.folder} @@ -173,7 +174,13 @@ function(CSSLoader, `; } } - this._table().children('tbody').html(html); + let tbody = this._table().children('tbody').html(html); + let displayWorkerFiles = function(e) { + const worker = $(e.currentTarget).attr("worker"); + Fwk.find("Workers", "Files").set_worker(worker); + Fwk.show("Workers", "Files"); + }; + tbody.find("tr.display-worker-files").click(displayWorkerFiles); } static _GiB = 1024 * 1024 * 1024; static _bytes2gb(bytes) {