X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=src%2FPVE%2FProcFSTools.pm;h=7687c13c4e30b9d483ebb624c8d522ca8a74ca3d;hp=80c04257783ea5db83b0fce6385fd390095103bd;hb=HEAD;hpb=a9b02ee637bd98cba2cf358884d647ac2403bfed diff --git a/src/PVE/ProcFSTools.pm b/src/PVE/ProcFSTools.pm index 80c0425..3826fcc 100644 --- a/src/PVE/ProcFSTools.pm +++ b/src/PVE/ProcFSTools.pm @@ -2,13 +2,15 @@ package PVE::ProcFSTools; use strict; use warnings; + +use Cwd qw(); +use IO::File; +use List::Util qw(sum); use POSIX; +use Socket qw(PF_INET PF_INET6 SOCK_DGRAM IPPROTO_IP); use Time::HiRes qw (gettimeofday); -use IO::File; -use PVE::Tools; -use Cwd qw(); -use Socket qw(PF_INET PF_INET6 SOCK_DGRAM IPPROTO_IP); +use PVE::Tools; use constant IFF_UP => 1; use constant IFNAMSIZ => 16; @@ -29,11 +31,13 @@ sub read_cpuinfo { mhz => 0, cpus => 1, sockets => 1, + flags => '', }; my $fh = IO::File->new ($fn, "r"); return $res if !$fh; + my $cpuid = 0; my $idhash = {}; my $count = 0; while (defined(my $line = <$fh>)) { @@ -43,19 +47,27 @@ sub read_cpuinfo { $res->{model} = $1 if $res->{model} eq 'unknown'; } elsif ($line =~ m/^cpu\s+MHz\s*:\s*(\d+\.\d+)\s*$/i) { $res->{mhz} = $1 if !$res->{mhz}; - } elsif ($line =~ m/^flags\s*:.*(vmx|svm)/) { - $res->{hvm} = 1; # Hardware Virtual Machine (Intel VT / AMD-V) + } elsif ($line =~ m/^flags\s*:\s*(.*)$/) { + $res->{flags} = $1 if !length $res->{flags}; } elsif ($line =~ m/^physical id\s*:\s*(\d+)\s*$/i) { - $idhash->{$1} = 1; + $cpuid = $1; + $idhash->{$1} = 1 if not defined($idhash->{$1}); + } elsif ($line =~ m/^cpu cores\s*:\s*(\d+)\s*$/i) { + $idhash->{$cpuid} = $1 if defined($idhash->{$cpuid}); } } + # Hardware Virtual Machine (Intel VT / AMD-V) + $res->{hvm} = $res->{flags} =~ m/\s(vmx|svm)\s/; + $res->{sockets} = scalar(keys %$idhash) || 1; + $res->{cores} = sum(values %$idhash) || 1; + $res->{cpus} = $count; $fh->close; - + $cpuinfo = $res; return $res; @@ -76,6 +88,40 @@ sub read_proc_uptime { return (0, 0); } +sub kernel_version { + my $line = PVE::Tools::file_read_firstline("/proc/version"); + + if ($line && $line =~ m|^Linux\sversion\s((\d+(?:\.\d+)+)-?(\S+)?)|) { + my ($fullversion, $version_numbers, $extra) = ($1, $2, $3); + + # variable names are the one from the Linux kernel Makefile + my ($version, $patchlevel, $sublevel) = split(/\./, $version_numbers); + + return wantarray + ? (int($version), int($patchlevel), int($sublevel), $extra, $fullversion) + : $fullversion; + } + + return (0, 0, 0, '', ''); +} + +# Check if the kernel is at least $major.$minor. Return either just a boolean, +# or a boolean and the kernel version's major.minor string from /proc/version +sub check_kernel_release { + my ($major, $minor) = @_; + + my ($k_major, $k_minor) = kernel_version(); + + my $ok; + if (defined($minor)) { + $ok = $k_major > $major || ($k_major == $major && $k_minor >= $minor); + } else { + $ok = $k_major >= $major; + } + + return wantarray ? ($ok, "$k_major.$k_minor") : $ok; +} + sub read_loadavg { my $line = PVE::Tools::file_read_firstline('/proc/loadavg'); @@ -87,22 +133,54 @@ sub read_loadavg { return wantarray ? (0, 0, 0) : 0; } +sub parse_pressure { + my ($path) = @_; + + my $res = {}; + my $v = qr/\d+\.\d+/; + my $fh = IO::File->new($path, "r") or return undef; + while (defined (my $line = <$fh>)) { + if ($line =~ /^(some|full)\s+avg10\=($v)\s+avg60\=($v)\s+avg300\=($v)\s+total\=(\d+)/) { + $res->{$1}->{avg10} = $2; + $res->{$1}->{avg60} = $3; + $res->{$1}->{avg300} = $4; + $res->{$1}->{total} = $4; + } + } + $fh->close; + return $res; +} + +sub read_pressure { + my $res = {}; + foreach my $type (qw(cpu memory io)) { + my $stats = parse_pressure("/proc/pressure/$type"); + $res->{$type} = $stats if $stats; + } + return $res; +} + my $last_proc_stat; sub read_proc_stat { - my $res = { user => 0, nice => 0, system => 0, idle => 0 , sum => 0}; + my $res = { user => 0, nice => 0, system => 0, idle => 0 , iowait => 0, irq => 0, softirq => 0, steal => 0, guest => 0, guest_nice => 0, sum => 0}; my $cpucount = 0; if (my $fh = IO::File->new ("/proc/stat", "r")) { while (defined (my $line = <$fh>)) { - if ($line =~ m|^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s|) { - $res->{user} = $1; - $res->{nice} = $2; + if ($line =~ m|^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)(?:\s+(\d+)\s+(\d+))?|) { + $res->{user} = $1 - ($9 // 0); + $res->{nice} = $2 - ($10 // 0); $res->{system} = $3; $res->{idle} = $4; - $res->{used} = $1+$2+$3; + $res->{used} = $1+$2+$3+$6+$7+$8; $res->{iowait} = $5; + $res->{irq} = $6; + $res->{softirq} = $7; + $res->{steal} = $8; + $res->{guest} = $9 // 0; + $res->{guest_nice} = $10 // 0; } elsif ($line =~ m|^cpu\d+\s|) { $cpucount++; } @@ -114,6 +192,18 @@ sub read_proc_stat { my $ctime = gettimeofday; # floating point time in seconds + # the sum of all fields + $res->{total} = $res->{user} + + $res->{nice} + + $res->{system} + + $res->{iowait} + + $res->{irq} + + $res->{softirq} + + $res->{steal} + + $res->{idle} + + $res->{guest} + + $res->{guest_nice}; + $res->{ctime} = $ctime; $res->{cpu} = 0; $res->{wait} = 0; @@ -125,10 +215,16 @@ sub read_proc_stat { if ($diff > 1000) { # don't update too often my $useddiff = $res->{used} - $last_proc_stat->{used}; $useddiff = $diff if $useddiff > $diff; - $res->{cpu} = $useddiff/$diff; + + my $totaldiff = $res->{total} - $last_proc_stat->{total}; + $totaldiff = $diff if $totaldiff > $diff; + + $res->{cpu} = $useddiff/$totaldiff; + my $waitdiff = $res->{iowait} - $last_proc_stat->{iowait}; $waitdiff = $diff if $waitdiff > $diff; - $res->{wait} = $waitdiff/$diff; + $res->{wait} = $waitdiff/$totaldiff; + $last_proc_stat = $res; } else { $res->{cpu} = $last_proc_stat->{cpu}; @@ -146,6 +242,7 @@ sub read_proc_pid_stat { if ($statstr && $statstr =~ m/^$pid \(.*\) (\S) (-?\d+) -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ (\d+) (\d+) (-?\d+) (-?\d+) -?\d+ -?\d+ -?\d+ 0 (\d+) (\d+) (-?\d+) \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ -?\d+ -?\d+ \d+ \d+ \d+/) { return { status => $1, + ppid => $2, utime => $3, stime => $4, starttime => $7, @@ -164,9 +261,9 @@ sub check_process_running { # for processes spanned by other processes. # kill(0, pid) return succes for zombies. # So we read the status form /proc/$pid/stat instead - + my $info = read_proc_pid_stat($pid); - + return $info && (!$pstart || ($info->{starttime} eq $pstart)) && ($info->{status} ne 'Z') ? $info : undef; } @@ -187,6 +284,7 @@ sub read_meminfo { swaptotal => 0, swapfree => 0, swapused => 0, + arcsize => 0, }; my $fh = IO::File->new ("/proc/meminfo", "r"); @@ -196,7 +294,7 @@ sub read_meminfo { while (my $line = <$fh>) { if ($line =~ m/^(\S+):\s+(\d+)\s*kB/i) { $d->{lc ($1)} = $2 * 1024; - } + } } close($fh); @@ -208,9 +306,14 @@ sub read_meminfo { $res->{swapfree} = $d->{swapfree}; $res->{swapused} = $res->{swaptotal} - $res->{swapfree}; - my $spages = PVE::Tools::file_read_firstline("/sys/kernel/mm/ksm/pages_sharing"); + my $spages = PVE::Tools::file_read_firstline("/sys/kernel/mm/ksm/pages_sharing") // 0 ; $res->{memshared} = int($spages) * 4096; + my $arc_stats = eval { PVE::Tools::file_get_contents("/proc/spl/kstat/zfs/arcstats") }; + if ($arc_stats && $arc_stats =~ m/^size\s+\d+\s+(\d+)$/m) { + $res->{arcsize} = int($1); + } + return $res; } @@ -256,10 +359,10 @@ sub read_proc_net_dev { sub write_proc_entry { my ($filename, $data) = @_;# - my $fh = IO::File->new($filename, O_WRONLY); + my $fh = IO::File->new($filename, O_WRONLY); die "unable to open file '$filename' - $!\n" if !$fh; - die "unable to write '$filename' - $!\n" unless print $fh $data; - die "closing file '$filename' failed - $!\n" unless close $fh; + print $fh $data or die "unable to write '$filename' - $!\n"; + close $fh or die "closing file '$filename' failed - $!\n"; $fh->close(); } @@ -292,7 +395,7 @@ sub read_proc_net_route { } sub read_proc_mounts { - return PVE::Tools::file_get_contents("/proc/mounts", 128*1024); + return PVE::Tools::file_get_contents("/proc/mounts", 512*1024); } # mounts encode spaces (\040), tabs (\011), newlines (\012), backslashes (\\ or \134) @@ -303,6 +406,7 @@ sub decode_mount { sub parse_mounts { my ($mounts) = @_; + my $mntent = []; while ($mounts =~ /^\s*([^#].*)$/gm) { # lines from the file are encoded so we can just split at spaces @@ -311,11 +415,14 @@ sub parse_mounts { # in glibc's parser frequency and pass seem to be optional $freq = $1 if $opts =~ s/\s+(\d+)$//; $passno = $1 if $opts =~ s/\s+(\d+)$//; - push @$mntent, [decode_mount($what), - decode_mount($dir), - decode_mount($fstype), - decode_mount($opts), - $freq, $passno]; + push @$mntent, [ + decode_mount($what), + decode_mount($dir), + decode_mount($fstype), + decode_mount($opts), + $freq, + $passno, + ]; } return $mntent; }