1#!/usr/local/bin/perl
2
3#
4# Authentic Theme (https://github.com/authentic-theme/authentic-theme)
5# Copyright Ilia Rostovtsev <programming@rostovtsev.io>
6# Licensed under MIT (https://github.com/authentic-theme/authentic-theme/blob/master/LICENSE)
7#
8use strict;
9no warnings 'uninitialized';
10
11use lib ("$ENV{'THEME_ROOT'}/lib");
12use Async;
13
14BEGIN {push(@INC, "..");}
15use WebminCore;
16
17our (%in, $config_directory, $var_directory, $current_theme, $remote_user);
18do("$ENV{'THEME_ROOT'}/authentic-funcs.pl");
19
20init_config();
21ReadParse();
22
23# Load theme language and settings
24our %text = load_language($current_theme);
25my %settings = settings("$config_directory/$current_theme/settings.js", 'settings_');
26
27my $option = sub {
28    if ($_[1]) {
29        return $settings{"settings_sysinfo_real_time_$_[0]"};
30    }
31    if (!scalar %settings) {
32        return 1;
33    }
34    return ($settings{"settings_sysinfo_real_time_$_[0]"} eq 'false') ? 0 : 1;
35};
36
37my %data;
38my $tdata  = {};
39my $sdata  = $in{'sdata'} ? 1 : 0;
40my $fdatad = "$var_directory/modules/$current_theme";
41my $fdata  = "$fdatad/stats-$remote_user.json";
42my $cdata  = read_file_contents($fdata);
43my $time   = time();
44my $ddata  = sub {
45    my ($k, $d) = @_;
46
47    # Save and return data
48    if (!$k) {
49        if (&$option('stored')) {
50
51            # Clear existing cache if available features was disabled
52            my %map = (cpu  => ['cpu',  'disk'],
53                       mem  => ['mem',  'virt'],
54                       load => ['proc', 'net']);
55
56            foreach my $key (keys %map) {
57                my $feature = acl_system_status($key);
58                if (ref($map{$key}) eq 'ARRAY') {
59                    foreach my $skey (@{ $map{$key} }) {
60                        if (!$feature) {
61                            delete $cdata->{$skey};
62                        }
63                    }
64                } else {
65                    if (!$feature) {
66                        delete $cdata->{ $map{$key} };
67                    }
68                }
69            }
70
71            # Cache complete dataset
72            mkdir($fdatad, 0700) if (!-d $fdatad);
73            write_file_contents($fdata, convert_to_json($cdata));
74
75            # Return requested data
76            if ($sdata) {
77                $data{'fcached'} = $cdata;
78            } else {
79                $data{'scached'} = $tdata;
80            }
81        }
82        return;
83    }
84
85    # Store complete dataset
86    if (ref($cdata->{$k}) ne 'ARRAY') {
87        $cdata->{$k} = [];
88    }
89    push(@{ $cdata->{$k} },
90         {  x => $time,
91            y => $d
92         });
93
94    my $n = &$option('stored_length', 1) || 600;
95
96    # User option sanity check
97    if ($n < 600 || $n > 86400) {
98        $n = 600;
99    }
100
101    # Trim dataset
102    for my $i (0 .. @{ $cdata->{$k} }) {
103        if (defined(@{ $cdata->{$k} }[$i])) {
104            if (($time - @{ $cdata->{$k} }[$i]->{'x'}) > $n) {
105                delete @{ $cdata->{$k} }[$i];
106            }
107        }
108    }
109    @{ $cdata->{$k} } = grep {$_ ne undef} @{ $cdata->{$k} };
110
111    # Store single dataset
112    if (ref($tdata->{$k}) ne 'ARRAY') {
113        $tdata->{$k} = [];
114    }
115    push(@{ $tdata->{$k} },
116         {  x => time(),
117            y => $d
118         });
119};
120
121eval {$cdata = convert_from_json($cdata) if ($cdata);};
122unlink($fdata), $cdata = {} if ($@);
123$cdata ||= {};
124
125if ($in{'xhr-stats'} =~ /[[:alpha:]]/) {
126    my $target = $in{'xhr-stats'};
127    if ($target eq 'general') {
128
129        # Run in async to reduce script execution time
130        my $network;
131        if (acl_system_status('load')) {
132            $network = Async->new(sub {network_stats('io')});
133        }
134
135        if (foreign_check("proc")) {
136            foreign_require("proc");
137
138            # CPU stats
139            if (acl_system_status('cpu')) {
140                my @cpuinfo  = defined(&proc::get_cpu_info)     ? proc::get_cpu_info()     : ();
141                my @cpuusage = defined(&proc::get_cpu_io_usage) ? proc::get_cpu_io_usage() : ();
142                if (@cpuinfo && @cpuusage) {
143
144                    # CPU load
145                    my $cpu = int($cpuusage[0] + $cpuusage[1] + $cpuusage[3]);
146                    $data{'cpu'} = [$cpu, text('body_load', ($cpuinfo[0], $cpuinfo[1], $cpuinfo[2]))];
147                    &$ddata('cpu', $cpu);
148
149                    # Disk I/O
150                    my $in  = $cpuusage[5];
151                    my $out = $cpuusage[6];
152                    if ($in && $out || $in eq '0' || $out eq '0') {
153                        $data{'io'} = [$in, $out];
154                        &$ddata('disk', [$in, $out]);
155                    }
156                }
157            }
158
159            # Memory stats
160            if (acl_system_status('mem')) {
161                my @memory = defined(&proc::get_memory_info) ? proc::get_memory_info() : ();
162                if (@memory) {
163                    $data{'mem'}  = [];
164                    $data{'virt'} = [];
165
166                    if (@memory && $memory[0] && $memory[0] > 0) {
167                        my $mem = (100 - int(($memory[1] / $memory[0]) * 100));
168                        $data{'mem'} = [$mem,
169                                        text(($memory[4] ? 'body_used_cached_total' : 'body_used'),
170                                             nice_size($memory[0] * 1024),
171                                             nice_size(($memory[0] - $memory[1]) * 1024),
172                                             ($memory[4] ? nice_size($memory[4] * 1024) : undef)
173                                        )];
174                        &$ddata('mem', $mem);
175                    }
176                    if (@memory && $memory[2] && $memory[2] > 0) {
177                        my $virt = (100 - int(($memory[3] / $memory[2]) * 100));
178                        $data{'virt'} = [$virt,
179                                         text('body_used',
180                                              nice_size(($memory[2]) * 1024),
181                                              nice_size(($memory[2] - $memory[3]) * 1024)
182                                         )];
183                        &$ddata('virt', $virt);
184                    }
185                }
186            }
187
188            # Number of running processes
189            if (acl_system_status('load')) {
190                my @processes = proc::list_processes();
191                my $proc      = scalar(@processes);
192                $data{'proc'} = $proc;
193                &$ddata('proc', $proc);
194            }
195        }
196
197        # Disk space
198        if (acl_system_status('disk')) {
199            if (foreign_check("mount") && &$option('status_disk')) {
200                foreign_require("mount");
201
202                my @disk_space = defined(&mount::local_disk_space) ? mount::local_disk_space() : ();
203                if (@disk_space) {
204                    $data{'disk'} = [];
205
206                    if (@disk_space && $disk_space[0] && $disk_space[0] > 0) {
207                        my $disk = int(($disk_space[0] - $disk_space[1]) / $disk_space[0] * 100);
208                        $data{'disk'} = [$disk,
209                                         text('body_used_and_free',
210                                              nice_size($disk_space[0]),
211                                              nice_size($disk_space[1]),
212                                              nice_size($disk_space[0] - $disk_space[1])
213                                         )];
214                    }
215                }
216            }
217        }
218
219        # Network I/O
220        if ($network) {
221            for (;;) {
222                if ($network->ready) {
223                    if (!$network->error) {
224                        my $nrs = unserialise_variable($network->result);
225                        my $in  = @{$nrs}[0];
226                        my $out = @{$nrs}[1];
227
228                        if ($in && $out || $in eq '0' || $out eq '0') {
229                            $data{'net'} = [$in, $out];
230                            &$ddata('net', [$in, $out]);
231                        }
232                    }
233                    last;
234                }
235                select(undef, undef, undef, 0.1);
236            }
237        }
238
239        # Reverse output for LTR users
240        if (get_text_ltr()) {
241            my @watched = ('mem', 'virt', 'disk');
242            foreach my $key (@watched) {
243                if ($data{$key} && $data{$key}[1]) {
244                    $data{$key}[1] = reverse_string($data{$key}[1], "/");
245                }
246            }
247        }
248    }
249    &$ddata();
250}
251
252print_json(\%data);
253