/etc/xymon/misc.d/zombies is in hobbit-plugins 20170219.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | #!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use File::Basename qw(dirname);
use List::MoreUtils qw(all);
# minimum age (in seconds) for a process to be reported as a zombie
# note: this is the age since the start of the process. We do not
# know how long the process has been a zombie
my $MIN_AGE = 5 * 60;
# Function to parse config file; splits at first blanks.
sub file_to_hash_of_regexp_keys_and_values($) {
my %regexps = ();
my $filename = shift;
open(my $fh, '<', $filename) or return %regexps;
while(<$fh>) {
unless (/^#|^\s*$/) {
chomp;
my ($key, $value) = split(/\s+/, $_, 2);
$regexps{$key} = $value;
}
}
return %regexps;
}
# status of a single process as a hash reference
# call with argument /proc/:pid/status
sub process_status_href {
my ($status_file) = @_;
my (%status_hash) = ();
# open will fail if the process died in the mean time and we return nothing
open(my $fh, '<', $status_file) or return;
while (my $line = <$fh>) {
chomp $line;
my ($key, $value) = split(qr(:\s+), $line, 2);
$status_hash{$key} = $value;
}
close($fh);
# if the process died some the status info may be imcomplete
# we require at least the keys shown here otherwise we return nothing
return unless all { defined($status_hash{$_}) } qw(Pid Name State PPid);
return \%status_hash;
}
# hash reference of all processes with pid as key and status href as value
sub all_processes_href {
my %proc_hash = ();
for my $status_file (glob('/proc/*/status')) {
my $stat_hash = process_status_href($status_file);
next unless $stat_hash;
$proc_hash{$stat_hash->{Pid}} = $stat_hash;
}
return \%proc_hash;
}
my $ext_apt_config = dirname($0).'/..';
my $ignore_file = $ext_apt_config."/zombies_ignore";
my %ignore = file_to_hash_of_regexp_keys_and_values($ignore_file);
my $zombies = 0;
my $ignored = 0;
my $all_procs = all_processes_href();
my %zombie_parents = ();
sleep 1; # allow parents to reap their children before we loop through the processes
for my $pid (keys %$all_procs) {
my $status = $all_procs->{$pid};
next unless $status->{State} =~ /Z/;
# ignore zombies that are too young
my $age_in_days = -M "/proc/$pid";
# may have vanished if the zombie process has been waited for in the mean time
next unless $age_in_days;
# choose minimum age of $MIN_AGE seconds (defined above)
next if ($age_in_days * 24 * 60 * 60) < $MIN_AGE;
my $ppid = $status->{PPid};
$zombie_parents{$ppid} ||= [$ppid];
push(@{$zombie_parents{$ppid}}, $pid);
}
for my $ppid (keys %zombie_parents) {
my $status = $all_procs->{$ppid};
my $user = getpwuid((split(/\s+/, $status->{Uid}))[0]);
my $name = $status->{Name};
my $proclist = join(',', @{$zombie_parents{$ppid}});
my $zombie = 1;
say "parent of zombies: $name by user $user";
system "ps fup $proclist";
foreach my $regexp (keys %ignore) {
if ($regexp =~ /:\^/) {
my ($user_re, $process_re) = split(/:\^/, $regexp, 2);
if ($user =~ /^$user_re$/ and $name =~ /^$process_re$/) {
say "$name zombies ignored under user $user: $ignore{$regexp}";
$zombie = 0;
}
}
else {
if ($name =~ /^$regexp$/) {
say "$name zombies ignored: $ignore{$regexp}";
$zombie = 0;
}
}
}
say ''; # separate zombie groups
$zombies += $zombie;
$ignored += (1 - $zombie);
}
say(($zombies + $ignored) . ' process(es) with zombie children, ' . $ignored . ' ignored');
exit ($zombies ? 1 : 0);
|