5 # This script reads Windows performance logs (normally saved to a samba share)
6 # and returns the last value for a given counter in a format suitable for
9 # Usage of Perl module has been avoided as it cuts down incredibly the
10 # loading time (The last removed module alone cut load time by 3!)
12 # Configuration is done statically - see below (~ 2 pages down) for options.
14 # Usage: getlog.pl <servername> <instance>
15 # getlog.pl <servername> list
16 # Ex: perl getlog.pl SRV6 '\LogicalDisk(C:)\% Disk Time'
17 # perl getlog.pl SRV6 "\\LogicalDisk(C:)\\% Disk Time"
18 # Output: Cacti data format (or nothing). Errors goes to STDERR.
20 # Log files should be saved under $LOG_PATH and named "<hostname>.csv"
21 # Everything is CaSe-SenSITive!
23 # Copyright (C) 2008 Thomas Guyot-Sionnest <tguyot@gmail.com>
25 # This program is free software; you can redistribute it and/or
26 # modify it under the terms of the GNU General Public License
27 # as published by the Free Software Foundation; either version 2
28 # of the License, or (at your option) any later version.
30 # This program is distributed in the hope that it will be useful,
31 # but WITHOUT ANY WARRANTY; without even the implied warranty of
32 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 # GNU General Public License for more details.
35 # You should have received a copy of the GNU General Public License
36 # along with this program; if not, write to the Free Software
37 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
40 # Uncomment for development purposes (increase loading time)
45 print STDERR
"Usage: $0 <servername> <instance>\n";
46 print STDERR
" $0 <servername> list\n";
51 my ($LOG_PATH, $MAX_AGE, $STALL_CMD, $READ_CHNK, $MAX_READ);
53 ## All CONFIG VARIABLES defined HERE
55 # This is the place where logs files are dropped
56 $LOG_PATH = '/var/log/cacti';
58 # If last line's timestamp is older that this (seconds), no data will be
59 # returned. Additionally, if $STALL_CMD is defined it will be run. Set it to 0
60 # or comment out the line to avoid this check.
61 $MAX_AGE = 120; # 2 minutes.
63 # This is a script that will be run if the log is stall for more than $MAX_AGE
64 # seconds (It's up to you to make good use of this). Have no effect if
65 # $MAX_AGE isn't defined. The original idea was to have a Nagios event handler
66 # restart the counter when stall (Implement it the way you like though).
67 $STALL_CMD = "/usr/local/nagios/libexec/eventhandlers/notify_stall_counter $ARGV[0]";
69 # This is the maximum read size for each read. Optimal performance can be
70 # obtained by setting this to the smallest number higher than your usual
71 # line length. THIS MUST BE A 512-BYTES MULTIPLE!! I.e. 512, 1024, 8192, 8704
72 # are all valid numbers.
73 $READ_CHNK = 512 * 2; # 1024 bytes
75 # Maximum buffered read size (Will stop reading lines longer than this!)
76 $MAX_READ = 1024 * 512; # 512KiB
78 ## END of CONFIG VARIABLES
80 my $log = "$LOG_PATH/$ARGV[0].csv";
81 die "File not found: $log" unless (-f
$log);
83 # Since we can't mix buffered and system reads, we'll do only system reads.
84 open (LOG
, "<$log") or die "Cannot open $log for reading :$!";
86 my $header = get_head
() or die ("Failed to read first line!");
87 my $last = get_tail
() or die ("Failed to read last line!");
91 # Strip the first column (we'll do the same to get the date field)
92 subst_col
(0, \
$header);
94 if ($ARGV[1] eq 'list') {
95 print "Available counters:\n";
96 while (defined(my $col = subst_col
(0, \
$header))) {
97 $col = substr ($col, index ($col, "\\", 3));
104 my $datestr = subst_col
(0, \
$last);
106 my $diff = datediff
($datestr);
107 die ("Couldn't parse date string '$datestr'") unless (defined($diff));
108 if ($diff > $MAX_AGE) {
110 # Run $STALL_CMD and exit, but don't block the Cacti poller!
112 system($STALL_CMD) if ($ret == 0);
118 my $index = find_index
($ARGV[1], $header);
119 die ("No matching index '$ARGV[1]'") unless (defined($index) && $index >= 0);
121 my $value = subst_col
($index, \
$last);
122 # Only check for definition; allow returning empty strings and zeros!
123 die ("Column $index not found!") unless (defined($value));
131 # Make sure we're at the beginning
134 # Loop until we get a newline
135 while (my $ret = sysread (LOG
, my $read, $READ_CHNK)) {
138 last if (($marker = index ($buf, "\n")) > 0);
139 last if (length ($buf) > $MAX_READ);
142 # Return the first line if we got one
144 $buf = substr ($buf, 0, $marker);
154 my $length = (stat(LOG
))[7]; # Size in bytes
156 # Try to read up to $READ_CHNK bytes at time, but make sure we read at
157 # 512-bytes boundaries. THIS IS TRICKY, don't change this unless you
158 # know what you're doing!
159 my $start = (int($length / 512) * 512);
160 if ($start >= $READ_CHNK && $start == $length) {
161 $start -= $READ_CHNK;
162 } elsif ($start >= $READ_CHNK) {
163 $start -= ($READ_CHNK - 512);
168 sysseek (LOG
, $start, 0);
169 while (sysread (LOG
, my $read, $READ_CHNK) > 0) {
172 if (($marker = index ($buf, "\n")) >= 0 && $marker != length ($buf) - 1) {
173 # Make sure we got the last newline
174 while ((my $tmpmark = index ($buf, "\n", $marker + 1)) > $marker) {
176 last if ($tmpmark == length ($buf) - 1);
181 last if (length ($buf) > $MAX_READ);
182 last if (($start -= $READ_CHNK) < 0);
183 sysseek (LOG
, $start, 0);
186 # Return the last line if we got one
188 $buf = substr ($buf, $marker + 1);
200 while ($line && defined(my $col = subst_col
(0, \
$line))) {
201 # Skip over the server name (\\name)
202 $col = substr ($col, index ($col, "\\", 3));
203 return $i if ($col eq $colname);
210 # Fetch the column indicated by $colnum and remove the scanned part from
211 # $lineref (this allow faster scanning by find_index).
212 # NOTE: CSV does not require delimiters on numeric values; but since Windows
213 # doesn't do that anyways it's not supported here. Could be easy to add
219 for (my $i = 0, my $marker = 0; $i <= $colnum; $i++, $marker = 0) {
220 my $delim = index ($$lineref, ',', $marker);
222 # this is the last column?
224 $delim = length ($$lineref) - 1;
226 # Possible infinite loop is you leave data in there
229 $curr = substr ($$lineref, 0, $delim);
231 # Look for starting and ending double-quotes...
232 if (index ($curr, '"', 0) != 0 || index ($curr, '"', length ($curr) - 1) != length ($curr) - 1) {
233 # The field isn't properly delimited; try next comma and hope for the best
234 $marker = $delim + 1;
235 # Continue while there's still data to parse
240 # We're done, extract the current column
241 $col = substr ($curr, 1, length($curr) - 2) if ($curr);
242 $$lineref = substr ($$lineref, $delim + 1) if ($$lineref);
245 last unless ($$lineref);
248 $$lineref = substr ($$lineref, $delim + 1);
256 # Date string to time diff. Ex. string: "01/22/2008 07:49:19.798"
259 $datestr =~ m
#^(\d{2})/(\d{2})/(\d{4})\s(\d{2}):(\d{2}):(\d{2})\.(?:\d{3})$#;
260 my ($month, $day, $year, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6);
261 return undef if(!defined($month) || !defined($day) || !defined($year) || !defined($hour) || !defined($min) || !defined($sec));
263 my ($nowsec, $nowmin, $nowhour, $nowday, $nowmon, $nowyear) = localtime();
264 $nowmon++; $nowyear += 1900;
266 # Seconds out from Google Calculator. Those are rounded averages; we
267 # don't care about precision as we really shouldn't need to exceed one
268 # day. We care about correctness though (i.e. same date last month is
270 my $diff = ($nowyear-$year)*31556926
271 +($nowmon-$month)*2629744
272 +($nowday-$day)*86400
273 +($nowhour-$hour)*3600