// 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 = $('