| 1 |
#!/usr/bin/perl -w |
|---|
| 2 |
# muninAggregator - a cgi-bin which presents the same graph for multiple nodes |
|---|
| 3 |
# and thus allows seeing the state of a whole farm of machines |
|---|
| 4 |
# |
|---|
| 5 |
# 20091020 - v1.00 - PaulM |
|---|
| 6 |
# - it's all working and fairly tidy too |
|---|
| 7 |
# 20091021 - v1.01 - PaulM |
|---|
| 8 |
# - expands graph types by looking at munin's rrd files |
|---|
| 9 |
# which I'd already envisaged but Philipp Niemann requested it |
|---|
| 10 |
# so I took up the challenge |
|---|
| 11 |
# |
|---|
| 12 |
# released under GPL to the munin community with the authority of my CTO @taptu.com |
|---|
| 13 |
|
|---|
| 14 |
use strict; |
|---|
| 15 |
use CGI qw(:standard); |
|---|
| 16 |
use POSIX ":sys_wait_h"; |
|---|
| 17 |
use FileHandle; |
|---|
| 18 |
use Data::Dumper; |
|---|
| 19 |
|
|---|
| 20 |
############################################################################### |
|---|
| 21 |
# constants |
|---|
| 22 |
|
|---|
| 23 |
# you'll need to tweak these to suit your installation |
|---|
| 24 |
my $muninBaseUrl = ($ENV{'HTTPS'} eq 'on' ? 'https://' : 'http://') . $ENV{'SERVER_NAME'} . '/munin'; |
|---|
| 25 |
#my $muninBaseUrl = 'https://foo.example.com/munin'; |
|---|
| 26 |
|
|---|
| 27 |
my $muninDataFiles = '/var/lib/munin/db'; |
|---|
| 28 |
|
|---|
| 29 |
# most distros have it here |
|---|
| 30 |
my $muninConf = '/etc/munin/munin.conf'; |
|---|
| 31 |
# but user self-builds might have it here: |
|---|
| 32 |
#my $muninConf = '/usr/local/etc/munin/munin.conf'; |
|---|
| 33 |
|
|---|
| 34 |
|
|---|
| 35 |
## you shouldn't need to touch anything below this line! ## |
|---|
| 36 |
|
|---|
| 37 |
# make the munin RRD names more human-friendly |
|---|
| 38 |
# at some point it'd be cool to scan the munin directories and add any missing items |
|---|
| 39 |
my %graphTypes = ( |
|---|
| 40 |
'apache_processes' => 'Apache Processes' |
|---|
| 41 |
, 'cpu' => 'CPU' |
|---|
| 42 |
, 'df' => 'Disk Usage' |
|---|
| 43 |
, 'forks' => 'Fork rate' |
|---|
| 44 |
, 'irqstats' => 'IRQ Statistics' |
|---|
| 45 |
, 'interrupts' => 'Interrupts & Context Switches' |
|---|
| 46 |
, 'iostat' => 'IOStat' |
|---|
| 47 |
, 'log_sizes' => 'Log File Sizes' |
|---|
| 48 |
, 'memory' => 'Memory Usage' |
|---|
| 49 |
, 'omreport_fan_speed' => 'OMSA Fan Speed' |
|---|
| 50 |
, 'omreport_pwrmon_current' => 'OMSA Power' |
|---|
| 51 |
, 'omreport_temp' => 'OMSA Temperature' |
|---|
| 52 |
, 'processes' => 'Process count' |
|---|
| 53 |
, 'vmstat' => 'VMStat process states' |
|---|
| 54 |
); |
|---|
| 55 |
|
|---|
| 56 |
# make the munin time ranges more human-friendly |
|---|
| 57 |
my %timeScales = |
|---|
| 58 |
( |
|---|
| 59 |
'day' => '24 Hours' |
|---|
| 60 |
, 'week' => '7 days' |
|---|
| 61 |
, 'month' => '31 days' |
|---|
| 62 |
, 'year' => 'year' |
|---|
| 63 |
); |
|---|
| 64 |
|
|---|
| 65 |
my $refreshRate = 10; # default refresh rate |
|---|
| 66 |
|
|---|
| 67 |
############################################################################### |
|---|
| 68 |
# user-supplied url params |
|---|
| 69 |
my $dbgLevel = (defined(param('debugLevel'))) ? param('debugLevel') : '0'; |
|---|
| 70 |
my $graphType = (defined(param('graphType'))) ? param('graphType') : ''; |
|---|
| 71 |
my $timeScale = (defined(param('timeScale'))) ? param('timeScale') : ''; |
|---|
| 72 |
my $nodeGroup = (defined(param('nodeGroup'))) ? param('nodeGroup') : ''; |
|---|
| 73 |
my $refresh = (defined(param('refresh'))) ? param('refresh') : 0; |
|---|
| 74 |
|
|---|
| 75 |
############################################################################### |
|---|
| 76 |
# writeSelectArray generates a select field of specified name with named options |
|---|
| 77 |
# from a hash and pre-selects a specific value (if defined). |
|---|
| 78 |
sub writeSelectHash |
|---|
| 79 |
{ |
|---|
| 80 |
my ($name, $value, %options) = @_; |
|---|
| 81 |
|
|---|
| 82 |
print "<select name=\"$name\">\n"; |
|---|
| 83 |
for my $option (sort keys %options) |
|---|
| 84 |
{ |
|---|
| 85 |
print "\t<option value=\"" . $option . "\""; |
|---|
| 86 |
print " selected " if ((defined $option) && ($option eq $value)); |
|---|
| 87 |
print ">" . $options{$option} . "</option>\n"; |
|---|
| 88 |
} |
|---|
| 89 |
print "</select>" |
|---|
| 90 |
} |
|---|
| 91 |
############################################################################### |
|---|
| 92 |
# writeSelectArray generates a select field of specified name with options |
|---|
| 93 |
# from an array and pre-selects a specific value (if defined). |
|---|
| 94 |
sub writeSelectArray |
|---|
| 95 |
{ |
|---|
| 96 |
my ($name, $value, @options) = @_; |
|---|
| 97 |
|
|---|
| 98 |
print "<select name=\"$name\">\n"; |
|---|
| 99 |
for my $option (sort @options) |
|---|
| 100 |
{ |
|---|
| 101 |
print "\t<option value=\"" . $option . "\""; |
|---|
| 102 |
print " selected " if ((defined $option) && ($option eq $value)); |
|---|
| 103 |
print ">" . $option . "</option>\n"; |
|---|
| 104 |
} |
|---|
| 105 |
print "</select>" |
|---|
| 106 |
} |
|---|
| 107 |
############################################################################### |
|---|
| 108 |
# generates a debug message in html-friendly form if the specified level is |
|---|
| 109 |
# equal or higher than current global debug level |
|---|
| 110 |
sub debugPrint |
|---|
| 111 |
{ |
|---|
| 112 |
my ($level, $dbgText) = @_; |
|---|
| 113 |
print "<i>Debug: $dbgText</i>\n<br />\n" if ($level >= $dbgLevel); |
|---|
| 114 |
} |
|---|
| 115 |
|
|---|
| 116 |
############################################################################### |
|---|
| 117 |
|
|---|
| 118 |
# minimise delays writing to the browser |
|---|
| 119 |
$| = 1; |
|---|
| 120 |
STDERR->autoflush; # already unbuffered in stdio |
|---|
| 121 |
|
|---|
| 122 |
|
|---|
| 123 |
|
|---|
| 124 |
print "Content-type: text/html\n\n" |
|---|
| 125 |
. "<html>\n<head>\n"; |
|---|
| 126 |
|
|---|
| 127 |
print "\t<meta http-equiv=\"refresh\" content=\"$refreshRate\">\n" if (defined($refresh) && ($refresh)); |
|---|
| 128 |
|
|---|
| 129 |
print "\t<title>muninAggregator</title>\n" |
|---|
| 130 |
. "</head>\n" |
|---|
| 131 |
. "<body>\n"; |
|---|
| 132 |
|
|---|
| 133 |
|
|---|
| 134 |
if (!open(FH, "<$muninConf")) |
|---|
| 135 |
{ |
|---|
| 136 |
print "Sorry, an error occurred reading file $muninConf.\n<br />\nPlease check this script for errors\n"; |
|---|
| 137 |
} |
|---|
| 138 |
else |
|---|
| 139 |
{ |
|---|
| 140 |
########## |
|---|
| 141 |
# read the munin configuration file to extract groups and hosts and dbdir |
|---|
| 142 |
my %nodeGroups; |
|---|
| 143 |
my %nodesInGroups; |
|---|
| 144 |
while(<FH>) |
|---|
| 145 |
{ |
|---|
| 146 |
chomp; |
|---|
| 147 |
if ($_ =~ /^\[(.*);(.*)\]$/) |
|---|
| 148 |
{ |
|---|
| 149 |
push @{$nodesInGroups{$1}}, $2; |
|---|
| 150 |
$nodeGroups{$1} = 1; |
|---|
| 151 |
} |
|---|
| 152 |
elsif ($_ =~ /^\[(.*)\]$/) |
|---|
| 153 |
{ |
|---|
| 154 |
my $host = $1; |
|---|
| 155 |
$host =~ /^([a-zA-Z0-9\-]*)\.(.*)$/; |
|---|
| 156 |
push @{$nodesInGroups{$2}}, $host; |
|---|
| 157 |
$nodeGroups{$1} = 1; |
|---|
| 158 |
} |
|---|
| 159 |
elsif ($_ =~ /^dbdir\s+(.*)$/) |
|---|
| 160 |
{ |
|---|
| 161 |
$muninDataFiles = $1; |
|---|
| 162 |
#debugPrint(1, "munin data files are stored in $muninDataFiles"); |
|---|
| 163 |
} |
|---|
| 164 |
} |
|---|
| 165 |
close(FH); |
|---|
| 166 |
|
|---|
| 167 |
########## |
|---|
| 168 |
# expand the graphTypes table with any extra ones by looking in the |
|---|
| 169 |
# munin data directories |
|---|
| 170 |
foreach my $group ( sort keys %nodeGroups ) |
|---|
| 171 |
{ |
|---|
| 172 |
if (-d "$muninDataFiles/$group") |
|---|
| 173 |
{ |
|---|
| 174 |
#debugPrint(1, "examining rrd files in $muninDataFiles/$group to populate the graphType hash"); |
|---|
| 175 |
chdir "$muninDataFiles/$group"; |
|---|
| 176 |
foreach my $rrdFile (<*>) |
|---|
| 177 |
{ |
|---|
| 178 |
#debugPrint(1, "found $1 $2 $3"); |
|---|
| 179 |
if ($rrdFile =~ /^(.*)-(.*)-(.*)-(.*)$/ ) |
|---|
| 180 |
{ |
|---|
| 181 |
if (!defined($graphTypes{$2})) |
|---|
| 182 |
{ |
|---|
| 183 |
#debugPrint(0, "adding auto-derived graph type $2"); |
|---|
| 184 |
$graphTypes{$2} = $2; |
|---|
| 185 |
} |
|---|
| 186 |
} |
|---|
| 187 |
} |
|---|
| 188 |
} |
|---|
| 189 |
} |
|---|
| 190 |
|
|---|
| 191 |
|
|---|
| 192 |
# present the graphing choices to the user as HTML form |
|---|
| 193 |
print "<form method=\"get\" action=\"\">\n"; |
|---|
| 194 |
|
|---|
| 195 |
print "Check to refresh every 10 minutes: <input type=\"checkbox\" name=\"refresh\" value=\"1\" " . (defined($refresh) && ($refresh) ? "checked" : "") . "/><br />\n"; |
|---|
| 196 |
|
|---|
| 197 |
print "Choose node group: "; |
|---|
| 198 |
writeSelectArray("nodeGroup", $nodeGroup, keys %nodesInGroups); |
|---|
| 199 |
print "\n<br />\n"; |
|---|
| 200 |
|
|---|
| 201 |
print "Choose graph type: "; |
|---|
| 202 |
writeSelectHash("graphType", $graphType, %graphTypes); |
|---|
| 203 |
print "\n<br />\n"; |
|---|
| 204 |
|
|---|
| 205 |
print "Choose time scale: "; |
|---|
| 206 |
writeSelectHash("timeScale", $timeScale, %timeScales); |
|---|
| 207 |
print "\n<br />\n"; |
|---|
| 208 |
|
|---|
| 209 |
print "\t<input type=\"submit\" name=\"go\" value=\"go\" />\n</form>\n\n"; |
|---|
| 210 |
|
|---|
| 211 |
|
|---|
| 212 |
# can we render the table of graph images? |
|---|
| 213 |
# i.e. has user selected any graphs to show? |
|---|
| 214 |
if ($graphType ne '') |
|---|
| 215 |
{ |
|---|
| 216 |
|
|---|
| 217 |
# get a list of nodes which have rrd files of selected type |
|---|
| 218 |
chdir("$muninDataFiles/$nodeGroup"); |
|---|
| 219 |
my %rrdFilesByHost; |
|---|
| 220 |
for my $rrdFile (<*>) |
|---|
| 221 |
{ |
|---|
| 222 |
$rrdFilesByHost{$1} = 1 if ($rrdFile =~ /^(.*)-$graphType-(.*)-(.*)$/ ); |
|---|
| 223 |
} |
|---|
| 224 |
|
|---|
| 225 |
# lets get rendering! |
|---|
| 226 |
print "<table>\n"; |
|---|
| 227 |
my $graphCount = 0; |
|---|
| 228 |
foreach my $nodeName (sort @{$nodesInGroups{$nodeGroup}}) |
|---|
| 229 |
{ |
|---|
| 230 |
$nodeName =~ /^([a-z0-9]*)\.(.*)$/; |
|---|
| 231 |
print "\t<tr>\n" if (!($graphCount %2)); |
|---|
| 232 |
print "\t\t<td><b>$nodeName</b> <a href=\"$muninBaseUrl/$nodeGroup/$nodeName-$graphType.html\" target=munin_" . $nodeName . '_' . $graphType . ">details</a><br />\n"; |
|---|
| 233 |
|
|---|
| 234 |
# check there's an rrd file for this host/group/type |
|---|
| 235 |
if (!defined($rrdFilesByHost{$nodeName})) |
|---|
| 236 |
{ |
|---|
| 237 |
print "<i>no graph of this type for this node</i>"; |
|---|
| 238 |
} |
|---|
| 239 |
else |
|---|
| 240 |
{ |
|---|
| 241 |
print "<img src=\"$muninBaseUrl/$nodeGroup/$nodeName-$graphType-$timeScale.png\" />\n\t\t</td>\n"; |
|---|
| 242 |
} |
|---|
| 243 |
print "\t</tr>\n" if ($graphCount %1); |
|---|
| 244 |
++$graphCount; |
|---|
| 245 |
} |
|---|
| 246 |
} |
|---|
| 247 |
else |
|---|
| 248 |
{ |
|---|
| 249 |
print "<i>No graph type chosen or program error</i><br />\n"; |
|---|
| 250 |
} |
|---|
| 251 |
} |
|---|
| 252 |
|
|---|
| 253 |
print "</body>\n</html>\n"; |
|---|
| 254 |
|
|---|
| 255 |
# end of muninAggregator |
|---|