| // Copyright 2015 The Goma Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| // Add starts with. |
| if (!String.prototype.startsWith) { |
| /** |
| * Returns true if the string starts with searchString. |
| * @param {string} searchString The characters to be searched for at the start |
| * of this string. |
| * @param {number} position Optional. The position in this string at which to |
| * begin searching for searchString; defaults to 0. |
| * @return {bool} True if the string starts with searchString. Otherwise false. |
| */ |
| String.prototype.startsWith = function(searchString, position) { |
| position = position || 0; |
| return this.lastIndexOf(searchString, position) === position; |
| }; |
| } |
| |
| /** |
| * Human readable bytes. |
| * @param{Number} bytes. |
| * @return{string} human readable bytes. |
| */ |
| function humanReadableBytes(num) { |
| var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; |
| if (num == 0) { |
| return '0'; |
| } |
| var i = parseInt(Math.floor(Math.log(num) / Math.log(1024))); |
| if (i >= sizes.length) { |
| i = sizes.length - 1; |
| } |
| return (Math.round(num / Math.pow(1024, i) * 100) / 100) + sizes[i]; |
| }; |
| |
| /* Returns a basename of path. For example: |
| * C:\Program Files (x86)\VC\bin\cl.exe --> cl.exe |
| * /usr/bin/clang++ --> clang++ |
| * |
| * @param {string} path path. |
| * @return {string} basename of path. |
| */ |
| function basename(path) { |
| var splitted = path.split(/[\/\\\\]/) |
| return splitted[splitted.length - 1]; |
| } |
| |
| function makeFlagSummary(flag) { |
| if (!flag) |
| return ''; |
| |
| var args = flag.split(' '); |
| |
| var compiler = basename(args[0]); |
| |
| var target = ''; |
| for (var i = 0; i < args.length; ++i) { |
| // Catches '-o <filename>', '/Fe <filename>', or '/Fo <filename>' cases. |
| if (args[i] == '-o' || args[i] == '/Fe' || args[i] == '/Fo') { |
| if (i + 1 < args.length) { |
| target = basename(args[i + 1]); |
| break; |
| } |
| } |
| |
| // Catches '-o<filename>' case. |
| if (args[i].startsWith('-o')) { |
| target = basename(args[i].substring(2)); |
| break; |
| } |
| |
| // Catches '/Fe<filename>', or '/Fo<filename>' cases. |
| if (args[i].startsWith('/Fe') || args[i].startsWith('/Fo')) { |
| target = basename(args[i].substring(3)); |
| break; |
| } |
| } |
| |
| return compiler + ' ... ' + target; |
| } |
| |
| /** |
| * Gets "task" status. |
| * @param {Object} task task object. |
| * @return {string} task status |
| */ |
| function taskStatus(task) { |
| var success = true; |
| if ('exit' in task) { |
| if (task['exit'] != 0) { |
| success = false; |
| } |
| } else if ('http' in task) { |
| if (task['http'] != 200) { |
| success = false; |
| } |
| } else { |
| // if no http, it would be running. |
| return 'running'; |
| } |
| if (task['canceled']) { |
| return 'cancel'; |
| } |
| if (task['goma_error'] || task['compiler_proxy_error']) { |
| return 'gomaerror'; |
| } |
| if (!success) { |
| if (task['flag'] && task['flag'].match(/ conftest\./)) { |
| return 'conftestfailure'; |
| } |
| return 'failure'; |
| } |
| if (task['fail_fallback']) { |
| return 'failure'; |
| } |
| |
| if (task['command_version_mismatch'] |
| || task['command_binary_hash_mismatch'] |
| || task['command_subprograms_mismatch']) { |
| return 'mismatch'; |
| } |
| |
| if (task['state'] != 'FINISHED') { |
| return 'local-fallback'; |
| } |
| if (task['retry'] > 0) { |
| return 'retry'; |
| } |
| if (task['cache'] == 'local hit') { |
| return 'local-cachehit'; |
| } |
| if (task['cache'] == 'hit') { |
| return 'cachehit'; |
| } |
| return 'success'; |
| } |
| |
| // Returns true if task has compiler mismatch. |
| function taskHasMismatch(task) { |
| var keys = ['command_version_mismatch', |
| 'command_binary_hash_mismatch', |
| 'command_subprograms_mismatch']; |
| for (var i = 0; i < keys.length; ++i) { |
| var key = keys[i]; |
| if ((key in task) && task[key] != "") { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| function taskHasGomaccRevisionMismatch(task) { |
| if ('gomacc_revision_mismatch' in task && |
| task['gomacc_revision_mismatch'] != "") { |
| return true; |
| } |
| |
| return false; |
| } |
| // ---------------------------------------------------------------------- |
| |
| function GomaTaskView() { |
| this.currentTaskView = 'active'; |
| this.taskStartingOffset = 0; |
| this.taskMaxSize = 25; |
| this.tasks = { |
| active: [], |
| finished: [], |
| failed: [], |
| long: [] |
| }; |
| // id -> Task |
| this.finishedTasks = {}, |
| this.failedTasks = {}, |
| // id -> Task |
| this.taskDetailCache = {}, |
| this.currentDetailTaskId = 0; |
| this.currentTaskOrderKey = null; |
| this.currentTaskOrderAscending = true; |
| |
| // For Task stats |
| this.maxActives = 0; |
| } |
| |
| GomaTaskView.prototype = { |
| setPageSize: function(size) { |
| this.taskMaxSize = size; |
| }, |
| |
| updateTaskView: function() { |
| var currentTasks = this._filteredTasks(); |
| this._updateTaskViewWith(currentTasks); |
| }, |
| |
| // Update compiler mismatch view. |
| // We don't filter tasks here. |
| updateMismatchView: function() { |
| var compilerMismatchDetected = false; |
| var gomaccRevisionMismatchDetected = false; |
| var mismatchedTaskIdSet = {}; |
| for (var i = 0; i < this.tasks.finished.length; ++i) { |
| var task = this.tasks.finished[i]; |
| if (taskHasMismatch(task)) { |
| compilerMismatchDetected = true; |
| mismatchedTaskIdSet[task.id] = true; |
| } |
| if (taskHasGomaccRevisionMismatch(task)) { |
| gomaccRevisionMismatchDetected = true; |
| } |
| } |
| for (var i = 0; i < this.tasks.failed.length; ++i) { |
| var task = this.tasks.failed[i]; |
| if (taskHasMismatch(task)) { |
| compilerMismatchDetected = true; |
| mismatchedTaskIdSet[task.id] = true; |
| } |
| if (taskHasGomaccRevisionMismatch(task)) { |
| gomaccRevisionMismatchDetected = true; |
| } |
| } |
| |
| var mismatchArea = $('#mismatch'); |
| if (!(compilerMismatchDetected || gomaccRevisionMismatchDetected)) { |
| mismatchArea.hide(); |
| return; |
| } |
| |
| mismatchArea.empty(); |
| |
| if (gomaccRevisionMismatchDetected) { |
| $('<div>revision between gomacc and compiler_proxy ' + |
| 'is mismatched. Probably goma has been updated. ' + |
| 'Restart compiler_proxy.</div>').appendTo(mismatchArea); |
| } |
| |
| if (compilerMismatchDetected) { |
| var mismatchedTaskIds = []; |
| for (var id in mismatchedTaskIdSet) { |
| mismatchedTaskIds.push(id); |
| } |
| mismatchedTaskIds.sort(function(lhs, rhs) { return lhs - rhs; }); |
| |
| $('<div>Compiler Mismatch</div>').appendTo(mismatchArea); |
| for (var i = 0; i < mismatchedTaskIds.length; ++i) { |
| var id = mismatchedTaskIds[i]; |
| $('<a>').attr('href', '#task' + id).text(id + "") |
| .appendTo(mismatchArea); |
| mismatchArea.append(' '); |
| } |
| } |
| |
| mismatchArea.show(); |
| }, |
| |
| updateTaskStats: function() { |
| var resp = this.resp; |
| var numActives = gomaTaskView.tasks['active'].length; |
| |
| if (numActives > this.maxActives) { |
| this.maxActives = numActives; |
| } |
| |
| function setTextAndMeter(id, value, maxValue) { |
| if (maxValue < 1) { |
| maxValue = 1; |
| } |
| |
| $(id + ' .text').text(value); |
| $(id + ' meter').attr('value', value) |
| .attr('min', 0) |
| .attr('max', maxValue) |
| .text((100 * value / maxValue) + '%'); |
| } |
| |
| var taskMeterMax = this.maxActives < 100 ? 100 : this.maxActives; |
| $('#task-stats-num-limit').text(resp['num_exec']['max_active_tasks']); |
| setTextAndMeter('#task-stats-num-max', this.maxActives, taskMeterMax); |
| setTextAndMeter('#task-stats-num-actives', numActives, taskMeterMax); |
| setTextAndMeter('#task-stats-num-pendings', resp['num_exec']['pending'], taskMeterMax); |
| |
| var requestMeterMax = resp['num_exec']['request'] < 1 ? 1 : resp['num_exec']['request']; |
| setTextAndMeter('#task-stats-request-total', resp['num_exec']['request'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-success', resp['num_exec']['success'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-failure', resp['num_exec']['failure'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-success-finished', resp['num_exec']['goma_finished'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-success-cache-hit', resp['num_exec']['goma_cache_hit'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-success-aborted', resp['num_exec']['goma_aborted'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-success-retry', resp['num_exec']['goma_retry'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-local-run', resp['num_exec']['local_run'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-local-finished', resp['num_exec']['local_finished'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-local-killed', resp['num_exec']['local_killed'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-fail-fallback', resp['num_exec']['fail_fallback'], requestMeterMax); |
| setTextAndMeter('#task-stats-request-compiler-proxy-fail', resp['num_exec']['compiler_proxy_fail'], requestMeterMax); |
| |
| var gomaMeterMax = resp['num_exec']['goma_finished'] + resp['num_exec']['goma_aborted']; |
| setTextAndMeter('#task-stats-goma-killed', resp['num_exec']['local_killed'], gomaMeterMax); |
| setTextAndMeter('#task-stats-goma-aborted', resp['num_exec']['goma_aborted'], gomaMeterMax); |
| |
| var localMeterMax = resp['num_exec']['local_run']; |
| setTextAndMeter('#task-stats-local-killed', resp['num_exec']['local_killed'], localMeterMax); |
| setTextAndMeter('#task-stats-local-aborted', resp['num_exec']['goma_aborted'], localMeterMax); |
| |
| var raceMeterMax = resp['num_exec']['local_killed'] + resp['num_exec']['goma_aborted']; |
| setTextAndMeter('#task-stats-compiler-race-total', raceMeterMax, raceMeterMax); |
| setTextAndMeter('#task-stats-compiler-race-goma-win', resp['num_exec']['local_killed'], raceMeterMax); |
| setTextAndMeter('#task-stats-compiler-race-local-win', resp['num_exec']['goma_aborted'], raceMeterMax); |
| |
| var compilerInfoMeterMax = resp['num_exec']['compiler_info_stores']; |
| setTextAndMeter('#task-stats-compiler-info-stores', resp['num_exec']['compiler_info_stores'], compilerInfoMeterMax); |
| setTextAndMeter('#task-stats-compiler-info-store-dups', resp['num_exec']['compiler_info_store_dups'], compilerInfoMeterMax); |
| setTextAndMeter('#task-stats-compiler-info-miss', resp['num_exec']['compiler_info_miss'], compilerInfoMeterMax); |
| setTextAndMeter('#task-stats-compiler-info-fail', resp['num_exec']['compiler_info_fail'], compilerInfoMeterMax); |
| |
| var fileInfoMeterMax = resp['num_file']['requested']; |
| setTextAndMeter('#task-stats-file-total', resp['num_file']['requested'], fileInfoMeterMax); |
| setTextAndMeter('#task-stats-file-uploaded', resp['num_file']['uploaded'], fileInfoMeterMax); |
| setTextAndMeter('#task-stats-file-missed', resp['num_file']['missed'], fileInfoMeterMax); |
| }, |
| |
| updateNetworkStats: function() { |
| var httprpc = this.resp['http_rpc']; |
| |
| $('#http-rpc-qps').attr('src', httprpc['qps_chart']); |
| $('#http-rpc-traffic').attr('src', httprpc['traffic_chart']); |
| |
| $('#rpc-health-status').text(httprpc['health_status']); |
| |
| $('#rpc-num-active').text(httprpc['num_active']); |
| $('#rpc-num-query').text(httprpc['num_query']); |
| $('#rpc-num-retry').text(httprpc['num_http_retry']); |
| $('#rpc-num-timeout').text(httprpc['num_http_timeout']); |
| $('#rpc-num-error').text(httprpc['num_http_error']); |
| |
| $('#rpc-socket-pool').text(httprpc['socket_pool']); |
| $('#rpc-read-bps').text(humanReadableBytes(httprpc['read_bps'])); |
| $('#rpc-read-byte').text(humanReadableBytes(httprpc['read_byte'])); |
| $('#rpc-write-bps').text(humanReadableBytes(httprpc['write_bps'])); |
| $('#rpc-write-byte').text(humanReadableBytes(httprpc['write_byte'])); |
| |
| $('#rpc-compression').text(httprpc['compression']); |
| $('#rpc-accept-encoding').text(httprpc['accept_encoding']); |
| $('#rpc-authorization').text(httprpc['authorization']); |
| $('#rpc-cookie').text(httprpc['cookie']); |
| $('#rpc-content-type').text(httprpc['content_type']); |
| $('#rpc-oauth2').text(httprpc['oauth2']); |
| $('#rpc-capture-response-header').text(httprpc['capture_response_header']); |
| $('#rpc-ssl').text(httprpc['ssl']); |
| |
| $('#rpc-url-path-prefix').text(httprpc['url_path_prefix']); |
| $('#rpc-extra-params').text(httprpc['extra_params']); |
| $('#rpc-ssl-extra-cert').text(httprpc['ssl_extra_cert']); |
| |
| $('#rpc-user-agent').text(httprpc['user_agent']); |
| |
| if (httprpc['health_status'] != 'ok') { |
| $('#http-rpc-info').addClass('warning'); |
| } else { |
| $('#http-rpc-info').removeClass('warning'); |
| } |
| }, |
| |
| setTaskPositionFirst: function() { |
| var currentTasks = this._filteredTasks(); |
| this.taskStartingOffset = 0; |
| this._updateTaskViewWith(currentTasks); |
| }, |
| |
| setTaskPositionPrev: function() { |
| var currentTasks = this._filteredTasks(); |
| this.taskStartingOffset -= this.taskMaxSize; |
| if (currentTasks.length < this.taskStartingOffset) |
| this.taskStartingOffset = 0; |
| if (this.taskStartingOffset < 0) |
| this.taskStartingOffset = 0; |
| this._updateTaskViewWith(currentTasks); |
| }, |
| |
| setTaskPositionNext: function() { |
| var currentTasks = this._filteredTasks(); |
| this.taskStartingOffset += this.taskMaxSize; |
| if (this.taskStartingOffset >= currentTasks.length) { |
| this.taskStartingOffset = currentTasks.length - this.taskMaxSize; |
| } |
| if (this.taskStartingOffset < 0) { |
| this.taskStartingOffset = 0; |
| } |
| this._updateTaskViewWith(currentTasks); |
| }, |
| |
| setTaskPositionLast: function() { |
| var currentTasks = this._filteredTasks(); |
| this.taskStartingOffset = currentTasks.length - this.taskMaxSize; |
| if (this.taskStartingOffst < 0) |
| this.taskStartingOffset = 0; |
| this._updateTaskViewWith(currentTasks); |
| }, |
| |
| // Changes task order. |
| changeTaskOrder: function(key) { |
| if (this.currentTaskOrderKey != key) { |
| // Setting New key |
| this.currentTaskOrderKey = key; |
| this.currentTaskOrderAscending = true; |
| } else if (this.currentTaskOrderAscending) { |
| // Setting the same key. Changing the order. |
| this.currentTaskOrderAscending = false; |
| } else { |
| // Setting the same key twice. Remove the key. |
| this.currentTaskOrderKey = null; |
| } |
| |
| this._updateTaskOrderView(); |
| }, |
| |
| showTaskDetail: function(taskId) { |
| console.log('showTaskDetail: ' + taskId); |
| |
| if (taskId < 0) |
| return; |
| |
| this.currentDetailTaskId = taskId; |
| |
| var xhr = new XMLHttpRequest(); |
| xhr.onreadystatechange = (function(that) { return function() { |
| if (this.readyState == 4) { |
| var responseText = this.responseText; |
| this.onreadystatechange = null; |
| if (this.status != 200) { |
| if (taskId in that.taskDetailCache) |
| that._showTaskDetailWith(that.taskDetailCache[taskId]); |
| else |
| that._showEmptyTaskDetailWith(taskId); |
| return; |
| } |
| |
| var detail = JSON.parse(responseText); |
| that._showTaskDetailWith(detail); |
| |
| // Cache the detail only when it's finished. |
| if (that.finishedTasks[taskId]) |
| that.taskDetailCache[taskId] = detail; |
| } |
| };})(this); |
| |
| var url = taskUpdater.url + '?id=' + taskId; |
| xhr.open('POST', url); |
| xhr.send(); |
| }, |
| |
| // Returns the list of tasks that are filtered. |
| _filteredTasks: function() { |
| var currentTasks = this.tasks[this.currentTaskView]; |
| var result = []; |
| |
| var checker = {}; |
| $('#task-filter input').each(function() { |
| if (this.checked) |
| checker[this.name] = this.checked; |
| }); |
| |
| for (var i = 0; i < currentTasks.length; ++i) { |
| var task = currentTasks[i]; |
| var className = 'task-status-' + taskStatus(task); |
| if (className in checker) { |
| result.push(task); |
| } |
| } |
| |
| // Sorts here. |
| if (this.currentTaskOrderKey != null) { |
| var sortKey = this.currentTaskOrderKey.replace('-', '_'); |
| |
| var compare = function(v1, v2, ascending) { |
| if (v1 == v2) { |
| return 0; |
| } |
| |
| if (v1 && v2) { |
| if (v1 < v2) { |
| return -ascending; |
| } |
| return ascending; |
| } |
| if (!v2) { |
| return ascending; |
| } |
| return -ascending; |
| } |
| |
| var sortFunc = (function(ascending) { |
| return function(a, b) { |
| var v1 = a[sortKey]; |
| var v2 = b[sortKey]; |
| return compare(v1, v2, ascending); |
| }; |
| })(this.currentTaskOrderAscending ? 1 : -1); |
| |
| // When key is 'time', if 'time' is missing, we should use 'elapsed' |
| // instead. |
| if (sortKey == 'time') { |
| sortFunc = (function(ascending) { |
| return function(a, b) { |
| var v1 = a['time'] || a['elapsed']; |
| var v2 = b['time'] || b['elapsed']; |
| return compare(v1, v2, ascending); |
| }; |
| })(this.currentTaskOrderAscending ? 1 : -1); |
| } |
| |
| result.sort(sortFunc); |
| } |
| |
| return result; |
| }, |
| |
| _updateTaskOrderView: function() { |
| $('.task-summary-head').removeClass('with-icon-ascending'); |
| $('.task-summary-head').removeClass('with-icon-descending'); |
| if (this.currentTaskOrderKey == null) |
| return; |
| |
| var id = 'task-summary-head-' + this.currentTaskOrderKey; |
| var elem = $('#' + id); |
| if (this.currentTaskOrderAscending) { |
| elem.addClass('with-icon-ascending'); |
| } else { |
| elem.addClass('with-icon-descending'); |
| } |
| }, |
| |
| _updateTaskViewWith: function(currentTasks) { |
| var taskSummaryList = $('#task-summary-list'); |
| taskSummaryList.empty(); |
| |
| var startPos = this.taskStartingOffset; |
| var endPos = startPos + this.taskMaxSize; |
| if (startPos < 0) |
| startPos = 0; |
| if (endPos >= currentTasks.length) |
| endPos = currentTasks.length; |
| if (endPos < startPos) |
| endPos = startPos; |
| |
| for (var i = startPos; i < endPos; ++i) { |
| var task = currentTasks[i]; |
| |
| var tr = $('<tr>'); |
| var taskStatusName = taskStatus(task); |
| tr.addClass('task-status-' + taskStatusName); |
| $('<td class="task-summary-id">').text(task.id).appendTo(tr); |
| if ('time' in task) { |
| $('<td class="task-summary-time">').text(task.time).appendTo(tr); |
| } else if ('elapsed' in task) { |
| $('<td class="task-summary-time">').text(task.elapsed).appendTo(tr); |
| } else { |
| $('<td class="task-sumamry-time">').appendTo(tr); |
| } |
| $('<td class="task-summary-pid">').text(task.pid).appendTo(tr); |
| $('<td class="task-summary-state">').text(task.state).appendTo(tr); |
| $('<td class="task-summary-status">').text(taskStatusName.toUpperCase().replace('-', ' ')).appendTo(tr); |
| $('<td class="task-summary-subproc-pid">').text(task.subproc_pid).appendTo(tr); |
| $('<td class="task-summary-subproc-state">').text(task.subproc_state).appendTo(tr); |
| // TODO: Show full flag when a cursor is hovered? |
| // It will take a big area, though... b/24883527 |
| $('<td class="task-summary-flag">').text(makeFlagSummary(task.flag)).appendTo(tr); |
| $('<td class="task-summary-major-factor">').text(task.major_factor).appendTo(tr); |
| |
| tr.click((function(taskId) { |
| return function() { |
| location.hash = '#task' + taskId; |
| }; |
| })(task.id)); |
| tr.css({'cursor': 'pointer'}); |
| taskSummaryList.append(tr); |
| } |
| |
| $('#task-summary-total').text(currentTasks.length); |
| $('#task-summary-offset-begin').text(startPos + 1); |
| $('#task-summary-offset-end').text(endPos); |
| |
| if (startPos == 0) { |
| $('#task-summary-first').addClass('disabled'); |
| $('#task-summary-prev').addClass('disabled'); |
| } else { |
| $('#task-summary-first').removeClass('disabled'); |
| $('#task-summary-prev').removeClass('disabled'); |
| } |
| |
| if (endPos >= currentTasks.length) { |
| $('#task-summary-next').addClass('disabled'); |
| $('#task-summary-last').addClass('disabled'); |
| } else { |
| $('#task-summary-next').removeClass('disabled'); |
| $('#task-summary-last').removeClass('disabled'); |
| } |
| }, |
| |
| // Just show task id if task is empty. |
| _showEmptyTaskDetailWith: function(taskId) { |
| var task = { |
| id: taskId, |
| }; |
| |
| this._showTaskDetailWith(task); |
| }, |
| |
| _showTaskDetailWith: function(task) { |
| var table = $('#task-detail-table'); |
| table.empty(); |
| |
| function addTextItem(key, value) { |
| var tr = $('<tr>'); |
| $('<td>').text(key).appendTo(tr); |
| $('<td>').text(value).appendTo(tr); |
| table.append(tr); |
| return true; |
| } |
| |
| function addHTMLItem(key, value) { |
| var tr = $('<tr>'); |
| $('<td>').text(key).appendTo(tr); |
| $('<td>').html(value).appendTo(tr); |
| table.append(tr); |
| |
| return true; |
| } |
| |
| function addLink(key) { |
| if (!(key in task)) |
| return false; |
| |
| var value = task[key]; |
| var tr = $('<tr>'); |
| $('<td>').text(key).appendTo(tr); |
| $('<td>').html($('<a>').attr('href', value).text(value)).appendTo(tr); |
| |
| table.append(tr); |
| return true; |
| } |
| |
| function addLineBreak() { |
| var tr = $('<tr>'); |
| tr.addClass('task-linebreak'); |
| $('<td>').text(''); |
| $('<td>').text(''); |
| table.append(tr); |
| } |
| |
| function add(key, opt_suffix) { |
| if (!(key in task)) |
| return false; |
| |
| var value = task[key]; |
| if (opt_suffix) |
| value += opt_suffix; |
| |
| return addTextItem(key, value); |
| } |
| |
| function addLongString(key, opt_usingPre) { |
| if (!(key in task)) |
| return false; |
| |
| var value = task[key]; |
| |
| var details = $('<details>'); |
| $('<summary>see more ...</summary>').appendTo(details); |
| if (opt_usingPre) { |
| $('<pre>').text(value).appendTo(details); |
| } else { |
| $('<div>').text(value).appendTo(details); |
| } |
| |
| return addHTMLItem(key, details); |
| } |
| |
| function addArrayItem(key) { |
| if (!(key in task)) |
| return false; |
| |
| var values = task[key]; |
| var value = values.join('<br>'); |
| |
| var details = $('<details>'); |
| $('<summary>see more...</summary>').appendTo(details); |
| $('<div>').html(value).appendTo(details); |
| |
| return addHTMLItem(key, details); |
| } |
| |
| add('id'); |
| add('elapsed'); |
| add('time'); |
| add('pid'); |
| add('state'); |
| add('subproc_state'); |
| add('subproc_pid'); |
| addLongString('flag'); |
| add('major_factor'); |
| add('command_version_mismatch'); |
| add('command_binary_hash_mismatch'); |
| add('command_subprograms_mismatch'); |
| addLineBreak(); |
| |
| add('start_time'); |
| addLineBreak(); |
| |
| if (addArrayItem('error_message')) { |
| addLineBreak(); |
| } |
| |
| add('cache_key'); |
| add('http'); |
| add('exit'); |
| add('retry'); |
| addLineBreak(); |
| if (addArrayItem('exec_request_retry_reason')) { |
| addLineBreak(); |
| } |
| |
| // # of files |
| add('total_input'); |
| add('uploading_input'); |
| add('missing_input'); |
| addLineBreak(); |
| |
| // size |
| add('gcc_req_size'); |
| add('gcc_resp_size'); |
| add('exec_req_size'); |
| add('exec_resp_size'); |
| add('output_file_size'); |
| add('chunk_resp_size'); |
| addLineBreak(); |
| |
| /* time */ |
| add('compiler_info_process_time'); |
| if (('depscache_used' in task) && task['depscache_used'] == 'true') { |
| add('include_preprocess_time', ' (cached)'); |
| } else { |
| add('include_preprocess_time'); |
| } |
| add('include_fileload_time'); |
| add('include_fileload_pending_time'); |
| add('include_fileload_run_time'); |
| add('rpc_call_time'); |
| add('file_response_time'); |
| addLineBreak(); |
| |
| |
| /* file_response_time break down */ |
| add('output_file_rpc'); |
| add('output_file_rpc_req_build_time'); |
| add('output_file_rpc_req_send_time'); |
| add('output_file_rpc_wait_time'); |
| add('output_file_rpc_resp_recv_time'); |
| add('output_file_rpc_resp_parse_time'); |
| addLineBreak(); |
| |
| /* local run */ |
| if (('local_pending_ms' in task) || 'local_run_ms' in task) { |
| add('local_pending_ms'); |
| add('local_run_ms'); |
| add('local_mem_kb'); |
| add('local_output_file_time'); |
| add('local_output_file_size'); |
| add('local_run_reason'); |
| addLineBreak(); |
| } |
| |
| /* command detail */ |
| add('cwd'); |
| addLineBreak(); |
| if (add('orig_flag')) { |
| addLineBreak(); |
| } |
| if (addArrayItem('env')) { |
| addLineBreak(); |
| } |
| addArrayItem('exec_output_file'); |
| addLineBreak(); |
| if (addLongString('stdout', true)) { |
| addLineBreak(); |
| } |
| if (addLongString('stderr', true)) { |
| addLineBreak(); |
| } |
| addArrayItem('inputs'); |
| addArrayItem('system_library_paths'); |
| add('response_header'); |
| |
| if (('state' in task) && task['state'] === 'FINISHED') { |
| var tr = $('<tr>'); |
| var exec_req_dump_url = |
| './api/taskz?id=' + encodeURIComponent(task['id']) + '&dump=req'; |
| $('<td>').text('dump_exec_req').appendTo(tr); |
| |
| var form = $('<form>') |
| .attr('method', 'POST') |
| .attr('action', exec_req_dump_url) |
| .html($('<button>').text('dump exec req')); |
| |
| $('<td>').html(form).appendTo(tr); |
| |
| table.append(tr); |
| } |
| |
| var prevTaskId = task.id - 1; |
| if (prevTaskId >= 0) { |
| $('#task-show-prev').attr('href', '#task' + prevTaskId); |
| } else { |
| $('#task-show-prev').removeAttr('href'); |
| } |
| var nextTaskId = task.id + 1; |
| $('#task-show-next').attr('href', '#task' + nextTaskId); |
| |
| showPage('task-detail'); |
| } |
| }; |
| |
| var gomaTaskView = new GomaTaskView(); |
| |
| // showPage shows only one page. |
| function showPage(pageName) { |
| $('.page').hide(); |
| $('#' + pageName + '-page').show(); |
| |
| gomaTaskView.updateTaskView(); |
| } |
| |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Current task updater. |
| * @type {Loader} |
| */ |
| var taskUpdater = null; |
| |
| /** |
| * Constructs loadder that gets from 'url' and calls 'updater'. |
| * @param {string} url url to request XHR. |
| * @param {Function} updater closure to update by response text. |
| * @constructor |
| */ |
| function Loader(url, updater) { |
| this.url = url; |
| this.updater = updater; |
| this.suspended = true; |
| this.xhr = null; |
| this.params = {}; |
| } |
| |
| /** |
| * Starts loading. |
| */ |
| Loader.prototype.start = function() { |
| this.suspended = false; |
| this.load(); |
| }; |
| |
| /** |
| * Stops loading. |
| */ |
| Loader.prototype.stop = function() { |
| this.suspended = true; |
| }; |
| |
| /** |
| * Updates load status shown in id='update-status'. |
| * @param {string} msg loader status message. |
| */ |
| Loader.prototype.updateLoaderStatus = function(msg) { |
| document.getElementById('update-status').innerText = msg; |
| }; |
| |
| /** |
| * Set parameter |
| * @param {string} key key for POST parameter |
| * @param {string} value value for POST parameter |
| */ |
| Loader.prototype.setParameter = function(key, value) { |
| this.params[key] = value; |
| }; |
| |
| /** |
| * Loads data from 'url' and call 'updater'. Repeats this with 1 sec interval |
| * while 'suspended' is false. |
| */ |
| Loader.prototype.load = function() { |
| var self = this; |
| |
| this.xhr = new XMLHttpRequest(); |
| this.xhr.onreadystatechange = function() { |
| if (this.readyState == 4) { |
| var responseText = self.xhr.responseText; |
| self.xhr.onreadystatechange = null; |
| self.xhr = null; |
| if (this.status == 200) { |
| self.updateLoaderStatus('parsing'); |
| setTimeout(function() { |
| if (self.suspended) { |
| self.updateLoaderStatus('suspended'); |
| } else { |
| self.updater(responseText); |
| // TODO: We may want validation here? |
| var updateFreq = |
| document.getElementById('update-freq').value | 0; |
| setTimeout(function() { self.load(); }, updateFreq); |
| self.updateLoaderStatus('waiting'); |
| } |
| }, 1); |
| } else { |
| var errorArea = $('#error'); |
| errorArea.show(); |
| errorArea.text( |
| 'request to ' + self.url + ' got error ' + this.status); |
| } |
| } |
| }; |
| |
| var url = self.url; |
| var param = $.param(self.params); |
| if (param != '') { |
| url += '?' + param; |
| } |
| |
| this.xhr.open('POST', url); |
| this.xhr.send(); |
| this.updateLoaderStatus('loading'); |
| }; |
| |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * Updates goma version. |
| * @param {Array} resp json response data [my_version, pulled_version]. |
| */ |
| function updateGomaVersion(resp) { |
| var my_version = resp[0]; |
| var pulled_version = resp[1]; |
| var d = document.getElementById('goma_version'); |
| if (my_version >= pulled_version) { |
| d.innerText = 'goma_version: ' + my_version; |
| } else { |
| d.innerText = 'goma_version: ' + my_version + |
| ' [' + pulled_version + ' available]'; |
| d.setAttribute('class', 'warning'); |
| } |
| } |
| |
| /** |
| * Update taskz response in 'domNode'. |
| * @param {Element} domNode dom node. |
| * @param {string} response JSON text. |
| */ |
| function updateTaskView(domNode, response) { |
| var resp = JSON.parse(response); |
| |
| if (resp['goma_version']) { |
| updateGomaVersion(resp['goma_version']); |
| } |
| |
| if (resp['active']) { |
| gomaTaskView.tasks['active'] = resp['active']; |
| $('#count-active-tasks').text('' + gomaTaskView.tasks['active'].length); |
| } |
| if (resp['finished']) { |
| var finished = resp['finished']; |
| for (var i = finished.length - 1; i >= 0; --i) { |
| var task = finished[i]; |
| if (!(task.id in gomaTaskView.finishedTasks)) { |
| gomaTaskView.tasks['finished'].unshift(task); |
| gomaTaskView.finishedTasks[task.id] = task; |
| } |
| } |
| $('#count-finished-tasks').text('' + gomaTaskView.tasks['finished'].length); |
| } |
| if (resp['failed']) { |
| var failed = resp['failed']; |
| for (var i = failed.length - 1; i >= 0; --i) { |
| var task = failed[i]; |
| if (!(task.id in gomaTaskView.failedTasks)) { |
| gomaTaskView.tasks['failed'].unshift(task) |
| gomaTaskView.failedTasks[task.id] = task; |
| } |
| } |
| $('#count-failed-tasks').text('' + gomaTaskView.tasks['failed'].length); |
| } |
| if (resp['long']) { |
| gomaTaskView.tasks['long'] = resp['long']; |
| $('#count-long-tasks').text('' + gomaTaskView.tasks['long'].length); |
| } |
| |
| if (resp['last_update_ms']) { |
| taskUpdater.setParameter('after', resp['last_update_ms']); |
| } |
| |
| gomaTaskView.resp = resp; |
| |
| gomaTaskView.updateTaskView(); |
| gomaTaskView.updateMismatchView(); |
| gomaTaskView.updateTaskStats(); |
| gomaTaskView.updateNetworkStats(); |
| } |
| |
| /** |
| * Request to update 'taskview' by /api/taskz. |
| */ |
| function startTaskUpdater() { |
| taskUpdater = new Loader('/api/taskz', |
| function(response) { |
| updateTaskView(document.getElementById('taskview'), response); |
| }); |
| taskUpdater.start(); |
| } |
| |
| function onHashChange() { |
| if (location.hash == '#active') { |
| gomaTaskView.currentTaskView = 'active'; |
| gomaTaskView.setTaskPositionFirst(); |
| showPage('task-summary'); |
| } else if (location.hash == '#finished') { |
| gomaTaskView.currentTaskView = 'finished'; |
| gomaTaskView.setTaskPositionFirst(); |
| showPage('task-summary'); |
| } else if (location.hash == '#failed') { |
| gomaTaskView.currentTaskView = 'failed'; |
| gomaTaskView.setTaskPositionFirst(); |
| showPage('task-summary'); |
| } else if (location.hash == '#long') { |
| gomaTaskView.currentTaskView = 'long'; |
| gomaTaskView.setTaskPositionFirst(); |
| showPage('task-summary'); |
| } else if (location.hash == '#task-stats') { |
| showPage('task-stats'); |
| } else if (location.hash == '#network-stats') { |
| showPage('network-stats'); |
| } else if (location.hash == '#settings') { |
| showPage('settings'); |
| } else if (location.hash.startsWith('#task')) { |
| var taskId = parseInt(location.hash.substr(5)); |
| if (!isNaN(taskId)) |
| gomaTaskView.showTaskDetail(taskId); |
| } |
| } |
| |
| function accountUpdate() { |
| $.get('/api/accountz', |
| function(data) { |
| $('#http_status').text(data.status); |
| $('#email').text(data.account); |
| var login = $('#login'); |
| login.text(data.text); |
| login.attr("href", data.href); |
| }) |
| .always(function() { |
| setTimeout(accountUpdate, 10*60*1000); |
| }); |
| } |
| |
| function init() { |
| startTaskUpdater(); |
| accountUpdate(); |
| |
| // Prevents text selection when clicking icons many times. |
| $('.menu-icon').mousedown(function(e) { e.preventDefault(); }); |
| |
| $('#task-summary-first').click(function() { |
| if ($(this).hasClass('disabled')) |
| return; |
| gomaTaskView.setTaskPositionFirst(); |
| }); |
| $('#task-summary-prev').click(function() { |
| if ($(this).hasClass('disabled')) |
| return; |
| gomaTaskView.setTaskPositionPrev(); |
| }); |
| $('#task-summary-next').click(function() { |
| if ($(this).hasClass('disabled')) |
| return; |
| gomaTaskView.setTaskPositionNext(); |
| }); |
| $('#task-summary-last').click(function() { |
| if ($(this).hasClass('disabled')) |
| return; |
| gomaTaskView.setTaskPositionLast(); |
| }); |
| |
| $('.task-summary-head').click(function() { |
| var id = $(this).attr('id'); |
| // remove 'task-summary-head-' |
| if (!id.startsWith('task-summary-head-')) { |
| console.error('task-summary-head class element should' |
| + ' have id starting with task-summary-head'); |
| return; |
| } |
| |
| var key = id.slice('task-summary-head-'.length); |
| gomaTaskView.changeTaskOrder(key); |
| }); |
| |
| $('input[name="update-task"]:radio').change(function() { |
| var value = $(this).val(); |
| if (taskUpdater == null) |
| return; |
| |
| if (value == 'on') |
| taskUpdater.start(); |
| else |
| taskUpdater.stop(); |
| }); |
| |
| $('input[name="pagesize"]:radio').change(function() { |
| var pagesize = parseInt($(this).val(), 10); |
| if (isNaN(pagesize)) |
| pagesize = 25; |
| gomaTaskView.setPageSize(pagesize); |
| }); |
| |
| $('#btn-task-check-all').click(function(event) { |
| $('#task-filter input').each(function() { |
| this.checked = true; |
| }); |
| event.preventDefault(); |
| }); |
| |
| $('#btn-task-uncheck-all').click(function(event) { |
| $('#task-filter input').each(function() { |
| this.checked = false; |
| }); |
| event.preventDefault(); |
| }); |
| |
| window.onhashchange = onHashChange; |
| |
| // The default view is 'active'. But we can check the hash value |
| // to determine what page should be shown first. |
| gomaTaskView.currentTaskView = 'active'; |
| showPage('task-summary'); |
| onHashChange(); |
| } |