mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-17 01:54:49 +00:00
dashboard: memory widget
Also some minor performance improvements as suggested by https://www.chartjs.org/docs/latest/general/performance.html. in contrast to the old dashboard memory calculation, this widget does not consider ARC to be part of used memory anymore.
This commit is contained in:
parent
6d770f2751
commit
a2e14b2b8c
@ -71,6 +71,12 @@ class DashboardController extends ApiControllerBase
|
||||
'trafficin' => gettext('Traffic In'),
|
||||
'trafficout' => gettext('Traffic Out'),
|
||||
],
|
||||
'memory' => [
|
||||
'title' => gettext('Memory'),
|
||||
'used' => gettext('Used'),
|
||||
'free' => gettext('Free'),
|
||||
'arc' => gettext('ARC'),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -260,4 +260,39 @@ class SystemController extends ApiControllerBase
|
||||
|
||||
return json_encode($response);
|
||||
}
|
||||
|
||||
public function systemResourcesAction()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$mem = json_decode((new Backend())->configdpRun('system sysctl values', implode(',', [
|
||||
'hw.physmem',
|
||||
'vm.stats.vm.v_page_count',
|
||||
'vm.stats.vm.v_inactive_count',
|
||||
'vm.stats.vm.v_cache_count',
|
||||
'vm.stats.vm.v_free_count',
|
||||
'kstat.zfs.misc.arcstats.size'
|
||||
])), true);
|
||||
|
||||
if (!empty($mem['vm.stats.vm.v_page_count'])) {
|
||||
$pc = $mem['vm.stats.vm.v_page_count'];
|
||||
$ic = $mem['vm.stats.vm.v_inactive_count'];
|
||||
$cc = $mem['vm.stats.vm.v_cache_count'];
|
||||
$fc = $mem['vm.stats.vm.v_free_count'];
|
||||
$result['memory']['total'] = $mem['hw.physmem'];
|
||||
$result['memory']['total_frmt'] = sprintf('%d', $mem['hw.physmem'] / 1024 / 1024);
|
||||
$result['memory']['used'] = round(((($pc - ($ic + $cc + $fc))) / $pc) * $mem['hw.physmem'], 0);
|
||||
$result['memory']['used_frmt'] = sprintf('%d', $result['memory']['used'] / 1024 / 1024);
|
||||
if (!empty($mem['kstat.zfs.misc.arcstats.size'])) {
|
||||
$arc_size = $mem['kstat.zfs.misc.arcstats.size'];
|
||||
$result['memory']['arc'] = $arc_size;
|
||||
$result['memory']['arc_frmt'] = sprintf('%d', $arc_size / 1024 / 1024);
|
||||
$result['memory']['arc_txt'] = sprintf(gettext('ARC size %d MB'), $arc_size / 1024 / 1024);
|
||||
}
|
||||
} else {
|
||||
$result['memory']['used'] = gettext('N/A');
|
||||
}
|
||||
|
||||
return json_encode($result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
$( document ).ready(function() {
|
||||
let widgetManager = new WidgetManager({
|
||||
float: false,
|
||||
column: 4,
|
||||
column: 5,
|
||||
margin: 10,
|
||||
alwaysShowResizeHandle: false,
|
||||
sizeToContent: true,
|
||||
|
||||
@ -172,26 +172,6 @@ td {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
/* Gauge */
|
||||
#gauge-container {
|
||||
width: 80%;
|
||||
margin: 5px auto;
|
||||
background-color: #ccc;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#gauge-fill {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background-color: green;
|
||||
border-radius: 5px;
|
||||
transition: width 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
|
||||
/* Custom flex table */
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -53,4 +53,9 @@ export default class BaseWidget {
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
_setAlpha(color, opacity) {
|
||||
const op = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255);
|
||||
return color + op.toString(16).toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,11 +51,6 @@ export default class InterfaceStatistics extends BaseWidget {
|
||||
`);
|
||||
}
|
||||
|
||||
_setAlpha(color, opacity) {
|
||||
const op = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255);
|
||||
return color + op.toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
_getIndexedData(data) {
|
||||
let indexedData = Array(this.labels.length).fill(null);
|
||||
let indexedColors = Array(this.labels.length).fill(null);
|
||||
@ -133,6 +128,7 @@ export default class InterfaceStatistics extends BaseWidget {
|
||||
layout: {
|
||||
padding: 10
|
||||
},
|
||||
normalized: true,
|
||||
parsing: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
|
||||
131
src/opnsense/www/js/widgets/Memory.js
Normal file
131
src/opnsense/www/js/widgets/Memory.js
Normal file
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (C) 2024 Deciso B.V.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
import BaseWidget from "./BaseWidget.js";
|
||||
|
||||
export default class Memory extends BaseWidget {
|
||||
constructor() {
|
||||
super();
|
||||
this.tickTimeout = 15000;
|
||||
|
||||
this.chart = null;
|
||||
this.curMemUsed = null;
|
||||
this.curMemTotal = null;
|
||||
}
|
||||
|
||||
getMarkup() {
|
||||
return $(`
|
||||
<div class="memory-chart-container">
|
||||
<div class="canvas-container">
|
||||
<canvas id="memory-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
async onMarkupRendered() {
|
||||
let context = document.getElementById("memory-chart").getContext("2d");
|
||||
let colorMap = ['#D94F00', '#A8C49B', '#E5E5E5'];
|
||||
|
||||
let config = {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: [this.translations.used, this.translations.arc, this.translations.free],
|
||||
datasets: [
|
||||
{
|
||||
data: [],
|
||||
backgroundColor: colorMap,
|
||||
hoverBackgroundColor: colorMap.map((color) => this._setAlpha(color, 0.5)),
|
||||
hoveroffset: 50,
|
||||
fill: true
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
aspectRatio: 2,
|
||||
layout: {
|
||||
padding: 10
|
||||
},
|
||||
cutout: '64%',
|
||||
rotation: 270,
|
||||
circumference: 180,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (tooltipItem) => {
|
||||
return `${tooltipItem.label}: ${tooltipItem.parsed} MB`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
id: 'custom_positioned_text',
|
||||
beforeDatasetsDraw: (chart, args, options) => {
|
||||
// custom plugin: draw text at 2/3 y position of chart
|
||||
if (chart.config.data.datasets[0].data.length !== 0) {
|
||||
let width = chart.width;
|
||||
let height = chart.height;
|
||||
let ctx = chart.ctx;
|
||||
ctx.restore();
|
||||
let fontSize = (height / 114).toFixed(2);
|
||||
ctx.font = fontSize + "em SourceSansProSemibold";
|
||||
ctx.textBaseline = "middle";
|
||||
let text = (this.curMemUsed / this.curMemTotal * 100).toFixed(2) + "%";
|
||||
let textX = Math.round((width - ctx.measureText(text).width) / 2);
|
||||
let textY = (height / 3) * 2;
|
||||
ctx.fillText(text, textX, textY);
|
||||
ctx.save();
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
this.chart = new Chart(context, config);
|
||||
}
|
||||
|
||||
async onWidgetTick() {
|
||||
ajaxGet('/api/core/system/systemResources', {}, (data, status) => {
|
||||
if (data.memory.total !== undefined) {
|
||||
let used = parseInt(data.memory.used_frmt);
|
||||
let arc = data.memory.hasOwnProperty('arc') ? parseInt(data.memory.arc_frmt) : 0;
|
||||
let total = parseInt(data.memory.total_frmt);
|
||||
let result = [(used - arc), arc, total - used];
|
||||
this.chart.config.data.datasets[0].data = result
|
||||
|
||||
this.curMemUsed = used - arc;
|
||||
this.curMemTotal = total;
|
||||
this.chart.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -75,6 +75,7 @@ export default class Traffic extends BaseWidget {
|
||||
pointDot: false,
|
||||
scaleShowGridLines: true,
|
||||
responsive: true,
|
||||
normalized: true,
|
||||
elements: {
|
||||
line: {
|
||||
fill: true,
|
||||
@ -98,7 +99,7 @@ export default class Traffic extends BaseWidget {
|
||||
type: 'realtime',
|
||||
realtime: {
|
||||
duration: 20000,
|
||||
delay: 1000,
|
||||
delay: 2000,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
@ -213,7 +214,7 @@ export default class Traffic extends BaseWidget {
|
||||
}
|
||||
|
||||
async onMarkupRendered() {
|
||||
this.eventSource = new EventSource('/api/diagnostics/traffic/stream/1');
|
||||
this.eventSource = new EventSource('/api/diagnostics/traffic/stream/2');
|
||||
this.eventSource.onmessage = this._onMessage.bind(this);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user