1 package PVE::ProcFSTools;
6 use Time::HiRes qw (gettimeofday);
11 use Socket qw(PF_INET PF_INET6 SOCK_DGRAM IPPROTO_IP);
13 use constant IFF_UP => 1;
14 use constant IFNAMSIZ => 16;
15 use constant SIOCGIFFLAGS => 0x8913;
17 my $clock_ticks = POSIX::sysconf(&POSIX::_SC_CLK_TCK);
22 my $fn = '/proc/cpuinfo';
24 return $cpuinfo if $cpuinfo;
27 user_hz => $clock_ticks,
34 my $fh = IO::File->new ($fn, "r");
39 while (defined(my $line = <$fh>)) {
40 if ($line =~ m/^processor\s*:\s*\d+\s*$/i) {
42 } elsif ($line =~ m/^model\s+name\s*:\s*(.*)\s*$/i) {
43 $res->{model} = $1 if $res->{model} eq 'unknown';
44 } elsif ($line =~ m/^cpu\s+MHz\s*:\s*(\d+\.\d+)\s*$/i) {
45 $res->{mhz} = $1 if !$res->{mhz};
46 } elsif ($line =~ m/^flags\s*:.*(vmx|svm)/) {
47 $res->{hvm} = 1; # Hardware Virtual Machine (Intel VT / AMD-V)
48 } elsif ($line =~ m/^physical id\s*:\s*(\d+)\s*$/i) {
53 $res->{sockets} = scalar(keys %$idhash) || 1;
55 $res->{cpus} = $count;
64 sub read_proc_uptime {
67 my $line = PVE::Tools::file_read_firstline("/proc/uptime");
68 if ($line && $line =~ m|^(\d+\.\d+)\s+(\d+\.\d+)\s*$|) {
70 return (int($1*$clock_ticks), int($2*$clock_ticks));
72 return (int($1), int($2));
81 my $line = PVE::Tools::file_read_firstline('/proc/loadavg');
83 if ($line =~ m|^(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+\d+/\d+\s+\d+\s*$|) {
84 return wantarray ? ($1, $2, $3) : $1;
87 return wantarray ? (0, 0, 0) : 0;
93 my $res = { user => 0, nice => 0, system => 0, idle => 0 , sum => 0};
97 if (my $fh = IO::File->new ("/proc/stat", "r")) {
98 while (defined (my $line = <$fh>)) {
99 if ($line =~ m|^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s|) {
104 $res->{used} = $1+$2+$3;
106 } elsif ($line =~ m|^cpu\d+\s|) {
113 $cpucount = 1 if !$cpucount;
115 my $ctime = gettimeofday; # floating point time in seconds
117 $res->{ctime} = $ctime;
121 $last_proc_stat = $res if !$last_proc_stat;
123 my $diff = ($ctime - $last_proc_stat->{ctime}) * $clock_ticks * $cpucount;
125 if ($diff > 1000) { # don't update too often
126 my $useddiff = $res->{used} - $last_proc_stat->{used};
127 $useddiff = $diff if $useddiff > $diff;
128 $res->{cpu} = $useddiff/$diff;
129 my $waitdiff = $res->{iowait} - $last_proc_stat->{iowait};
130 $waitdiff = $diff if $waitdiff > $diff;
131 $res->{wait} = $waitdiff/$diff;
132 $last_proc_stat = $res;
134 $res->{cpu} = $last_proc_stat->{cpu};
135 $res->{wait} = $last_proc_stat->{wait};
141 sub read_proc_pid_stat {
144 my $statstr = PVE::Tools::file_read_firstline("/proc/$pid/stat");
146 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+/) {
160 sub check_process_running {
161 my ($pid, $pstart) = @_;
163 # note: waitpid only work for child processes, but not
164 # for processes spanned by other processes.
165 # kill(0, pid) return succes for zombies.
166 # So we read the status form /proc/$pid/stat instead
168 my $info = read_proc_pid_stat($pid);
170 return $info && (!$pstart || ($info->{starttime} eq $pstart)) && ($info->{status} ne 'Z') ? $info : undef;
173 sub read_proc_starttime {
176 my $info = read_proc_pid_stat($pid);
177 return $info ? $info->{starttime} : 0;
192 my $fh = IO::File->new ("/proc/meminfo", "r");
196 while (my $line = <$fh>) {
197 if ($line =~ m/^(\S+):\s+(\d+)\s*kB/i) {
198 $d->{lc ($1)} = $2 * 1024;
203 $res->{memtotal} = $d->{memtotal};
204 $res->{memfree} = $d->{memfree} + $d->{buffers} + $d->{cached};
205 $res->{memused} = $res->{memtotal} - $res->{memfree};
207 $res->{swaptotal} = $d->{swaptotal};
208 $res->{swapfree} = $d->{swapfree};
209 $res->{swapused} = $res->{swaptotal} - $res->{swapfree};
211 my $spages = PVE::Tools::file_read_firstline("/sys/kernel/mm/ksm/pages_sharing");
212 $res->{memshared} = int($spages) * 4096;
217 # memory usage of current process
218 sub read_memory_usage {
220 my $res = { size => 0, resident => 0, shared => 0 };
224 my $line = PVE::Tools::file_read_firstline("/proc/$$/statm");
226 if ($line =~ m/^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*/) {
227 $res->{size} = $1*$ps;
228 $res->{resident} = $2*$ps;
229 $res->{shared} = $3*$ps;
235 sub read_proc_net_dev {
239 my $fh = IO::File->new ("/proc/net/dev", "r");
242 while (defined (my $line = <$fh>)) {
243 if ($line =~ m/^\s*(.*):\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+/) {
256 sub write_proc_entry {
257 my ($filename, $data) = @_;#
259 my $fh = IO::File->new($filename, O_WRONLY);
260 die "unable to open file '$filename' - $!\n" if !$fh;
261 die "unable to write '$filename' - $!\n" unless print $fh $data;
262 die "closing file '$filename' failed - $!\n" unless close $fh;
266 sub read_proc_net_route {
267 my $filename = "/proc/net/route";
271 my $fh = IO::File->new ($filename, "r");
274 my $int_to_quad = sub {
275 return join '.' => map { ($_[0] >> 8*(3-$_)) % 256 } (3, 2, 1, 0);
278 while (defined(my $line = <$fh>)) {
279 next if $line =~/^Iface\s+Destination/; # skip head
280 my ($iface, $dest, $gateway, $metric, $mask, $mtu) = (split(/\s+/, $line))[0,1,2,6,7,8];
282 dest => &$int_to_quad(hex($dest)),
283 gateway => &$int_to_quad(hex($gateway)),
284 mask => &$int_to_quad(hex($mask)),
294 sub read_proc_mounts {
295 return PVE::Tools::file_get_contents("/proc/mounts", 128*1024);
298 # mounts encode spaces (\040), tabs (\011), newlines (\012), backslashes (\\ or \134)
301 return $str =~ s/\\(?:040|01[12]|134|\\)/"\"$&\""/geer;
307 while ($mounts =~ /^\s*([^#].*)$/gm) {
308 # lines from the file are encoded so we can just split at spaces
309 my ($what, $dir, $fstype, $opts) = split(/[ \t]/, $1, 4);
310 my ($freq, $passno) = (0, 0);
311 # in glibc's parser frequency and pass seem to be optional
312 $freq = $1 if $opts =~ s/\s+(\d+)$//;
313 $passno = $1 if $opts =~ s/\s+(\d+)$//;
314 push @$mntent, [decode_mount($what),
316 decode_mount($fstype),
323 sub parse_proc_mounts {
324 return parse_mounts(read_proc_mounts());
328 my ($mountpoint) = @_;
330 $mountpoint = Cwd::realpath($mountpoint);
332 return 0 if !defined($mountpoint); # path does not exist
334 my $mounts = parse_proc_mounts();
335 return (grep { $_->[1] eq $mountpoint } @$mounts) ? 1 : 0;
338 sub read_proc_net_ipv6_route {
339 my $filename = "/proc/net/ipv6_route";
343 my $fh = IO::File->new ($filename, "r");
346 my $read_v6addr = sub { $_[0] =~ s/....(?!$)/$&:/gr };
348 # ipv6_route has no header
349 while (defined(my $line = <$fh>)) {
350 my ($dest, $prefix, $nexthop, $metric, $iface) = (split(/\s+/, $line))[0,1,4,5,9];
352 dest => &$read_v6addr($dest),
353 prefix => hex("$prefix"),
354 gateway => &$read_v6addr($nexthop),
355 metric => hex("$metric"),
364 my ($upid, $waitfunc, $sleep_intervall) = @_;
366 my $task = PVE::Tools::upid_decode($upid);
368 $sleep_intervall = $sleep_intervall ? $sleep_intervall : 1;
370 my $next_time = time + $sleep_intervall;
372 while (check_process_running($task->{pid}, $task->{pstart})) {
374 if (time >= $next_time && $waitfunc && ref($waitfunc) eq 'CODE'){
376 $next_time = time + $sleep_intervall;
383 # struct ifreq { // FOR SIOCGIFFLAGS:
384 # char ifrn_name[IFNAMSIZ]
387 my $STRUCT_IFREQ_SIOCGIFFLAGS = 'Z' . IFNAMSIZ . 's1';
388 sub get_active_network_interfaces {
389 # Use the interface name list from /proc/net/dev
390 open my $fh, '<', '/proc/net/dev'
391 or die "failed to open /proc/net/dev: $!\n";
392 # And filter by IFF_UP flag fetched via a PF_INET6 socket ioctl:
394 socket($sock, PF_INET6, SOCK_DGRAM, &IPPROTO_IP)
395 or socket($sock, PF_INET, SOCK_DGRAM, &IPPROTO_IP)
399 while(defined(my $line = <$fh>)) {
400 next if $line !~ /^\s*([^:\s]+):/;
402 my $ifreq = pack($STRUCT_IFREQ_SIOCGIFFLAGS, $ifname, 0);
403 if (!defined(ioctl($sock, SIOCGIFFLAGS, $ifreq))) {
404 warn "failed to get interface flags for: $ifname\n";
407 my ($name, $flags) = unpack($STRUCT_IFREQ_SIOCGIFFLAGS, $ifreq);
408 push @$ifaces, $ifname if ($flags & IFF_UP);