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