mirror of https://github.com/portainer/portainer
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
parent
49bd139466
commit
6e9f472723
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue