From 65081a0c018447a551c113252995dc36766f1af3 Mon Sep 17 00:00:00 2001 From: Thomas Guyot-Sionnest Date: Thu, 19 Nov 2009 12:16:04 -0500 Subject: [PATCH] Initial rewrite of getlog in C --- cacti/win_perf/getlog.c | 129 +++++++++++++++++++ cacti/win_perf/getlog.h | 47 +++++++ cacti/win_perf/getlog_base.c | 290 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 466 insertions(+) create mode 100755 cacti/win_perf/getlog.c create mode 100755 cacti/win_perf/getlog.h create mode 100755 cacti/win_perf/getlog_base.c diff --git a/cacti/win_perf/getlog.c b/cacti/win_perf/getlog.c new file mode 100755 index 0000000..45d717e --- /dev/null +++ b/cacti/win_perf/getlog.c @@ -0,0 +1,129 @@ +/***************************************************************************** +* +* Getlog - Windows CSV Performance Data Log Parser for Cacti +* +* Version v.0.1 +* +* License: GPL +* Copyright (c) 2009 Thomas Guyot-Sionnest +* +* This program reads Windows performance logs (normally saved to a samba share) +* and returns the last value for a given counter in a format suitable for +* Cacti. +* +* Configuration is done statically - see below (~ 2 pages down) for options. +* +* Usage: getlog +* getlog list +* Ex: ./getlog SRV6 '\LogicalDisk(C:)\% Disk Time' +* ./getlog SRV6 "\\LogicalDisk(C:)\\% Disk Time" +* Output: Cacti data format (or nothing). Errors goes to STDERR. +* +* Log files should be saved under LOG_PATH and named ".csv" +* Everything is CaSe-SenSITive! +* +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*****************************************************************************/ + +#include "getlog.h" +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) { + char *log = NULL; + char *header, *last, *col, *datestr, *value; + int fd, idx; + + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, " %s list\n", argv[0]); + exit(1); + } + + + /* Open the log and get the first and last lines */ + log = malloc(strlen(LOG_PATH)+strlen(argv[1])+6); + sprintf(log, "%s/%s.csv", LOG_PATH, argv[1]); + + if ((fd = open(log, O_RDONLY)) == -1) { + perror("open"); + exit(1); + } + + if ((header = get_head(fd)) == NULL) { + fprintf(stderr, "Failed to read first line!\n"); + exit(1); + } + if ((last = get_tail(fd)) == NULL) { + fprintf(stderr, "Failed to read last line!\n"); + exit(1); + } + close(fd); + + /* Strip the first column (we'll do the same to get the date field) */ + subst_col(0, &header); + + if (strcmp(argv[2], "list") == 0) { + printf("Available counters:\n"); + while ((col = subst_col(0, &header)) != NULL) { + col+= 2; + if ((col = strchr(col, '\\')) == NULL) { + fprintf(stderr, "Invalid collumn header\n"); + exit(1); + } + printf("%s\n", col); +#ifdef MALLOC_FREE + free(col); +#endif + } + exit(0); + } + + /* Check the date */ + datestr = subst_col(0, &last); +#if 0 + if (MAX_AGE > 0) { + my $diff = datediff($datestr); + die ("Couldn't parse date string '$datestr'") unless (defined($diff)); + if ($diff > $MAX_AGE) { + if ($STALL_CMD) { + /* Run STALL_CMD and exit, but don't block the Cacti poller! */ + my $ret = fork; + system($STALL_CMD) if ($ret == 0); + } + exit; + } + } +#endif + + if ((idx = find_index(argv[2], header)) == -1) { + fprintf(stderr, "No matching index: '%s'\n", argv[2]); + exit(1); + } + + if ((value = subst_col(idx, &last)) == NULL) { + fprintf(stderr, "Column '%s' not found!", idx); + exit(1); + } + + printf("%s\n", value); + exit(0); +} + diff --git a/cacti/win_perf/getlog.h b/cacti/win_perf/getlog.h new file mode 100755 index 0000000..2029eb6 --- /dev/null +++ b/cacti/win_perf/getlog.h @@ -0,0 +1,47 @@ +/* getlog.h - common parameters for getlog */ + +/* ***** CONFIG ***** */ + +/* This is the place where logs files are dropped */ +#define LOG_PATH "/var/log/cacti" + +/* If last line's timestamp is older that this (seconds), no data will be + * returned. Additionally, if STALL_CMD is defined it will be run. Set it + * to 0 to avoid this check. */ +#define MAX_AGE 120 /* 2 minutes */ + +/* This is a script that will be run if the log is stall for more than + * MAX_AGE seconds (It's up to you to make good use of this). Have no + * effect if MAX_AGE isn't positive. The original idea was to have a Nagios + * event handler restart the counter when stall (Implement it the way you + * like though). */ +#define STALL_CMD "/usr/local/nagios/libexec/eventhandlers/notify_stall_counter $ARGV[0]" + +/* This is the maximum read size for each read. Optimal performance can be + * obtained by setting this to the smallest number higher than your usual + * line length. THIS MUST BE A 512-BYTES MULTIPLE!! I.e. 512, 1024, 8192, 8704 + * are all valid numbers. + * + * The current implementation limit this value to 32256 */ +#define READ_CHNK 512 * 2 /* 1024 bytes */ + +/* Maximum buffered read size (Will stop reading lines longer than this!) */ +#define MAX_READ 1024 * 512 /* 512KiB */ + +/* Define MALLOC_FREE if you want to free all dynamically allocated memory. + * Normally, the OS does a better job at is when the process exits */ +/* #define MALLOC_FREE */ + + +/* ***** PROTOTYPES ***** */ + +char *get_head(int); +char *get_tail(int); +int find_index(const char *, char *); +char *subst_col(int, char **); +int datediff(const char*); +int myatoi(const char*); + + +/* End of getlog.h */ + diff --git a/cacti/win_perf/getlog_base.c b/cacti/win_perf/getlog_base.c new file mode 100755 index 0000000..d900348 --- /dev/null +++ b/cacti/win_perf/getlog_base.c @@ -0,0 +1,290 @@ +/***************************************************************************** +* +* Getlog base library +* +* License: GPL +* Copyright (c) 2009 Thomas Guyot-Sionnest +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*****************************************************************************/ + +#include "getlog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Get the first line of the file... easy stuff */ +char *get_head(int log) { + char readbuf[READ_CHNK]; + int read_sz; + char *buf = NULL; + int buf_sz = 0; + char *tmp; + + /* Make sure we're at the beginning */ + lseek(log, 0, SEEK_SET); + + /* Loop until we get a newline */ + while (read_sz = read(log, readbuf, READ_CHNK) > 0) { + if ((buf = realloc(buf, buf_sz+read_sz)) == NULL) { + fprintf(stderr, "malloc failed in get_head()\n"); + exit(3); + } + memcpy(buf+buf_sz, readbuf, read_sz); + buf_sz += read_sz; + + /* Terminate buf as a string if we reached end of line */ + if ((tmp = memchr(buf, '\n', buf_sz)) != NULL) { + if (tmp[-1] == '\r') tmp--; + tmp[0] = '\0'; + break; + } + if (buf_sz >= MAX_READ) break; /* endless line? */ + } + if (read_sz == -1) { + perror("read"); + exit(3); + } + + /* return whatever line de got, or NULL */ + if (tmp) { +#ifdef MALLOC_FREE + tmp = strdup(buf); + free(buf); + buf = tmp; +#endif + return strdup(buf); + } + return NULL; +} + +/* Get the last line of the file, reading backwards to make this quick on + * large log files. */ +char *get_tail(int log) { + char readbuf[READ_CHNK]; + int read_sz; + char *buf = NULL; + char *tmpbuf; + int buf_sz = 0; + char *tmp1, *tmp2; + off_t length, start; + struct stat sb; + + if (fstat(log, &sb) == -1) { + perror("fstat"); + exit(3); + } + length = sb.st_size; /* Size in bytes */ + + /* Try to read up to READ_CHNK bytes at time, but make sure we read at + * 512-bytes boundaries. THIS IS TRICKY, don't change this unless you + * know what you're doing! */ + start = (length / 512) * 512; + if (start >= READ_CHNK && start == length) { + start -= READ_CHNK; + } else if (start >= READ_CHNK) { + start -= READ_CHNK - 512; + } else { + start = 0; + } + + /* Set our position and start reading */ + lseek(log, start, SEEK_SET); + while (read_sz = read(log, readbuf, READ_CHNK)) { + if ((buf = realloc(buf, buf_sz+read_sz)) == NULL) { + fprintf(stderr, "malloc failed in get_head()\n"); + exit(3); + } + + /* Prepend to buffer - straight memcpy() if memory don't overlap */ + if (read_sz == READ_CHNK) + memcpy(buf, buf+READ_CHNK, buf_sz); + else + memmove(buf, buf+read_sz, buf_sz); + memcpy(buf, readbuf, read_sz); + buf_sz += read_sz; + + /* Terminate buf as a string if we got a full line */ + if ((tmp1 = memchr(buf, '\n', buf_sz)) != NULL) { + if (tmp1 == buf+buf_sz) continue; /* last newline only */ + + /* Make sure we got the last line */ + while ((tmp2 = memchr(tmp1+1, '\n', buf_sz-(tmp1-buf-1))) != NULL) { + if (tmp2 == buf+buf_sz) continue; + tmp1 = tmp2; + } + + /* terminate tmp2 such as tmp1 becomes a string */ + if (tmp2[-1] == '\r') tmp2--; + tmp2[0] = '\0'; + break; + } + + if (buf_sz >= MAX_READ) break; /* endless line? */ + if ((start -= READ_CHNK) < 0) break; + lseek(log, start, SEEK_SET); + } + + /* Return the last line if we got one */ + if (tmp1 && tmp2 && tmp2 - tmp1 > 0) { +#ifdef MALLOC_FREE + tmp2 = strdup(tmp1); + free(buf); + tmp1 = tmp2; +#endif + return strdup(tmp1); + } + return NULL; +} + +/* Return position of index `colname` on `line`. */ +int find_index(const char *colname, char *line) { + char *col; + int i = 0; + + while (line && (col = subst_col(0, &line)) != NULL) { + /* Skip over the server name (\\name) */ + col = strchr(col+3, '\\'); + if (strcmp(col, colname) == 0) return i; + i++; + } + return -1; +} + +/* + * Fetch the column indicated by colnum and remove the scanned part from + * lineref (this allow faster scanning by find_index() ). + * NOTE: CSV does not require delimiters on numeric values; but since Windows + * doesn't do that anyways it's not supported here. Could be easy to add + * though... + */ +char *subst_col(int colnum, char **lineref) { + char *col, *delim; + int i; + + for (i=0; i <= colnum; i++) { + delim = strchr(*lineref, ','); + + if (delim == NULL) { /* this is the last column? */ + col = strdup(*lineref); + /* Possible infinite loop is you leave data in there ?*/ + //*lineref = NULL; + } else { + col = malloc(delim-*lineref+1); + strncpy(col, *lineref, delim-*lineref); + col[delim-*lineref] = '\0'; + } + /* Look for starting and ending double-quotes... */ + if (col[0] != '"' || col[strlen(col)] != '"') { + /* The field isn't properly delimited; try next comma and hope for the best */ + if (!delim) + return NULL; + } + *lineref = delim + 1; + + if (i == colnum) /* We're done, extract the current column */ + break; +#ifdef MALLOC_FREE + free(col); +#else + col = NULL; +#endif + } + return col; +} + +/* Date string to time diff. Ex. string: "01/22/2008 07:49:19.798" */ +int datediff(const char *datestr) { + char *array[7]; + char *tmp; + char dup[25]; + int i, month, day, year, hour, min, sec; + time_t *now; + struct tm *tmnow; + + if (strlen(datestr) != 23) + return -1; + + strncpy(dup, datestr, 24); + dup[23] = dup[24] = '\0'; + tmp = array[0] = dup; + + /* Loop over the string and replace separators with NULLs */ + for (i=1; i<=6; i++) { + if ((tmp = strpbrk(tmp, "/ :.")) == NULL) + return -1; + + tmp[0] = '\0'; + tmp++; + array[i] = tmp; + } + + month = myatoi(array[0]); + day = myatoi(array[1]); + year = myatoi(array[2]); + hour = myatoi(array[3]); + min = myatoi(array[4]); + sec = myatoi(array[5]); + + *now = time(NULL); + tmnow = localtime(now); + + tmnow->tm_mon++; + tmnow->tm_year += 1900; + + /* Seconds out from Google Calculator. Those are rounded averages; we + * don't care about precision as we really shouldn't need to exceed one + * day. We care about correctness though (i.e. same date last month is + * NOT correct). */ + return (tmnow->tm_year-year)*31556926 + +(tmnow->tm_mon-month)*2629744 + +(tmnow->tm_mday-day)*86400 + +(tmnow->tm_hour-hour)*3600 + +(tmnow->tm_min-min)*60 + +(tmnow->tm_sec-sec); +} + +/* like atoi with error checking */ +int myatoi(const char *num) { + char *endptr; + long val; + + errno = 0; + val = strtol(num, &endptr, 10); + + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || (errno != 0 && val == 0)) { + perror("strtol"); + exit(3); + } + if (val >= INT_MAX || val < 0) { + fprintf(stderr, "Number our of bounds\n"); + exit(3); + } + + if (endptr == num) { + fprintf(stderr, "No digits were found\n"); + exit(EXIT_FAILURE); + } + + return (int)val; +} + -- 2.11.4.GIT