blob: 72e34a5036bc3f5a362084a5045c7352b488b28a [file] [log] [blame]
<!DOCTYPE html>
<!--
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.
-->
<html>
<head>
<!-- <script src="sorttable.js"></script> -->
<meta charset="UTF-8">
<title>Traffic Monitor</title>
<style>
body {
font-family: "Lato", sans-serif;
font-size: 14px;
margin: 0;
max-width: 100vw;
}
table {
border-collapse: separate;
border-spacing: 0px 0;
width: 100%;
}
th, td {
padding:5px 20px 5px 5px;
}
th {
white-space: nowrap;
}
tbody tr:nth-child(even) {
background: #dfe
}
tbody tr:nth-child(odd) {
background: #fff
}
li.endpoint {
margin: 4px 0;
}
div#top-bar {
display: inline-flex;
justify-content: space-around;
align-items: center;
width: 100%;
margin: 15px 0;
}
div#links {
display: grid;
grid-template-columns: 1fr 1fr;
max-width: 100ch;
}
div#links div {
margin-left: 4px;
}
div#links a {
display: block;
}
tbody tr.error {
background-color: #f00;
}
tbody tr.warning {
background-color: #f80;
}
input[type=radio] {
visibility: hidden;
display: none;
}
label {
display: block;
padding: 14px 21px;
border-radius: 2px 2px 0 0;
cursor: pointer;
position: relative;
top: 4px;
transition: background-color ease-in-out 0.3s;
text-align: center;
border: 1px solid green;
}
label:hover {
background-color: #cfd;
}
ul.tabs {
list-style: none;
max-width: 100%;
border: 1px solid #ccc;
background-color: #f1f1f1;
position: relative;
}
div.tabcontent {
z-index: 2;
display: none;
visibility: hidden;
overflow: hidden;
width: 100%;
position: absolute;
top: 53px;
left: 0;
padding: 6px 0;
border-top: none;
}
input.tab:checked ~ div.tabcontent {
display: block;
visibility: visible;
}
input.tab:checked ~ label{
background-color: #adb;
border-bottom-width: 0;
}
ul.tabs li {
float: left;
display: block;
}
</style>
<script>
function init() {
getTopBar();
setInterval(getCacheCount, 4755);
setInterval(getCacheAvailableCount, 4800);
setInterval(getBandwidth, 4621);
setInterval(getBandwidthCapacity, 4591);
setInterval(getCacheDownCount, 4832);
setInterval(getVersion, 10007); // change to retry on failure, and only do on startup
setInterval(getTrafficOpsUri, 10019); // change to retry on failure, and only do on startup
setInterval(getTrafficOpsCdn, 10500); // change to retry on failure, and only do on startup
setInterval(getEvents, 2004); // change to retry on failure, and only do on startup
setInterval(getCacheStatuses, 5009);
setInterval(getDsStats, 4003);
}
// source: http://stackoverflow.com/a/2901298/292623
function numberStrWithCommas(x) {
return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function getCacheCount() {
ajax("/api/cache-count", function(r) {
document.getElementById("cache-count").innerHTML = r;
});
}
function getCacheAvailableCount() {
ajax("/api/cache-available-count", function(r) {
document.getElementById("cache-available").innerHTML = r;
});
}
function getBandwidth() {
ajax("/api/bandwidth-kbps", function(r) {
document.getElementById("bandwidth").innerHTML = numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
});
}
function getBandwidthCapacity() {
ajax("/api/bandwidth-capacity-kbps", function(r) {
document.getElementById("bandwidth-capacity").innerHTML = numberStrWithCommas((r / kilobitsInGigabit).toString());
});
}
function getCacheDownCount() {
ajax("/api/cache-down-count", function(r) {
document.getElementById("cache-down").innerHTML = r;
});
}
function getVersion() {
ajax("/api/version", function(r) {
document.getElementById("version").innerHTML = r;
});
}
function getTrafficOpsUri() {
ajax("/api/traffic-ops-uri", function(r) {
document.getElementById("source-uri").innerHTML = "<a href='" + r + "'>" + r + "</a>";
});
}
function getTrafficOpsCdn() {
ajax("/publish/ConfigDoc", function(r) {
var j = JSON.parse(r);
document.getElementById("cdn-name").innerHTML = j.cdnName;
});
}
var lastEvent = 0;
function getEvents() {
/// \todo add /api/events-since/{index} (and change Traffic Monitor to keep latest
ajax("/publish/EventLog", function(r) {
var jdata = JSON.parse(r);
for (i = jdata.events.length - 1; i >= 0; i--) {
var event = jdata.events[i];
if (event.index <= lastEvent) {
continue;
}
lastEvent = event.index
var row = document.getElementById("event-log").insertRow(0); //document.createElement("tr");
row.classList.add("stripes");
row.insertCell(0).id = row.id + "-name";
document.getElementById(row.id + "-name").textContent = event.name;
document.getElementById(row.id + "-name").style.whiteSpace = "nowrap";
row.insertCell(1).textContent = event.type;
row.insertCell(2).textContent = event.isAvailable ? "available" : "offline";
if(event.isAvailable) {
row.classList.add("stripes");
row.classList.remove("error");
} else {
row.classList.add("error");
row.classList.remove("stripes");
}
row.insertCell(3).textContent = event.description;
row.insertCell(4).id = row.id + "-last";
document.getElementById(row.id + "-last").textContent = new Date(event.time * 1000).toISOString();
document.getElementById(row.id + "-last").style.whiteSpace = "nowrap";
document.getElementById(row.id + "-last").style.textAlign = "right";
}
});
}
function getCacheStates() {
ajax("/api/cache-statuses", function(r) {
var jdata = JSON.parse(r);
var servers = Object.keys(jdata); //debug
var table = document.getElementById("cache-states");
for (i = 0; i < servers.length; i++) {
var server = servers[i];
if (!document.getElementById("cache-states-" + server)) {
var row = table.insertRow(0); //document.createElement("tr");
row.classList.add("stripes");
row.id = "cache-states-" + server
row.insertCell(0).id = row.id + "-server";
row.insertCell(1).id = row.id + "-type";
row.insertCell(2).id = row.id + "-status";
row.insertCell(3).id = row.id + "-load-average";
row.insertCell(4).id = row.id + "-query-time";
row.insertCell(5).id = row.id + "-health-time";
row.insertCell(6).id = row.id + "-stat-time";
row.insertCell(7).id = row.id + "-health-span";
row.insertCell(8).id = row.id + "-stat-span";
row.insertCell(9).id = row.id + "-bandwidth";
row.insertCell(10).id = row.id + "-connection-count";
document.getElementById(row.id + "-server").textContent = server;
document.getElementById(row.id + "-server").style.whiteSpace = "nowrap";
document.getElementById(row.id + "-load-average").style.textAlign = "right";
document.getElementById(row.id + "-query-time").style.textAlign = "right";
document.getElementById(row.id + "-health-time").style.textAlign = "right";
document.getElementById(row.id + "-stat-time").style.textAlign = "right";
document.getElementById(row.id + "-health-span").style.textAlign = "right";
document.getElementById(row.id + "-stat-span").style.textAlign = "right";
document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
document.getElementById(row.id + "-connection-count").style.textAlign = "right";
}
/* \todo change to iterate over members, and make cells id constructed from these*/
if (jdata[server].hasOwnProperty("type")) {
document.getElementById("cache-states-" + server + "-type").textContent = jdata[server].type;
}
if (jdata[server].hasOwnProperty("status")) {
document.getElementById("cache-states-" + server + "-status").textContent = jdata[server].status;
var row = document.getElementById("cache-states-" + server);
if (jdata[server].status.indexOf("ADMIN_DOWN") != -1 || jdata[server].status.indexOf("OFFLINE") != -1) {
row.classList.add("warning");
row.classList.remove("error");
row.classList.remove("stripes");
} else if (jdata[server].status.indexOf(" available") == -1 && jdata[server].status.indexOf("ONLINE") != 0) {
row.classList.add("error");
row.classList.remove("warning");
row.classList.remove("stripes");
} else {
row.classList.add("stripe");
row.classList.remove("warning");
row.classList.remove("error");
}
}
if (jdata[server].hasOwnProperty("load_average")) {
document.getElementById("cache-states-" + server + "-load-average").textContent = jdata[server].load_average;
}
if (jdata[server].hasOwnProperty("query_time_ms")) {
document.getElementById("cache-states-" + server + "-query-time").textContent = jdata[server].query_time_ms;
}
if (jdata[server].hasOwnProperty("health_time_ms")) {
document.getElementById("cache-states-" + server + "-health-time").textContent = jdata[server].health_time_ms;
}
if (jdata[server].hasOwnProperty("stat_time_ms")) {
document.getElementById("cache-states-" + server + "-stat-time").textContent = jdata[server].stat_time_ms;
}
if (jdata[server].hasOwnProperty("health_span_ms")) {
document.getElementById("cache-states-" + server + "-health-span").textContent = jdata[server].health_span_ms;
}
if (jdata[server].hasOwnProperty("stat_span_ms")) {
document.getElementById("cache-states-" + server + "-stat-span").textContent = jdata[server].stat_span_ms;
}
if (jdata[server].hasOwnProperty("bandwidth_kbps")) {
var kbps = (jdata[server].bandwidth_kbps / kilobitsInMegabit).toFixed(2);
var max = numberStrWithCommas((jdata[server].bandwidth_capacity_kbps / kilobitsInMegabit).toFixed(0));
document.getElementById("cache-states-" + server + "-bandwidth").textContent = '' + kbps + ' / ' + max;
} else {
document.getElementById("cache-states-" + server + "-bandwidth").textContent = "N/A";
}
if (jdata[server].hasOwnProperty("connection_count")) {
document.getElementById("cache-states-" + server + "-connection-count").textContent = jdata[server].connection_count;
} else {
document.getElementById("cache-states-" + server + "-connection-count").textContent = "N/A";
}
}
for (var i = 1, row; row = table.rows[i]; i++) { // start at 1, because row[0] is the header
var server = row.id.replace(/^cache-states-/, '');
if(!(server in jdata)) {
table.deleteRow(i);
}
}
})
}
var millisecondsInSecond = 1000;
var kilobitsInGigabit = 1000000;
var kilobitsInMegabit = 1000;
// dsDisplayFloat takes a float, and returns the string to display. For nonzero values, it returns two decimal places. For zero values, it returns an empty string, to make nonzero values more visible.
function dsDisplayFloat(f) {
var s = f
if (f != 0.0) {
s = f.toFixed(2);
}
return s
}
function getDsStats() {
var now = Date.now();
/// \todo add /api/delivery-service-stats which only returns the data needed by the UI, for efficiency
ajax("/publish/DsStats", function(r) {
var j = JSON.parse(r);
var jds = j.deliveryService
var deliveryServiceNames = Object.keys(jds); //debug
//decrementing for loop so DsNames are alphabetical A-Z
//TODO allow for filtering of columns so this isn't necessary
for (var i = deliveryServiceNames.length - 1; i >= 0; i--) {
var deliveryService = deliveryServiceNames[i];
if (!document.getElementById("deliveryservice-stats-" + deliveryService)) {
var row = document.getElementById("deliveryservice-stats").insertRow(0); //document.createElement("tr");
row.id = "deliveryservice-stats-" + deliveryService
row.insertCell(0).id = row.id + "-delivery-service";
row.insertCell(1).id = row.id + "-status";
row.insertCell(2).id = row.id + "-caches-reporting";
row.insertCell(3).id = row.id + "-bandwidth";
row.insertCell(4).id = row.id + "-tps";
row.insertCell(5).id = row.id + "-2xx";
row.insertCell(6).id = row.id + "-3xx";
row.insertCell(7).id = row.id + "-4xx";
row.insertCell(8).id = row.id + "-5xx";
row.insertCell(9).id = row.id + "-disabled-locations";
document.getElementById(row.id + "-delivery-service").textContent = deliveryService;
document.getElementById(row.id + "-delivery-service").style.whiteSpace = "nowrap";
document.getElementById(row.id + "-caches-reporting").style.textAlign = "right";
document.getElementById(row.id + "-bandwidth").style.textAlign = "right";
document.getElementById(row.id + "-tps").style.textAlign = "right";
document.getElementById(row.id + "-2xx").style.textAlign = "right";
document.getElementById(row.id + "-3xx").style.textAlign = "right";
document.getElementById(row.id + "-4xx").style.textAlign = "right";
document.getElementById(row.id + "-5xx").style.textAlign = "right";
}
// \todo check that array has a member before dereferencing [0]
if (jds[deliveryService].hasOwnProperty("isAvailable")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-status").textContent = jds[deliveryService]["isAvailable"][0].value == "true" ? "available" : "unavailable - " + jds[deliveryService]["error-string"][0].value;
}
if (jds[deliveryService].hasOwnProperty("caches-reporting") && jds[deliveryService].hasOwnProperty("caches-available") && jds[deliveryService].hasOwnProperty("caches-configured")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-caches-reporting").textContent = jds[deliveryService]['caches-reporting'][0].value + " / " + jds[deliveryService]['caches-available'][0].value + " / " + jds[deliveryService]['caches-configured'][0].value;
}
if (jds[deliveryService].hasOwnProperty("total.kbps")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = (jds[deliveryService]['total.kbps'][0].value / kilobitsInMegabit).toFixed(2);
} else {
document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = "N/A";
}
if (jds[deliveryService].hasOwnProperty("total.tps_total")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_total'][0].value));
} else {
document.getElementById("deliveryservice-stats-" + deliveryService + "-tps").textContent = "N/A";
}
if (jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
} else {
document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = "N/A";
}
if (jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
} else {
document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = "N/A";
}
if (jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
} else {
document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = "N/A";
}
if (jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
} else {
document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = "N/A";
}
// \todo implement disabled locations
var row = document.getElementById("deliveryservice-stats-" + deliveryService);
if (jds[deliveryService]["isAvailable"][0].value == "true") {
row.classList.add("stripes");
row.classList.remove("error");
} else {
row.classList.add("error");
row.classList.remove("stripes");
}
}
})
}
function getCacheStatuses() {
getCacheCount();
getCacheAvailableCount();
getCacheDownCount();
getCacheStates();
}
function getTopBar() {
getVersion();
getTrafficOpsUri();
getTrafficOpsCdn();
getCacheStatuses();
}
function ajax(endpoint, f) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
f(xhttp.responseText);
}
};
xhttp.open("GET", endpoint, true);
xhttp.send();
}
</script>
</head>
<body onload="init()">
<div id="top-bar">
<div>Caches: count=<span id="cache-count">0</span> available=<span id="cache-available">0</span> down=<span id="cache-down">0</span> </div>
<div>Bandwidth: <span id="bandwidth">0</span> / <span id="bandwidth-capacity"></span> gbps</div>
<div>Traffic Ops: <span id="source-uri">unknown</span></div>
<div>CDN: <span id="cdn-name">unknown</span></div>
<div>Version: <span id="version">unknown</span></div>
</div>
<div id="links">
<div>
<a href="/publish/EventLog">EventLog</a>
<a href="/publish/CacheStats">CacheStats</a>
<a href="/publish/DsStats">DsStats</a>
<a href="/publish/CrStates">CrStates (as published to Traffic Routers)</a>
<a href="/publish/CrConfig">CrConfig (json)</a>
<a href="/publish/PeerStates">PeerStates</a>
<a href="/publish/Stats">Stats</a>
<a href="/publish/StatSummary">StatSummary</a>
<a href="/publish/ConfigDoc">ConfigDoc</a>
</div>
<div>
<a href="/api/cache-count">/api/cache-count</a>
<a href="/api/cache-available-count">/api/cache-available-count</a>
<a href="/api/cache-down-count">/api/cache-down-count</a>
<a href="/api/version">/api/version</a>
<a href="/api/traffic-ops-uri">/api/traffic-ops-uri</a>
<a href="/api/cache-statuses">/api/cache-statuses</a>
<a href="/api/bandwidth-kbps">/api/bandwidth-kbps</a>
<a href="/api/bandwidth-capacity-kbps">/api/bandwidth-capacity-kbps</a>
<a href="/api/monitor-config">/api/monitor-config</a>
<a href="/api/crconfig-history">/api/crconfig-history</a>
</div>
</div>
<div id="update-num-text">Number of updates: <span id="update-num">0</span></div>
<div id="last-val-text">Last Val: <span id="last-val">0</span></div>
<ul class="tabs" role="tablist">
<li class="tab tab-header" id="cache-states-content-tab">
<input type="radio" name="tabs" class="tab" id="cache-states-input" checked />
<label for="cache-states-input" aria-selected="true">Cache States</label>
<div id="cache-states-content" class="tabcontent">
<table class="tab-grid sortable">
<thead>
<tr>
<th>Server</th>
<th>Type</th>
<th>Status</th>
<th align="right">Load Average</th>
<th align="right">Query Time (ms)</th>
<th align="right">Health Time (ms)</th>
<th align="right">Stat Time (ms)</th>
<th align="right">Health Span (ms)</th>
<th align="right">Stat Span (ms)</th>
<th align="right">Bandwidth (mbps)</th>
<th align="right">Connection Count</th>
</tr>
</thead>
<tbody id="cache-states"></tbody>
</table>
</div>
</li>
<li class="tab tab-header" id="deliveryservice-stats-content-tab">
<input type="radio" name="tabs" class="tab" id="deliveryservice-stats-input"/>
<label for="deliveryservice-stats-input" aria-selected="false">Delivery Service States</label>
<div id="deliveryservice-stats-content" class="tabcontent">
<table class="tab-grid sortable">
<thead>
<tr>
<th>Delivery Service</th>
<th>Status</th>
<th align="right">Caches Reporting/Available/Configured</th>
<th align="right">Bandwidth (mbps)</th>
<th align="right">t/sec</th>
<th align="right">2xx/sec</th>
<th align="right">3xx/sec</th>
<th align="right">4xx/sec</th>
<th align="right">5xx/sec</th>
<th>Disabled Locations</th>
</tr>
</thead>
<tbody id="deliveryservice-stats"></tbody>
</table>
</div>
</li>
<li id="event-log-content-tab" class="tab tab-header">
<input type="radio" name="tabs" class="tab" id="event-log-input"/>
<label for="event-log-input" aria-selected="false">Event Log</label>
<div id="event-log-content" class="tabcontent">
<table class="tab-grid sortable">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Status</th>
<th>Description</th>
<th align="center" id="event-log-last-header">Event Time</th>
</tr>
</thead>
<tbody id="event-log"></tbody>
</table>
</div>
</li>
</ul>
</body>
</html>