/
lib
/
sonarpush
/
SonarPush
/
Providers
/
File Upload :
llllll
Current File: //lib/sonarpush/SonarPush/Providers/State.pm
package SonarPush::Providers::State; use base qw(SonarPush::Providers); use strict; use SonarPush::Util; use POSIX; use JSON::Tiny qw(from_json to_json); use constant CPANEL_EMAIL_FILE => '/usr/local/lp/var/sonarpush/emailaccountexport'; =head1 NAME SonarPush::Providers::State - collects state information =head1 DESCRIPTION class of tasks for collecing various information about the current state of services or process's on a machine. =cut sub configurations { my $self = shift; my $provider = 'configurations'; my $xml = { name => 'configurations', attributes => { providedby => $provider, datatype => 'None' }, }; my $content = do { local $/ = undef; open my $file, '<', '/etc/resolv.conf'; <$file>; }; push(@{ $xml->{value} }, { name => 'resolv-conf', attributes => { providedby => $provider, datatype => "Text", }, value => SonarPush::Util::encode_entities($content), }); return $xml; } sub cpanelUserData { my ($self) = @_; my $provider = 'cpanelUserData'; my %attrs = ( textConfig => { providedby => $provider, datatype => 'Text', }, numConfig => { providedby => $provider, datatype => 'Numeric', incrementing => 'N', exponent => 0, unit => '', }, noConfig => { providedby => $provider, datatype => 'None', }, ); my @subNodes = (); my $struct = { name => $provider, attributes => $attrs{noConfig}, value => \@subNodes, }; my $userData = $self->extractCpanelUserDetails(); return unless $userData; push(@subNodes, { name => 'count', attributes => $attrs{numConfig}, value => scalar keys %$userData, }); push(@subNodes, { name => 'list', attributes => $attrs{textConfig}, value => join(',', sort keys %$userData), }); return $self->cpanelDataWrapper($provider, $struct); } sub cpanelEmailData { my ($self) = @_; my $provider = 'cpanelMailboxData'; my %attrs = ( noConfig => { providedby => $provider, dataType => 'None', }, numConfig => { providedby => $provider, dataType => 'Numeric', incrementing => 'N', exponent => 0, unit => '', }, textConfig => { providedby => $provider, dataType => 'Text', }, sizeConfig => { providedby => $provider, dataType => 'Numeric', incrementing => 'N', exponent => 0, unit => 'Bytes', }, ); unless (-e CPANEL_EMAIL_FILE) { return; } open(my $file, '<', CPANEL_EMAIL_FILE); my $json = <$file>; close($file); # prevent "Missing or empty input" error from from_json when CPANEL_EMAIL_FILE # exists but is empty. return unless ($json); my $data = from_json($json); my $serverMailboxCount; my $serverMailboxSize; foreach my $user (keys %$data) { foreach my $domainData (@{ $data->{$user} }) { $serverMailboxCount += $domainData->{'total_mailboxes'}; $serverMailboxSize += $domainData->{'total_mailbox_size'}; } } my @nodes; push(@nodes, { name => 'count', attributes => $attrs{numConfig}, value => $serverMailboxCount, }); push(@nodes, { name => 'size', attributes => $attrs{sizeConfig}, value => $serverMailboxSize, }); push(@nodes, { name => 'mailbox_json_data', attributes => $attrs{textConfig}, value => $json, }); my $struct = { name => $provider, attributes => $attrs{noConfig}, value => \@nodes, }; return $self->cpanelDataWrapper($provider, $struct); } sub cpanelDomainData { my ($self) = @_; my $provider = 'cpanelDomainData'; my %attrs = ( textConfig => { providedby => $provider, datatype => 'Text', }, numConfig => { providedby => $provider, datatype => 'Numeric', incrementing => 'N', exponent => 0, unit => '', }, noConfig => { providedby => $provider, datatype => 'None', }, ); my @subNodes = (); my $struct = { name => $provider, attributes => $attrs{noConfig}, value => \@subNodes, }; my $userData = $self->extractCpanelUserDetails(); return unless $userData; push(@subNodes, { name => 'count', attributes => $attrs{numConfig}, value => scalar map { @{ $_->{domains} } } values %$userData, }); push(@subNodes, { name => 'list', attributes => $attrs{textConfig}, value => join(',', sort map { @{ $_->{domains} } } values %$userData), }); return $self->cpanelDataWrapper($provider, $struct); } sub diskusage { my $self = shift; my $DF_BINARY = '/bin/df'; my @buffers = split(/\n/, `$DF_BINARY --exclude-type=nfs -P| /bin/grep -v shm | /bin/grep -v Filesystem | /bin/grep -v volume_group`); my $partitions; foreach my $buff (@buffers) { if ($buff =~ /^(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*$/) { $partitions->{$6}{'BSize'} = $2; $partitions->{$6}{'BUsed'} = $3; $partitions->{$6}{'BAvailable'} = $4; } } my @inodes = split(/\n/, `$DF_BINARY --exclude-type=nfs -iP | /bin/grep -v shm | /bin/grep -v Filesystem | /bin/grep -v volume_group`); foreach my $inode (@inodes) { if ($inode =~ /^(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\S+)\s*$/) { $partitions->{$6}{'Icount'} = $2; $partitions->{$6}{'IUsed'} = $3; $partitions->{$6}{'IAvailable'} = $4; } } my $mapping = { blocks => { BSize => 'total', BUsed => 'used', BAvailable => 'available' }, inodes => { Icount => 'total', IUsed => 'used', IAvailable => 'available', } }; my $provider = 'diskusage'; my $xml = { name => 'diskusage', attributes => { providedby => $provider, datatype => 'None' }, value => [], }; foreach my $partname (sort keys %$partitions) { push(@{ $xml->{value} }, { name => 'ihavebadchars', attributes => { realname => $partname, providedby => $provider, datatype => 'None', }, value => [ { name => 'blocks', attributes => { providedby => $provider, datatype => 'None', }, value => [ map { { name => $mapping->{blocks}{$_}, attributes => { providedby => $provider, datatype => 'Numeric', unit => '1k blocks', exponent => 3, incrementing => 'N' }, value => $partitions->{$partname}{$_} } } qw/BSize BUsed BAvailable/ ], }, { name => 'inodes', attributes => { providedby => $provider, datatype => 'None', }, value => [ map { { name => $mapping->{inodes}{$_}, attributes => { providedby => $provider, datatype => 'Numeric', unit => 'inodes', exponent => 0, incrementing => 'N' }, value => $partitions->{$partname}{$_} } } qw/Icount IUsed IAvailable/ ], } ], }); } return $xml; } sub loadaverage { my $self = shift; my $provider = 'loadaverage'; my $data; open(my $loadavg, '<', '/proc/loadavg') or $self->sonar->printlog("can't open /proc/loadavg for reading!\n", 1); while (<$loadavg>) { if ($_ =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\/(\d+)\s+(\d+)/) { $data->{loadaverage} = { 'one-min' => $1, 'five-min' => $2, 'fifteen-min' => $3, }; $data->{processes} = { running => $4, total => $5, }; } } my @xml; foreach my $field (sort keys %$data) { my $struct = { name => $field, attributes => { providedby => $provider, dataType => 'None', }, value => [ map { { name => $_, attributes => { exponent => 0, providedby => $provider, type => 'Numeric', incrementing => 'N', unit => '' }, value => $data->{$field}{$_} } } sort keys %{ $data->{$field} } ], }; push(@xml, $struct); } return @xml; } sub lvm { my ($self) = @_; my $provider = 'lvm'; my %attrs = ( config => { providedby => $provider, datatype => "Text", }, noConfig => { providedby => $provider, datatype => "None", }, ); my $xml = { name => $provider, attributes => $attrs{noConfig}, value => [], }; my @lvmFields = qw( volumegroupaccess volumegroupstatus volumegroupnumber maxlogicalvolumes currentlogicalvolumes openlogicalvolumes maxlogicalvolumesize maxphysicalvolumes currentphysicalvolumes actualphysicalvolumes volumegroupsize physicalextentsize totalphysicalextents allocatedphysicalextents freephysicalextents uuid ); my %propertyNameMap = ( 'LV Name' => 'lvname', 'LV Write Access' => 'lvaccess', 'LV Status' => 'lvstatus', '# open' => 'numopen', 'LV Size' => 'lvsize', 'Block device' => 'Block device', 'LV UUID' => 'lvuuid', ); if (-x "/sbin/vgdisplay") { #and -r "/etc/lvmtab" ) my @inputarray = split(/\n/, `vgdisplay -c | grep ':'`); foreach my $input (@inputarray) { my $VGName; my %vgData; $input =~ s/^\s+|\s+$//g; ($VGName, @vgData{@lvmFields}) = split(/:/, $input); my $vgNode = { name => $VGName, attributes => $attrs{config}, value => [] }; @{ $vgNode->{value} } = map { { name => $_, value => $vgData{$_}, attributes => $attrs{config} } } sort keys %vgData; if ($VGName =~ /^([\w_-]+)$/) { $VGName = $1; } else { next; } my $vgDisplayRaw = `vgdisplay -v $VGName`; my @volumes = split(/\n \n/, $vgDisplayRaw); VOL: for my $volume (@volumes) { my @properties = split(/\n/, $volume); my $type = shift(@properties); next VOL unless $type =~ /Logical/; s/^\s*|\s*$//g for @properties; my %lvDetails; PROP: for my $property (@properties) { my ($field, $value) = split(/\s{4,}/, $property); next PROP unless $propertyNameMap{$field}; $lvDetails{$field} = $value; } my $nodeName = delete $lvDetails{'Block device'}; $nodeName =~ s/\d+:(\d+)/$1/; my $lvNode = { name => "lv-$nodeName", attributes => $attrs{noConfig}, value => [] }; @{ $lvNode->{value} } = map { { name => $propertyNameMap{$_}, value => $lvDetails{$_}, attributes => $attrs{config} } } sort keys %lvDetails; push(@{ $vgNode->{value} }, $lvNode); } push(@{ $xml->{value} }, $vgNode); } } return $xml; } sub lwbackup { my $self = shift; my $content = do { local $/; my $input; eval { open($input, "<", "/usr/local/lp/logs/backup.summary.xml") or die $!; }; if ($@) { open($input, "<", "/usr/local/backup/summary.xml"); } <$input> }; my $xml; if ($content) { # we have a successful open, let's get to work my $stats; if ($content =~ /<backupDirectory>(.*)<\/backupDirectory>/) { $stats->{backupDirectory} = $1; } if ($content =~ /<backupDevice>(.*)<\/backupDevice>/) { $stats->{backupDevice} = $1; } if ($content =~ /<diskUsageLimit>(.*)<\/diskUsageLimit>/) { $stats->{diskUsageLimit} = $1; } if ($content =~ /<diskUsageLimitType>(.*)<\/diskUsageLimitType>/) { $stats->{diskUsageLimitType} = $1; } if ($content =~ /<allowedVariance>(.*)<\/allowedVariance>/) { $stats->{allowedVariance} = $1; } if ($content =~ /<diskSize>(.*)<\/diskSize>/) { $stats->{diskSize} = $1; } if ($content =~ /<gigsFree>(.*)<\/gigsFree>/) { $stats->{gigsFree} = $1; } if ($content =~ /<percentFree>(.*)<\/percentFree>/) { $stats->{percentFree} = $1; } $stats->{NewestBackup} = 0; $stats->{OldestBackup} = 9999999999999; $stats->{backupCount} = 0; while ($content =~ /<backup date=\"(\d{1,2})\/(\d{1,2})\/(\d{4})\" time=\"(\d{1,2}):(\d{1,2})\" \/>/g) { #create the unix timestamp for this backup my $Stamp = mktime(0, $5, $4, $2, ($1 - 1), ($3 - 1900)); if ($Stamp > $stats->{NewestBackup}) { $stats->{NewestBackup} = $Stamp; } if ($Stamp < $stats->{OldestBackup}) { $stats->{OldestBackup} = $Stamp; } $stats->{backupCount}++; } $xml = { name => 'lwbackup', attributes => { providedby => 'lwbackup', datatype => 'None', }, value => [ map { { name => $_, attributes => { providedby => 'lwbackup', datatype => 'Text' }, value => $stats->{$_} } } sort keys %$stats ], }; } return $xml; } sub memory { my ($self) = @_; my $provider = 'memory'; my %attrs = ( config => { providedby => $provider, datatype => "Numeric", incrementing => 'N', unit => 'Bytes', exponent => 3, }, noConfig => { providedby => $provider, datatype => "None", }, ); my $xml = { name => $provider, attributes => $attrs{noConfig}, value => [], }; my %memInfo = %{ getMemInfo() }; if (%memInfo) { my %keyNameMap = ( MemTotal => 'total', TotalMemUsed => 'used', Buffers => 'buffers', Cached => 'cached', MemUsed => 'realused', SwapTotal => 'total', SwapUsed => 'used', ); my $physicalNode = { name => 'physical', attributes => $attrs{noConfig}, value => undef, }; my $swapNode = { name => 'swap', attributes => $attrs{noConfig}, value => undef, }; @{ $physicalNode->{value} } = map { { name => $keyNameMap{$_}, value => $memInfo{$_}, attributes => $attrs{config} } } qw(MemTotal TotalMemUsed Buffers Cached MemUsed); @{ $swapNode->{value} } = map { { name => $keyNameMap{$_}, value => $memInfo{$_}, attributes => $attrs{config} } } qw(SwapTotal SwapUsed); push(@{ $xml->{value} }, $physicalNode, $swapNode); } return $xml; } sub network { my $self = shift; my $content = do { local $/ = undef; open(my $input, "< /proc/net/dev"); <$input>; }; my $xml = { name => 'network', attributes => { providedby => 'network', datatype => 'None', }, value => [], }; if ($content) { while ($content =~ /\s*(.*?):\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)\s*([0-9]*)/g) { # $interface needs to be uppercased because the data parsing doesn't uppercase ihavebadchars nodes my $interface = uc($1); my $parent = { name => 'ihavebadchars', attributes => { realname => $interface, datatype => 'None', providedby => 'network', }, value => [], }; my $receive = { name => 'receive', attributes => { providedby => 'network', datatype => 'None', }, value => [], }; push(@{ $receive->{value} }, { name => 'bytes', attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Bytes' }, value => $2, }); push(@{ $receive->{value} }, { name => 'packets', attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Packets' }, value => $3, }); push(@{ $receive->{value} }, { name => 'errs', attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => '' }, value => $4, }); push(@{ $parent->{value} }, $receive); my $transmit = { name => 'transmit', attributes => { providedby => 'network', datatype => 'None', }, value => [], }; push(@{ $transmit->{value} }, { name => 'bytes', attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Bytes' }, value => $10, }); push(@{ $transmit->{value} }, { name => 'packets', attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => 'Packets' }, value => $11, }); push(@{ $transmit->{value} }, { name => 'errs', attributes => { providedby => 'network', datatype => 'Numeric', exponent => '0', Incrementing => 'Y', unit => '' }, value => $12, }); push(@{ $parent->{value} }, $transmit); push(@{ $xml->{value} }, $parent); } } my @xml; my $conntrack = $self->conntrack; foreach my $struct ($xml, $conntrack) { push(@xml, $struct); } return @xml; } sub conntrack { my $self = shift; my $provider = 'network'; my $xml = { name => 'conntrack', attributes => { providedby => $provider, datatype => 'None', }, value => [], }; my %files; foreach my $type (qw/buckets count max/) { $files{$type}{$_} = "/proc/sys/net/" . join('/', $_ eq 'old' ? ('ipv4', 'netfilter', 'ip') : ('netfilter', 'nf')) . "_conntrack_$type" for (qw/new old/); } if ((-r $files{'buckets'}{'new'}) or (-r $files{'buckets'}{'old'})) { my $nftype = "unknown"; if (-r $files{'buckets'}{'new'}) { $nftype = "new"; } elsif (-r $files{'buckets'}{'old'}) { $nftype = "old"; } if ($nftype ne "unknown") { my @list = ("buckets", "count", "max"); foreach my $key (@list) { my $file = "file_" . $key . "_" . $nftype; my $content = do { local $/; open(my $input, "< $files{$key}{$nftype}"); <$input>; }; if ($content && $content =~ /^(\d+)$/) { push(@{ $xml->{value} }, { name => $key, attributes => { providedby => $provider, datatype => 'Numeric', exponent => 0, Incrementing => 'N', unit => '', }, value => $1, }); } } } } return $xml; } sub timezone { my $self = shift; my $provider = 'timezone'; my $xml = { name => 'timezone', attributes => { providedby => $provider, datatype => 'None' }, }; my $field1 = strftime "%z", localtime; my $field2 = strftime "%Z", localtime; my $datetime = ($field1 =~ /^[+-]?[0-9]{3,4}$/) ? { offset => $field1, abbreviation => $field2 } : ($field2 =~ /^[+-]?[0-9]{3,4}$/) ? { offset => $field2, abbreviation => $field1 } : {}; my @values; foreach my $field (sort keys %$datetime) { push(@values, { name => $field, attributes => { providedby => $provider, datatype => "Text", }, value => $datetime->{$field}, }); } $xml->{value} = \@values; return $xml; } sub uptime { my $self = shift; my $content = do { local $/ = undef; open my $file, '<', '/proc/uptime'; <$file>; }; my $xml; if ($content && $content =~ /([0-9]*.[0-9]{0,2})\s*([0-9]*.[0-9]{0,2})/) { my $Field1 = $1; my $Field2 = $2; $xml = { name => 'uptime', attributes => { providedby => 'uptime', datatype => 'None', }, value => [ { name => 'seconds', attributes => { providedby => 'uptime', datatype => 'Text', }, value => $1, } ], }; my $hms = {}; if ($Field1 >= (24 * 60 * 60)) { my $Days = floor(int($Field1) / int(24 * 60 * 60)); $Field1 = floor(int($Field1) % int(24 * 60 * 60)); $hms->{days} = $Days . "D"; } if ($Field1 > (60 * 60)) { my $Hours = floor(int($Field1) / int(60 * 60)); $Field1 = floor(int($Field1) % int(60 * 60)); $hms->{hours} = $Hours . "H"; } if ($Field1 > 60) { my $Minutes = floor(int($Field1) / int(60)); $Field1 = floor(int($Field1) % int(60)); $hms->{minutes} = $Minutes . "M"; } $hms->{seconds} = $Field1 . "S"; push(@{ $xml->{value} }, { name => 'human', attributes => { providedby => 'uptime', datatype => 'Text', }, value => "$hms->{days} $hms->{hours} $hms->{minutes} $hms->{seconds}", }); } return $xml; } sub vzbackup { my $self = shift; my $provider = 'vzbackup'; my %attrs = ( config => { providedby => $provider, datatype => "Text", }, noConfig => { providedby => $provider, datatype => "None", }, ); my $xml = { name => 'vzbackup2', attributes => $attrs{noConfig}, value => [], }; my %backuphash; my $file = "/usr/local/lp/etc/vps-backup-manifest"; if (-e $file and -r $file) { if (open(MANIFESTINPUT, "< $file")) { while (<MANIFESTINPUT>) { if ($_ =~ /^(\S+),(\S+)$/) { my $Field1 = $1; my $Field2 = $2; my $file = "/usr/local/lp/logs/vpsbackup/backup-status-$Field1"; if (-e $file and -r $file) { if (open(STATUSINPUT, "< $file")) { my $backupNode = { name => 'ihavebadchars', attributes => { %{ $attrs{noConfig} }, realname => $Field2 }, value => [] }; push(@{ $backupNode->{value} }, { name => 'server', value => $1, attributes => $attrs{config} }); %backuphash = undef; while (<STATUSINPUT>) { if ($_ =~ /^BACKUP(\d)(START|FINISH|STATUS)=(\S+)$/) { $backuphash{"$1"}{"$2"} = "$3"; } } close(STATUSINPUT); for (my $x = 0; $x < 7; $x++) { my $backup = { name => "backup$x", attributes => $attrs{noConfig}, value => [ map { { name => $_, value => $backuphash{$x}{$_}, attributes => $attrs{config} } } sort keys %{ $backuphash{$x} } ], }; push(@{ $backupNode->{value} }, $backup); } push(@{ $xml->{value} }, $backupNode); } } } } close(MANIFESTINPUT); } } return $xml; } =head1 Daemon state providers These functions form a psuedo-group of providers which report on the state of a select set of running services. These methods return data wrapped in a daemon state node, for ease of display. =cut sub pagentStatus { my ($self) = @_; my $provider = 'pagentStatus'; my $pagentPidFile = '/usr/local/lp/var/p-agent.pid'; my @subNodes; my $xml = { name => 'pagentStatus', attributes => { providedby => $provider, datatype => 'None' }, value => \@subNodes, }; if ((-e $pagentPidFile) and (-r $pagentPidFile)) { open(my $file, '<', $pagentPidFile); my ($pid) = <$file>; close($file); chomp($pid); if ($pid =~ /^(\d+)$/) { $pid = $1; } else { return; } push(@subNodes, $self->memInfoForPID($pid, $provider, 'pagent')); push(@subNodes, $self->runtimeForPID($pid, $provider, 'pagent')); } else { return; } return $self->daemonStateWrapper($provider, $xml); } sub eximstatus { my ($self) = @_; my $provider = 'eximstatus'; my %attrs = ( emails => { unit => "Email", exponent => "1", incrementing => "N", providedby => $provider, datatype => "Numeric", }, noConfig => { providedby => $provider, datatype => "None", }, ); my @subNodes; my $topXML = { name => $provider, attributes => $attrs{noConfig}, value => \@subNodes, }; unless (-e '/usr/local/lp/var/sonarpush/eximdata.json') { return; } open(my $file, '<', '/usr/local/lp/var/sonarpush/eximdata.json') or die $!; my $json = <$file>; close($file); # prevent "Missing or empty input" error from from_json when file # exists but is empty. return unless ($json); my $data = from_json($json); my %fields = ( queue_length => 'emails' ); for my $field (keys %fields) { if (exists $data->{$field}) { push(@subNodes, { name => $field, value => $data->{$field}, attributes => $attrs{$fields{$field}}, }); } } return $self->daemonStateWrapper($provider, $topXML); } sub sonarpushStatus { my ($self) = @_; my $provider = 'sonarpushStatus'; my $xml = { name => 'sonarpushStatus', attributes => { providedby => $provider, datatype => 'None' }, }; my @subNodes; my $memNode = $self->memInfoForPID($$, $provider, 'sonarpush'); my $uptimeNode = $self->runtimeForPID($$, $provider, 'sonarpush'); push(@subNodes, $memNode, $uptimeNode); push(@{ $uptimeNode->{value} }, { name => 'started', attributes => { providedby => $provider, datatype => "Text", }, value => $self->sonar->{Started}, }); my $updateAttrs = { providedby => $provider, datatype => "Numeric", unit => "", exponent => "0", incrementing => "Y" }; for my $nameSpace (sort keys %{ $self->sonar->{status} }) { my $values = $self->sonar->{status}{$nameSpace}; push(@subNodes, { name => "sonarpush$nameSpace", attributes => $xml->{attributes}, value => [ map { { name => $_, attributes => $updateAttrs, value => $values->{$_} } } sort keys %{$values} ], } ); } $xml->{value} = \@subNodes; return $self->daemonStateWrapper($provider, $xml); } my %WEBSERVERNAMES = ( apache => [qw(httpd apache2 apache)], nginx => [qw(nginx)], lightspeed => [qw(lshttpd)], lighttpd => [qw(lighttpd)], ); sub webserverStatus { my ($self) = @_; my $provider = 'webserverStatus'; my $serverType; my $serverPid; my @topLevelProcs = `ps -o comm,pid --ppid 1`; splice(@topLevelProcs, 0, 1); my %daemons = map { /^(\S+)\s+(\d+)$/g } map { s/^\s+|\s+$//r } @topLevelProcs; TYPE: for my $type (sort keys %WEBSERVERNAMES) { my @serverNames = @{$WEBSERVERNAMES{$type}}; for my $serverName (@serverNames) { if (my $pid = $daemons{$serverName}) { $serverType = $type; $serverPid = $pid; last TYPE; } } } return unless $serverPid; my @httpProcs = `ps -o pid,pcpu,pmem,rss,etime,comm --ppid $serverPid $serverPid`; splice(@httpProcs, 0, 1); return unless @httpProcs; my @fieldNames = qw(pid percentCPU percentMEM totalMEM runtime command); my $procLeader; my @breakdown; for my $proc (@httpProcs) { $proc =~ s/^\s+|\s+$//; my @fields = split(/\s+/, $proc); my %namedFields; @namedFields{@fieldNames} = @fields; my ($days, $hours, $minutes, $seconds) = (delete $namedFields{runtime}) =~ /(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)/; $days ||= '00'; $hours ||= '00'; my $totalSeconds = (($days * 24 + $hours) * 60 + $minutes) * 60 + $seconds; $namedFields{runtime} = $totalSeconds; $namedFields{humanUptime} = "${days}D ${hours}H ${minutes}M ${seconds}S"; if ($namedFields{pid} eq $serverPid) { $procLeader = \%namedFields; } else { push(@breakdown, \%namedFields); delete $namedFields{humanUptime}; } delete $namedFields{command}; delete $namedFields{pid}; } return unless $procLeader; my %aggregate = map { $_ => 0 } qw(percentCPU percentMEM totalMEM runtime); for my $proc (@breakdown) { map { $aggregate{$_} += $proc->{$_} } sort keys %{$proc}; } $aggregate{uptime} = $procLeader->{runtime}; $aggregate{avgRuntime} = (delete $aggregate{runtime}) / (scalar(@breakdown) || 1); $aggregate{avgMEM} = (delete($aggregate{totalMEM}) + $procLeader->{totalMEM}) / (scalar(@breakdown) + 1); $aggregate{humanUptime} = $procLeader->{humanUptime}; $aggregate{numServers} = scalar(@breakdown); $aggregate{serverType} = $serverType; my %attrs = ( serverType => { datatype => "Text" }, humanUptime => { datatype => "Text" }, avgMEM => { datatype => "Numeric", unit => "Bytes", exponent => "3", incrementing => "N" }, numServers => { datatype => "Numeric", unit => "", exponent => "1", incrementing => "N" }, uptime => { datatype => "Numeric", unit => "Seconds", incrementing => "N", exponent => "1", }, percentMEM => { datatype => "Numeric", unit => "Percent", exponent => "1", incrementing => "N" }, avgRuntime => { datatype => "Numeric", unit => "Seconds", incrementing => "N", exponent => "1", }, percentCPU => { datatype => "Numeric", unit => "Percent", exponent => "1", incrementing => "N" }, ); $_->{providedby} = $provider for values %attrs; my $xml = { name => $provider, attributes => { providedby => $provider, datatype => 'None', }, value => [ map { { name => $_, value => $aggregate{$_}, attributes => $attrs{$_} } } sort keys %aggregate ], }; return $self->daemonStateWrapper($provider, $xml); } sub iostatData { my ($self) = @_; my $provider = 'iostatData'; my %attrs = ( cpuTime => { unit => "Seconds", exponent => "1", incrementing => "Y", providedby => $provider, datatype => "Numeric", }, percent => { unit => "Percent", exponent => "1", incrementing => "N", providedby => $provider, datatype => "Numeric", }, sectors => { unit => "Sectors", exponent => "1", incrementing => "Y", providedby => $provider, datatype => "Numeric", }, time => { unit => "Seconds", exponent => "1", incrementing => "Y", providedby => $provider, datatype => "Numeric", }, reads => { unit => "Reads", exponent => "1", incrementing => "Y", providedby => $provider, datatype => "Numeric", }, writes => { unit => "Writes", exponent => "1", incrementing => "Y", providedby => $provider, datatype => "Numeric", }, ops => { unit => "Ops", exponent => "1", incrementing => "N", providedby => $provider, datatype => "Numeric", }, noConfig => { providedby => $provider, datatype => "None", }, ); my %devicePropertyAttrs = ( sectors_read => $attrs{sectors}, sectors_written => $attrs{sectors}, time_reading => $attrs{time}, time_writing => $attrs{time}, io_time => $attrs{time}, io_time_weighted => $attrs{time}, reads_completed => $attrs{reads}, reads_merged => $attrs{reads}, writes_completed => $attrs{writes}, writes_merged => $attrs{writes}, curr_io_ops => $attrs{ops}, ); my @subNodes; my $topXML = { name => $provider, attributes => $attrs{noConfig}, value => \@subNodes, }; # We're going to be doing some number crunching # on this data that involves how it changes over a defined interval, # as opposed to "from boot", which is what the files actually track. # We can find the time interval more precisely using the data in the files, # but we need to give it some time to change, hence the sleep. my $stat1 = readProcStat(); my $diskStatsPre = readProcDiskStat(); sleep(1); my $stat2 = readProcStat(); my $diskStats = readProcDiskStat(); my $timeDiff; my @cpuNodes; my $cpuXML = { name => 'CpuIostats', attributes => $attrs{noConfig}, value => \@cpuNodes, }; push(@subNodes, $cpuXML); for my $cpuLabel (sort keys %$stat2) { # Omit Individual Proc stats next unless $cpuLabel eq 'cpu-total'; my @cpuProperties; my $cpuStatXML = { name => $cpuLabel, attributes => $attrs{noConfig}, value => \@cpuProperties, }; push(@cpuNodes, $cpuStatXML); my %statDeltas; my $elapsed1 = 0; my $elapsed2 = 0; for my $propertyName (sort keys %{$stat2->{$cpuLabel}}) { $elapsed1 += $stat1->{$cpuLabel}{$propertyName}; $elapsed2 += $stat2->{$cpuLabel}{$propertyName}; $statDeltas{$propertyName} = $stat2->{$cpuLabel}{$propertyName} - $stat1->{$cpuLabel}{$propertyName}; push(@cpuProperties, { name => $propertyName, value => sprintf('%0.09f', ($statDeltas{$propertyName})/100), attributes => $attrs{cpuTime}, }); } $timeDiff = $elapsed2 - $elapsed1; for my $property (sort keys %statDeltas) { push(@cpuProperties, { name => "${property}_percentage", value => sprintf('%0.09f', 100* ($statDeltas{$property} / $timeDiff)), attributes => $attrs{percent}, }); } } my @diskNodes; my $diskXML = { name => 'DiskIostats', attributes => $attrs{noConfig}, value => \@diskNodes, }; push(@subNodes, $diskXML); my $deviceMap = parseProcDevice(); my $diskTypes = extractDiskType(); my %devicesByType; for my $diskLabel (sort grep { !/^loop\d*$/ } keys %$diskStats) { my $diskProperties = $diskStats->{$diskLabel}; my $deviceType = $deviceMap->{$diskProperties->{major}}; my $diskType = $diskTypes->{$diskLabel} || 'other'; delete @{ $diskProperties }{qw(major minor)}; my $anyActivity = 0; $anyActivity += $_ for values %$diskProperties; next unless $anyActivity; next unless $deviceType; next if $deviceType eq 'device-mapper'; next if $diskType eq 'iscsi'; my @deviceProperties; my $deviceXML = { name => $diskLabel, attributes => $attrs{noConfig}, value => \@deviceProperties, }; push(@{$devicesByType{$diskType}}, $deviceXML); for my $propertyName (sort keys %{ $diskProperties }) { push(@deviceProperties, { name => $propertyName, value => sprintf('%0.09f', $diskProperties->{$propertyName} / ($devicePropertyAttrs{$propertyName}{unit} eq 'Seconds'? 1000 : 1)), attributes => $devicePropertyAttrs{$propertyName}, }); } for my $propertyName (qw(reads_completed writes_completed reads_merged writes_merged sectors_read sectors_written)) { my $value = $diskProperties->{$propertyName} - $diskStatsPre->{$diskLabel}{$propertyName}; push(@deviceProperties, { name => "${propertyName}_per_second", value => $value, attributes => { unit => "OpsPerSecond", exponent => "1", incrementing => "N", providedby => $provider, datatype => "Numeric", }, }); } for my $propertyName (qw(io_time time_reading time_writing)) { my $value = $diskProperties->{$propertyName} - $diskStatsPre->{$diskLabel}{$propertyName}; push(@deviceProperties, { name => "${propertyName}_per_second", value => sprintf('%0.09f', $value/1000), attributes => $attrs{time}, }); push(@deviceProperties, { name => "${propertyName}_percent_busy", value => 10 * ($value / $timeDiff), attributes => $attrs{percent}, }); } my %adjustmentMap = ( time_reading => 'reads_completed', time_writing => 'writes_completed', ); my $iotimeDiff = $diskProperties->{io_time} - $diskStatsPre->{$diskLabel}{io_time}; for my $propertyName (qw(time_reading time_writing)) { my $totalValue = $diskProperties->{$propertyName} - $diskStatsPre->{$diskLabel}{$propertyName}; my $adjustmentFactor = $adjustmentMap{$propertyName}; my $adjustmentFactorDelta = $diskProperties->{$adjustmentFactor} - $diskStatsPre->{$diskLabel}{$adjustmentFactor}; my $value = $totalValue / ($adjustmentFactorDelta || 1); push(@deviceProperties, { name => "${propertyName}_adj_per_second", value => sprintf('%0.09f', $value/1000), attributes => $attrs{time}, }); push(@deviceProperties, { name => "${propertyName}_adj_percent_iotime", value => $iotimeDiff && $value ? 100 * ($value / $iotimeDiff) : 0, attributes => $attrs{percent}, }); push(@deviceProperties, { name => "${propertyName}_adj_percent_busy", value => 10 * ($value / $timeDiff), attributes => $attrs{percent}, }); } } for my $deviceType (sort keys %devicesByType) { push(@diskNodes, { name => $deviceType, attributes => $attrs{noConfig}, value => $devicesByType{$deviceType}, }); } return $topXML; } =head1 Helper functions =cut sub extractDiskType { my $devPathDir = '/dev/disk/by-path/'; my %diskType; if (-d $devPathDir) { opendir(my $pathDir, $devPathDir) or die $!; my @files = grep { -l "$devPathDir/$_" } readdir($pathDir); for my $diskPath (@files) { my @pathParts = split("-", $diskPath); my $type = $pathParts[2]; my $deviceRelativePath = readlink("$devPathDir/$diskPath"); my ($deviceName) = $deviceRelativePath =~ m{/([\w\d]+?)$}g; $diskType{$deviceName} = $type; } } return \%diskType; }; sub parseProcDevice { my $deviceFile = '/proc/devices'; my %deviceData; my %deviceMap; if ((-e $deviceFile) && (-r $deviceFile)) { open(my $fh, '<', $deviceFile) or die $!; my @deviceLines = <$fh>; close($fh); chomp for @deviceLines; for my $device (@deviceLines) { my ($majorNum, $type) = $device =~ /^\s*(\d+)\s+(\S+)$/g; if ($majorNum) { $deviceMap{$majorNum}=$type; } } } return \%deviceMap; } my @cpuPropertyNames = qw( user nice system idle iowait irq softirq steal guest guest_nice ); sub readProcStat { my $cpuStatFile = '/proc/stat'; my %cpuStats; if ((-e $cpuStatFile) && (-r $cpuStatFile)) { open(my $statFile, '<', $cpuStatFile) or die $!; my @statLines = <$statFile>; close($statFile); chomp for @statLines; for my $statLine (@statLines) { my @fields = split(/\s+/, $statLine); my $name = shift(@fields); if ($name =~ /^cpu(\d+)?$/) { my $cpuNumber = defined($1) ? $1 : 'total'; for my $propertyName (@cpuPropertyNames) { $cpuStats{"cpu-$cpuNumber"}{$propertyName} = shift(@fields); } } } } return \%cpuStats; } my @devicePropertyNames = qw( reads_completed reads_merged sectors_read time_reading writes_completed writes_merged sectors_written time_writing curr_io_ops io_time io_time_weighted ); sub readProcDiskStat { my $regex = '(sd|hd|vd|xvd)[a-z]{1,2}\d+$'; my $diskStatFile = '/proc/diskstats'; my %diskStats; if ((-e $diskStatFile) && (-r $diskStatFile)) { open(my $statFile, '<', $diskStatFile) or die $!; my @statLines = <$statFile>; close($statFile); chomp for @statLines; for my $statLine (@statLines) { my @fields = split(/\s+/, $statLine); shift(@fields); my ($major, $minor, $deviceName) = @fields; # Skip partitions next if lc($deviceName) =~ /$regex/; $diskStats{$deviceName}{major} = $major; $diskStats{$deviceName}{minor} = $minor; # Throw away unneeded fields @fields = @fields[ 3 .. 13 ]; for my $propertyName (@devicePropertyNames) { $diskStats{$deviceName}{$propertyName} = shift(@fields); } } } return \%diskStats; } =head2 memInfoForPID =over 4 =item Description: helper function to generate the structure to report on memory information for a given pid as found in proc/$pid/status =item Parameters pid = the pid you want meminfo for provider = the name of the provider that this info is being gathered for name = the name of the service or program that is being reported on. the name of the returned node will be ${name}Memory =item Returns an encodeXML compatible datastructure containing memory information for $pid =back =cut sub memInfoForPID { my ($self, $pid, $provider, $name) = @_; return if is_tainted($pid); my $memNode = { name => "${name}Memory", attributes => { providedby => $provider, datatype => 'None' }, value => [] }; my $attrHash = { providedby => $provider, datatype => "Numeric", unit => "Bytes", exponent => "3", incrementing => "N" }; my $file = "/proc/$pid/status"; if ((-e $file) and (-r $file)) { open(my $status, "< $file") or print "can't open '$file' for reading!\n"; my @statusLines = grep {/^Vm\w+:/} <$status>; close($status); for my $vmLine (@statusLines) { my ($field, $value) = $vmLine =~ /^(Vm\w+):\s+(\d+).+$/; push(@{ $memNode->{value} }, { name => $field, value => $value, attributes => $attrHash, } ); } } return $memNode; } sub runtimeForPID { my ($self, $pid, $provider, $name) = @_; return if is_tainted($pid); my $runNode = { name => "${name}Uptime", attributes => { providedby => $provider, datatype => 'None' }, value => [] }; my $uptime = (`ps -o etime $pid`)[1]; $uptime =~ s/\s//g; my ($days, $hours, $minutes, $seconds) = $uptime =~ /(?:(?:(\d+)-)?(\d+):)?(\d+):(\d+)/; $days ||= '00'; $hours ||= '00'; my $totalSeconds = (($days * 24 + $hours) * 60 + $minutes) * 60 + $seconds; my $humanReadable = "${days}D ${hours}H ${minutes}M ${seconds}S"; push(@{ $runNode->{value} }, { name => 'seconds', attributes => { providedby => $provider, datatype => "Numeric", unit => "Seconds", incrementing => "N", exponent => "0", }, value => $totalSeconds, }, { name => 'humanReadable', attributes => { providedby => $provider, datatype => "Text" }, value => "${days}D ${hours}H ${minutes}M ${seconds}S", }); return $runNode; } =head2 getMemInfo =over 4 =item Description: helper function to gather general system memory usage, as defined in /proc/meminfo =item Parameters none =item Returns a hashref containing the information in /proc/meminfo =back =cut sub getMemInfo { my $file = "/proc/meminfo"; if ((-e $file) and (-r $file)) { my %memHash = (); open(my $memInfo, "< $file") or print "can't open '$file' for reading!\n"; my @memInfoLines = <$memInfo>; close($memInfo); for my $memFact (@memInfoLines) { my ($field, $value) = $memFact =~ /^(\S+):\s+(\d+).+$/; next unless defined $field && defined $value; $memHash{$field} = $value; } unless (grep { !defined $memHash{$_} } qw(MemTotal MemFree Cached Buffers)) { $memHash{MemUsed} = $memHash{MemTotal} - ($memHash{MemFree} + $memHash{Cached} + $memHash{Buffers}); } unless (grep { !defined $memHash{$_} } qw(MemTotal MemFree)) { $memHash{TotalMemUsed} = $memHash{MemTotal} - $memHash{MemFree}; } unless (grep { !defined $memHash{$_} } qw(SwapFree SwapTotal)) { $memHash{SwapUsed} = $memHash{SwapTotal} - $memHash{SwapFree}; } return \%memHash; } return; } =head2 daemonStateWrapper =over 4 =item Description: helper function to generate an xml wrapper for ensuring that software state providers are correctly grouped together in the tree that radar uses to display information =item Parameters provider = the name of the provider that the wrapped info is being gathered for nodeData = an encodeXML compatible structure to be wrapped in a 'daemonState' node. =item Returns a string of xml date, which is the encoding of nodeDate wrapped for daemonState =back =cut sub daemonStateWrapper { my ($self, $provider, $nodeData) = @_; my $xml = { name => 'daemonState', attributes => { providedby => $provider, datatype => 'None' }, value => $nodeData, }; return $xml; } =head2 cpanelDataWrapper =over 4 =item Description: helper function to generate an xml wrapper for ensuring that cpanel data providers are correctly grouped together in the tree that radar uses to display information =item Parameters provider = the name of the provider that the wrapped info is being gathered for nodeData = an encodeXML compatible structure to be wrapped in a 'cpanelData' node. =item Returns a string of xml data, which is the encoding of nodeDate wrapped for cpanelData =back =cut sub cpanelDataWrapper { my ($self, $provider, $nodeData) = @_; my $xml = { name => 'cpanelData', value => $nodeData, attributes => { providedby => $provider, datatype => 'None' }, }; return $xml; } sub extractCpanelUserDetails { my ($self) = @_; unless (-e '/usr/local/lp/var/sonarpush/accountexport') { return; } my @domainUsers = do { open(my $file, '<', '/usr/local/lp/var/sonarpush/accountexport'); <$file>; }; chomp for @domainUsers; my %userData; for my $domainUserEntry (@domainUsers) { my ($domain, $user) = split(':', $domainUserEntry, 2); $domain =~ s/(^\s+)|(\s+$)//g; $user =~ s/(^\s+)|(\s+$)//g; # cPanel adds a catchall entry pointing to # user 'nobody' that we needn't track. next if $domain eq '*'; next unless $user; push(@{ $userData{$user}{domains} }, $domain); } return \%userData; } sub is_tainted { local $@; return !eval { eval("#" . substr(join("", @_), 0, 0)); 1 }; } 1;
Copyright ©2k19 -
Hexid
|
Tex7ure