// 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 = $('
'); 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 = '' + cgroup.substr(0, 30) + ' '; 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 = $('
'); if (!cur.filesystem) { return; } for (var i = 0; i < cur.filesystem.length; i++) { var data = cur.filesystem[i]; el.append( $('
') .addClass('row col-sm-12') .append($('

').text('FS #' + (i + 1) + ': ' + data.device))); var progressElement = $('
').addClass('progress-bar progress-bar-danger'); el.append( $('
') .addClass('col-sm-9') .append($('
').addClass('progress').append(progressElement))); var textElement = $('
').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($('').text('Interface: ')) .append($('').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($('
  • ') .attr('role', 'presentation') .append($('') .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( $('
  • ') .attr('role', 'presentation') .append($('') .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($('').text('Label: ')) .append($('').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 = $('
    '); 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 = '
    '; divText += '
    '; divText += '
    '; el.append($(divText)); } } el.append($('
    ')); $('#' + 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); }); }