http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/screen.css ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/screen.css b/server/monitor/src/main/resources/resources/screen.css new file mode 100644 index 0000000..9f8f2b6 --- /dev/null +++ b/server/monitor/src/main/resources/resources/screen.css @@ -0,0 +1,507 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* basic screen style for all pages */ +html,body { + height: 100%; + margin: 0; + padding: 0; + color: #333333; + font-size: 10pt; + font-family: verdana, arial, sans-serif; + text-align: center; +} + +#banner { + height: 1.5em; + font-size: 1.0em; +} + +#content-wrapper { + position: relative; + min-height: 95%; +} + +* #content-wrapper { + height: 95%; +} + +#content { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 1.5em; + padding-bottom: 0; +} + +#header { + position: absolute; + padding-top: 0; + top: 0; + left: 0; + right: 0; + height: 5em; + color: #c4c4c4; + background-color: #304065; + text-align: center; +} + +#headertitle { + /*position: absolute; + top: -1.0em; + left: 0.5em; + right: 1.0em;*/ + font-size: 20pt; +} + +#sidebar { + text-align: left; +} + +#subheader { + position: absolute; + bottom: 0; + left: 0; + right: 0; + font-size: .92em; +} + +#footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 0; + /*height: 1.5em;*/ + font-size: 1.0em; +} + +#main { + position: absolute; + top: 5.0em; + /*left: 11em;*/ + left: 0; + right: 0; + bottom: 0; + padding: 0.6em; + /*overflow: scroll;*/ + margin-left: auto; + margin-right: auto; + width: 90%; +} + +#nav { + position: absolute; + top: 0.5em; + /*left: 0;*/ + right: 0; + /*bottom: 0;*/ + padding: 0.5em; + /*width: 10em;*/ + /*color: #304065;*/ + /*background-color: #c4c4c4;*/ + /*text-align: left;*/ + /*height: 3.0em;*/ +} + +#nav-title { + margin-left: auto; + margin-right: auto; + text-align: center; + font-size: 1.1em; +} + +h1 { + font-size: 2.2em; + font-variant: small-caps; + text-align: center; +} + +h2 { + width: 100%; + font-size: 1.3em; + text-align: center; +} + +h2.error { + width: 100%; + color: #ff0000; + background-color: #000000; +} + +h2.error a { + color: #ff0000; + background-color: #000000; +} + +caption { + margin-left: auto; + margin-right: auto; +} + +caption.error { + color: #ff0000; + background-color: #000000; +} + +hr { + border: 0; + height: 1px; + background-color: #304065; + color: #304065; +} + +table { + /*margin-left: auto; + margin-right: auto; + min-width: 60%;*/ + /*border: 1px #333333 solid; + border-spacing-top: 0; + border-spacing-bottom: 0;*/ + font-size: 9pt; + /*border: 1px #333333 solid; + border: 1px #333333 solid;*/ +} + +/* +The following is to avoid a problem with the flot +javascript library. This file sets min-width to 60% +for all tables. This make the html generated by +flot.js not work. Flot creates a class called legend +used in a div that has a child table. This sets +min-width to 0 for that child table enabling it +to render correctly. +*/ +div.legend > table { + min-width: 0%; +} + +table.sortable { + border-left: 0; + border-right: 0; + border-top: 1px #333333 dotted; + border-bottom: 1px #333333 dotted; +} + +/* +table.sortable tbody tr:nth-child(2n) td { + background: #ffffff; +} + +table.sortable tbody tr:nth-child(2n+1) td { + background: #cef4b5; +}*/ + +td { + border-top: 0; + border-bottom: 0; + border-left: 0; + border-right: 0; + border-spacing-top: 0; + border-spacing-bottom: 0; + padding-left: 0.5em; + padding-right: 0.5em; + padding-top: 0.15em; + padding-bottom: 0.15em; + color: #333333; +} + +th { + border-top: 0; + border-bottom: 3px #333333 solid; + border-left: 1px #333333 dotted; + border-right: 0; + border-spacing-top: 0; + border-spacing-bottom: 0; + text-align: center; + font-variant: small-caps; + padding-left: 0.5em; + padding-right: 0.5em; + padding-top: 0.2em; + padding-bottom: 0.2em; + color: #333333; + font-size: 10pt; + vertical-align: bottom; +} + +td.firstcell { + border-left: 0; +} + +th.sortable { + color: blue; +} + +a { + text-decoration: none; + color: #0000ff; + line-height: 1.5em; +} + +a:hover { + color: #004400; + text-decoration: underline; +} + +a img { + border: 0px; +} + +div.show { + display: block; +} + +div.hide { + display: none; +} + +div.progress-chart { + border: 1px solid #333333; + border-spacing: 0; + width: 150px; + float: left; +} + +div.progress-chart>div { + height: 20px; + background-color: #0000ff; +} + +pre.logevent { + margin: 0; +} + +.noborder { + border: 0; + border-spacing: 0; + border-bottom: 0; + border-right: 0; +} + +.overview-table { + width: 100%; + border: 0; + border-spacing: 0; + border-bottom: 0; + border-right: 0; +} + +.overview-table td { + vertical-align: top; + width: 50%; +} + +/** + * Need this to only apply the previous CSS to the top child + * could probably do a selector + */ +.overview-table td td { + vertical-align: inherit; + width: 0; +} + +.table-caption { + font-size: 2em; + font-variant: small-caps; + text-align: center; +} + +.table-subcaption { + font-size: 0.9em; +} + +.left { + text-align: left; +} + +.right { + text-align: right; + margin-right: 0; +} + +.error, .error a { + color: #ffffff; + background-color: #d9534f; +} + +.error-inv, .error-inv a { + color: #d9534f; +} + +.warning, .warning a { + background-color: #f0ad4e; +} + +.warning-inv, .warning-inv a { + color: #f0ad4e; +} + +.normal, .normal a { + background-color: #5cb85c; +} + +.normal-inv, .normal-inv a { + color: #5cb85c; +} + +.highlight { + background-color: #cef4b5; +} + +.icon-dot { + display: inline-block; + min-width: 1em; + height: 1em; + padding: 3px 7px; + font-size: @font-size-small; + font-weight: @badge-font-weight; + color: @badge-color; + line-height: @badge-line-height; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: @badge-bg; + border-radius: 1em; +} + +.smalltext { + font-size: 0.8em; +} + +.center { + text-align: center; + padding: 0.5em; +} + +.plotHeading { + text-align: center; + font-size: 1.5em; +} +.nowrap { + white-space:nowrap; +} + +.viscontrol { + border: 1px solid #c4c4c4; + padding: 5px; +} + +#vishoverinfo { + visibility: hidden; + position: absolute; + border: 1px solid #c4c4c4; + background-color: #ffffff; +} + +#shell { + text-align: left; + color: #55d839; + border: 1px; + padding-left: 5px; + background-color: #000000; + height: 40em; + overflow: auto; + font-family: Monaco, monospace; + font-size: 100%; +} + +#shell span { + display: inline; +} + +#shell input { + display: inline; + border: none; + width: 60%; + color: #55d839; + background-color: #000000; + font-family: Monaco, monospace; + font-size: 100%; +} + +#shell pre { + font-family: Monaco, monospace; + font-size: 100%; +} + +#login { + text-align: left; +} + +#login table { + margin-left: 0; + min-width: 0%; +} + +#loginError { + text-align: left; + color: red; +} + +#filters, #tables { + /*overflow-y: scroll;*/ + /*height: 100%;*/ + width: 80%; + margin-left: 15%; + /*float: left;*/ +} + +.center-block { + width: 100%; +} + +.nav-pills > li > a { + color: #c4c4c4; +} + +.nav-pills > li > a:hover, .dropdown.open > a:hover, .dropdown.open > a, .dropdown.open > a:focus { + background-color: #101010; +} + +.dropdown-menu { + right: 0; + left: unset; +} + +.axis path, .axis line +{ + fill: none; + stroke: #777; + shape-rendering: crispEdges; +} + +.axis text +{ + font-family: 'Arial'; + font-size: 13px; +} +.tick +{ + stroke-dasharray: 1, 2; +} +.bar +{ + fill: FireBrick; +} + +.legend +{ + /*padding: 5px;*/ + font: 10px sans-serif; + /*background: yellow; + box-shadow: 2px 2px 1px #888;*/ +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/server.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/server.js b/server/monitor/src/main/resources/resources/server.js new file mode 100644 index 0000000..d9b4f79 --- /dev/null +++ b/server/monitor/src/main/resources/resources/server.js @@ -0,0 +1,429 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +var serv; +/** + * Makes the REST calls, generates the tables with the new information + */ +function refreshServer() { + $.ajaxSetup({ + async: false + }); + getTServer(serv); + $.ajaxSetup({ + async: true + }); + refreshDetailTable(); + refreshHistoryTable(); + refreshCurrentTable(); + refreshResultsTable(); +} + +/** + * Used to redraw the page + */ +function refresh() { + refreshServer(); +} + +/** + * Generates the server details table + */ +function refreshDetailTable() { + + $('#tServerDetail tr:gt(0)').remove(); + + var data = sessionStorage.server === undefined ? + [] : JSON.parse(sessionStorage.server); + + var items = []; + + if (data.length === 0 || data.details === undefined) { + items.push('<td class="center" colspan="5"><i>Empty</i></td>'); + } else { + items.push('<td class="firstcell right" data-value="' + + data.details.hostedTablets + '">' + + bigNumberForQuantity(data.details.hostedTablets) + '</td>'); + + items.push('<td class="right" data-value="' + + data.details.entries + '">' + + bigNumberForQuantity(data.details.entries) + '</td>'); + + items.push('<td class="right" data-value="' + + data.details.minors + '">' + + bigNumberForQuantity(data.details.minors) + '</td>'); + + items.push('<td class="right" data-value="' + + data.details.majors + '">' + + bigNumberForQuantity(data.details.majors) + '</td>'); + + items.push('<td class="right" data-value="' + + data.details.splits + '">' + + bigNumberForQuantity(data.details.splits) + '</td>'); + } + + $('<tr/>', { + html: items.join('') + }).appendTo('#tServerDetail'); +} + +/** + * Generates the server history table + */ +function refreshHistoryTable() { + + $('#opHistoryDetails tr:gt(0)').remove(); + + var data = sessionStorage.server === undefined ? + [] : JSON.parse(sessionStorage.server); + + if (data.length === 0 || data.allTimeTabletResults === undefined) { + var row = []; + + row.push('<td class="center" colspan="8"><i>Empty</i></td>'); + + $('<tr/>', { + html: row.join('') + }).appendTo('#opHistoryDetails'); + } else { + var totalTimeSpent = 0; + $.each(data.allTimeTabletResults, function(key, val) { + totalTimeSpent += val.timeSpent; + }); + + var count = 0; + $.each(data.allTimeTabletResults, function(key, val) { + var row = []; + + row.push('<td class="firstcell left" data-value="' + val.operation + + '">' + val.operation + '</td>'); + + row.push('<td class="right" data-value="' + val.success + + '">' + bigNumberForQuantity(val.success) + '</td>'); + + row.push('<td class="right" data-value="' + val.failure + + '">' + bigNumberForQuantity(val.failure) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.avgQueueTime == null ? '-' : val.avgQueueTime * 1000.0) + + '">' + (val.avgQueueTime == null ? + '—' : timeDuration(val.avgQueueTime * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.queueStdDev == null ? '-' : val.queueStdDev * 1000.0) + + '">' + (val.queueStdDev == null ? + '—' : timeDuration(val.queueStdDev * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.avgTime == null ? '-' : val.avgTime * 1000.0) + + '">' + (val.avgTime == null ? + '—' : timeDuration(val.avgTime * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.stdDev == null ? '-' : val.stdDev * 1000.0) + + '">' + (val.stdDev == null ? + '—' : timeDuration(val.stdDev * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + ((val.timeSpent / totalTimeSpent) * 100) + + '"><div class="progress"><div class="progress-bar"' + + ' role="progressbar" style="min-width: 2em; width:' + + Math.floor((val.timeSpent / totalTimeSpent) * 100) + + '%;">' + Math.floor((val.timeSpent / totalTimeSpent) * 100) + '%</div></div></td>'); + + $('<tr/>', { + html: row.join('') + }).appendTo('#opHistoryDetails'); + + }); + } +} + +/** + * Generates the current server table + */ +function refreshCurrentTable() { + + $('#currentTabletOps tr:gt(0)').remove(); + + var data = sessionStorage.server === undefined ? + [] : JSON.parse(sessionStorage.server); + + var items = []; + if (data.length === 0 || data.currentTabletOperationResults === undefined) { + items.push('<td class="center" colspan="4"><i>Empty</i></td>'); + } else { + var current = data.currentTabletOperationResults; + + items.push('<td class="firstcell right" data-value="' + + (current.currentMinorAvg == null ? + '-' : current.currentMinorAvg * 1000.0) + '">' + + (current.currentMinorAvg == null ? + '—' : timeDuration(current.currentMinorAvg * 1000.0)) + + '</td>'); + + items.push('<td class="right" data-value="' + + (current.currentMinorStdDev == null ? + '-' : current.currentMinorStdDev * 1000.0) + '">' + + (current.currentMinorStdDev == null ? + '—' : timeDuration(current.currentMinorStdDev * 1000.0)) + + '</td>'); + + items.push('<td class="right" data-value="' + + (current.currentMajorAvg == null ? + '-' : current.currentMajorAvg * 1000.0) + '">' + + (current.currentMajorAvg == null ? + '—' : timeDuration(current.currentMajorAvg * 1000.0)) + + '</td>'); + + items.push('<td class="right" data-value="' + + (current.currentMajorStdDev == null ? + '-' : current.currentMajorStdDev * 1000.0) + '">' + + (current.currentMajorStdDev == null ? + '—' : timeDuration(current.currentMajorStdDev * 1000.0)) + + '</td>'); + } + + $('<tr/>', { + html: items.join('') + }).appendTo('#currentTabletOps'); + +} + +/** + * Generates the server results table + */ +function refreshResultsTable() { + + $('#perTabletResults tr:gt(0)').remove(); + + var data = sessionStorage.server === undefined ? + [] : JSON.parse(sessionStorage.server); + + if (data.length === 0 || data.currentOperations === undefined) { + var row = []; + + row.push('<td class="center" colspan="11"><i>Empty</i></td>'); + + $('<tr/>', { + html: row.join('') + }).appendTo('#perTabletResults'); + } else { + $.each(data.currentOperations, function(key, val) { + var row = []; + + row.push('<td class="firstcell left" data-value="' + val.name + + '"><a href="/tables/' + val.tableID + '">' + val.name + '</a></td>'); + + row.push('<td class="left" data-value="' + val.tablet + '"><code>' + + val.tablet + '</code></td>'); + + row.push('<td class="right" data-value="' + + (val.entries == null ? 0 : val.entries) + '">' + + bigNumberForQuantity(val.entries) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.ingest == null ? 0 : val.ingest) + '">' + + bigNumberForQuantity(Math.floor(val.ingest)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.query == null ? 0 : val.query) + '">' + + bigNumberForQuantity(Math.floor(val.query)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.minorAvg == null ? '-' : val.minorAvg * 1000.0) + '">' + + (val.minorAvg == null ? + '—' : timeDuration(val.minorAvg * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.minorStdDev == null ? '-' : val.minorStdDev * 1000.0) + '">' + + (val.minorStdDev == null ? + '—' : timeDuration(val.minorStdDev * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.minorAvgES == null ? 0 : val.minorAvgES) + '">' + + bigNumberForQuantity(Math.floor(val.minorAvgES)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.majorAvg == null ? '-' : val.majorAvg * 1000.0) + '">' + + (val.majorAvg == null ? + '—' : timeDuration(val.majorAvg * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.majorStdDev == null ? '-' : val.majorStdDev * 1000.0) + '">' + + (val.majorStdDev == null ? + '—' : timeDuration(val.majorStdDev * 1000.0)) + '</td>'); + + row.push('<td class="right" data-value="' + + (val.majorAvgES == null ? 0 : val.majorAvgES) + '">' + + bigNumberForQuantity(Math.floor(val.majorAvgES)) + '</td>'); + + $('<tr/>', { + html: row.join('') + }).appendTo('#perTabletResults'); + }); + } +} + +/** + * Sorts the different server status tables on the selected column + * + * @param {string} table Table ID to sort + * @param {number} n Column number to sort by + */ +function sortTable(table, n) { + var tableIDs = ['tServerDetail', 'opHistoryDetails', + 'currentTabletOps', 'perTabletResults']; + + if (sessionStorage.tableColumnSort !== undefined && + sessionStorage.tableColumnSort == n && + sessionStorage.direction !== undefined) { + direction = sessionStorage.direction === 'asc' ? 'desc' : 'asc'; + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + sessionStorage.tableColumn = tableIDs[table]; + sessionStorage.tableColumnSort = n; + + sortTables(tableIDs[table], direction, n); +} + +/** + * Creates the server detail header + * + * @param {string} server Server name + */ +function createDetailHeader(server) { + var caption = []; + serv = server; + + caption.push('<span class="table-caption">Details</span><br />'); + caption.push('<span class="table-subcaption">' + server + '</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#tServerDetail'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(0,0)">Hosted ' + + 'Tablets </th>'); + items.push('<th onclick="sortTable(0,1)">Entries </th>'); + items.push('<th onclick="sortTable(0,2)">Minor Compacting </th>'); + items.push('<th onclick="sortTable(0,3)">Major Compacting </th>'); + items.push('<th onclick="sortTable(0,4)">Splitting </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#tServerDetail'); +} + +/** + * Creates the server history header + */ +function createHistoryHeader() { + var caption = []; + + caption.push('<span class="table-caption">All-Time Tablet ' + + 'Operation Results</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#opHistoryDetails'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(1,0)">' + + 'Operation </th>'); + items.push('<th onclick="sortTable(1,1)">Success </th>'); + items.push('<th onclick="sortTable(1,2)">Failure </th>'); + items.push('<th onclick="sortTable(1,3)">Average<br />Queue ' + + 'Time </th>'); + items.push('<th onclick="sortTable(1,4)">Std. Dev.<br />Queue ' + + 'Time </th>'); + items.push('<th onclick="sortTable(1,5)">Average<br />Time </th>'); + items.push('<th onclick="sortTable(1,6)">Std. Dev.<br />Time' + + ' </th>'); + items.push('<th onclick="sortTable(1,7)">Percentage Time ' + + 'Spent '); + + $('<tr/>', { + html: items.join('') + }).appendTo('#opHistoryDetails'); +} + +/** + * Creates the current server header + */ +function createCurrentHeader() { + var caption = []; + + caption.push('<span class="table-caption">Current Tablet ' + + 'Operation Results</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#currentTabletOps'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(2,0)">Minor ' + + 'Average </th>'); + items.push('<th onclick="sortTable(2,1)">Minor Std Dev </th>'); + items.push('<th onclick="sortTable(2,2)">Major Avg </th>'); + items.push('<th onclick="sortTable(2,3)">Major Std Dev </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#currentTabletOps'); +} + +/** + * Creates the server result header + */ +function createResultsHeader() { + var caption = []; + + caption.push('<span class="table-caption">Detailed Current ' + + 'Operations</span><br />'); + caption.push('<span class="table-subcaption">Per-tablet ' + + 'Details</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#perTabletResults'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(3,0)">Table </th>'); + items.push('<th onclick="sortTable(3,1)">Tablet </th>'); + items.push('<th onclick="sortTable(3,2)">Entries </th>'); + items.push('<th onclick="sortTable(3,3)">Ingest </th>'); + items.push('<th onclick="sortTable(3,4)">Query </th>'); + items.push('<th onclick="sortTable(3,5)">Minor Avg </th>'); + items.push('<th onclick="sortTable(3,6)">Minor Std Dev </th>'); + items.push('<th onclick="sortTable(3,7)">Minor Avg e/s </th>'); + items.push('<th onclick="sortTable(3,8)">Major Avg </th>'); + items.push('<th onclick="sortTable(3,9)">Major Std Dev </th>'); + items.push('<th onclick="sortTable(3,10)">Major ' + + 'Avg e/s </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#perTabletResults'); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/show.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/show.js b/server/monitor/src/main/resources/resources/show.js new file mode 100644 index 0000000..bae71f8 --- /dev/null +++ b/server/monitor/src/main/resources/resources/show.js @@ -0,0 +1,165 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +var id; +/** + * Makes the REST calls, generates the tables with the new information + */ +function refreshTraceShow() { + $.ajaxSetup({ + async: false + }); + getTraceShow(id); + $.ajaxSetup({ + async: true + }); + refreshTraceShowTable(); +} + +/** + * Used to redraw the page + */ +function refresh() { + refreshTraceShow(); +} + +/** + * Generates the trace show table + */ +function refreshTraceShowTable() { + clearTable('trace'); + $('#trace caption span span').remove(); + var data = sessionStorage.traceShow === undefined ? + [] : JSON.parse(sessionStorage.traceShow); + + if (data.traces.length !== 0) { + var date = new Date(data.start); + $('#caption').append('<span>' + date.toLocaleString() + '</span>'); + + $.each(data.traces, function(key, val) { + var id = val.spanID.toString(16); + var items = []; + + items.push('<tr>'); + items.push('<td class="right">' + val.time + '+</td>'); + items.push('<td class="left">' + val.start + '</td>'); + items.push('<td style="text-indent: ' + val.level + '0px">' + + val.location + '</td>'); + items.push('<td>' + val.name + '</td>'); + + if (val.addlData.data.length !== 0 || + val.addlData.annotations.length !== 0) { + + items.push('<td><input type="checkbox" id="' + id + + '_checkbox" onclick="toggle(\'' + id + '\')"></td>'); + items.push('</tr>'); + items.push('<tr id="' + id + '" style="display:none">'); + items.push('<td colspan="5">'); + items.push('<table class="table table-bordered table-striped' + + ' table-condensed">'); + + if (val.addlData.data.length !== 0) { + items.push('<tr><th>Key</th><th>Value</th></tr>'); + + $.each(val.addlData.data, function(key2, val2) { + items.push('<tr><td>' + val2.key + '</td><td>' + val2.value + + '</td></tr>'); + }); + } + + if (val.addlData.annotations.length !== 0) { + items.push('<tr><th>Annotation</th><th>Time Offset</th></tr>'); + + $.each(val.addlData.annotations, function(key2, val2) { + items.push('<tr><td>' + val2.annotation + '</td><td>' + val2.time + + '</td></tr>'); + }); + } + + items.push('</table>'); + items.push('</td>'); + } else { + items.push('<td></td>'); + } + + items.push('</tr>'); + + $('#trace').append(items.join('')); + }); + } else { + var items = []; + items.push('<tr>'); + items.push('<td class="center" colspan="5"><i>No trace information for ID ' + + id + '</i></td>'); + items.push('</tr>'); + $('#trace').append(items.join('')); + } + +} + +/** + * Sorts the trace table on the selected column + * + * @param {number} n Column number to sort by + */ +function sortTable(n) { + if (sessionStorage.tableColumnSort !== undefined && + sessionStorage.tableColumnSort == n && + sessionStorage.direction !== undefined) { + direction = sessionStorage.direction === 'asc' ? 'desc' : 'asc'; + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + sessionStorage.tableColumnSort = n; + sortTables('trace', direction, n); +} + +/** + * Create tooltip for table column information + */ +$(function() { + $(document).tooltip(); +}); + +/** + * Creates the trace show header + * + * @param {string} ID ID of the trace + */ +function createHeader(ID) { + id = ID; + var caption = []; + + caption.push('<span id="caption" class="table-caption">Trace ' + id + + ' started at<br></span>'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#trace'); + + var items = []; + + items.push('<th>Time </th>'); + items.push('<th>Start </th>'); + items.push('<th>Service@Location </th>'); + items.push('<th>Name </th>'); + items.push('<th>Addl Data </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#trace'); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/sidebar.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/sidebar.js b/server/monitor/src/main/resources/resources/sidebar.js new file mode 100644 index 0000000..ca8aa7c --- /dev/null +++ b/server/monitor/src/main/resources/resources/sidebar.js @@ -0,0 +1,133 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * Creates the initial sidebar + */ +$(document).ready(function() { + refreshSidebar(); +}); + +/** + * Makes the REST calls, generates the sidebar with the new information + */ +function refreshSidebar() { + $.ajaxSetup({ + async: false + }); + getStatus(); + $.ajaxSetup({ + async: true + }); + refreshSideBarNotifications(); +} + +/** + * Used to redraw the navbar + */ +function refreshNavBar() { + refreshSidebar(); +} + +/** + * Generates the sidebar notifications for servers and logs + */ +function refreshSideBarNotifications() { + + $('#currentDate').html(Date()); + + var data = sessionStorage.status === undefined ? + undefined : JSON.parse(sessionStorage.status); + + // Setting individual status notification + if (data.masterStatus == 'OK') { + $('#masterStatusNotification').removeClass('error').addClass('normal'); + } else { + $('#masterStatusNotification').removeClass('normal').addClass('error'); + } + if (data.tServerStatus == 'OK') { + $('#serverStatusNotification').removeClass('error').removeClass('warning'). + addClass('normal'); + } else if (data.tServerStatus == 'WARN') { + $('#serverStatusNotification').removeClass('error').removeClass('normal'). + addClass('warning'); + } else { + $('#serverStatusNotification').removeClass('normal').removeClass('warning'). + addClass('error'); + } + if (data.gcStatus == 'OK') { + $('#gcStatusNotification').removeClass('error').addClass('normal'); + } else { + $('#gcStatusNotification').addClass('error').removeClass('normal'); + } + + // Setting overall status notification + if (data.masterStatus == 'OK' && + data.tServerStatus == 'OK' && + data.gcStatus == 'OK') { + $('#statusNotification').removeClass('error').removeClass('warning'). + addClass('normal'); + } else if (data.masterStatus == 'ERROR' || + data.tServerStatus == 'ERROR' || + data.gcStatus == 'ERROR') { + $('#statusNotification').removeClass('normal').removeClass('warning'). + addClass('error'); + } else if (data.tServerStatus == 'WARN') { + $('#statusNotification').removeClass('normal').removeClass('error'). + addClass('warning'); + } + + // Setting individual logs notifications + // Color + if (data.logNumber > 0) { + if (data.logsHaveError) { + $('#recentLogsNotifications').removeClass('warning').addClass('error'); + } else { + $('#recentLogsNotifications').removeClass('error').addClass('warning'); + } + } else { + $('#recentLogsNotifications').removeClass('error').removeClass('warning'); + } + // Number + var logNumber = data.logNumber > 99 ? '99+' : data.logNumber; + $('#recentLogsNotifications').html(logNumber); + // Color + if (data.problemNumber > 0) { + $('#tableProblemsNotifications').addClass('error'); + } else { + $('#tableProblemsNotifications').removeClass('error'); + } + // Number + var problemNumber = data.problemNumber > 99 ? '99+' : data.problemNumber; + $('#tableProblemsNotifications').html(problemNumber); + // Setting overall logs notifications + // Color + if (data.logNumber > 0 || data.problemNumber > 0) { + if (data.logsHaveError || data.problemNumber > 0) { + $('#errorsNotification').removeClass('warning').addClass('error'); + } else { + $('#errorsNotification').removeClass('error').addClass('warning'); + } + } else { + $('#errorsNotification').removeClass('error').removeClass('warning'); + } + + // Number + var totalNumber = data.logNumber + data.problemNumber > 99 ? + '99+' : data.logNumber + data.problemNumber; + $('#errorsNotification').html(totalNumber); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/summary.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/summary.js b/server/monitor/src/main/resources/resources/summary.js new file mode 100644 index 0000000..a451f2f --- /dev/null +++ b/server/monitor/src/main/resources/resources/summary.js @@ -0,0 +1,152 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +var minutes; +/** + * Makes the REST calls, generates the tables with the new information + */ +function refreshSummary() { + $.ajaxSetup({ + async: false + }); + getTraceSummary(minutes); + $.ajaxSetup({ + async: true + }); + refreshTraceSummaryTable(minutes); +} + +/** + * Used to redraw the page + */ +function refresh() { + refreshSummary(); +} + +/** + * Generates the trace summary table + * + * @param {string} minutes Minutes to display traces + */ +function refreshTraceSummaryTable(minutes) { + clearTable('traceSummary'); + + var data = sessionStorage.traceSummary === undefined ? + [] : JSON.parse(sessionStorage.traceSummary); + + if (data.length === 0 || data.recentTraces.length === 0) { + var items = []; + items.push('<td class="center" colspan="6"><i>No traces in the last ' + + minutes + ' minute(s)</i></td>'); + $('<tr/>', { + html: items.join('') + }).appendTo('#traceSummary'); + } else { + $.each(data.recentTraces, function(key, val) { + + var items = []; + + items.push('<td class="firstcell left"><a href="/trace/listType?type=' + + val.type + '&minutes=' + minutes + '">' + val.type + '</a></td>'); + items.push('<td class ="right">' + bigNumberForQuantity(val.total) + + '</td>'); + items.push('<td class="right">' + timeDuration(val.min) + '</td>'); + items.push('<td class="right">' + timeDuration(val.max) + '</td>'); + items.push('<td class="right">' + timeDuration(val.avg) + '</td>'); + items.push('<td class="left">'); + items.push('<table>'); + items.push('<tr>'); + + $.each(val.histogram, function(key2, val2) { + items.push('<td style="width:5em">' + (val2 == 0 ? '-' : val2) + + '</td>'); + }); + items.push('</tr>'); + items.push('</table>'); + items.push('</td>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#traceSummary'); + + }); + } +} + +/** + * Sorts the traceSummary table on the selected column + * + * @param {number} n Column number to sort by + */ +function sortTable(n) { + if (sessionStorage.tableColumnSort !== undefined && + sessionStorage.tableColumnSort == n && + sessionStorage.direction !== undefined) { + direction = sessionStorage.direction === 'asc' ? 'desc' : 'asc'; + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + sessionStorage.tableColumnSort = n; + sortTables('traceSummary', direction, n); +} + +/** + * Create tooltip for table column information + */ +$(function() { + $(document).tooltip(); +}); + +/** + * Creates the trace summary header + * + * @param {string} min Minutes to display trace + */ +function createHeader(min) { + minutes = min; + var caption = []; + + caption.push('<span class="table-caption">All Traces</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#traceSummary'); + + var items = []; + + items.push('<th class="firstcell" title="' + descriptions['Trace Type'] + + '">Type </th>'); + + items.push('<th title="' + descriptions['Total Spans'] + + '">Total </th>'); + + items.push('<th title="' + descriptions['Short Span'] + + '">min </th>'); + + items.push('<th title="' + descriptions['Long Span'] + + '">max </th>'); + + items.push('<th title="' + descriptions['Avg Span'] + + '">avg </th>'); + + items.push('<th title="' + descriptions['Histogram'] + + '">Histogram </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#traceSummary'); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/table.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/table.js b/server/monitor/src/main/resources/resources/table.js new file mode 100644 index 0000000..6e22b72 --- /dev/null +++ b/server/monitor/src/main/resources/resources/table.js @@ -0,0 +1,208 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +var tableID; +/** + * Makes the REST calls, generates the tables with the new information + */ +function refreshTable() { + $.ajaxSetup({ + async: false + }); + getTableServers(tableID); + $.ajaxSetup({ + async: true + }); + refreshTableServersTable(); +} + +/** + * Used to redraw the page + */ +function refresh() { + refreshTable(); +} + +/** + * Generates the table servers table + */ +function refreshTableServersTable() { + $('#participatingTServers tr:gt(0)').remove(); + + var data = sessionStorage.tableServers === undefined ? + [] : JSON.parse(sessionStorage.tableServers); + + if (data.length === 0 || data.servers.length === 0) { + var items = []; + items.push('<td class="center" colspan="13"><i>Empty</i></td>'); + $('<tr/>', { + html: items.join('') + }).appendTo('#participatingTServers'); + } else { + + $.each(data.servers, function(key, val) { + var items = []; + items.push('<td class="firstcell left" data-value="' + val.hostname + + '"><a href="/tservers?s=' + val.id + + '">' + val.hostname + '</a></td>'); + + items.push('<td class="right" data-value="' + val.tablets + + '">' + bigNumberForQuantity(val.tablets) + '</td>'); + + items.push('<td class="right" data-value="' + val.lastContact + + '">' + timeDuration(val.lastContact) + '</td>'); + + items.push('<td class="right" data-value="' + val.entries + + '">' + bigNumberForQuantity(val.entries) + '</td>'); + + items.push('<td class="right" data-value="' + val.ingest + + '">' + bigNumberForQuantity(Math.floor(val.ingest)) + '</td>'); + + items.push('<td class="right" data-value="' + val.query + + '">' + bigNumberForQuantity(Math.floor(val.query)) + '</td>'); + + items.push('<td class="right" data-value="' + val.holdtime + + '">' + timeDuration(val.holdtime) + '</td>'); + + items.push('<td class="right" data-value="' + + (val.compactions.scans.running + val.compactions.scans.queued) + + '">' + bigNumberForQuantity(val.compactions.scans.running) + + ' (' + bigNumberForQuantity(val.compactions.scans.queued) + + ')</td>'); + + items.push('<td class="right" data-value="' + + (val.compactions.minor.running + val.compactions.minor.queued) + + '">' + bigNumberForQuantity(val.compactions.minor.running) + + ' (' + bigNumberForQuantity(val.compactions.minor.queued) + + ')</td>'); + + items.push('<td class="right" data-value="' + + (val.compactions.major.running + val.compactions.major.queued) + + '">' + bigNumberForQuantity(val.compactions.major.running) + + ' (' + bigNumberForQuantity(val.compactions.major.queued) + + ')</td>'); + + items.push('<td class="right" data-value="' + + val.indexCacheHitRate * 100 + + '">' + Math.round(val.indexCacheHitRate * 100) + '%</td>'); + + items.push('<td class="right" data-value="' + val.dataCacheHitRate * 100 + + '">' + Math.round(val.dataCacheHitRate * 100) + '%</td>'); + + items.push('<td class="right" data-value="' + val.osload + + '">' + bigNumberForQuantity(val.osload) + '</td>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#participatingTServers'); + + }); + if (data.servers.length === 0) { + var items = []; + items.push('<td class="center" colspan="13"><i>Empty</i></td>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#participatingTServers'); + } + } +} + +/** + * Sorts the participatingTServers table on the selected column + * + * @param {number} n Column number to sort by + */ +function sortTable(n) { + if (!JSON.parse(sessionStorage.namespaceChanged)) { + if (sessionStorage.tableColumnSort !== undefined && + sessionStorage.tableColumnSort == n && + sessionStorage.direction !== undefined) { + direction = sessionStorage.direction === 'asc' ? 'desc' : 'asc'; + } + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + sessionStorage.tableColumnSort = n; + sortTables('participatingTServers', direction, n); +} + +/** + * Creates the table servers header + * + * @param {string} table Table Name + * @param {string} tabID Table ID + */ +function createHeader(table, tabID) { + tableID = tabID; + var caption = []; + + caption.push('<span class="table-caption">Participating ' + + 'Tablet Servers</span><br />'); + caption.push('<span class="table-subcaption">' + table + '</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#participatingTServers'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(0)">Server </th>'); + + items.push('<th onclick="sortTable(1)">Hosted Tablets </th>'); + + items.push('<th onclick="sortTable(2)">Last Contact </th>'); + + items.push('<th onclick="sortTable(3)" title="' + descriptions['Entries'] + + '">Entries </th>'); + + items.push('<th onclick="sortTable(4)" title="' + descriptions['Ingest'] + + '">Ingest </th>'); + + items.push('<th onclick="sortTable(5)" title="' + descriptions['Query'] + + '">Query </th>'); + + items.push('<th onclick="sortTable(6)" title="' + descriptions['Hold Time'] + + '">Hold Time </th>'); + + items.push('<th onclick="sortTable(7)" title="' + + descriptions['Running Scans'] + '">Running<br />Scans </th>'); + + items.push('<th onclick="sortTable(8)" title="' + + descriptions['Minor Compactions'] + + '">Minor<br />Compactions </th>'); + + items.push('<th onclick="sortTable(9)" title="' + + descriptions['Major Compactions'] + + '">Major<br />Compactions </th>'); + + items.push('<th onclick="sortTable(10)" title="' + + descriptions['Index Cache Hit Rate'] + + '">Index Cache<br />Hit Rate </th>'); + + items.push('<th onclick="sortTable(11)" title="' + + descriptions['Data Cache Hit Rate'] + + '">Data Cache<br />Hit Rate </th>'); + + items.push('<th onclick="sortTable(12)" title="' + + descriptions['OS Load'] + '">OS Load </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#participatingTServers'); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/tables.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/tables.js b/server/monitor/src/main/resources/resources/tables.js new file mode 100644 index 0000000..842f38c --- /dev/null +++ b/server/monitor/src/main/resources/resources/tables.js @@ -0,0 +1,355 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * Makes the REST calls, generates the tables with the new information + */ +function refreshTables() { + $.ajaxSetup({ + async: false + }); + getNamespaces(); + $.ajaxSetup({ + async: true + }); + createNamespacesDropdown(); + // If the namespaces in sessionStorage is undefined, select all namespaces + if (sessionStorage.namespaces === undefined) { + sessionStorage.namespaces = '[]'; + populateTable('*'); + } + populateTable(undefined); + sortTable(sessionStorage.tableColumnSort === undefined ? + 0 : sessionStorage.tableColumnSort); +} + +/** + * Used to redraw the page + */ +function refresh() { + // If tables are in master page, refresh master and tables + if (!hasMaster) { + refreshTables(); + } else { + refreshMaster(); + refreshTables(); + } +} + +var hasMaster = false; +function toggleMaster(master) { + hasMaster = master +} + +/** + * Creates listeners for when the namespaces are selected or unselected + */ +function namespaceChanged() { + var $namespaceSelect = $('#namespaces'); + + $namespaceSelect.off(); + + $namespaceSelect.on('select2:select', function(e) { + var id = e.params === null ? undefined : e.params['data']['id']; + populateTable(id); + }); + + $namespaceSelect.on('select2:unselect', function(e) { + var id = e.params === null ? undefined : e.params['data']['id']; + populateTable(id); + }); +} + +/** + * Creates the namespaces dropdown menu + */ +function createNamespacesDropdown() { + var data = JSON.parse(NAMESPACES).namespaces; + var caption = []; + + caption.push('<span class="table-caption">Table List</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#filters'); + + var data2 = [{ id: '*', text: '* (All Tables)'}]; + $.each(data, function(key, val) { + var namespace = val === '' ? '- (DEFAULT)' : val; + data2.push({id: val === '' ? '-' : val, text: namespace}); + }); + + $('#namespaces').select2({ + data: data2, + allowClear: true + }); + namespaceChanged(); +} + +/** + * Creates the tables table with the selected namespace + * + * @param {string} ns Selected namespace + */ +function populateTable(ns) { + var tmpArr = sessionStorage.namespaces === undefined ? + [] : JSON.parse(sessionStorage.namespaces); + sessionStorage.namespaceChanged = true; + var namespaces = JSON.parse(NAMESPACES).namespaces; + + // If there is a selected namespace, change the displayed tables + if (ns !== undefined) { + /* + * If the namespace has not been selected, + * add it to the namespace array, otherwise remove it + */ + if (tmpArr.indexOf(ns) == -1) { + /* If the namespace is *, add all namespaces to the array, + * otherwise just add the selected namespace + */ + if (ns === '*') { + tmpArr = []; + + tmpArr.push('*'); + $.each(namespaces, function(key, val) { + tmpArr.push(val === '' ? '-' : val); + }); + } else { + tmpArr.push(ns); + /* + * If the namespace array is the same size + * as the total number of namespaces, add * + */ + if (tmpArr.length == namespaces.length) { + tmpArr.push('*'); + } + } + } else { + /* + * If * is in the array, and the selected + * namespace is not *, remove * from array + */ + if (tmpArr.indexOf('*') !== -1 && ns !== '*') { + tmpArr.splice(tmpArr.indexOf('*'), 1); + } + /* + * If the selected namespace is not *, + * remove it from array, otherwise, remove all + */ + if (ns !== '*') { + tmpArr.splice(tmpArr.indexOf(ns), 1); + } else { + tmpArr = []; + } + } + } + + $('#namespaces').select2().val(tmpArr).trigger('change'); // TODO Fix this, causes null dataAdapter + + sessionStorage.namespaces = JSON.stringify(tmpArr); + + $.ajaxSetup({ + async: false + }); + getNamespaceTables(tmpArr); + $.ajaxSetup({ + async: true + }); + + var data = sessionStorage.tables === undefined ? + [] : JSON.parse(sessionStorage.tables); + clearTable('tableList'); + + var numTables = 0; + + $.each(data.tables, function(keyT, tab) { + // Only add tables that are part of the array, or all if * is in the array + if (tmpArr.indexOf(tab.namespace === '' ? '-' : tab.namespace) !== -1 || + tmpArr.indexOf('*') !== -1) { + $.each(tab.table, function(key, val) { + + var row = []; + row.push('<td class="firstcell left" data-value="' + val.tablename + + '"><a href="/tables/' + val.tableId + + '">' + val.tablename + '</a></td>'); + row.push('<td class="center" data-value="' + val.tableState + + '"><span>' + val.tableState + '</span></td>'); + + if (val.tableState === 'ONLINE') { + row.push('<td class="right" data-value="' + val.tablets + + '">' + bigNumberForQuantity(val.tablets) + '</td>'); + + row.push('<td class="right" data-value="' + val.offlineTablets + + '">' + bigNumberForQuantity(val.offlineTablets) + '</td>'); + + row.push('<td class="right" data-value="' + val.recs + '">' + + bigNumberForQuantity(val.recs) + '</td>'); + + row.push('<td class="right" data-value="' + val.recsInMemory + + '">' + bigNumberForQuantity(val.recsInMemory) + '</td>'); + + row.push('<td class="right" data-value="' + val.ingest + + '">' + bigNumberForQuantity(Math.floor(val.ingest)) + '</td>'); + + row.push('<td class="right" data-value="' + val.entriesRead + + '">' + bigNumberForQuantity(Math.floor(val.entriesRead)) + + '</td>'); + + row.push('<td class="right" data-value="' + val.entriesReturned + + '">' + bigNumberForQuantity(Math.floor(val.entriesReturned)) + + '</td>'); + + row.push('<td class="right" data-value="' + val.holdTime + '">' + + timeDuration(val.holdTime) + '</td>'); + + if (val.scans === null) { + row.push('<td class="right" data-value="-">-</td>'); + } else { + row.push('<td class="right" data-value="' + + (val.scans.running + val.scans.queued) + '">' + + bigNumberForQuantity(val.scans.running) + ' (' + + bigNumberForQuantity(val.scans.queued) + ')</td>'); + } + if (val.minorCompactions === null) { + row.push('<td class="right" data-value="-">-</td>'); + } else { + row.push('<td class="right" data-value="' + + (val.minorCompactions.running + val.minorCompactions.queued) + + '">' + bigNumberForQuantity(val.minorCompactions.running) + + ' (' + bigNumberForQuantity(val.minorCompactions.queued) + + ')</td>'); + } + if (val.majorCompactions === null) { + row.push('<td class="right" data-value="-">-</td>'); + } else { + row.push('<td class="right" data-value="' + + (val.majorCompactions.running + val.majorCompactions.queued) + + '">' + bigNumberForQuantity(val.majorCompactions.running) + + ' (' + bigNumberForQuantity(val.majorCompactions.queued) + + ')</td>'); + } + } else { + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">—</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + row.push('<td class="right" data-value="-">-</td>'); + } + + $('<tr/>', { + html: row.join(''), + id: tab.namespace === '' ? '-' : tab.namespace + }).appendTo('#tableList'); + + numTables++; + }); + } + }); + /* + * If there are no selected namespaces, + * or selected namespaces result in no tables, display empty + */ + if (numTables === 0) { + var item = '<td class="center" colspan="13"><i>Empty</i></td>'; + + $('<tr/>', { + html: item + }).appendTo('#tableList'); + } +} + +/** + * Sorts the tableList table on the selected column + * + * @param {number} n Column number to sort by + */ +function sortTable(n) { + if (!JSON.parse(sessionStorage.namespaceChanged)) { + if (sessionStorage.tableColumnSort !== undefined && + sessionStorage.tableColumnSort == n && + sessionStorage.direction !== undefined) { + direction = sessionStorage.direction === 'asc' ? 'desc' : 'asc'; + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + + sessionStorage.tableColumnSort = n; + + sortTables('tableList', direction, n); + sessionStorage.namespaceChanged = false; +} + +/** + * Create tooltip for table column information + */ +$(function() { + $(document).tooltip(); +}); + +/** + * Creates the tables header + */ +function createTablesHeader() { + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(0)">' + + 'Table Name </th>'); + + items.push('<th onclick="sortTable(1)">State </th>'); + items.push('<th onclick="sortTable(2)" title="' + + descriptions['# Tablets'] + '"># Tablets </th>'); + items.push('<th onclick="sortTable(3)" title="' + + descriptions['# Offline Tablets'] + + '"># Offline<br />Tablets </th>'); + items.push('<th onclick="sortTable(4)" title="' + + descriptions['Entries'] + '">Entries </th>'); + items.push('<th onclick="sortTable(5)" title="' + + descriptions['Entries in Memory'] + + '">Entries<br />In Memory </th>'); + items.push('<th onclick="sortTable(6)" title="' + + descriptions['Ingest'] + '">Ingest </th>'); + items.push('<th onclick="sortTable(7)" title="' + + descriptions['Entries Read'] + '">Entries<br />Read </th>'); + items.push('<th onclick="sortTable(8)" title="' + + descriptions['Entries Returned'] + + '">Entries<br />Returned </th>'); + items.push('<th onclick="sortTable(9)" title="' + + descriptions['Hold Time'] + '">Hold Time </th>'); + items.push('<th onclick="sortTable(10)" title="' + + descriptions['Running Scans'] + '">Running<br />Scans </th>'); + items.push('<th onclick="sortTable(11)" title="' + + descriptions['Minor Compactions'] + + '">Minor<br />Compactions </th>'); + items.push('<th onclick="sortTable(12)" title="' + + descriptions['Major Compactions'] + + '">Major<br />Compactions </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#tableList'); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/tservers.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/tservers.js b/server/monitor/src/main/resources/resources/tservers.js new file mode 100644 index 0000000..d8c109a --- /dev/null +++ b/server/monitor/src/main/resources/resources/tservers.js @@ -0,0 +1,320 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * Creates tservers initial table + */ +$(document).ready(function() { + createHeader(); + refreshTServers(); +}); + +/** + * Makes the REST calls, generates the tables with the new information + */ +function refreshTServers() { + $.ajaxSetup({ + async: false + }); + getTServers(); + $.ajaxSetup({ + async: true + }); + refreshBadTServersTable(); + refreshDeadTServersTable(); + refreshTServersTable(); +} + +/** + * Used to redraw the page + */ +function refresh() { + refreshTServers(); +} + +/** + * Generates the tservers table + */ +function refreshBadTServersTable() { + var data = sessionStorage.tservers === undefined ? + [] : JSON.parse(sessionStorage.tservers); + + $('#badtservers tr').remove(); + $('#badtservers caption').remove(); + + if (data.length === 0 || data.badServers.length === 0) { + + $('#badtservers').hide(); + } else { + + $('#badtservers').show(); + + var caption = []; + + caption.push('<span class="table-caption">Non-Functioning ' + + 'Tablet Servers</span><br />'); + caption.push('<span class="table-subcaption">The following tablet' + + ' servers reported a status other than Online</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#badtservers'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(0)">' + + 'Tablet Server </th>'); + items.push('<th onclick="sortTable(1)">Tablet Server ' + + 'Status </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#badtservers'); + + $.each(data.badServers, function(key, val) { + var items = []; + items.push('<td class="firstcell left" data-value="' + val.id + + '">' + val.id + '</td>'); + items.push('<td class="right" data-value="' + val.status + + '">' + val.status + '</td>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#badtservers'); + }); + } +} + +/** + * Generates the deadtservers table + */ +function refreshDeadTServersTable() { + var data = sessionStorage.tservers === undefined ? + [] : JSON.parse(sessionStorage.tservers); + + $('#deadtservers tr').remove(); + $('#deadtservers caption').remove(); + + if (data.length === 0 || data.deadServers.length === 0) { + + $('#deadtservers').hide(); + } else { + + $('#deadtservers').show(); + + + var caption = []; + + caption.push('<span class="table-caption">Dead ' + + 'Tablet Servers</span><br />'); + caption.push('<span class="table-subcaption">The following' + + ' tablet servers are no longer reachable.</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#deadtservers'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(0)">' + + 'Server </th>'); + items.push('<th onclick="sortTable(1)">Last Updated </th>'); + items.push('<th onclick="sortTable(2)">Event </th>'); + items.push('<th>Clear</th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#deadtservers'); + + $.each(data.deadServers, function(key, val) { + var items = []; + items.push('<td class="firstcell left" data-value="' + val.server + + '">' + val.server + '</td>'); + + var date = new Date(val.lastStatus); + date = date.toLocaleString().split(' ').join(' '); + items.push('<td class="right" data-value="' + val.lastStatus + + '">' + date + '</td>'); + items.push('<td class="right" data-value="' + val.status + + '">' + val.status + '</td>'); + items.push('<td class="right"> ' + + '<a href="javascript:clearDeadTServers(\'' + + val.server + '\');">clear</a></td>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#deadtservers'); + }); + } +} + +/** + * Makes the REST POST call to clear dead table server + * + * @param {string} server Dead TServer to clear + */ +function clearDeadTServers(server) { + clearDeadServers(server); + refreshTServers(); + refreshNavBar(); +} + +/** + * Generates the tserver table + */ +function refreshTServersTable() { + var data = sessionStorage.tservers === undefined ? + [] : JSON.parse(sessionStorage.tservers); + + $('#tservers tr:gt(0)').remove(); + + if (data.length === 0 || data.servers.length === 0) { + var item = '<td class="center" colspan="13"><i>Empty</i></td>'; + + $('<tr/>', { + html: item + }).appendTo('#tservers'); + } else { + + $.each(data.servers, function(key, val) { + var items = []; + items.push('<td class="firstcell left" data-value="' + val.hostname + + '"><a href="/tservers?s=' + val.id + '">' + val.hostname + + '</a></td>'); + + items.push('<td class="right" data-value="' + val.tablets + + '">' + bigNumberForQuantity(val.tablets) + '</td>'); + + items.push('<td class="right" data-value="' + val.lastContact + + '">' + timeDuration(val.lastContact) + '</td>'); + + items.push('<td class="right" data-value="' + val.entries + + '">' + bigNumberForQuantity(val.entries) + '</td>'); + + items.push('<td class="right" data-value="' + val.ingest + + '">' + bigNumberForQuantity(Math.floor(val.ingest)) + '</td>'); + + items.push('<td class="right" data-value="' + val.query + + '">' + bigNumberForQuantity(Math.floor(val.query)) + '</td>'); + + items.push('<td class="right" data-value="' + val.holdtime + + '">' + timeDuration(val.holdtime) + '</td>'); + + items.push('<td class="right" data-value="' + + (val.compactions.scans.running + val.compactions.scans.queued) + + '">' + bigNumberForQuantity(val.compactions.scans.running) + + ' (' + bigNumberForQuantity(val.compactions.scans.queued) + + ')</td>'); + + items.push('<td class="right" data-value="' + + (val.compactions.minor.running + val.compactions.minor.queued) + + '">' + bigNumberForQuantity(val.compactions.minor.running) + + ' (' + bigNumberForQuantity(val.compactions.minor.queued) + + ')</td>'); + + items.push('<td class="right" data-value="' + + (val.compactions.major.running + val.compactions.major.queued) + + '">' + bigNumberForQuantity(val.compactions.major.running) + + ' (' + bigNumberForQuantity(val.compactions.major.queued) + + ')</td>'); + + items.push('<td class="right" data-value="' + + val.indexCacheHitRate * 100 + '">' + + Math.round(val.indexCacheHitRate * 100) + + '%</td>'); + + items.push('<td class="right" data-value="' + val.dataCacheHitRate * 100 + + '">' + Math.round(val.dataCacheHitRate * 100) + + '%</td>'); + + items.push('<td class="right" data-value="' + val.osload + + '">' + bigNumberForQuantity(val.osload) + '</td>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#tservers'); + }); + } +} + +/** + * Sorts the tservers table on the selected column + * + * @param {number} n Column number to sort by + */ +function sortTable(n) { + if (sessionStorage.tableColumnSort !== undefined && + sessionStorage.tableColumnSort == n && + sessionStorage.direction !== undefined) { + direction = sessionStorage.direction === 'asc' ? 'desc' : 'asc'; + } else { + direction = sessionStorage.direction === undefined ? + 'asc' : sessionStorage.direction; + } + sessionStorage.tableColumnSort = n; + sortTables('tservers', direction, n); +} + +/** + * Creates the tservers header + */ +function createHeader() { + var caption = []; + + caption.push('<span class="table-caption">Tablet Servers</span><br />'); + caption.push('<span class="table-subcaption">Click on the ' + + '<span style="color: #0000ff;">server address</span> to ' + + 'view detailed performance statistics for that server.</span><br />'); + + $('<caption/>', { + html: caption.join('') + }).appendTo('#tservers'); + + var items = []; + + items.push('<th class="firstcell" onclick="sortTable(0)">Server </th>'); + items.push('<th onclick="sortTable(1)">Hosted Tablets </th>'); + items.push('<th onclick="sortTable(2)">Last Contact </th>'); + items.push('<th onclick="sortTable(3)" title="' + + descriptions['Entries'] + '">Entries </th>'); + items.push('<th onclick="sortTable(4)" title="' + + descriptions['Ingest'] + '">Ingest </th>'); + items.push('<th onclick="sortTable(5)" title="' + + descriptions['Query'] + '">Query </th>'); + items.push('<th onclick="sortTable(6)" title="' + + descriptions['Hold Time'] + '">Hold Time </th>'); + items.push('<th onclick="sortTable(7)" title="' + + descriptions['Running Scans'] + '">Running<br />Scans </th>'); + items.push('<th onclick="sortTable(8)" title="' + + descriptions['Minor Compactions'] + + '">Minor<br />Compactions </th>'); + items.push('<th onclick="sortTable(9)" title="' + + descriptions['Major Compactions'] + + '">Major<br />Compactions </th>'); + items.push('<th onclick="sortTable(10)" title="' + + descriptions['Index Cache Hit Rate'] + + '">Index Cache<br />Hit Rate </th>'); + items.push('<th onclick="sortTable(11)" title="' + + descriptions['Data Cache Hit Rate'] + + '">Data Cache<br />Hit Rate </th>'); + items.push('<th onclick="sortTable(12)" title="' + + descriptions['OS Load'] + '">OS Load </th>'); + + $('<tr/>', { + html: items.join('') + }).appendTo('#tservers'); +} http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/up.gif ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/up.gif b/server/monitor/src/main/resources/resources/up.gif new file mode 100644 index 0000000..b272cbb Binary files /dev/null and b/server/monitor/src/main/resources/resources/up.gif differ http://git-wip-us.apache.org/repos/asf/accumulo/blob/0ca5cd33/server/monitor/src/main/resources/resources/vis.js ---------------------------------------------------------------------- diff --git a/server/monitor/src/main/resources/resources/vis.js b/server/monitor/src/main/resources/resources/vis.js new file mode 100644 index 0000000..bcf5327 --- /dev/null +++ b/server/monitor/src/main/resources/resources/vis.js @@ -0,0 +1,506 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Server Stats +var statNames = {}, maxStatValues = {}, adjustMax = {}, significance = {}; + +/** + * Sets the options for the server visualization + * + * @param {string} shape Circle or Square + * @param {number} size Size of shape + * @param {string} motion Stat to display motion + * @param {string} color Stat to display color + */ +function setOptions(shape, size, motion, color) { + var data = sessionStorage.serverStats === undefined ? + {} : JSON.parse(sessionStorage.serverStats); + $('#motion').empty(); + $('#color').empty(); + + $.each(data.serverStats, function(key, val) { + var item = val.description; + + $('#motion').append('<option ' + (motion === val.name ? 'selected' : '') + + '>' + item + '</option>'); + $('#color').append('<option ' + (color === val.name ? 'selected' : '') + + '>' + item + '</option>'); + + }); + if (speedStatType === undefined) { + speedStatType = motion !== undefined || motion !== '' ? motion : ''; + speedDisabled = motion !== '' ? false : true; + } + if (speedStatType !== motion && motion !== undefined && + motion !== 'osload' && motion !== '') { + speedStatType = motion; + speedDisabled = false; + } + if (colorStatType === undefined) { + colorStatType = color !== undefined ? color : 'osload'; + } + if (colorStatType !== color && color !== undefined) { + colorStatType = color; + } +} + +/** + * Sets the stats values from the REST call + */ +function setStats() { + + var data = JSON.parse(sessionStorage.serverStats); + + $.each(data.serverStats, function(key, val) { + statNames[val.name] = val.derived; + maxStatValues[val.name] = val.max; + adjustMax[val.name] = val.adjustMax; + significance[val.name] = val.significance; + }); + + var numNormalStats = 8; +} + +// size and spacing variables +var dotSpacing = 10; // spacing between centers of dots (radius) +var dotPadding = 0.5; // dot padding +var minDotRadius = 3; // min dot radius +var maxDotRadius = dotSpacing - dotPadding; + +// arrays of information about each dot +var stats = {'servers': []}; +var dots = new Array(0); // current sizes and change directions +var mousedDot = -1; // the dot currently under the mouse + +var colorPalette = ['#0000CC', '#0014B8', '#0029A3', '#003D8F', '#00527A', + '#006666', '#007A52', '#008F3D', '#00A329', '#00B814', '#00CC00', + '#14D100', '#29D600', '#3DDB00', '#52E000', '#66E600', '#7AEB00', + '#8FF000', '#A3F500', '#B8FA00', '#CCFF00', '#CCFF00', '#CCF200', + '#CCE600', '#CCD900', '#CCCC00', '#CCBF00', '#CCB200', '#CCA600', + '#CC9900', '#CC8C00', '#CC8000', '#CC7300', '#CC6600', '#CC5900', + '#CC4C00', '#CC4000', '#CC3300', '#CC2600', '#CC1A00', '#CC0D00', + '#CC0000']; + +var nullColor = '#F5F8FA'; +var deadColor = '#B000CC'; + +// animation variables +var drawing = false; +var canvas = document.getElementById('visCanvas'); +var context = canvas.getContext('2d'); + +// mouse handling for server information display +document.getElementById('hoverable').addEventListener('mouseover', + showId, false); +document.getElementById('hoverable').addEventListener('mousemove', + showId, false); +document.getElementById('hoverable').addEventListener('mouseout', + hideId, false); +document.getElementById('vishoverinfo').addEventListener('click', + goToServer, false); +canvas.addEventListener('click', goToServer, false); + +// initialize settings based on request parameters +var main = document.getElementById('main'); +var speedStatType; +var colorStatType; +var speedDisabled = true; +var useCircles = true; +setShape(document.getElementById('shape')); +setSize(document.getElementById('size')); +setMotion(document.getElementById('motion')); +setColor(document.getElementById('color')); + +// xml loading variables +var xmlReturned = true; +// don't bother allowing for IE 5 or 6 since canvas won't work +var xmlhttp = new XMLHttpRequest(); +xmlhttp.onreadystatechange = function() { + handleNewData(); +}; + +self.setInterval('getXML()', 5000); + +//self.setInterval('drawDots()',20); + +window.requestAnimFrame = (function(callback) { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; +})(); + +function handleNewData() { + if (xmlhttp.readyState != 4) { + return; + } + if (xmlhttp.status != 200 || xmlhttp.responseText == null) { + xmlReturned = true; + return; + } + var newstats = JSON.parse(xmlhttp.responseText); + for (var i in newstats.servers) { + for (var s in statNames) { + if (statNames[s]) + continue; + newstats.servers[i][s] = Math.max(0, Math.floor(significance[s] * + newstats.servers[i][s]) / significance[s]); + if (adjustMax[s]) + maxStatValues[s] = Math.max(newstats.servers[i][s], maxStatValues[s]); + } + } + + initDerivedInfo(newstats); + var oldstats = stats; + while (drawing) {} + stats = newstats; + delete oldstats; + xmlReturned = true; +} + +// set max and average +function setDerivedStats(serverStats) { + var avgStat = 0; + var maxStat = 0; + var maxIndex = 0; + for (var s in statNames) { + if (statNames[s]) + continue; + normStat = serverStats[s] / maxStatValues[s]; + if (normStat > 0) + avgStat += normStat; + if (maxStat < normStat) { + maxStat = normStat; + maxIndex = s; + } + } + serverStats.allmax = Math.floor(significance.allmax * + Math.min(1, maxStat)) / significance.allmax; + serverStats.allavg = Math.floor(significance.allavg * + avgStat / numNormalStats) / significance.allavg; + serverStats.maxStat = maxIndex; +} + +function initDerivedInfo(newstats) { + for (var i in newstats.servers) { + if ('dead' in newstats.servers[i] || 'bad' in newstats.servers[i]) { + continue; + } + if (i >= dots.length) { + dots.push({'size': maxDotRadius, 'growing': false}); + } + setDerivedStats(newstats.servers[i]); + } +} + +// construct server info for hover +function getInfo(serverStats) { + var extra = '<strong>' + serverStats.ip + '</strong>'; + if ('dead' in serverStats || 'bad' in serverStats) + return extra; + extra = extra + ' (' + serverStats.hostname + ')'; + var j = 0; + for (var s in statNames) { + if (j % 4 == 0) + extra = extra + '<br>\n'; + extra = extra + ' ' + s + ': <strong>' + + serverStats[s] + '</strong>'; + j++; + } + extra = extra + ' (' + serverStats.maxStat + ')'; + return extra; +} + +// reload xml +function getXML() { + if (xmlReturned == true) { + xmlReturned = false; + xmlhttp.open('GET', jsonurl, true); + xmlhttp.send(); + } +} + +// redraw +function drawDots() { + requestAnimFrame(drawDots); + var width = Math.ceil(Math.sqrt(stats.servers.length)); + var height = Math.ceil(stats.servers.length / width); + var x; + var y; + var server; + var sizeChange; + drawing = true; + for (var i in stats.servers) { + server = stats.servers[i]; + x = i % width; + y = Math.floor(i / width); + if ('bad' in server || 'dead' in server) { + strokeDot(x, y, maxDotRadius - 1, deadColor); + continue; + } + if (speedDisabled || Math.floor(dots[i].size) > maxDotRadius) { + // check for resize by the user + dots[i].size = maxDotRadius; + } else if (server[speedStatType] <= 0) { + // if not changing size, increase to max radius + if (dots[i].size < maxDotRadius) + dots[i].size = dots[i].size + 1; + if (dots[i].size > maxDotRadius) + dots[i].size = maxDotRadius; + } else { + sizeChange = getStat(i, speedStatType); + if (dots[i].growing) { + dots[i].size = dots[i].size + sizeChange; + if (dots[i].size + sizeChange > maxDotRadius) { + dots[i].growing = false; + } + } + else { + dots[i].size = dots[i].size - sizeChange; + if (dots[i].size - sizeChange < minDotRadius) { + dots[i].growing = true; + } + } + } + drawDot(x, y, Math.floor(dots[i].size), + getColor(getStat(i, colorStatType))); + } + /* + * mousedDot shouldn't be set to an invalid dot, + * but stats might have changed since then + */ + if (mousedDot >= 0 && mousedDot < stats.servers.length) + document.getElementById('vishoverinfo').innerHTML = + getInfo(stats.servers[mousedDot]); + drawing = false; +} + +// fill in a few grey dots +function drawGrid() { + context.clearRect(0, 0, canvas.width, canvas.height); + for (var i = 0, k = 0; i < canvas.width; i += dotSpacing * 2, k++) { + for (var j = 0, l = 0; j < canvas.height; j += dotSpacing * 2, l++) { + drawDot(k, l, maxDotRadius, nullColor); + } + } +} + +// fill a dot specified by indices into dot grid +function drawDot(i, j, r, c) { + var x = i * dotSpacing * 2 + dotSpacing; + var y = j * dotSpacing * 2 + dotSpacing; + context.clearRect(x - dotSpacing, y - dotSpacing, dotSpacing * 2, + dotSpacing * 2); + if (useCircles) + fillCircle(x, y, r - dotPadding, c); + else + fillSquare(x - r, y - r, (r - dotPadding) * 2, c); +} + +// stroke a dot specified by indices into dot grid +function strokeDot(i, j, r, c) { + var x = i * dotSpacing * 2 + dotSpacing; + var y = j * dotSpacing * 2 + dotSpacing; + context.clearRect(x - dotSpacing, y - dotSpacing, dotSpacing * 2, + dotSpacing * 2); + if (useCircles) + strokeCircle(x, y, r - dotPadding, c); + else + strokeSquare(x - r, y - r, (r - dotPadding) * 2, c); +} + +function getStat(dotIndex, statIndex) { + return Math.min(1, stats.servers[dotIndex][statIndex] / + maxStatValues[statIndex]); +} + +// translate color between 0 and maxObservedColor into an html color code +function getColor(normColor) { + return colorPalette[Math.round((colorPalette.length - 1) * normColor)]; +} + +function strokeCircle(x, y, r, c) { + context.strokeStyle = c; + context.lineWidth = 2; + context.beginPath(); + context.arc(x, y, r, 0, Math.PI * 2, true); + context.closePath(); + context.stroke(); +} + +function fillCircle(x, y, r, c) { + context.fillStyle = c; + context.beginPath(); + context.arc(x, y, r, 0, Math.PI * 2, true); + context.closePath(); + context.fill(); +} + +function strokeSquare(x, y, d, c) { + context.strokeStyle = c; + context.lineWidth = 2; + context.strokeRect(x, y, d, d); +} + +function fillSquare(x, y, d, c) { + context.fillStyle = c; + context.fillRect(x, y, d, d); +} + +// callback for shape selection +function setShape(obj) { + switch (obj.selectedIndex) { + case 0: + useCircles = true; + break; + case 1: + useCircles = false; + break; + default: + useCircles = true; + } + drawGrid(); + setState(); +} + +// callback for size selection +function setSize(obj) { + switch (obj.selectedIndex) { + case 0: + dotSpacing = 5; + minDotRadius = 1; + break; + case 1: + dotSpacing = 10; + minDotRadius = 3; + break; + case 2: + dotSpacing = 20; + minDotRadius = 5; + break; + case 3: + dotSpacing = 40; + minDotRadius = 7; + break; + default: + dotSpacing = 10; + minDotRadius = 3; + } + maxDotRadius = dotSpacing - dotPadding; + drawGrid(); + setState(); +} + +// callback for motion selection +function setMotion(obj) { + if (obj.selectedIndex <= 0) { + speedDisabled = true; + setState(); + return; + } + var i = 0; + for (var s in statNames) { + if (i == obj.selectedIndex) { + speedStatType = s; + break; + } + i++; + } + speedDisabled = false; + setState(); +} + +// callback for color selection +function setColor(obj) { + var i = 0; + for (var s in statNames) { + if (i == obj.selectedIndex) { + colorStatType = s; + break; + } + i++; + } + setState(); +} + +// hide server info on mouseout +function hideId(e) { + document.getElementById('vishoverinfo').style.visibility = 'hidden'; +} + +// display server info on mouseover +function showId(e) { + var x; + var y; + if (e.pageX || e.pageY) { + x = e.pageX + main.scrollLeft; + y = e.pageY + main.scrollTop; + } + else { + // clientX and clientY unimplemented + return; + } + var relx = x - canvas.offsetLeft - main.offsetLeft; + var rely = y - canvas.offsetTop - main.offsetTop; + var width = Math.ceil(Math.sqrt(stats.servers.length)); + gotDot = Math.floor(relx / (dotSpacing * 2)) + width * + Math.floor(rely / (dotSpacing * 2)); + mousedDot = -1; + if (relx < (width * dotSpacing * 2) && gotDot >= 0 && + gotDot < stats.servers.length) { + mousedDot = gotDot; + document.getElementById('vishoverinfo').style.left = relx + + canvas.offsetLeft; + document.getElementById('vishoverinfo').style.top = Math.max(0, + rely + canvas.offsetTop - 70); + document.getElementById('vishoverinfo').style.visibility = 'visible'; + } + else { + document.getElementById('vishoverinfo').style.visibility = 'hidden'; + } +} + +function setState() { + var url = visurl + '?shape=' + (useCircles ? 'circles' : 'squares') + + '&size=' + (dotSpacing * 2) + (speedDisabled ? '' : + '&motion=' + speedStatType) + + '&color=' + colorStatType; + window.history.replaceState(window.history.state, 'Server Activity', url); + + setOptions(useCircles ? 'circles' : 'squares', dotSpacing * 2, + speedDisabled ? '' : speedStatType, colorStatType); +} + +// go to server page on click +function goToServer(e) { + /* + * mousedDot shouldn't be set to an invalid dot, + * but stats might have changed since then + */ + if (mousedDot >= 0 && mousedDot < stats.servers.length) + window.location = serverurl + stats.servers[mousedDot].ip; +} + +/*window.onload = function() { + drawGrid(); + drawDots(); + getXML(); +}*/
