feat(container-stats): introduce container block I/O stats (#5017)

* feat(container-stats):introduce container block io stats

* Change charts to 2x2 view

* fix(container-stats): handle missing io stats by detecting stats based on op codes

Co-authored-by: DarkAEther <30438425+DarkAEther@users.noreply.github.com>
pull/5143/head
zees-dev 2021-06-14 15:57:00 +12:00 committed by GitHub
parent 49bd139466
commit 6e9f472723
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 8 deletions

View File

@ -91,6 +91,20 @@ export function ContainerStatsViewModel(data) {
this.CPUCores = data.cpu_stats.cpu_usage.percpu_usage.length;
}
this.Networks = _.values(data.networks);
if (data.blkio_stats !== undefined) {
//TODO: take care of multiple block devices
var readData = data.blkio_stats.io_service_bytes_recursive.find((d) => d.op === 'Read');
if (readData !== undefined) {
this.BytesRead = readData.value;
}
var writeData = data.blkio_stats.io_service_bytes_recursive.find((d) => d.op === 'Write');
if (writeData !== undefined) {
this.BytesWrite = writeData.value;
}
} else {
//no IO related data is available
this.noIOdata = true;
}
}
export function ContainerDetailsViewModel(data) {

View File

@ -14,6 +14,7 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
$scope.state = {
refreshRate: '5',
networkStatsUnavailable: false,
ioStatsUnavailable: false,
};
$scope.$on('$destroy', function () {
@ -44,6 +45,13 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
ChartService.UpdateMemoryChart(label, stats.MemoryUsage, stats.MemoryCache, chart);
}
function updateIOChart(stats, chart) {
var label = moment(stats.read).format('HH:mm:ss');
if (stats.noIOData !== true) {
ChartService.UpdateIOChart(label, stats.BytesRead, stats.BytesWrite, chart);
}
}
function updateCPUChart(stats, chart) {
var label = moment(stats.read).format('HH:mm:ss');
var value = stats.isWindows ? calculateCPUPercentWindows(stats) : calculateCPUPercentUnix(stats);
@ -77,14 +85,15 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
var networkChart = $scope.networkChart;
var cpuChart = $scope.cpuChart;
var memoryChart = $scope.memoryChart;
var ioChart = $scope.ioChart;
stopRepeater();
setUpdateRepeater(networkChart, cpuChart, memoryChart);
setUpdateRepeater(networkChart, cpuChart, memoryChart, ioChart);
$('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500);
};
function startChartUpdate(networkChart, cpuChart, memoryChart) {
function startChartUpdate(networkChart, cpuChart, memoryChart, ioChart) {
$q.all({
stats: ContainerService.containerStats($transition$.params().id),
top: ContainerService.containerTop($transition$.params().id),
@ -95,10 +104,14 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
if (stats.Networks.length === 0) {
$scope.state.networkStatsUnavailable = true;
}
if (stats.noIOData === true) {
$scope.state.ioStatsUnavailable = true;
}
updateNetworkChart(stats, networkChart);
updateMemoryChart(stats, memoryChart);
updateCPUChart(stats, cpuChart);
setUpdateRepeater(networkChart, cpuChart, memoryChart);
updateIOChart(stats, ioChart);
setUpdateRepeater(networkChart, cpuChart, memoryChart, ioChart);
})
.catch(function error(err) {
stopRepeater();
@ -106,7 +119,7 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
});
}
function setUpdateRepeater(networkChart, cpuChart, memoryChart) {
function setUpdateRepeater(networkChart, cpuChart, memoryChart, ioChart) {
var refreshRate = $scope.state.refreshRate;
$scope.repeater = $interval(function () {
$q.all({
@ -119,6 +132,7 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
updateNetworkChart(stats, networkChart);
updateMemoryChart(stats, memoryChart);
updateCPUChart(stats, cpuChart);
updateIOChart(stats, ioChart);
})
.catch(function error(err) {
stopRepeater();
@ -140,7 +154,11 @@ angular.module('portainer.docker').controller('ContainerStatsController', [
var memoryChart = ChartService.CreateMemoryChart(memoryChartCtx);
$scope.memoryChart = memoryChart;
startChartUpdate(networkChart, cpuChart, memoryChart);
var ioChartCtx = $('#ioChart');
var ioChart = ChartService.CreateIOChart(ioChartCtx);
$scope.ioChart = ioChart;
startChartUpdate(networkChart, cpuChart, memoryChart, ioChart);
}
function initView() {

View File

@ -42,6 +42,11 @@
<span class="small text-muted"> <i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i> Network stats are unavailable for this container. </span>
</div>
</div>
<div class="form-group" ng-if="state.ioStatsUnavailable">
<div class="col-sm-12">
<span class="small text-muted"> <i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true"></i> I/O stats are unavailable for this container. </span>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
@ -49,7 +54,7 @@
</div>
<div class="row">
<div ng-class="{ true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12' }[state.networkStatsUnavailable]">
<div class="col-lg-6 col-md-6 col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-chart-area" title-text="Memory usage"></rd-widget-header>
<rd-widget-body>
@ -59,7 +64,8 @@
</rd-widget-body>
</rd-widget>
</div>
<div ng-class="{ true: 'col-md-6 col-sm-12', false: 'col-lg-4 col-md-6 col-sm-12' }[state.networkStatsUnavailable]">
<div class="col-lg-6 col-md-6 col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-chart-area" title-text="CPU usage"></rd-widget-header>
<rd-widget-body>
@ -69,7 +75,8 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-4 col-md-12 col-sm-12" ng-if="!state.networkStatsUnavailable">
<div class="col-lg-6 col-md-6 col-sm-12" ng-if="!state.networkStatsUnavailable">
<rd-widget>
<rd-widget-header icon="fa-chart-area" title-text="Network usage (aggregate)"></rd-widget-header>
<rd-widget-body>
@ -80,6 +87,19 @@
</rd-widget>
</div>
<div class="col-lg-6 col-md-6 col-sm-12" ng-if="!state.ioStatsUnavailable">
<rd-widget>
<rd-widget-header icon="fa-chart-area" title-text="I/O usage (aggregate)"></rd-widget-header>
<rd-widget-body>
<div class="chart-container" style="position: relative;">
<canvas id="ioChart" width="770" height="300"></canvas>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<container-processes-datatable
title-text="Processes"

View File

@ -98,10 +98,48 @@ angular.module('portainer.app').factory('ChartService', [
});
}
function CreateIOChart(context, tooltipCallback, scalesCallback) {
return new Chart(context, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Read (Aggregate)',
data: [],
fill: true,
backgroundColor: 'rgba(151,187,205,0.4)',
borderColor: 'rgba(151,187,205,0.6)',
pointBackgroundColor: 'rgba(151,187,205,1)',
pointBorderColor: 'rgba(151,187,205,1)',
pointRadius: 2,
borderWidth: 2,
},
{
label: 'Write (Aggregate)',
data: [],
fill: true,
backgroundColor: 'rgba(255,180,174,0.4)',
borderColor: 'rgba(255,180,174,0.6)',
pointBackgroundColor: 'rgba(255,180,174,1)',
pointBorderColor: 'rgba(255,180,174,1)',
pointRadius: 2,
borderWidth: 2,
},
],
},
options: defaultChartOptions('nearest', tooltipCallback, scalesCallback, true),
});
}
service.CreateCPUChart = function (context) {
return CreateChart(context, 'CPU', percentageBasedTooltipLabel, percentageBasedAxisLabel);
};
service.CreateIOChart = function (context) {
return CreateIOChart(context, byteBasedTooltipLabel, byteBasedAxisLabel);
};
service.CreateMemoryChart = function (context) {
return CreateMemoryChart(context, byteBasedTooltipLabel, byteBasedAxisLabel);
};
@ -176,6 +214,13 @@ angular.module('portainer.app').factory('ChartService', [
chart.update(0);
};
service.UpdateCPUChart = UpdateChart;
service.UpdateIOChart = function (label, read, write, chart) {
chart.data.labels.push(label);
chart.data.datasets[0].data.push(read);
chart.data.datasets[1].data.push(write);
LimitChartItems(chart);
chart.update(0);
};
service.UpdateNetworkChart = function (label, rx, tx, chart) {
chart.data.labels.push(label);