cadvisor/pages/assets/js/containers.js
2016-05-10 14:23:32 -07:00

1104 lines
34 KiB
JavaScript

// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed 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.
function humanize(num, size, units) {
var unit;
for (unit = units.pop(); units.length && num >= size; unit = units.pop()) {
num /= size;
}
return [num, unit];
}
// Following the IEC naming convention
function humanizeIEC(num) {
var ret = humanize(num, 1024, ['TiB', 'GiB', 'MiB', 'KiB', 'B']);
return ret[0].toFixed(2) + ' ' + ret[1];
}
// Following the Metric naming convention
function humanizeMetric(num) {
var ret = humanize(num, 1000, ['TB', 'GB', 'MB', 'KB', 'Bytes']);
return ret[0].toFixed(2) + ' ' + ret[1];
}
// Draw a table.
function drawTable(
seriesTitles, titleTypes, data, elementId, numPages, sortIndex) {
var dataTable = new google.visualization.DataTable();
for (var i = 0; i < seriesTitles.length; i++) {
dataTable.addColumn(titleTypes[i], seriesTitles[i]);
}
dataTable.addRows(data);
if (!(elementId in window.charts)) {
window.charts[elementId] =
new google.visualization.Table(document.getElementById(elementId));
}
var cssClassNames = {
'headerRow': '',
'tableRow': 'table-row',
'oddTableRow': 'table-row'
};
var opts = {
alternatingRowStyle: true,
page: 'enable',
pageSize: numPages,
allowHtml: true,
sortColumn: sortIndex,
sortAscending: false,
cssClassNames: cssClassNames
};
window.charts[elementId].draw(dataTable, opts);
}
// Draw a line chart.
function drawLineChart(seriesTitles, data, elementId, unit) {
var min = Infinity;
var max = -Infinity;
for (var i = 0; i < data.length; i++) {
// Convert the first column to a Date.
if (data[i] != null) {
data[i][0] = new Date(data[i][0]);
}
// Find min, max.
for (var j = 1; j < data[i].length; j++) {
var val = data[i][j];
if (val < min) {
min = val;
}
if (val > max) {
max = val;
}
}
}
// We don't want to show any values less than 0 so cap the min value at that.
// At the same time, show 10% of the graph below the min value if we can.
var minWindow = min - (max - min) / 10;
if (minWindow < 0) {
minWindow = 0;
}
// Add the definition of each column and the necessary data.
var dataTable = new google.visualization.DataTable();
dataTable.addColumn('datetime', seriesTitles[0]);
for (var i = 1; i < seriesTitles.length; i++) {
dataTable.addColumn('number', seriesTitles[i]);
}
dataTable.addRows(data);
// Create and draw the visualization.
if (!(elementId in window.charts)) {
window.charts[elementId] =
new google.visualization.LineChart(document.getElementById(elementId));
}
// TODO(vmarmol): Look into changing the view window to get a smoother
// animation.
var opts = {
curveType: 'function',
height: 300,
legend: {position: 'none'},
focusTarget: 'category',
vAxis: {
title: unit,
viewWindow: {
min: minWindow,
}
},
legend: {
position: 'bottom'
}
};
// If the whole data series has the same value, try to center it in the chart.
if (min == max) {
opts.vAxis.viewWindow.max = 1.1 * max;
opts.vAxis.viewWindow.min = 0.9 * max;
}
window.charts[elementId].draw(dataTable, opts);
}
// Gets the length of the interval in nanoseconds.
function getInterval(current, previous) {
var cur = new Date(current);
var prev = new Date(previous);
// ms -> ns.
return (cur.getTime() - prev.getTime()) * 1000000;
}
// Checks if the specified stats include the specified resource.
function hasResource(stats, resource) {
return stats.stats.length > 0 && stats.stats[0][resource];
}
// Checks if all containers in provided list include the specified resource.
function hasResourceForAll(containerInfos, resource) {
if (containerInfos.length === 0) {
return false;
}
for (var i = 0; i < containerInfos.length; i++) {
if (!hasResource(containerInfos[i], resource)) {
return false;
}
}
return true;
}
// Draw a set of gauges. Data is comprised of an array of arrays with two
// elements:
// a string label and a numeric value for the gauge.
function drawGauges(elementId, gauges) {
gauges.unshift(['Label', 'Value']);
// Create and populate the data table.
var data = google.visualization.arrayToDataTable(gauges);
// Create and draw the visualization.
var options = {
height: 100,
redFrom: 90,
redTo: 100,
yellowFrom: 75,
yellowTo: 90,
minorTicks: 5,
animation: {duration: 900, easing: 'linear'}
};
var chart =
new google.visualization.Gauge(document.getElementById(elementId));
chart.draw(data, options);
}
// Get the machine info.
function getMachineInfo(rootDir, callback) {
$.getJSON(rootDir + 'api/v1.0/machine', function(data) { callback(data); });
}
// Get ps info.
function getProcessInfo(rootDir, containerName, callback) {
$.getJSON(rootDir + 'api/v2.0/ps' + containerName)
.done(function(data) { callback(data); })
.fail(function(jqhxr, textStatus, error) { callback([]); });
}
// Get the container stats for the specified container.
function getStats(rootDir, containerName, callback) {
// Request 60s of container history and no samples.
var request = JSON.stringify({
// Update main.statsRequestedByUI while updating "num_stats" here.
'num_stats': 60,
'num_samples': 0
});
$.when(
$.post(rootDir + 'api/v1.0/containers' + containerName, request),
$.post(rootDir + 'api/v1.1/subcontainers' + containerName, request))
.done(function(containersResp, subcontainersResp) {
callback(containersResp[0], subcontainersResp[0]);
});
}
// Draw the graph for CPU usage.
function drawCpuTotalUsage(elementId, machineInfo, stats) {
if (stats.spec.has_cpu && !hasResource(stats, 'cpu')) {
return;
}
var titles = ['Time', 'Total'];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
var intervalNs = getInterval(cur.timestamp, prev.timestamp);
var elements = [];
elements.push(cur.timestamp);
elements.push((cur.cpu.usage.total - prev.cpu.usage.total) / intervalNs);
data.push(elements);
}
drawLineChart(titles, data, elementId, 'Cores');
}
// Draw the graph for CPU load.
function drawCpuLoad(elementId, machineInfo, stats) {
var titles = ['Time', 'Average'];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var elements = [];
elements.push(cur.timestamp);
elements.push(cur.cpu.load_average / 1000);
data.push(elements);
}
drawLineChart(titles, data, elementId, 'Runnable threads');
}
// Draw the graph for per-core CPU usage.
function drawCpuPerCoreUsage(elementId, machineInfo, stats) {
if (stats.spec.has_cpu && !hasResource(stats, 'cpu')) {
return;
}
// Add a title for each core.
var titles = ['Time'];
for (var i = 0; i < machineInfo.num_cores; i++) {
titles.push('Core ' + i);
}
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
var intervalNs = getInterval(cur.timestamp, prev.timestamp);
var elements = [];
elements.push(cur.timestamp);
for (var j = 0; j < machineInfo.num_cores; j++) {
elements.push(
(cur.cpu.usage.per_cpu_usage[j] - prev.cpu.usage.per_cpu_usage[j]) /
intervalNs);
}
data.push(elements);
}
drawLineChart(titles, data, elementId, 'Cores');
}
// Draw the graph for CPU usage breakdown.
function drawCpuUsageBreakdown(elementId, machineInfo, containerInfo) {
if (containerInfo.spec.has_cpu && !hasResource(containerInfo, 'cpu')) {
return;
}
var titles = ['Time', 'User', 'Kernel'];
var data = [];
for (var i = 1; i < containerInfo.stats.length; i++) {
var cur = containerInfo.stats[i];
var prev = containerInfo.stats[i - 1];
var intervalNs = getInterval(cur.timestamp, prev.timestamp);
var elements = [];
elements.push(cur.timestamp);
elements.push((cur.cpu.usage.user - prev.cpu.usage.user) / intervalNs);
elements.push(
(cur.cpu.usage.system - prev.cpu.usage.system) / intervalNs);
data.push(elements);
}
drawLineChart(titles, data, elementId, 'Cores');
}
// Return chart titles and data from an array of subcontainerInfos, using the
// passed dataFn to return the individual data points.
function getSubcontainerChartData(subcontainerInfos, dataFn) {
var titles = ['Time'];
var data = [];
for (var i = 0; i < subcontainerInfos.length; i++) {
titles.push(subcontainerInfos[i].name);
for (var j = 1; j < subcontainerInfos[i].stats.length; j++) {
var cur = subcontainerInfos[i].stats[j];
var prev = subcontainerInfos[i].stats[j - 1];
// Generate a sparse array with timestamp at index zero and data point at
// index i+1, to be used as a DataTable row.
var elements = [cur.timestamp];
subcontainerInfos.forEach(function() {
elements.push(null);
});
elements[i+1] = dataFn(cur, prev);
data.push(elements);
}
}
return {
titles: titles,
data: data,
}
}
// Draw the graph for per-subcontainer CPU usage.
function drawCpuPerSubcontainerUsage(elementId, subcontainerInfos) {
if (!hasResourceForAll(subcontainerInfos, 'cpu')) {
return;
}
var chartData = getSubcontainerChartData(subcontainerInfos,
function(cur, prev) {
var intervalNs = getInterval(cur.timestamp, prev.timestamp);
return (cur.cpu.usage.total - prev.cpu.usage.total) / intervalNs;
});
drawLineChart(chartData.titles, chartData.data, elementId, 'Cores');
}
// Draw the graph for per-subcontainer memory usage.
function drawMemoryPerSubcontainerUsage(elementId, subcontainerInfos) {
if (!hasResourceForAll(subcontainerInfos, 'memory')) {
return;
}
var chartData = getSubcontainerChartData(subcontainerInfos,
function(cur, prev) {
return cur.memory.usage / oneMegabyte;
});
drawLineChart(chartData.titles, chartData.data, elementId, 'Megabytes');
}
// Draw the gauges for overall resource usage.
function drawOverallUsage(elementId, machineInfo, containerInfo) {
var cur = containerInfo.stats[containerInfo.stats.length - 1];
var gauges = [];
var cpuUsage = 0;
if (containerInfo.spec.has_cpu && containerInfo.stats.length >= 2) {
var prev = containerInfo.stats[containerInfo.stats.length - 2];
var rawUsage = cur.cpu.usage.total - prev.cpu.usage.total;
var intervalNs = getInterval(cur.timestamp, prev.timestamp);
// Convert to millicores and take the percentage
cpuUsage =
Math.round(((rawUsage / intervalNs) / machineInfo.num_cores) * 100);
if (cpuUsage > 100) {
cpuUsage = 100;
}
gauges.push(['CPU', cpuUsage]);
}
var memoryUsage = 0;
if (containerInfo.spec.has_memory) {
// Saturate to the machine size.
var limit = containerInfo.spec.memory.limit;
if (limit > machineInfo.memory_capacity) {
limit = machineInfo.memory_capacity;
}
memoryUsage = Math.round((cur.memory.usage / limit) * 100);
gauges.push(['Memory', memoryUsage]);
}
var numGauges = gauges.length;
if (cur.filesystem) {
for (var i = 0; i < cur.filesystem.length; i++) {
var data = cur.filesystem[i];
var totalUsage = Math.floor((data.usage * 100.0) / data.capacity);
var els = window.cadvisor.fsUsage.elements[data.device];
// Update the gauges in the right order.
gauges[numGauges + els.index] = ['FS #' + (els.index + 1), totalUsage];
}
// Limit the number of filesystem gauges displayed to 5.
// 'Filesystem details' section still shows information for all filesystems.
var max_gauges = numGauges + 5;
if (gauges.length > max_gauges) {
gauges = gauges.slice(0, max_gauges);
}
}
drawGauges(elementId, gauges);
}
var oneMegabyte = 1024 * 1024;
var oneGigabyte = 1024 * oneMegabyte;
function drawMemoryUsage(elementId, machineInfo, containerInfo) {
if (containerInfo.spec.has_memory && !hasResource(containerInfo, 'memory')) {
return;
}
var titles = ['Time', 'Total', 'Hot'];
var data = [];
for (var i = 0; i < containerInfo.stats.length; i++) {
var cur = containerInfo.stats[i];
var elements = [];
elements.push(cur.timestamp);
elements.push(cur.memory.usage / oneMegabyte);
elements.push(cur.memory.working_set / oneMegabyte);
data.push(elements);
}
// Get the memory limit, saturate to the machine size.
var memory_limit = machineInfo.memory_capacity;
if (containerInfo.spec.memory.limit &&
(containerInfo.spec.memory.limit < memory_limit)) {
memory_limit = containerInfo.spec.memory.limit;
}
// Updating the progress bar.
var cur = containerInfo.stats[containerInfo.stats.length - 1];
var hotMemory = Math.floor((cur.memory.working_set * 100.0) / memory_limit);
var totalMemory = Math.floor((cur.memory.usage * 100.0) / memory_limit);
var coldMemory = totalMemory - hotMemory;
$('#progress-hot-memory').width(hotMemory + '%');
$('#progress-cold-memory').width(coldMemory + '%');
$('#memory-text')
.text(
humanizeIEC(cur.memory.usage) + ' / ' + humanizeIEC(memory_limit) +
' (' + totalMemory + '%)');
drawLineChart(titles, data, elementId, 'Megabytes');
}
// Get the index of the interface with the specified name.
function getNetworkInterfaceIndex(interfaceName, interfaces) {
for (var i = 0; i < interfaces.length; i++) {
if (interfaces[i].name == interfaceName) {
return i;
}
}
return -1;
}
// Draw the graph for network tx/rx bytes.
function drawNetworkBytes(elementId, machineInfo, stats) {
if (stats.spec.has_network && !hasResource(stats, 'network')) {
return;
}
// Get interface index.
var interfaceIndex = -1;
if (stats.stats.length > 0) {
interfaceIndex = getNetworkInterfaceIndex(
window.cadvisor.network.interface, stats.stats[0].network.interfaces);
}
if (interfaceIndex < 0) {
console.log(
'Unable to find interface"', interfaceName, '" in ',
stats.stats.network);
return;
}
var titles = ['Time', 'Tx bytes', 'Rx bytes'];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
var intervalInSec = getInterval(cur.timestamp, prev.timestamp) / 1000000000;
var elements = [];
elements.push(cur.timestamp);
elements.push(
(cur.network.interfaces[interfaceIndex].tx_bytes -
prev.network.interfaces[interfaceIndex].tx_bytes) /
intervalInSec);
elements.push(
(cur.network.interfaces[interfaceIndex].rx_bytes -
prev.network.interfaces[interfaceIndex].rx_bytes) /
intervalInSec);
data.push(elements);
}
drawLineChart(titles, data, elementId, 'Bytes per second');
}
// Draw the graph for network errors
function drawNetworkErrors(elementId, machineInfo, stats) {
if (stats.spec.has_network && !hasResource(stats, 'network')) {
return;
}
// Get interface index.
var interfaceIndex = -1;
if (stats.stats.length > 0) {
interfaceIndex = getNetworkInterfaceIndex(
window.cadvisor.network.interface, stats.stats[0].network.interfaces);
}
if (interfaceIndex < 0) {
console.log(
'Unable to find interface"', interfaceName, '" in ',
stats.stats.network);
return;
}
var titles = ['Time', 'Tx', 'Rx'];
var data = [];
for (var i = 1; i < stats.stats.length; i++) {
var cur = stats.stats[i];
var prev = stats.stats[i - 1];
var intervalInSec = getInterval(cur.timestamp, prev.timestamp) / 1000000000;
var elements = [];
elements.push(cur.timestamp);
elements.push(
(cur.network.interfaces[interfaceIndex].tx_errors -
prev.network.interfaces[interfaceIndex].tx_errors) /
intervalInSec);
elements.push(
(cur.network.interfaces[interfaceIndex].rx_errors -
prev.network.interfaces[interfaceIndex].rx_errors) /
intervalInSec);
data.push(elements);
}
drawLineChart(titles, data, elementId, 'Errors per second');
}
// Update the filesystem usage values.
function drawFileSystemUsage(machineInfo, stats) {
var cur = stats.stats[stats.stats.length - 1];
if (!cur.filesystem) {
return;
}
var el = $('<div>');
for (var i = 0; i < cur.filesystem.length; i++) {
var data = cur.filesystem[i];
var totalUsage = Math.floor((data.usage * 100.0) / data.capacity);
// Update DOM elements.
var els = window.cadvisor.fsUsage.elements[data.device];
els.progressElement.width(totalUsage + '%');
els.textElement.text(
humanizeMetric(data.usage) + ' / ' + humanizeMetric(data.capacity) +
' (' + totalUsage + '%)');
}
}
function drawImages(images) {
if (images == null || images.length == 0) {
return;
}
window.charts = {};
var titles = ['Repository', 'Tags', 'ID', 'Virtual Size', 'Creation Time'];
var titleTypes = ['string', 'string', 'string', 'number', 'number'];
var sortIndex = 0;
var data = [];
for (var i = 0; i < images.length; i++) {
var elements = [];
var tags = [];
var repos = images[i].repo_tags[0].split(':');
repos.splice(-1, 1);
for (var j = 0; j < images[i].repo_tags.length; j++) {
var splits = images[i].repo_tags[j].split(':');
if (splits.length > 1) {
tags.push(splits[splits.length - 1]);
}
}
elements.push(repos.join(':'));
elements.push(tags.join(', '));
elements.push(images[i].id.substr(0, 24));
elements.push(
{v: images[i].virtual_size, f: humanizeIEC(images[i].virtual_size)});
var d = new Date(images[i].created * 1000);
elements.push({v: images[i].created, f: d.toLocaleString()});
data.push(elements);
}
drawTable(titles, titleTypes, data, 'docker-images', 30, sortIndex);
}
function drawProcesses(isRoot, rootDir, processInfo) {
if (processInfo.length == 0) {
$('#processes-top').text('No processes found');
return;
}
var titles = [
'User', 'PID', 'PPID', 'Start Time', 'CPU %', 'MEM %', 'RSS',
'Virtual Size', 'Status', 'Running Time', 'Command'
];
var titleTypes = [
'string', 'number', 'number', 'string', 'number', 'number', 'number',
'number', 'string', 'string', 'string'
];
var sortIndex = 4;
if (isRoot) {
titles.push('Container');
titleTypes.push('string');
}
var data = [];
for (var i = 0; i < processInfo.length; i++) {
var elements = [];
elements.push(processInfo[i].user);
elements.push(processInfo[i].pid);
elements.push(processInfo[i].parent_pid);
elements.push(processInfo[i].start_time);
elements.push({
v: processInfo[i].percent_cpu,
f: processInfo[i].percent_cpu.toFixed(2)
});
elements.push({
v: processInfo[i].percent_mem,
f: processInfo[i].percent_mem.toFixed(2)
});
elements.push({v: processInfo[i].rss, f: humanizeIEC(processInfo[i].rss)});
elements.push({
v: processInfo[i].virtual_size,
f: humanizeIEC(processInfo[i].virtual_size)
});
elements.push(processInfo[i].status);
elements.push(processInfo[i].running_time);
elements.push(processInfo[i].cmd);
if (isRoot) {
var cgroup = processInfo[i].cgroup_path;
// Use the raw cgroup link as it works for all containers.
var cgroupLink = '<a href="' + rootDir + 'containers/' + cgroup + '">' +
cgroup.substr(0, 30) + ' </a>';
elements.push({v: cgroup, f: cgroupLink});
}
data.push(elements);
}
drawTable(titles, titleTypes, data, 'processes-top', 25, sortIndex);
}
// Draw the filesystem usage nodes.
function startFileSystemUsage(elementId, machineInfo, stats) {
window.cadvisor.fsUsage = {};
// A map of device name to DOM elements.
window.cadvisor.fsUsage.elements = {};
var cur = stats.stats[stats.stats.length - 1];
var el = $('<div>');
if (!cur.filesystem) {
return;
}
for (var i = 0; i < cur.filesystem.length; i++) {
var data = cur.filesystem[i];
el.append(
$('<div>')
.addClass('row col-sm-12')
.append($('<h4>').text('FS #' + (i + 1) + ': ' + data.device)));
var progressElement =
$('<div>').addClass('progress-bar progress-bar-danger');
el.append(
$('<div>')
.addClass('col-sm-9')
.append($('<div>').addClass('progress').append(progressElement)));
var textElement = $('<div>').addClass('col-sm-3');
el.append(textElement);
window.cadvisor.fsUsage.elements[data.device] = {
'progressElement': progressElement,
'textElement': textElement,
'index': i,
};
}
$('#' + elementId).empty().append(el);
drawFileSystemUsage(machineInfo, stats);
}
// Expects an array of closures to call. After each execution the JS runtime is
// given control back before continuing.
// This function returns asynchronously
function stepExecute(steps) {
// No steps, stop.
if (steps.length == 0) {
return;
}
// Get a step and execute it.
var step = steps.shift();
step();
// Schedule the next step.
setTimeout(function() { stepExecute(steps); }, 0);
}
// Draw all the charts on the page.
function drawCharts(machineInfo, containerInfo, subcontainers) {
var steps = [];
if (containerInfo.spec.has_cpu || containerInfo.spec.has_memory) {
steps.push(function() {
drawOverallUsage('usage-gauge', machineInfo, containerInfo);
});
}
// CPU.
if (containerInfo.spec.has_cpu) {
steps.push(function() {
drawCpuTotalUsage('cpu-total-usage-chart', machineInfo, containerInfo);
});
// TODO(rjnagal): Re-enable CPU Load after understanding resource usage.
// steps.push(function() {
// drawCpuLoad("cpu-load-chart", machineInfo, containerInfo);
// });
steps.push(function() {
drawCpuPerCoreUsage(
'cpu-per-core-usage-chart', machineInfo, containerInfo);
});
steps.push(function() {
drawCpuUsageBreakdown(
'cpu-usage-breakdown-chart', machineInfo, containerInfo);
});
}
// Memory.
if (containerInfo.spec.has_memory) {
steps.push(function() {
drawMemoryUsage('memory-usage-chart', machineInfo, containerInfo);
});
}
// Network.
if (containerInfo.spec.has_network) {
steps.push(function() {
drawNetworkBytes('network-bytes-chart', machineInfo, containerInfo);
});
steps.push(function() {
drawNetworkErrors('network-errors-chart', machineInfo, containerInfo);
});
}
// Filesystem.
if (containerInfo.spec.has_filesystem) {
steps.push(function() { drawFileSystemUsage(machineInfo, containerInfo); });
}
// Custom Metrics
if (containerInfo.spec.has_custom_metrics) {
steps.push(function() {
getCustomMetrics(
window.cadvisor.rootDir, window.cadvisor.containerName,
function(metricsInfo) {
drawCustomMetrics(
'custom-metrics-chart', containerInfo, metricsInfo);
});
});
}
// Subcontainers.
var subcontainerInfos = filterSubcontainers(containerInfo, subcontainers);
if (subcontainerInfos.length > 0) {
if (hasResourceForAll(subcontainerInfos, 'cpu')) {
steps.push(function() {
var displayCount = $('#cpu-per-subcontainer-display-count').val();
drawCpuPerSubcontainerUsage(
'cpu-per-subcontainer-usage-chart',
sliceByCpu(subcontainerInfos, displayCount));
});
}
if (hasResourceForAll(subcontainerInfos, 'memory')) {
steps.push(function() {
var displayCount = $('#memory-per-subcontainer-display-count').val();
drawMemoryPerSubcontainerUsage(
'memory-per-subcontainer-usage-chart',
sliceByMemory(subcontainerInfos, displayCount));
});
}
}
stepExecute(steps);
}
// Return an slice of subcontainers sorted by CPU, with at most 'count' entries.
function sliceByCpu(subcontainerInfos, count) {
subcontainerInfos.sort(function(a, b) {
if (a.averages.cpu > b.averages.cpu) {
return -1;
} else if (a.averages.cpu < b.averages.cpu) {
return 1;
} else {
return compareByName(a, b);
}
});
return subcontainerInfos.slice(0, Math.min(subcontainerInfos.length, count))
.sort(compareByName);
}
// Return an slice of subcontainers sorted by memory, with at most 'count'
// entries.
function sliceByMemory(subcontainerInfos, count) {
subcontainerInfos.sort(function(a, b) {
if (a.averages.memory > b.averages.memory) {
return -1;
} else if (a.averages.memory < b.averages.memory) {
return 1;
} else {
return compareByName(a, b);
}
});
return subcontainerInfos.slice(0, Math.min(subcontainerInfos.length, count))
.sort(compareByName);
}
// Return sort comparitor based on subcontroller name.
function compareByName(subA, subB) {
if (subA.name > subB.name) {
return 1;
} else if (subA.name < subB.name) {
return -1;
} else {
return 0;
}
}
// Return a map of the averages of the subcontainer stats.
function getSubcontainerAverages(subcontainer) {
var cpuSum = 0;
var memorySum = 0;
subcontainer.stats.forEach(function(stat) {
cpuSum += stat.cpu.usage.total;
memorySum += stat.memory.usage / oneMegabyte;
});
return {
cpu: cpuSum / subcontainer.stats.length,
memory: memorySum / subcontainer.stats.length,
}
}
// Return a list of immediate subcontainers, including metric averages.
function filterSubcontainers(containerInfo, subcontainers) {
if (!containerInfo.subcontainers ||
containerInfo.subcontainers.length === 0 ||
!subcontainers) {
return [];
}
var subcontainerNames = {};
containerInfo.subcontainers.forEach(function(subcontainer) {
subcontainerNames[subcontainer.name] = subcontainer.name;
});
var subcontainerInfos = [];
subcontainers.forEach(function(subcontainer) {
if (subcontainerNames[subcontainer.name] !== undefined) {
subcontainer.averages = getSubcontainerAverages(subcontainer);
subcontainerInfos.push(subcontainer);
}
});
return subcontainerInfos;
}
function setNetwork(interfaceName) {
$('#network-selection-text')
.empty()
.append($('<span>').text('Interface: '))
.append($('<b>').text(interfaceName));
window.cadvisor.network.interface = interfaceName;
// Draw the new stats.
refreshStats();
}
// Creates the network selection dropdown.
function startNetwork(selectionElement, containerInfo) {
if (!hasResource(containerInfo, 'network') ||
containerInfo.stats.length == 0 ||
!containerInfo.stats[0].network.interfaces ||
containerInfo.stats[0].network.interfaces.length == 0) {
return;
}
window.cadvisor.network = {};
window.cadvisor.network.interface = '';
// Add all interfaces to the dropdown.
var el = $('#' + selectionElement);
for (var i = 0; i < containerInfo.stats[0].network.interfaces.length; i++) {
var interfaceName = containerInfo.stats[0].network.interfaces[i].name;
el.append($('<li>')
.attr('role', 'presentation')
.append($('<a>')
.attr('role', 'menuitem')
.attr('tabindex', -1)
.click(setNetwork.bind(null, interfaceName))
.text(interfaceName)));
}
setNetwork(containerInfo.stats[0].network.interfaces[0].name);
}
// Refresh the stats on the page.
function refreshStats() {
var machineInfo = window.cadvisor.machineInfo;
getStats(
window.cadvisor.rootDir, window.cadvisor.containerName,
function(containerInfo, subcontainers) {
if (window.cadvisor.firstRun) {
window.cadvisor.firstRun = false;
if (containerInfo.spec.has_filesystem) {
startFileSystemUsage(
'filesystem-usage', machineInfo, containerInfo);
}
if (containerInfo.spec.has_network) {
startNetwork('network-selection', containerInfo);
}
if (containerInfo.spec.has_custom_metrics) {
startCustomMetrics('custom-metrics-chart', containerInfo);
}
}
drawCharts(machineInfo, containerInfo, subcontainers);
});
}
function addAllLabels(containerInfo, metricsInfo) {
if (metricsInfo.length == 0) {
return;
}
var metricSpec = containerInfo.spec.custom_metrics;
for (var containerName in metricsInfo) {
var container = metricsInfo[containerName];
for (i = 0; i < metricSpec.length; i++) {
metricName = metricSpec[i].name;
metricLabelVal = container[metricName];
firstLabel = true;
for (var label in metricLabelVal) {
if (label == '') {
$('#button-' + metricName).hide();
}
$('#' + metricName + '_labels')
.append(
$('<li>')
.attr('role', 'presentation')
.append($('<a>')
.attr('role', 'menuitem')
.click(setLabel.bind(null, metricName, label))
.text(label)));
if (firstLabel) {
firstLabel = false;
setLabel(metricName, label);
}
}
}
}
}
function getMetricIndex(metricName) {
for (i = 0; i < window.cadvisor.metricLabelPair.length; ++i) {
if (window.cadvisor.metricLabelPair[i][0] == metricName) { return i; }
}
return -1;
}
function setLabel(metric, label) {
$('#' + metric + '-selection-text')
.empty()
.append($('<span>').text('Label: '))
.append($('<b>').text(label));
index = getMetricIndex(metric);
if (index == -1) {
window.cadvisor.metricLabelPair.push([metric, label]);
} else {
window.cadvisor.metricLabelPair[index][1] = label;
}
refreshStats();
}
function getSelectedLabel(metricName) {
index = getMetricIndex(metricName);
if (index == -1) { return ''; }
return window.cadvisor.metricLabelPair[index][1];
}
function startCustomMetrics(elementId, containerInfo) {
var metricSpec = containerInfo.spec.custom_metrics;
var metricStats = containerInfo.stats.custom_metrics;
var el = $('<div>');
if (metricSpec.length < window.cadvisor.maxCustomMetrics) {
window.cadvisor.maxCustomMetrics = metricSpec.length;
for (i = 0; i < window.cadvisor.maxCustomMetrics; i++) {
metricName = metricSpec[i].name;
var divText =
'<div class=\'dropdown\'> <button class=\'btn btn-default' +
' dropdown-toggle\' type=\'button\' id=\'button-' + metricName;
divText +=
'\' data-toggle=\'dropdown\' aria-haspopup=\'true\'' +
' aria-expanded=\'false\'>';
divText += '<span id=\'' + metricName +
'-selection-text\'></span> <span class=\'caret\'></span> </button>';
divText += '<ul id=\'' + metricName +
'_labels\' class=\'dropdown-menu\' role=\'menu\'' +
' aria-labelledby=\'button-' + metricName + '\'> </ul> </div>';
divText += '<div id=\'' + elementId + '-' + metricName + '\'> </div>';
el.append($(divText));
}
}
el.append($('</div>'));
$('#' + elementId).append(el);
}
function getCustomMetrics(rootDir, containerName, callback) {
$.getJSON(rootDir + 'api/v2.0/appmetrics/' + containerName)
.done(function(data) { callback(data); })
.fail(function(jqhxr, textStatus, error) { callback([]); });
}
function drawCustomMetrics(elementId, containerInfo, metricsInfo) {
if (metricsInfo.length == 0) {
return;
}
var metricSpec = containerInfo.spec.custom_metrics;
for (var containerName in metricsInfo) {
var container = metricsInfo[containerName];
for (i = 0; i < window.cadvisor.maxCustomMetrics; i++) {
metricName = metricSpec[i].name;
metricUnits = metricSpec[i].units;
var titles = ['Time', metricName];
metricLabelVal = container[metricName];
if (window.cadvisor.firstCustomCollection) {
window.cadvisor.firstCustomCollection = false;
addAllLabels(containerInfo, metricsInfo);
}
var data = [];
selectedLabel = getSelectedLabel(metricName);
metricVal = metricLabelVal[selectedLabel];
for (var index in metricVal) {
metric = metricVal[index];
var elements = [];
for (var attribute in metric) {
value = metric[attribute];
elements.push(value);
}
if (elements.length < 2) {
elements.push(0);
}
data.push(elements);
}
drawLineChart(titles, data, elementId + '-' + metricName, metricUnits);
}
}
}
// Executed when the page finishes loading.
function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) {
// Don't fetch data if we don't have any resource.
if (!hasCpu && !hasMemory) {
return;
}
window.charts = {};
window.cadvisor = {};
window.cadvisor.firstRun = true;
window.cadvisor.rootDir = rootDir;
window.cadvisor.containerName = containerName;
window.cadvisor.firstCustomCollection = true;
window.cadvisor.metricLabelPair = [];
window.cadvisor.maxCustomMetrics = 10;
// Draw process information at start and refresh every 60s.
getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(isRoot, rootDir, processInfo);
});
setInterval(function() {
getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(isRoot, rootDir, processInfo);
});
}, 60000);
// Get machine info, then get the stats every 1s.
getMachineInfo(rootDir, function(machineInfo) {
window.cadvisor.machineInfo = machineInfo;
setInterval(function() { refreshStats(); }, 1000);
});
}