cadvisor/pages/assets/js/containers.js
Giuseppe Scrivano 60f064ee41
cgroup: initial support for cgroups v2
add some initial support for cgroups v2.  Not all the stats
supported on cgroups v1 are supported, e.g. it is not possible to read
percpu usage.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2019-09-06 21:26:35 +02:00

1108 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);
if (cur.cpu.usage.per_cpu_usage == undefined) {
return;
}
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);
});
}