????
| Current Path : /usr/lib/sonarpush/SonarPush/Providers/ |
| Current File : //usr/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;