]> git.proxmox.com Git - pve-manager.git/blame - PVE/Service/pvestatd.pm
followup code cleanup for: add get_local_services for ceph
[pve-manager.git] / PVE / Service / pvestatd.pm
CommitLineData
efd04666
DM
1package PVE::Service::pvestatd;
2
3use strict;
4use warnings;
5
6use PVE::SafeSyslog;
7use PVE::Daemon;
8
9use Time::HiRes qw (gettimeofday);
10use PVE::Tools qw(dir_glob_foreach file_read_firstline);
11use PVE::ProcFSTools;
41db757b 12use PVE::CpuSet;
efd04666
DM
13use Filesys::Df;
14use PVE::INotify;
0fcced16 15use PVE::Network;
efd04666
DM
16use PVE::Cluster qw(cfs_read_file);
17use PVE::Storage;
18use PVE::QemuServer;
19use PVE::LXC;
41db757b 20use PVE::LXC::Config;
efd04666
DM
21use PVE::RPCEnvironment;
22use PVE::API2::Subscription;
23use PVE::AutoBalloon;
5ea29d13 24use PVE::AccessControl;
efd04666
DM
25
26use PVE::Status::Plugin;
27use PVE::Status::Graphite;
58541b94 28use PVE::Status::InfluxDB;
efd04666
DM
29
30PVE::Status::Graphite->register();
58541b94 31PVE::Status::InfluxDB->register();
efd04666
DM
32PVE::Status::Plugin->init();
33
34use base qw(PVE::Daemon);
35
36my $opt_debug;
37my $restart_request;
38
39my $nodename = PVE::INotify::nodename();
40
41my $cmdline = [$0, @ARGV];
42
43my %daemon_options = (restart_on_error => 5, stop_wait_time => 5);
44my $daemon = __PACKAGE__->new('pvestatd', $cmdline, %daemon_options);
45
46sub init {
47 my ($self) = @_;
48
49 $opt_debug = $self->{debug};
50
51 PVE::Cluster::cfs_update();
52}
53
54sub shutdown {
55 my ($self) = @_;
56
57 syslog('info' , "server closing");
58
59 # wait for children
60 1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
61
62 $self->exit_daemon(0);
63}
64
65sub hup {
66 my ($self) = @_;
67
68 $restart_request = 1;
69}
70
50786956
DM
71my $generate_rrd_string = sub {
72 my ($data) = @_;
73
74 return join(':', map { $_ // 'U' } @$data);
75};
76
efd04666
DM
77sub update_node_status {
78 my ($status_cfg) = @_;
79
80 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
81
82 my $stat = PVE::ProcFSTools::read_proc_stat();
83
84 my $netdev = PVE::ProcFSTools::read_proc_net_dev();
85
86 my ($uptime) = PVE::ProcFSTools::read_proc_uptime();
87
88 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
89
90 my $maxcpu = $cpuinfo->{cpus};
91
92 my $subinfo = PVE::INotify::read_file('subscription');
93 my $sublevel = $subinfo->{level} || '';
94
95 # traffic from/to physical interface cards
96 my $netin = 0;
97 my $netout = 0;
98 foreach my $dev (keys %$netdev) {
0fcced16 99 next if $dev !~ m/^$PVE::Network::PHYSICAL_NIC_RE$/;
efd04666
DM
100 $netin += $netdev->{$dev}->{receive};
101 $netout += $netdev->{$dev}->{transmit};
102 }
103
104 my $meminfo = PVE::ProcFSTools::read_meminfo();
105
106 my $dinfo = df('/', 1); # output is bytes
107
108 my $ctime = time();
109
110 # everything not free is considered to be used
111 my $dused = $dinfo->{blocks} - $dinfo->{bfree};
112
50786956
DM
113 my $data = $generate_rrd_string->(
114 [$uptime, $sublevel, $ctime, $avg1, $maxcpu, $stat->{cpu}, $stat->{wait},
115 $meminfo->{memtotal}, $meminfo->{memused},
116 $meminfo->{swaptotal}, $meminfo->{swapused},
117 $dinfo->{blocks}, $dused, $netin, $netout]);
efd04666
DM
118
119 PVE::Cluster::broadcast_rrd("pve2-node/$nodename", $data);
120
121 foreach my $id (keys %{$status_cfg->{ids}}) {
122 my $plugin_config = $status_cfg->{ids}->{$id};
123 next if $plugin_config->{disable};
124 my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type});
125
126 my $d = {};
127 $d->{uptime} = $uptime;
128 $d->{cpustat} = $stat;
129 $d->{cpustat}->{avg1} = $avg1;
130 $d->{cpustat}->{avg5} = $avg5;
131 $d->{cpustat}->{avg15} = $avg15;
132 $d->{cpustat}->{cpus} = $maxcpu;
133 $d->{memory} = $meminfo;
134 $d->{blockstat} = $dinfo;
135 $d->{nics} = $netdev;
136
137 $plugin->update_node_status($plugin_config, $nodename, $d, $ctime);
138 }
139}
140
141sub auto_balloning {
142 my ($vmstatus) = @_;
143
144 my $log = sub {
145 return if !$opt_debug;
146 print @_;
147 };
148
149 my $hostmeminfo = PVE::ProcFSTools::read_meminfo();
150
151 # to debug, run 'pvestatd -d' and set memtotal here
152 #$hostmeminfo->{memtotal} = int(2*1024*1024*1024/0.8); # you can set this to test
153
154 my $hostfreemem = $hostmeminfo->{memtotal} - $hostmeminfo->{memused};
155
156 # we try to use about 80% host memory
157 # goal: we want to change memory usage by this amount (positive or negative)
158 my $goal = int($hostmeminfo->{memtotal}*0.8 - $hostmeminfo->{memused});
159
160 my $maxchange = 100*1024*1024;
161 my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, $maxchange);
162
163 &$log("host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
164
165 foreach my $vmid (keys %$vmstatus) {
166 next if !$res->{$vmid};
167 my $d = $vmstatus->{$vmid};
168 my $diff = int($res->{$vmid} - $d->{balloon});
169 my $absdiff = $diff < 0 ? -$diff : $diff;
170 if ($absdiff > 0) {
171 &$log("BALLOON $vmid to $res->{$vmid} ($diff)\n");
172 eval {
173 PVE::QemuServer::vm_mon_cmd($vmid, "balloon",
174 value => int($res->{$vmid}));
175 };
176 warn $@ if $@;
177 }
178 }
179}
180
181sub update_qemu_status {
182 my ($status_cfg) = @_;
183
184 my $ctime = time();
185
186 my $vmstatus = PVE::QemuServer::vmstatus(undef, 1);
187
188 eval { auto_balloning($vmstatus); };
189 syslog('err', "auto ballooning error: $@") if $@;
190
191 foreach my $vmid (keys %$vmstatus) {
192 my $d = $vmstatus->{$vmid};
193 my $data;
194 my $status = $d->{qmpstatus} || $d->{status} || 'stopped';
195 my $template = $d->{template} ? $d->{template} : "0";
196 if ($d->{pid}) { # running
50786956
DM
197 $data = $generate_rrd_string->(
198 [$d->{uptime}, $d->{name}, $status, $template, $ctime, $d->{cpus}, $d->{cpu},
199 $d->{maxmem}, $d->{mem}, $d->{maxdisk}, $d->{disk},
200 $d->{netin}, $d->{netout}, $d->{diskread}, $d->{diskwrite}]);
efd04666 201 } else {
50786956
DM
202 $data = $generate_rrd_string->(
203 [0, $d->{name}, $status, $template, $ctime, $d->{cpus}, undef,
204 $d->{maxmem}, undef, $d->{maxdisk}, $d->{disk}, undef, undef, undef, undef]);
efd04666
DM
205 }
206 PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
207
208 foreach my $id (keys %{$status_cfg->{ids}}) {
209 my $plugin_config = $status_cfg->{ids}->{$id};
210 next if $plugin_config->{disable};
211 my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type});
09f19204 212 $plugin->update_qemu_status($plugin_config, $vmid, $d, $ctime, $nodename);
efd04666
DM
213 }
214 }
215}
216
217sub remove_stale_lxc_consoles {
218
219 my $vmstatus = PVE::LXC::vmstatus();
220 my $pidhash = PVE::LXC::find_lxc_console_pids();
221
222 foreach my $vmid (keys %$pidhash) {
223 next if defined($vmstatus->{$vmid});
224 syslog('info', "remove stale lxc-console for CT $vmid");
225 foreach my $pid (@{$pidhash->{$vmid}}) {
226 kill(9, $pid);
227 }
228 }
229}
230
b3f1adb2
DM
231my $rebalance_error_count = {};
232
41db757b 233sub rebalance_lxc_containers {
41db757b
DM
234
235 return if !-d '/sys/fs/cgroup/cpuset/lxc'; # nothing to do...
236
237 my $all_cpus = PVE::CpuSet->new_from_cgroup('lxc', 'effective_cpus');
238 my @allowed_cpus = $all_cpus->members();
239 my $cpucount = scalar(@allowed_cpus);
127470f4 240 my $max_cpuid = $allowed_cpus[-1];
41db757b 241
127470f4 242 my @cpu_ctcount = (0) x ($max_cpuid+1);
41db757b
DM
243 my @balanced_cts;
244
0b959507
DM
245 my $modify_cpuset = sub {
246 my ($vmid, $cpuset, $newset) = @_;
247
b3f1adb2
DM
248 if (!$rebalance_error_count->{$vmid}) {
249 syslog('info', "modified cpu set for lxc/$vmid: " .
250 $newset->short_string());
251 }
252
0b959507 253 eval {
cbce367d
DM
254
255 if (-d "/sys/fs/cgroup/cpuset/lxc/$vmid/ns") {
256 # allow all, so that we can set new cpuset in /ns
257 $all_cpus->write_to_cgroup("lxc/$vmid");
258 eval {
259 $newset->write_to_cgroup("lxc/$vmid/ns");
260 };
261 if (my $err = $@) {
262 warn $err if !$rebalance_error_count->{$vmid}++;
263 # restore original
264 $cpuset->write_to_cgroup("lxc/$vmid");
265 } else {
266 # also apply to container root cgroup
267 $newset->write_to_cgroup("lxc/$vmid");
268 $rebalance_error_count->{$vmid} = 0;
269 }
0b959507 270 } else {
cbce367d 271 # old style container
0b959507 272 $newset->write_to_cgroup("lxc/$vmid");
b3f1adb2 273 $rebalance_error_count->{$vmid} = 0;
0b959507
DM
274 }
275 };
b3f1adb2
DM
276 if (my $err = $@) {
277 warn $err if !$rebalance_error_count->{$vmid}++;
278 }
0b959507
DM
279 };
280
e0dc09ad
DM
281 my $ctlist = PVE::LXC::config_list();
282
283 foreach my $vmid (sort keys %$ctlist) {
284 next if ! -d "/sys/fs/cgroup/cpuset/lxc/$vmid";
41db757b
DM
285
286 my ($conf, $cpuset);
287 eval {
288
289 $conf = PVE::LXC::Config->load_config($vmid);
290
291 $cpuset = PVE::CpuSet->new_from_cgroup("lxc/$vmid");
292 };
293 if (my $err = $@) {
294 warn $err;
295 next;
296 }
297
298 my @cpuset_members = $cpuset->members();
299
8b750abc 300 if (!PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup.cpuset.cpus')) {
2499255b 301
8b750abc
DM
302 my $cores = $conf->{cores} || $cpucount;
303 $cores = $cpucount if $cores > $cpucount;
41db757b 304
2499255b
DM
305 # see if the number of cores was hot-reduced or
306 # hasn't been enacted at all yet
307 my $newset = PVE::CpuSet->new();
308 if ($cores < scalar(@cpuset_members)) {
309 for (my $i = 0; $i < $cores; $i++) {
310 $newset->insert($cpuset_members[$i]);
311 }
312 } elsif ($cores > scalar(@cpuset_members)) {
313 my $count = $newset->insert(@cpuset_members);
314 foreach my $cpu (@allowed_cpus) {
315 $count += $newset->insert($cpu);
316 last if $count >= $cores;
317 }
318 } else {
319 $newset->insert(@cpuset_members);
320 }
07f9595f 321
2499255b
DM
322 # Apply hot-plugged changes if any:
323 if (!$newset->is_equal($cpuset)) {
324 @cpuset_members = $newset->members();
0b959507 325 $modify_cpuset->($vmid, $cpuset, $newset);
2499255b 326 }
07f9595f 327
2499255b
DM
328 # Note: no need to rebalance if we already use all cores
329 push @balanced_cts, [$vmid, $cores, $newset]
8b750abc 330 if defined($conf->{cores}) && ($cores != $cpucount);
2499255b 331 }
07f9595f 332
2499255b 333 foreach my $cpu (@cpuset_members) {
ccfff920 334 $cpu_ctcount[$cpu]++ if $cpu <= $max_cpuid;
07f9595f 335 }
2499255b 336 }
07f9595f 337
2499255b
DM
338 my $find_best_cpu = sub {
339 my ($cpulist, $cpu) = @_;
07f9595f 340
2499255b
DM
341 my $cur_cost = $cpu_ctcount[$cpu];
342 my $cur_cpu = $cpu;
41db757b 343
2499255b
DM
344 foreach my $candidate (@$cpulist) {
345 my $cost = $cpu_ctcount[$candidate];
346 if ($cost < ($cur_cost -1)) {
347 $cur_cost = $cost;
348 $cur_cpu = $candidate;
349 }
07f9595f
DM
350 }
351
2499255b
DM
352 return $cur_cpu;
353 };
354
355 foreach my $bct (@balanced_cts) {
356 my ($vmid, $cores, $cpuset) = @$bct;
41db757b
DM
357
358 my $newset = PVE::CpuSet->new();
359
2499255b
DM
360 my $rest = [];
361 foreach my $cpu (@allowed_cpus) {
362 next if $cpuset->has($cpu);
363 push @$rest, $cpu;
364 }
365
366 my @members = $cpuset->members();
367 foreach my $cpu (@members) {
368 my $best = &$find_best_cpu($rest, $cpu);
369 if ($best != $cpu) {
370 $cpu_ctcount[$best]++;
371 $cpu_ctcount[$cpu]--;
372 }
373 $newset->insert($best);
41db757b
DM
374 }
375
376 if (!$newset->is_equal($cpuset)) {
0b959507 377 $modify_cpuset->($vmid, $cpuset, $newset);
41db757b
DM
378 }
379 }
380}
381
efd04666
DM
382sub update_lxc_status {
383 my ($status_cfg) = @_;
384
385 my $ctime = time();
386
387 my $vmstatus = PVE::LXC::vmstatus();
388
389 foreach my $vmid (keys %$vmstatus) {
390 my $d = $vmstatus->{$vmid};
391 my $template = $d->{template} ? $d->{template} : "0";
392 my $data;
393 if ($d->{status} eq 'running') { # running
50786956
DM
394 $data = $generate_rrd_string->(
395 [$d->{uptime}, $d->{name}, $d->{status}, $template,
396 $ctime, $d->{cpus}, $d->{cpu},
397 $d->{maxmem}, $d->{mem},
398 $d->{maxdisk}, $d->{disk},
399 $d->{netin}, $d->{netout},
400 $d->{diskread}, $d->{diskwrite}]);
efd04666 401 } else {
50786956
DM
402 $data = $generate_rrd_string->(
403 [0, $d->{name}, $d->{status}, $template, $ctime, $d->{cpus}, undef,
404 $d->{maxmem}, undef, $d->{maxdisk}, $d->{disk}, undef, undef, undef, undef]);
efd04666
DM
405 }
406 PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
407
408 foreach my $id (keys %{$status_cfg->{ids}}) {
409 my $plugin_config = $status_cfg->{ids}->{$id};
410 next if $plugin_config->{disable};
411 my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type});
09f19204 412 $plugin->update_lxc_status($plugin_config, $vmid, $d, $ctime, $nodename);
efd04666
DM
413 }
414 }
415}
416
417sub update_storage_status {
418 my ($status_cfg) = @_;
419
bbcfdc08 420 my $cfg = PVE::Storage::config();
efd04666
DM
421
422 my $ctime = time();
423
424 my $info = PVE::Storage::storage_info($cfg);
425
426 foreach my $storeid (keys %$info) {
427 my $d = $info->{$storeid};
428 next if !$d->{active};
429
50786956 430 my $data = $generate_rrd_string->([$ctime, $d->{total}, $d->{used}]);
efd04666
DM
431
432 my $key = "pve2-storage/${nodename}/$storeid";
433 PVE::Cluster::broadcast_rrd($key, $data);
434
435 foreach my $id (keys %{$status_cfg->{ids}}) {
436 my $plugin_config = $status_cfg->{ids}->{$id};
437 next if $plugin_config->{disable};
438 my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type});
439 $plugin->update_storage_status($plugin_config, $nodename, $storeid, $d, $ctime);
440 }
441 }
442}
443
5ea29d13
FG
444sub rotate_authkeys {
445 PVE::AccessControl::rotate_authkey() if !PVE::AccessControl::check_authkey(1);
446}
447
efd04666
DM
448sub update_status {
449
450 # update worker list. This is not really required and
451 # we just call this to make sure that we have a correct
452 # list in case of an unexpected crash.
8a9bf777
DM
453 my $rpcenv = PVE::RPCEnvironment::get();
454
efd04666 455 eval {
8a9bf777 456 my $tlist = $rpcenv->active_workers();
efd04666
DM
457 PVE::Cluster::broadcast_tasklist($tlist);
458 };
459 my $err = $@;
460 syslog('err', $err) if $err;
461
462 my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
463
464 eval {
465 update_node_status($status_cfg);
466 };
467 $err = $@;
468 syslog('err', "node status update error: $err") if $err;
469
470 eval {
471 update_qemu_status($status_cfg);
472 };
473 $err = $@;
474 syslog('err', "qemu status update error: $err") if $err;
475
476 eval {
477 update_lxc_status($status_cfg);
478 };
479 $err = $@;
480 syslog('err', "lxc status update error: $err") if $err;
481
e0dc09ad
DM
482 eval {
483 rebalance_lxc_containers();
484 };
485 $err = $@;
486 syslog('err', "lxc cpuset rebalance error: $err") if $err;
487
efd04666
DM
488 eval {
489 update_storage_status($status_cfg);
490 };
491 $err = $@;
492 syslog('err', "storage status update error: $err") if $err;
493
494 eval {
495 remove_stale_lxc_consoles();
496 };
497 $err = $@;
498 syslog('err', "lxc console cleanup error: $err") if $err;
5ea29d13
FG
499
500 eval {
501 rotate_authkeys();
502 };
503 $err = $@;
504 syslog('err', "authkey rotation error: $err") if $err;
505
efd04666
DM
506}
507
508my $next_update = 0;
509
510# do not update directly after startup, because install scripts
511# have a problem with that
512my $cycle = 0;
513my $updatetime = 10;
514
515my $initial_memory_usage;
516
517sub run {
518 my ($self) = @_;
519
520 for (;;) { # forever
521
522 $next_update = time() + $updatetime;
523
524 if ($cycle) {
525 my ($ccsec, $cusec) = gettimeofday ();
526 eval {
527 # syslog('info', "start status update");
528 PVE::Cluster::cfs_update();
529 update_status();
530 };
531 my $err = $@;
532
533 if ($err) {
534 syslog('err', "status update error: $err");
535 }
536
537 my ($ccsec_end, $cusec_end) = gettimeofday ();
538 my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
539
540 syslog('info', sprintf("status update time (%.3f seconds)", $cptime))
541 if ($cptime > 5);
542 }
543
544 $cycle++;
545
546 my $mem = PVE::ProcFSTools::read_memory_usage();
547
548 if (!defined($initial_memory_usage) || ($cycle < 10)) {
549 $initial_memory_usage = $mem->{resident};
550 } else {
551 my $diff = $mem->{resident} - $initial_memory_usage;
552 if ($diff > 5*1024*1024) {
553 syslog ('info', "restarting server after $cycle cycles to " .
554 "reduce memory usage (free $mem->{resident} ($diff) bytes)");
555 $self->restart_daemon();
556 }
557 }
558
559 my $wcount = 0;
560 while ((time() < $next_update) &&
561 ($wcount < $updatetime) && # protect against time wrap
562 !$restart_request) { $wcount++; sleep (1); };
563
564 $self->restart_daemon() if $restart_request;
565 }
566}
567
568$daemon->register_start_command();
569$daemon->register_restart_command(1);
570$daemon->register_stop_command();
571$daemon->register_status_command();
572
573our $cmddef = {
574 start => [ __PACKAGE__, 'start', []],
575 restart => [ __PACKAGE__, 'restart', []],
576 stop => [ __PACKAGE__, 'stop', []],
577 status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
578};
579
efd04666
DM
5801;
581
efd04666
DM
582
583
584
585