File: //usr/share/webmin/authentic-theme/stats.cgi
#!/usr/bin/perl
#
# Authentic Theme (https://github.com/authentic-theme/authentic-theme)
# Copyright Ilia Rostovtsev <programming@rostovtsev.io>
# Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE)
#
use strict;
no warnings 'uninitialized';
use lib ("$ENV{'THEME_ROOT'}/lib");
use Async;
BEGIN {push(@INC, "..");}
use WebminCore;
our (%in, $config_directory, $var_directory, $current_theme, $remote_user);
do("$ENV{'THEME_ROOT'}/authentic-funcs.pl");
init_config();
ReadParse();
# Load theme language and settings
our %text = load_language($current_theme);
my %settings = settings("$config_directory/$current_theme/settings.js", 'settings_');
my $option = sub {
if ($_[1]) {
return $settings{"settings_sysinfo_real_time_$_[0]"};
}
if (!scalar %settings) {
return 1;
}
return ($settings{"settings_sysinfo_real_time_$_[0]"} eq 'false') ? 0 : 1;
};
my %data;
my $tdata = {};
my $sdata = $in{'sdata'} ? 1 : 0;
my $fdatad = "$var_directory/modules/$current_theme";
my $fdata = "$fdatad/stats-$remote_user.json";
my $cdata = read_file_contents($fdata);
my $time = time();
my $ddata = sub {
my ($k, $d) = @_;
# Save and return data
if (!$k) {
if (&$option('stored')) {
# Clear existing cache if available features was disabled
my %map = (cpu => ['cpu', 'disk'],
mem => ['mem', 'virt'],
load => ['proc', 'net']);
foreach my $key (keys %map) {
my $feature = acl_system_status($key);
if (ref($map{$key}) eq 'ARRAY') {
foreach my $skey (@{ $map{$key} }) {
if (!$feature) {
delete $cdata->{$skey};
}
}
} else {
if (!$feature) {
delete $cdata->{ $map{$key} };
}
}
}
# Cache complete dataset
mkdir($fdatad, 0700) if (!-d $fdatad);
write_file_contents($fdata, convert_to_json($cdata));
# Return requested data
if ($sdata) {
$data{'fcached'} = $cdata;
} else {
$data{'scached'} = $tdata;
}
}
return;
}
# Store complete dataset
if (ref($cdata->{$k}) ne 'ARRAY') {
$cdata->{$k} = [];
}
push(@{ $cdata->{$k} },
{ x => $time,
y => $d
});
my $n = &$option('stored_length', 1) || 600;
# User option sanity check
if ($n < 600 || $n > 86400) {
$n = 600;
}
# Trim dataset
for my $i (0 .. @{ $cdata->{$k} }) {
if (defined(@{ $cdata->{$k} }[$i])) {
if (($time - @{ $cdata->{$k} }[$i]->{'x'}) > $n) {
delete @{ $cdata->{$k} }[$i];
}
}
}
@{ $cdata->{$k} } = grep {$_ ne undef} @{ $cdata->{$k} };
# Store single dataset
if (ref($tdata->{$k}) ne 'ARRAY') {
$tdata->{$k} = [];
}
push(@{ $tdata->{$k} },
{ x => time(),
y => $d
});
};
eval {$cdata = convert_from_json($cdata) if ($cdata);};
unlink($fdata), $cdata = {} if ($@);
$cdata ||= {};
if ($in{'xhr-stats'} =~ /[[:alpha:]]/) {
my $target = $in{'xhr-stats'};
if ($target eq 'general') {
# Run in async to reduce script execution time
my $network;
if (acl_system_status('load')) {
$network = Async->new(sub {network_stats('io')});
}
if (foreign_check("proc")) {
foreign_require("proc");
# CPU stats
if (acl_system_status('cpu')) {
my @cpuinfo = defined(&proc::get_cpu_info) ? proc::get_cpu_info() : ();
my @cpuusage = defined(&proc::get_cpu_io_usage) ? proc::get_cpu_io_usage() : ();
if (@cpuinfo && @cpuusage) {
# CPU load
my $cpu = int($cpuusage[0] + $cpuusage[1] + $cpuusage[3]);
$data{'cpu'} = [$cpu, text('body_load', ($cpuinfo[0], $cpuinfo[1], $cpuinfo[2]))];
&$ddata('cpu', $cpu);
# Disk I/O
my $in = $cpuusage[5];
my $out = $cpuusage[6];
if ($in && $out || $in eq '0' || $out eq '0') {
$data{'io'} = [$in, $out];
&$ddata('disk', [$in, $out]);
}
}
}
# Memory stats
if (acl_system_status('mem')) {
my @memory = defined(&proc::get_memory_info) ? proc::get_memory_info() : ();
if (@memory) {
$data{'mem'} = [];
$data{'virt'} = [];
if (@memory && $memory[0] && $memory[0] > 0) {
my $mem = (100 - int(($memory[1] / $memory[0]) * 100));
$data{'mem'} = [$mem,
text(($memory[4] ? 'body_used_cached_total' : 'body_used'),
nice_size($memory[0] * 1024),
nice_size(($memory[0] - $memory[1]) * 1024),
($memory[4] ? nice_size($memory[4] * 1024) : undef)
)];
&$ddata('mem', $mem);
}
if (@memory && $memory[2] && $memory[2] > 0) {
my $virt = (100 - int(($memory[3] / $memory[2]) * 100));
$data{'virt'} = [$virt,
text('body_used',
nice_size(($memory[2]) * 1024),
nice_size(($memory[2] - $memory[3]) * 1024)
)];
&$ddata('virt', $virt);
}
}
}
# Number of running processes
if (acl_system_status('load')) {
my @processes = proc::list_processes();
my $proc = scalar(@processes);
$data{'proc'} = $proc;
&$ddata('proc', $proc);
}
}
# Disk space
if (acl_system_status('disk')) {
if (foreign_check("mount") && &$option('status_disk')) {
foreign_require("mount");
my @disk_space = defined(&mount::local_disk_space) ? mount::local_disk_space() : ();
if (@disk_space) {
$data{'disk'} = [];
if (@disk_space && $disk_space[0] && $disk_space[0] > 0) {
my $disk = int(($disk_space[0] - $disk_space[1]) / $disk_space[0] * 100);
$data{'disk'} = [$disk,
text('body_used_and_free',
nice_size($disk_space[0]),
nice_size($disk_space[1]),
nice_size($disk_space[0] - $disk_space[1])
)];
}
}
}
}
# Network I/O
if ($network) {
for (;;) {
if ($network->ready) {
if (!$network->error) {
my $nrs = unserialise_variable($network->result);
my $in = @{$nrs}[0];
my $out = @{$nrs}[1];
if ($in && $out || $in eq '0' || $out eq '0') {
$data{'net'} = [$in, $out];
&$ddata('net', [$in, $out]);
}
}
last;
}
select(undef, undef, undef, 0.1);
}
}
# Reverse output for LTR users
if (get_text_ltr()) {
my @watched = ('mem', 'virt', 'disk');
foreach my $key (@watched) {
if ($data{$key} && $data{$key}[1]) {
$data{$key}[1] = reverse_string($data{$key}[1], "/");
}
}
}
}
&$ddata();
}
print_json(\%data);