]> git.proxmox.com Git - pve-manager.git/blob - bin/pvestatd
pvestatd: fix server reload
[pve-manager.git] / bin / pvestatd
1 #!/usr/bin/perl -w
2
3 use strict;
4 use PVE::SafeSyslog;
5 use POSIX ":sys_wait_h";
6 use Fcntl ':flock';
7 use Getopt::Long;
8 use Time::HiRes qw (gettimeofday);
9 use PVE::Tools qw(dir_glob_foreach file_read_firstline);
10 use PVE::ProcFSTools;
11 use Filesys::Df;
12 use PVE::INotify;
13 use PVE::Cluster qw(cfs_read_file);
14 use PVE::Storage;
15 use PVE::QemuServer;
16 use PVE::OpenVZ;
17 use PVE::RPCEnvironment;
18 use PVE::API2::Subscription;
19
20 $SIG{'__WARN__'} = sub {
21 my $err = $@;
22 my $t = $_[0];
23 chomp $t;
24 syslog('warning', "WARNING: %s", $t);
25 $@ = $err;
26 };
27
28 initlog('pvestatd');
29
30 $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
31
32 die "please run as root\n" if $> != 0;
33
34 my $nodename = PVE::INotify::nodename();
35
36 my $opt_debug;
37
38 if (!GetOptions ('debug' => \$opt_debug)) {
39 die "USAGE: $0 [--debug]\n";
40 }
41
42 my $opt_pidfile = "/var/run/pvestatd.pid";
43
44 sub lockpidfile {
45 my $pidfile = shift;
46 my $lkfn = "$pidfile.lock";
47
48 if (!open (FLCK, ">>$lkfn")) {
49 my $msg = "can't aquire lock on file '$lkfn' - $!";
50 syslog ('err', $msg);
51 die "ERROR: $msg\n";
52 }
53
54 if (!flock (FLCK, LOCK_EX|LOCK_NB)) {
55 close (FLCK);
56 my $msg = "can't aquire lock '$lkfn' - $!";
57 syslog ('err', $msg);
58 die "ERROR: $msg\n";
59 }
60 }
61
62 sub writepidfile {
63 my $pidfile = shift;
64
65 if (!open (PIDFH, ">$pidfile")) {
66 my $msg = "can't open pid file '$pidfile' - $!";
67 syslog ('err', $msg);
68 die "ERROR: $msg\n";
69 }
70 print PIDFH "$$\n";
71 close (PIDFH);
72 }
73
74 # try to get the lock
75 lockpidfile($opt_pidfile);
76
77 # run in background
78 my $spid;
79
80 my $restart = $ENV{RESTART_PVESTATD};
81
82 if (!$opt_debug) {
83 open STDIN, '</dev/null' || die "can't read /dev/null";
84 open STDOUT, '>/dev/null' || die "can't write /dev/null";
85 }
86
87 if (!$restart && !$opt_debug) {
88 $spid = fork();
89 if (!defined ($spid)) {
90 my $msg = "can't put server into background - fork failed";
91 syslog('err', $msg);
92 die "ERROR: $msg\n";
93 } elsif ($spid) { #parent
94 exit (0);
95 }
96 }
97
98 writepidfile($opt_pidfile);
99
100 open STDERR, '>&STDOUT' || die "can't close STDERR\n";
101
102 sub cleanup {
103 unlink "$opt_pidfile.lock";
104 unlink "$opt_pidfile";
105 }
106
107 $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub {
108 syslog('info' , "server closing");
109
110 $SIG{INT} = 'DEFAULT';
111
112 # wait for children
113 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
114
115 cleanup();
116
117 exit (0);
118 };
119
120 PVE::INotify::inotify_init();
121
122 my $reload_config;
123
124 if ($restart) {
125 syslog('info' , "restarting server");
126 } else {
127 syslog('info' , "starting server");
128 }
129
130 $SIG{HUP} = sub {
131 $reload_config = 1;
132 };
133
134 sub update_node_status {
135
136 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
137
138 my $stat = PVE::ProcFSTools::read_proc_stat();
139
140 my $netdev = PVE::ProcFSTools::read_proc_net_dev();
141
142 my ($uptime) = PVE::ProcFSTools::read_proc_uptime();
143
144 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
145
146 my $maxcpu = $cpuinfo->{cpus};
147
148 my $subinfo = PVE::INotify::read_file('subscription');
149 my $sublevel = $subinfo->{level} || '';
150
151 # traffic from/to physical interface cards
152 my $netin = 0;
153 my $netout = 0;
154 foreach my $dev (keys %$netdev) {
155 next if $dev !~ m/^eth\d+$/;
156 $netin += $netdev->{$dev}->{receive};
157 $netout += $netdev->{$dev}->{transmit};
158 }
159
160 my $meminfo = PVE::ProcFSTools::read_meminfo();
161
162 my $dinfo = df('/', 1); # output is bytes
163
164 my $ctime = time();
165
166 # everything not free is considered to be used
167 my $dused = $dinfo->{blocks} - $dinfo->{bfree};
168
169 my $data = "$uptime:$sublevel:$ctime:$avg1:$maxcpu:$stat->{cpu}:$stat->{wait}:" .
170 "$meminfo->{memtotal}:$meminfo->{memused}:" .
171 "$meminfo->{swaptotal}:$meminfo->{swapused}:" .
172 "$dinfo->{blocks}:$dused:$netin:$netout";
173
174 PVE::Cluster::broadcast_rrd("pve2-node/$nodename", $data);
175 }
176
177 sub update_qemu_status {
178
179 my $ctime = time();
180
181 my $vmstatus = PVE::QemuServer::vmstatus(undef, 1);
182
183 foreach my $vmid (keys %$vmstatus) {
184 my $d = $vmstatus->{$vmid};
185 my $data;
186 if ($d->{pid}) { # running
187 $data = "$d->{uptime}:$d->{name}:$ctime:$d->{cpus}:$d->{cpu}:" .
188 "$d->{maxmem}:$d->{mem}:" .
189 "$d->{maxdisk}:$d->{disk}:" .
190 "$d->{netin}:$d->{netout}:" .
191 "$d->{diskread}:$d->{diskwrite}";
192 } else {
193 $data = "0:$d->{name}:$ctime:$d->{cpus}::" .
194 "$d->{maxmem}::" .
195 "$d->{maxdisk}:$d->{disk}:" .
196 ":::";
197 }
198 PVE::Cluster::broadcast_rrd("pve2-vm/$vmid", $data);
199 }
200 }
201
202 sub find_vzctl_console_pids {
203
204 my $res = {};
205
206 dir_glob_foreach('/proc', '\d+', sub {
207 my ($pid) = @_;
208
209 my $cmdline = file_read_firstline("/proc/$pid/cmdline");
210 return if !$cmdline;
211
212 my @args = split(/\0/, $cmdline);
213
214 # serach for vzctl console <vmid>
215 return if scalar(@args) != 3;
216 return if $args[1] ne 'console';
217 return if $args[2] !~ m/^\d+$/;
218 return if $args[0] !~ m|^(/usr/sbin/)?vzctl$|;
219
220 my $vmid = $args[2];
221
222 push @{$res->{$vmid}}, $pid;
223 });
224
225 return $res;
226 }
227 sub remove_stale_openvz_consoles {
228
229 my $vmstatus = PVE::OpenVZ::vmstatus();
230 my $pidhash = find_vzctl_console_pids();
231
232 foreach my $vmid (keys %$pidhash) {
233 next if defined($vmstatus->{$vmid});
234 syslog('info', "remove stale vzctl console for CT $vmid");
235 foreach my $pid (@{$pidhash->{$vmid}}) {
236 kill(9, $pid);
237 }
238 }
239 }
240
241 sub update_openvz_status {
242
243 my $ctime = time();
244
245 my $vmstatus = PVE::OpenVZ::vmstatus();
246
247 foreach my $vmid (keys %$vmstatus) {
248 my $d = $vmstatus->{$vmid};
249 my $data;
250 if ($d->{status} eq 'running') { # running
251 $data = "$d->{uptime}:$d->{name}:$ctime:$d->{cpus}:$d->{cpu}:" .
252 "$d->{maxmem}:$d->{mem}:" .
253 "$d->{maxdisk}:$d->{disk}:" .
254 "$d->{netin}:$d->{netout}:" .
255 "$d->{diskread}:$d->{diskwrite}";
256 } else {
257 $data = "0:$d->{name}:$ctime:$d->{cpus}::" .
258 "$d->{maxmem}::" .
259 "$d->{maxdisk}:$d->{disk}:" .
260 ":::";
261 }
262 PVE::Cluster::broadcast_rrd("pve2-vm/$vmid", $data);
263 }
264 }
265
266 sub update_storage_status {
267
268 my $cfg = cfs_read_file("storage.cfg");
269
270 my $ctime = time();
271
272 my $info = PVE::Storage::storage_info($cfg);
273
274 foreach my $storeid (keys %$info) {
275 my $d = $info->{$storeid};
276 next if !$d->{active};
277
278 # everything not free is considered to be used
279 my $realused = $d->{total} - $d->{avail};
280
281 my $data = "$ctime:$d->{total}:$realused";
282
283 my $key = "pve2-storage/${nodename}/$storeid";
284 PVE::Cluster::broadcast_rrd($key, $data);
285 }
286 }
287
288 sub update_status {
289
290 # update worker list. This is not really required and
291 # we just call this to make sure that we have a correct
292 # list in case of an unexpected crash.
293 eval {
294 my $tlist = PVE::RPCEnvironment::active_workers();
295 PVE::Cluster::broadcast_tasklist($tlist);
296 };
297 my $err = $@;
298 syslog('err', $err) if $err;
299
300 eval {
301 update_node_status();
302 };
303 $err = $@;
304 syslog('err', "node status update error: $err") if $err;
305
306 eval {
307 update_qemu_status();
308 };
309 $err = $@;
310 syslog('err', "qemu status update error: $err") if $err;
311
312 eval {
313 update_openvz_status();
314 };
315 $err = $@;
316 syslog('err', "openvz status update error: $err") if $err;
317
318 eval {
319 update_storage_status();
320 };
321 $err = $@;
322 syslog('err', "storage status update error: $err") if $err;
323
324 eval {
325 remove_stale_openvz_consoles();
326 };
327 $err = $@;
328 syslog('err', "openvz console cleanup error: $err") if $err;
329 }
330
331 my $next_update = 0;
332
333 # do not update directly after startup, because install scripts
334 # have a problem with that
335 my $cycle = 0;
336 my $updatetime = 10;
337
338 my $commandline = [$0, @ARGV];
339
340 $0 = "pvestatd";
341
342 sub restart_server {
343 my $waittime = shift;
344
345 syslog('info', "server shutdown (restart)");
346
347 $ENV{RESTART_PVESTATD} = 1;
348
349 sleep($waittime) if $waittime; # avoid high server load due to restarts
350
351 exec (@$commandline);
352 exit (-1); # never reached?
353 }
354
355 my $initial_memory_usage;
356
357 for (;;) { # forever
358
359 eval {
360 $next_update = time() + $updatetime;
361
362 if ($cycle) {
363 my ($ccsec, $cusec) = gettimeofday ();
364 eval {
365 $reload_config = 0;
366 # syslog('info', "start status update");
367 PVE::Cluster::cfs_update();
368 update_status();
369 };
370 my $err = $@;
371
372 if ($err) {
373 syslog('err', "status update error: $err");
374 }
375
376 my ($ccsec_end, $cusec_end) = gettimeofday ();
377 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
378
379 syslog('info', sprintf("status update time (%.3f seconds)", $cptime))
380 if ($cptime > 5);
381 }
382
383 $cycle++;
384
385 my $mem = PVE::ProcFSTools::read_memory_usage();
386
387 if (!defined($initial_memory_usage)) {
388 $initial_memory_usage = $mem->{resident};
389 } else {
390 my $diff = $mem->{resident} - $initial_memory_usage;
391 if ($diff > 5*1024*1024) {
392 syslog ('info', "restarting server after $cycle cycles to " .
393 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
394 restart_server ();
395 }
396 }
397
398 my $wcount = 0;
399 while ((time() < $next_update) &&
400 ($wcount < $updatetime) && # protect against time wrap
401 !$reload_config) { $wcount++; sleep (1); };
402 };
403
404 my $err = $@;
405
406 if ($err) {
407 syslog ('err', "ERROR: $err");
408 restart_server(5);
409 exit (0);
410 }
411 }
412
413 exit (0);
414
415 __END__
416
417 =head1 NAME
418
419 pvestatd - PVE Status Daemon
420
421 =head1 SYNOPSIS
422
423 pvestatd
424
425 =head1 DESCRIPTION
426
427 Documentation is available at www.proxmox.com
428
429
430
431
432