]> git.proxmox.com Git - pve-common.git/blob - src/PVE/ProcFSTools.pm
bump version to 8.2.0
[pve-common.git] / src / PVE / ProcFSTools.pm
1 package PVE::ProcFSTools;
2
3 use strict;
4 use warnings;
5 use POSIX;
6 use Time::HiRes qw (gettimeofday);
7 use IO::File;
8 use List::Util qw(sum);
9 use PVE::Tools;
10 use Cwd qw();
11
12 use Socket qw(PF_INET PF_INET6 SOCK_DGRAM IPPROTO_IP);
13
14 use constant IFF_UP => 1;
15 use constant IFNAMSIZ => 16;
16 use constant SIOCGIFFLAGS => 0x8913;
17
18 my $clock_ticks = POSIX::sysconf(&POSIX::_SC_CLK_TCK);
19
20 my $cpuinfo;
21
22 sub read_cpuinfo {
23 my $fn = '/proc/cpuinfo';
24
25 return $cpuinfo if $cpuinfo;
26
27 my $res = {
28 user_hz => $clock_ticks,
29 model => 'unknown',
30 mhz => 0,
31 cpus => 1,
32 sockets => 1,
33 flags => '',
34 };
35
36 my $fh = IO::File->new ($fn, "r");
37 return $res if !$fh;
38
39 my $cpuid = 0;
40 my $idhash = {};
41 my $count = 0;
42 while (defined(my $line = <$fh>)) {
43 if ($line =~ m/^processor\s*:\s*\d+\s*$/i) {
44 $count++;
45 } elsif ($line =~ m/^model\s+name\s*:\s*(.*)\s*$/i) {
46 $res->{model} = $1 if $res->{model} eq 'unknown';
47 } elsif ($line =~ m/^cpu\s+MHz\s*:\s*(\d+\.\d+)\s*$/i) {
48 $res->{mhz} = $1 if !$res->{mhz};
49 } elsif ($line =~ m/^flags\s*:\s*(.*)$/) {
50 $res->{flags} = $1 if !length $res->{flags};
51 } elsif ($line =~ m/^physical id\s*:\s*(\d+)\s*$/i) {
52 $cpuid = $1;
53 $idhash->{$1} = 1 if not defined($idhash->{$1});
54 } elsif ($line =~ m/^cpu cores\s*:\s*(\d+)\s*$/i) {
55 $idhash->{$cpuid} = $1 if defined($idhash->{$cpuid});
56 }
57 }
58
59 # Hardware Virtual Machine (Intel VT / AMD-V)
60 $res->{hvm} = $res->{flags} =~ m/\s(vmx|svm)\s/;
61
62 $res->{sockets} = scalar(keys %$idhash) || 1;
63
64 $res->{cores} = sum(values %$idhash) || 1;
65
66 $res->{cpus} = $count;
67
68 $fh->close;
69
70 $cpuinfo = $res;
71
72 return $res;
73 }
74
75 sub read_proc_uptime {
76 my $ticks = shift;
77
78 my $line = PVE::Tools::file_read_firstline("/proc/uptime");
79 if ($line && $line =~ m|^(\d+\.\d+)\s+(\d+\.\d+)\s*$|) {
80 if ($ticks) {
81 return (int($1*$clock_ticks), int($2*$clock_ticks));
82 } else {
83 return (int($1), int($2));
84 }
85 }
86
87 return (0, 0);
88 }
89
90 sub kernel_version {
91 my $line = PVE::Tools::file_read_firstline("/proc/version");
92
93 if ($line && $line =~ m|^Linux\sversion\s((\d+(?:\.\d+)+)-?(\S+)?)|) {
94 my ($fullversion, $version_numbers, $extra) = ($1, $2, $3);
95
96 # variable names are the one from the Linux kernel Makefile
97 my ($version, $patchlevel, $sublevel) = split(/\./, $version_numbers);
98
99 return wantarray
100 ? (int($version), int($patchlevel), int($sublevel), $extra, $fullversion)
101 : $fullversion;
102 }
103
104 return (0, 0, 0, '', '');
105 }
106
107 # Check if the kernel is at least $major.$minor. Return either just a boolean,
108 # or a boolean and the kernel version's major.minor string from /proc/version
109 sub check_kernel_release {
110 my ($major, $minor) = @_;
111
112 my ($k_major, $k_minor) = kernel_version();
113
114 my $ok;
115 if (defined($minor)) {
116 $ok = $k_major > $major || ($k_major == $major && $k_minor >= $minor);
117 } else {
118 $ok = $k_major >= $major;
119 }
120
121 return wantarray ? ($ok, "$k_major.$k_minor") : $ok;
122 }
123
124 sub read_loadavg {
125
126 my $line = PVE::Tools::file_read_firstline('/proc/loadavg');
127
128 if ($line =~ m|^(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+\d+/\d+\s+\d+\s*$|) {
129 return wantarray ? ($1, $2, $3) : $1;
130 }
131
132 return wantarray ? (0, 0, 0) : 0;
133 }
134
135 sub read_pressure {
136
137 my $res = {};
138 foreach my $type (qw(cpu memory io)) {
139 if (my $fh = IO::File->new ("/proc/pressure/$type", "r")) {
140 while (defined (my $line = <$fh>)) {
141 if ($line =~ /^(some|full)\s+avg10\=(\d+\.\d+)\s+avg60\=(\d+\.\d+)\s+avg300\=(\d+\.\d+)\s+total\=(\d+)/) {
142 $res->{$type}->{$1}->{avg10} = $2;
143 $res->{$type}->{$1}->{avg60} = $3;
144 $res->{$type}->{$1}->{avg300} = $4;
145 }
146 }
147 $fh->close;
148 }
149 }
150 return $res;
151 }
152
153 my $last_proc_stat;
154
155 sub read_proc_stat {
156 my $res = { user => 0, nice => 0, system => 0, idle => 0 , sum => 0};
157
158 my $cpucount = 0;
159
160 if (my $fh = IO::File->new ("/proc/stat", "r")) {
161 while (defined (my $line = <$fh>)) {
162 if ($line =~ m|^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s|) {
163 $res->{user} = $1;
164 $res->{nice} = $2;
165 $res->{system} = $3;
166 $res->{idle} = $4;
167 $res->{used} = $1+$2+$3;
168 $res->{iowait} = $5;
169 } elsif ($line =~ m|^cpu\d+\s|) {
170 $cpucount++;
171 }
172 }
173 $fh->close;
174 }
175
176 $cpucount = 1 if !$cpucount;
177
178 my $ctime = gettimeofday; # floating point time in seconds
179
180 $res->{ctime} = $ctime;
181 $res->{cpu} = 0;
182 $res->{wait} = 0;
183
184 $last_proc_stat = $res if !$last_proc_stat;
185
186 my $diff = ($ctime - $last_proc_stat->{ctime}) * $clock_ticks * $cpucount;
187
188 if ($diff > 1000) { # don't update too often
189 my $useddiff = $res->{used} - $last_proc_stat->{used};
190 $useddiff = $diff if $useddiff > $diff;
191 $res->{cpu} = $useddiff/$diff;
192
193 my $waitdiff = $res->{iowait} - $last_proc_stat->{iowait};
194 $waitdiff = $diff if $waitdiff > $diff;
195 $res->{wait} = $waitdiff/$diff;
196
197 $last_proc_stat = $res;
198 } else {
199 $res->{cpu} = $last_proc_stat->{cpu};
200 $res->{wait} = $last_proc_stat->{wait};
201 }
202
203 return $res;
204 }
205
206 sub read_proc_pid_stat {
207 my $pid = shift;
208
209 my $statstr = PVE::Tools::file_read_firstline("/proc/$pid/stat");
210
211 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+/) {
212 return {
213 status => $1,
214 ppid => $2,
215 utime => $3,
216 stime => $4,
217 starttime => $7,
218 vsize => $8,
219 rss => $9 * 4096,
220 };
221 }
222
223 return undef;
224 }
225
226 sub check_process_running {
227 my ($pid, $pstart) = @_;
228
229 # note: waitpid only work for child processes, but not
230 # for processes spanned by other processes.
231 # kill(0, pid) return succes for zombies.
232 # So we read the status form /proc/$pid/stat instead
233
234 my $info = read_proc_pid_stat($pid);
235
236 return $info && (!$pstart || ($info->{starttime} eq $pstart)) && ($info->{status} ne 'Z') ? $info : undef;
237 }
238
239 sub read_proc_starttime {
240 my $pid = shift;
241
242 my $info = read_proc_pid_stat($pid);
243 return $info ? $info->{starttime} : 0;
244 }
245
246 sub read_meminfo {
247
248 my $res = {
249 memtotal => 0,
250 memfree => 0,
251 memused => 0,
252 memshared => 0,
253 swaptotal => 0,
254 swapfree => 0,
255 swapused => 0,
256 };
257
258 my $fh = IO::File->new ("/proc/meminfo", "r");
259 return $res if !$fh;
260
261 my $d = {};
262 while (my $line = <$fh>) {
263 if ($line =~ m/^(\S+):\s+(\d+)\s*kB/i) {
264 $d->{lc ($1)} = $2 * 1024;
265 }
266 }
267 close($fh);
268
269 $res->{memtotal} = $d->{memtotal};
270 $res->{memfree} = $d->{memfree} + $d->{buffers} + $d->{cached};
271 $res->{memused} = $res->{memtotal} - $res->{memfree};
272
273 $res->{swaptotal} = $d->{swaptotal};
274 $res->{swapfree} = $d->{swapfree};
275 $res->{swapused} = $res->{swaptotal} - $res->{swapfree};
276
277 my $spages = PVE::Tools::file_read_firstline("/sys/kernel/mm/ksm/pages_sharing") // 0 ;
278 $res->{memshared} = int($spages) * 4096;
279
280 return $res;
281 }
282
283 # memory usage of current process
284 sub read_memory_usage {
285
286 my $res = { size => 0, resident => 0, shared => 0 };
287
288 my $ps = 4096;
289
290 my $line = PVE::Tools::file_read_firstline("/proc/$$/statm");
291
292 if ($line =~ m/^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*/) {
293 $res->{size} = $1*$ps;
294 $res->{resident} = $2*$ps;
295 $res->{shared} = $3*$ps;
296 }
297
298 return $res;
299 }
300
301 sub read_proc_net_dev {
302
303 my $res = {};
304
305 my $fh = IO::File->new ("/proc/net/dev", "r");
306 return $res if !$fh;
307
308 while (defined (my $line = <$fh>)) {
309 if ($line =~ m/^\s*(.*):\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+/) {
310 $res->{$1} = {
311 receive => $2,
312 transmit => $3,
313 };
314 }
315 }
316
317 close($fh);
318
319 return $res;
320 }
321
322 sub write_proc_entry {
323 my ($filename, $data) = @_;#
324
325 my $fh = IO::File->new($filename, O_WRONLY);
326 die "unable to open file '$filename' - $!\n" if !$fh;
327 die "unable to write '$filename' - $!\n" unless print $fh $data;
328 die "closing file '$filename' failed - $!\n" unless close $fh;
329 $fh->close();
330 }
331
332 sub read_proc_net_route {
333 my $filename = "/proc/net/route";
334
335 my $res = [];
336
337 my $fh = IO::File->new ($filename, "r");
338 return $res if !$fh;
339
340 my $int_to_quad = sub {
341 return join '.' => map { ($_[0] >> 8*(3-$_)) % 256 } (3, 2, 1, 0);
342 };
343
344 while (defined(my $line = <$fh>)) {
345 next if $line =~/^Iface\s+Destination/; # skip head
346 my ($iface, $dest, $gateway, $metric, $mask, $mtu) = (split(/\s+/, $line))[0,1,2,6,7,8];
347 push @$res, {
348 dest => &$int_to_quad(hex($dest)),
349 gateway => &$int_to_quad(hex($gateway)),
350 mask => &$int_to_quad(hex($mask)),
351 metric => $metric,
352 mtu => $mtu,
353 iface => $iface,
354 };
355 }
356
357 return $res;
358 }
359
360 sub read_proc_mounts {
361 return PVE::Tools::file_get_contents("/proc/mounts", 512*1024);
362 }
363
364 # mounts encode spaces (\040), tabs (\011), newlines (\012), backslashes (\\ or \134)
365 sub decode_mount {
366 my ($str) = @_;
367 return $str =~ s/\\(?:040|01[12]|134|\\)/"\"$&\""/geer;
368 }
369
370 sub parse_mounts {
371 my ($mounts) = @_;
372 my $mntent = [];
373 while ($mounts =~ /^\s*([^#].*)$/gm) {
374 # lines from the file are encoded so we can just split at spaces
375 my ($what, $dir, $fstype, $opts) = split(/[ \t]/, $1, 4);
376 my ($freq, $passno) = (0, 0);
377 # in glibc's parser frequency and pass seem to be optional
378 $freq = $1 if $opts =~ s/\s+(\d+)$//;
379 $passno = $1 if $opts =~ s/\s+(\d+)$//;
380 push @$mntent, [decode_mount($what),
381 decode_mount($dir),
382 decode_mount($fstype),
383 decode_mount($opts),
384 $freq, $passno];
385 }
386 return $mntent;
387 }
388
389 sub parse_proc_mounts {
390 return parse_mounts(read_proc_mounts());
391 }
392
393 sub is_mounted {
394 my ($mountpoint) = @_;
395
396 $mountpoint = Cwd::realpath($mountpoint);
397
398 return 0 if !defined($mountpoint); # path does not exist
399
400 my $mounts = parse_proc_mounts();
401 return (grep { $_->[1] eq $mountpoint } @$mounts) ? 1 : 0;
402 }
403
404 sub read_proc_net_ipv6_route {
405 my $filename = "/proc/net/ipv6_route";
406
407 my $res = [];
408
409 my $fh = IO::File->new ($filename, "r");
410 return $res if !$fh;
411
412 my $read_v6addr = sub { $_[0] =~ s/....(?!$)/$&:/gr };
413
414 # ipv6_route has no header
415 while (defined(my $line = <$fh>)) {
416 my ($dest, $prefix, $nexthop, $metric, $iface) = (split(/\s+/, $line))[0,1,4,5,9];
417 push @$res, {
418 dest => &$read_v6addr($dest),
419 prefix => hex("$prefix"),
420 gateway => &$read_v6addr($nexthop),
421 metric => hex("$metric"),
422 iface => $iface
423 };
424 }
425
426 return $res;
427 }
428
429 sub upid_wait {
430 my ($upid, $waitfunc, $sleep_intervall) = @_;
431
432 my $task = PVE::Tools::upid_decode($upid);
433
434 $sleep_intervall = $sleep_intervall ? $sleep_intervall : 1;
435
436 my $next_time = time + $sleep_intervall;
437
438 while (check_process_running($task->{pid}, $task->{pstart})) {
439
440 if (time >= $next_time && $waitfunc && ref($waitfunc) eq 'CODE'){
441 &$waitfunc($task);
442 $next_time = time + $sleep_intervall;
443 }
444
445 CORE::sleep(1);
446 }
447 }
448
449 # struct ifreq { // FOR SIOCGIFFLAGS:
450 # char ifrn_name[IFNAMSIZ]
451 # short ifru_flags
452 # };
453 my $STRUCT_IFREQ_SIOCGIFFLAGS = 'Z' . IFNAMSIZ . 's1';
454 sub get_active_network_interfaces {
455 # Use the interface name list from /proc/net/dev
456 open my $fh, '<', '/proc/net/dev'
457 or die "failed to open /proc/net/dev: $!\n";
458 # And filter by IFF_UP flag fetched via a PF_INET6 socket ioctl:
459 my $sock;
460 socket($sock, PF_INET6, SOCK_DGRAM, &IPPROTO_IP)
461 or socket($sock, PF_INET, SOCK_DGRAM, &IPPROTO_IP)
462 or return [];
463
464 my $ifaces = [];
465 while(defined(my $line = <$fh>)) {
466 next if $line !~ /^\s*([^:\s]+):/;
467 my $ifname = $1;
468 my $ifreq = pack($STRUCT_IFREQ_SIOCGIFFLAGS, $ifname, 0);
469 if (!defined(ioctl($sock, SIOCGIFFLAGS, $ifreq))) {
470 warn "failed to get interface flags for: $ifname\n";
471 next;
472 }
473 my ($name, $flags) = unpack($STRUCT_IFREQ_SIOCGIFFLAGS, $ifreq);
474 push @$ifaces, $ifname if ($flags & IFF_UP);
475 }
476 close $fh;
477 close $sock;
478 return $ifaces;
479 }
480
481 1;