fix(metrics): node chart race condition EE-5447 (#9249)

pull/9944/head
Dakota Walsh 2023-07-27 11:46:38 +12:00 committed by GitHub
parent ca617e2ac9
commit 400d95c1a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 197 additions and 188 deletions

View File

@ -26,95 +26,93 @@
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
<div ng-if="ctrl.state.viewReady"> <information-panel ng-if="!ctrl.state.getMetrics" title-text="Unable to retrieve container metrics">
<information-panel ng-if="!ctrl.state.getMetrics" title-text="Unable to retrieve container metrics"> <span class="small text-warning vertical-center">
<span class="small text-warning vertical-center"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> Portainer was unable to retrieve any metrics associated to that container. Please contact your administrator to ensure that the Kubernetes metrics feature is properly
Portainer was unable to retrieve any metrics associated to that container. Please contact your administrator to ensure that the Kubernetes metrics feature is properly configured.
configured. </span>
</span> </information-panel>
</information-panel> <div class="row" ng-if="ctrl.state.getMetrics">
<div class="row" ng-if="ctrl.state.getMetrics"> <div class="col-md-12">
<div class="col-md-12"> <rd-widget>
<rd-widget> <div class="toolBar px-5 pt-5">
<div class="toolBar px-5 pt-5"> <div class="toolBarTitle flex">
<div class="toolBarTitle flex"> <div class="widget-icon space-right">
<div class="widget-icon space-right"> <pr-icon icon="'info'"></pr-icon>
<pr-icon icon="'info'"></pr-icon>
</div>
<span class="vertical-center"> About statistics </span>
</div> </div>
<span class="vertical-center"> About statistics </span>
</div> </div>
<rd-widget-body> </div>
<form class="form-horizontal"> <rd-widget-body>
<div class="form-group"> <form class="form-horizontal">
<div class="col-sm-12"> <div class="form-group">
<span class="small text-warning"> <div class="col-sm-12">
This view displays real-time statistics about the container <b>{{ ctrl.state.transition.containerName | trimcontainername }}</b <span class="small text-warning">
>. This view displays real-time statistics about the container <b>{{ ctrl.state.transition.containerName | trimcontainername }}</b
</span> >.
</div>
</div>
<div class="form-group">
<label for="refreshRate" class="col-sm-3 col-md-2 col-lg-2 margin-sm-top control-label text-left"> Refresh rate </label>
<div class="col-sm-3 col-md-2">
<select id="refreshRate" ng-model="ctrl.state.refreshRate" ng-change="ctrl.changeUpdateRepeater()" class="form-control">
<option value="30">30s</option>
<option value="60">60s</option>
</select>
</div>
<span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" size="'sm'"></pr-icon>
</span> </span>
</div> </div>
<div class="form-group" ng-if="ctrl.state.networkStatsUnavailable"> </div>
<div class="col-sm-12"> <div class="form-group">
<span class="small text-muted"> <label for="refreshRate" class="col-sm-3 col-md-2 col-lg-2 margin-sm-top control-label text-left"> Refresh rate </label>
<pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon> <div class="col-sm-3 col-md-2">
Network stats are unavailable for this container. <select id="refreshRate" ng-model="ctrl.state.refreshRate" ng-change="ctrl.changeUpdateRepeater()" class="form-control">
</span> <option value="30">30s</option>
</div> <option value="60">60s</option>
</select>
</div> </div>
</form> <span>
</rd-widget-body> <pr-icon id="refreshRateChange" icon="'check'" mode="'success'" size="'sm'"></pr-icon>
</rd-widget> </span>
</div> </div>
</div> <div class="form-group" ng-if="ctrl.state.networkStatsUnavailable">
<div class="col-sm-12">
<div class="row" ng-if="ctrl.state.getMetrics"> <span class="small text-muted">
<div class="col-lg-6 col-md-12 col-sm-12"> <pr-icon icon="'alert-triangle'" mode="'warning'"></pr-icon>
<rd-widget> Network stats are unavailable for this container.
<div class="toolBar px-5 pt-5"> </span>
<div class="toolBarTitle flex">
<div class="widget-icon space-right">
<pr-icon icon="'svg-memory'"></pr-icon>
</div> </div>
<span class="vertical-center"> Memory usage </span>
</div> </div>
</div> </form>
<rd-widget-body> </rd-widget-body>
<div class="chart-container" style="position: relative"> </rd-widget>
<canvas id="memoryChart" width="770" height="300"></canvas> </div>
</div> </div>
</rd-widget-body>
</rd-widget> <div class="row" ng-if="ctrl.state.getMetrics">
</div> <div class="col-lg-6 col-md-12 col-sm-12">
<div class="col-lg-6 col-md-12 col-sm-12" ng-if="!ctrl.state.networkStatsUnavailable"> <rd-widget>
<rd-widget> <div class="toolBar px-5 pt-5">
<div class="toolBar px-5 pt-5"> <div class="toolBarTitle flex">
<div class="toolBarTitle flex"> <div class="widget-icon space-right">
<div class="widget-icon space-right"> <pr-icon icon="'svg-memory'"></pr-icon>
<pr-icon icon="'cpu'"></pr-icon> </div>
</div> <span class="vertical-center"> Memory usage </span>
<span class="vertical-center"> CPU usage </span> </div>
</div> </div>
</div> <rd-widget-body>
<rd-widget-body> <div class="chart-container" style="position: relative">
<div class="chart-container" style="position: relative"> <canvas id="memoryChart" width="770" height="300"></canvas>
<canvas id="cpuChart" width="770" height="300"></canvas> </div>
</div> </rd-widget-body>
</rd-widget-body> </rd-widget>
</rd-widget> </div>
</div> <div class="col-lg-6 col-md-12 col-sm-12" ng-if="!ctrl.state.networkStatsUnavailable">
<rd-widget>
<div class="toolBar px-5 pt-5">
<div class="toolBarTitle flex">
<div class="widget-icon space-right">
<pr-icon icon="'cpu'"></pr-icon>
</div>
<span class="vertical-center"> CPU usage </span>
</div>
</div>
<rd-widget-body>
<div class="chart-container" style="position: relative">
<canvas id="cpuChart" width="770" height="300"></canvas>
</div>
</rd-widget-body>
</rd-widget>
</div> </div>
</div> </div>

View File

@ -19,6 +19,7 @@ class KubernetesApplicationStatsController {
this.ChartService = ChartService; this.ChartService = ChartService;
this.onInit = this.onInit.bind(this); this.onInit = this.onInit.bind(this);
this.initCharts = this.initCharts.bind(this);
} }
changeUpdateRepeater() { changeUpdateRepeater() {
@ -68,17 +69,26 @@ class KubernetesApplicationStatsController {
} }
initCharts() { initCharts() {
const cpuChartCtx = $('#cpuChart'); let i = 0;
const cpuChart = this.ChartService.CreateCPUChart(cpuChartCtx); const findCharts = setInterval(() => {
this.cpuChart = cpuChart; let cpuChartCtx = $('#cpuChart');
let memoryChartCtx = $('#memoryChart');
const memoryChartCtx = $('#memoryChart'); if (cpuChartCtx.length !== 0 && memoryChartCtx.length !== 0) {
const memoryChart = this.ChartService.CreateMemoryChart(memoryChartCtx); const cpuChart = this.ChartService.CreateCPUChart(cpuChartCtx);
this.memoryChart = memoryChart; this.cpuChart = cpuChart;
const memoryChart = this.ChartService.CreateMemoryChart(memoryChartCtx);
this.updateCPUChart(); this.memoryChart = memoryChart;
this.updateMemoryChart(); this.updateCPUChart();
this.setUpdateRepeater(); this.updateMemoryChart();
this.setUpdateRepeater();
clearInterval(findCharts);
return;
}
i++;
if (i >= 10) {
clearInterval(findCharts);
}
}, 200);
} }
getStats() { getStats() {

View File

@ -15,86 +15,84 @@
<kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading> <kubernetes-view-loading view-ready="ctrl.state.viewReady"></kubernetes-view-loading>
<div ng-if="ctrl.state.viewReady"> <information-panel ng-if="!ctrl.state.getMetrics" title-text="Unable to retrieve node metrics">
<information-panel ng-if="!ctrl.state.getMetrics" title-text="Unable to retrieve node metrics"> <span class="small text-muted vertical-center">
<span class="small text-muted vertical-center"> <pr-icon icon="'alert-triangle'" mode="'primary'"></pr-icon>
<pr-icon icon="'alert-triangle'" mode="'primary'"></pr-icon> Portainer was unable to retrieve any metrics associated to that node. Please contact your administrator to ensure that the Kubernetes metrics feature is properly configured.
Portainer was unable to retrieve any metrics associated to that node. Please contact your administrator to ensure that the Kubernetes metrics feature is properly configured. </span>
</span> </information-panel>
</information-panel> <div class="row" ng-if="ctrl.state.getMetrics">
<div class="row" ng-if="ctrl.state.getMetrics"> <div class="col-md-12">
<div class="col-md-12"> <rd-widget>
<rd-widget> <div class="toolBar px-5 pt-5">
<div class="toolBar px-5 pt-5"> <div class="toolBarTitle flex">
<div class="toolBarTitle flex"> <div class="widget-icon space-right">
<div class="widget-icon space-right"> <pr-icon icon="'info'"></pr-icon>
<pr-icon icon="'info'"></pr-icon>
</div>
<span class="vertical-center"> About statistics </span>
</div> </div>
<span class="vertical-center"> About statistics </span>
</div> </div>
<rd-widget-body> </div>
<form class="form-horizontal"> <rd-widget-body>
<div class="form-group"> <form class="form-horizontal">
<div class="col-sm-12"> <div class="form-group">
<span class="small text-muted"> <div class="col-sm-12">
This view displays real-time statistics about the node <b>{{ ctrl.state.transition.nodeName }}</b <span class="small text-muted">
>. This view displays real-time statistics about the node <b>{{ ctrl.state.transition.nodeName }}</b
</span> >.
</div>
</div>
<div class="form-group">
<label for="refreshRate" class="col-sm-3 col-md-2 col-lg-2 margin-sm-top control-label text-left"> Refresh rate </label>
<div class="col-sm-3 col-md-2">
<select id="refreshRate" ng-model="ctrl.state.refreshRate" ng-change="ctrl.changeUpdateRepeater()" class="form-control">
<option value="30">30s</option>
<option value="60">60s</option>
</select>
</div>
<span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
</span> </span>
</div> </div>
</form> </div>
</rd-widget-body> <div class="form-group">
</rd-widget> <label for="refreshRate" class="col-sm-3 col-md-2 col-lg-2 margin-sm-top control-label text-left"> Refresh rate </label>
</div> <div class="col-sm-3 col-md-2">
</div> <select id="refreshRate" ng-model="ctrl.state.refreshRate" ng-change="ctrl.changeUpdateRepeater()" class="form-control">
<option value="30">30s</option>
<div class="row" ng-show="ctrl.state.getMetrics"> <option value="60">60s</option>
<div class="col-lg-6 col-md-12 col-sm-12"> </select>
<rd-widget>
<div class="toolBar px-5 pt-5">
<div class="toolBarTitle flex">
<div class="widget-icon space-right">
<pr-icon icon="'svg-memory'"></pr-icon>
</div> </div>
<span class="vertical-center"> Memory usage </span> <span>
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
</span>
</div> </div>
</div> </form>
<rd-widget-body> </rd-widget-body>
<div class="chart-node" style="position: relative"> </rd-widget>
<canvas id="memoryChart" width="770" height="300"></canvas> </div>
</div> </div>
</rd-widget-body>
</rd-widget> <div class="row" ng-show="ctrl.state.getMetrics">
</div> <div class="col-lg-6 col-md-12 col-sm-12">
<div class="col-lg-6 col-md-12 col-sm-12"> <rd-widget>
<rd-widget> <div class="toolBar px-5 pt-5">
<div class="toolBar px-5 pt-5"> <div class="toolBarTitle flex">
<div class="toolBarTitle flex"> <div class="widget-icon space-right">
<div class="widget-icon space-right"> <pr-icon icon="'svg-memory'"></pr-icon>
<pr-icon icon="'cpu'"></pr-icon> </div>
</div> <span class="vertical-center"> Memory usage </span>
<span class="vertical-center"> CPU usage </span> </div>
</div> </div>
</div> <rd-widget-body>
<rd-widget-body> <div class="chart-node" style="position: relative">
<div class="chart-node" style="position: relative"> <canvas id="memoryChart" width="770" height="300"></canvas>
<canvas id="cpuChart" width="770" height="300"></canvas> </div>
</div> </rd-widget-body>
</rd-widget-body> </rd-widget>
</rd-widget> </div>
</div> <div class="col-lg-6 col-md-12 col-sm-12">
<rd-widget>
<div class="toolBar px-5 pt-5">
<div class="toolBarTitle flex">
<div class="widget-icon space-right">
<pr-icon icon="'cpu'"></pr-icon>
</div>
<span class="vertical-center"> CPU usage </span>
</div>
</div>
<rd-widget-body>
<div class="chart-node" style="position: relative">
<canvas id="cpuChart" width="770" height="300"></canvas>
</div>
</rd-widget-body>
</rd-widget>
</div> </div>
</div> </div>

View File

@ -17,6 +17,7 @@ class KubernetesNodeStatsController {
this.ChartService = ChartService; this.ChartService = ChartService;
this.onInit = this.onInit.bind(this); this.onInit = this.onInit.bind(this);
this.initCharts = this.initCharts.bind(this);
} }
changeUpdateRepeater() { changeUpdateRepeater() {
@ -63,17 +64,20 @@ class KubernetesNodeStatsController {
} }
initCharts() { initCharts() {
const cpuChartCtx = $('#cpuChart'); const findCharts = setInterval(() => {
const cpuChart = this.ChartService.CreateCPUChart(cpuChartCtx); let cpuChartCtx = $('#cpuChart');
this.cpuChart = cpuChart; let memoryChartCtx = $('#memoryChart');
if (cpuChartCtx.length !== 0 && memoryChartCtx.length !== 0) {
const memoryChartCtx = $('#memoryChart'); const cpuChart = this.ChartService.CreateCPUChart(cpuChartCtx);
const memoryChart = this.ChartService.CreateMemoryChart(memoryChartCtx); this.cpuChart = cpuChart;
this.memoryChart = memoryChart; const memoryChart = this.ChartService.CreateMemoryChart(memoryChartCtx);
this.memoryChart = memoryChart;
this.updateCPUChart(); this.updateCPUChart();
this.updateMemoryChart(); this.updateMemoryChart();
this.setUpdateRepeater(); this.setUpdateRepeater();
clearInterval(findCharts);
}
}, 200);
} }
getStats() { getStats() {
@ -84,7 +88,7 @@ class KubernetesNodeStatsController {
const memory = filesizeParser(stats.usage.memory); const memory = filesizeParser(stats.usage.memory);
const cpu = KubernetesResourceReservationHelper.parseCPU(stats.usage.cpu); const cpu = KubernetesResourceReservationHelper.parseCPU(stats.usage.cpu);
this.stats = { this.stats = {
read: stats.creationTimestamp, read: stats.metadata.creationTimestamp,
MemoryUsage: memory, MemoryUsage: memory,
CPUUsage: (cpu / this.nodeCPU) * 100, CPUUsage: (cpu / this.nodeCPU) * 100,
}; };
@ -118,12 +122,6 @@ class KubernetesNodeStatsController {
this.nodeCPU = node.CPU || 1; this.nodeCPU = node.CPU || 1;
await this.getStats(); await this.getStats();
if (this.state.getMetrics) {
this.$document.ready(() => {
this.initCharts();
});
}
} else { } else {
this.state.getMetrics = false; this.state.getMetrics = false;
} }
@ -132,6 +130,11 @@ class KubernetesNodeStatsController {
this.Notifications.error('Failure', err, 'Unable to retrieve node stats'); this.Notifications.error('Failure', err, 'Unable to retrieve node stats');
} finally { } finally {
this.state.viewReady = true; this.state.viewReady = true;
if (this.state.getMetrics) {
this.$document.ready(() => {
this.initCharts();
});
}
} }
} }

View File

@ -189,7 +189,7 @@ class KubernetesConfigureController {
await getMetricsForAllNodes(this.endpoint.Id); await getMetricsForAllNodes(this.endpoint.Id);
this.state.metrics.isServerRunning = true; this.state.metrics.isServerRunning = true;
this.state.metrics.pending = false; this.state.metrics.pending = false;
this.state.metrics.userClick = false; this.state.metrics.userClick = true;
this.formValues.UseServerMetrics = true; this.formValues.UseServerMetrics = true;
} catch (_) { } catch (_) {
this.state.metrics.isServerRunning = false; this.state.metrics.isServerRunning = false;