blob: 8c339266b7865359aae780374fe4fa4bdc1b4a0f [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "ink_unused.h"
// Includes and namespaces etc.
#include "LogStandalone.cc"
#include "LogObject.h"
#include "hdrs/HTTP.h"
#include <math.h>
#include <sys/utsname.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <string>
#include <algorithm>
#include <vector>
#define _BACKWARD_BACKWARD_WARNING_H // needed for gcc 4.3
#include <ext/hash_map>
#include <ext/hash_set>
#undef _BACKWARD_BACKWARD_WARNING_H
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif
#include <fcntl.h>
using namespace __gnu_cxx;
// Constants, please update the VERSION number when you make a new build!!!
#define PROGRAM_NAME "traffic_logstats"
const int MAX_LOGBUFFER_SIZE = 65536;
const int DEFAULT_LINE_LEN = 78;
const double LOG10_1024 = 3.0102999566398116;
// Optimizations for "strcmp()", treat some fixed length (3 or 4 bytes) strings
// as integers.
const int GET_AS_INT = 5522759;
const int PUT_AS_INT = 5526864;
const int HEAD_AS_INT = 1145128264;
const int POST_AS_INT = 1414745936;
const int TEXT_AS_INT = 1954047348;
const int JPEG_AS_INT = 1734701162;
const int JPG_AS_INT = 6778986;
const int GIF_AS_INT = 6711655;
const int PNG_AS_INT = 6778480;
const int BMP_AS_INT = 7368034;
const int CSS_AS_INT = 7566179;
const int XML_AS_INT = 7105912;
const int HTML_AS_INT = 1819112552;
const int ZIP_AS_INT = 7367034;
const int JAVA_AS_INT = 1635148138; // For "javascript"
const int PLAI_AS_INT = 1767992432; // For "plain"
const int IMAG_AS_INT = 1734438249; // For "image"
const int HTTP_AS_INT = 1886680168; // For "http" followed by "s://" or "://"
// Store our "state" (position in log file etc.)
struct LastState
{
off_t offset;
ino_t st_ino;
};
LastState last_state;
// Store the collected counters and stats, per Origin Server (or total)
struct StatsCounter
{
ink64 count;
ink64 bytes;
};
struct ElapsedStats
{
int min;
int max;
float avg;
float stddev;
};
struct OriginStats
{
char *server;
StatsCounter total;
struct
{
struct
{
ElapsedStats hit;
ElapsedStats ims;
ElapsedStats refresh;
ElapsedStats other;
ElapsedStats total;
} hits;
struct
{
ElapsedStats miss;
ElapsedStats ims;
ElapsedStats refresh;
ElapsedStats other;
ElapsedStats total;
} misses;
} elapsed;
struct
{
struct
{
StatsCounter hit;
StatsCounter ims;
StatsCounter refresh;
StatsCounter other;
StatsCounter total;
} hits;
struct
{
StatsCounter miss;
StatsCounter ims;
StatsCounter refresh;
StatsCounter other;
StatsCounter total;
} misses;
struct
{
StatsCounter client_abort;
StatsCounter connect_fail;
StatsCounter invalid_req;
StatsCounter unknown;
StatsCounter other;
StatsCounter total;
} errors;
StatsCounter other;
} results;
struct
{
StatsCounter c_000; // Bad
StatsCounter c_200;
StatsCounter c_204;
StatsCounter c_206;
StatsCounter c_2xx;
StatsCounter c_301;
StatsCounter c_302;
StatsCounter c_304;
StatsCounter c_3xx;
StatsCounter c_400;
StatsCounter c_403;
StatsCounter c_404;
StatsCounter c_4xx;
StatsCounter c_501;
StatsCounter c_502;
StatsCounter c_503;
StatsCounter c_5xx;
StatsCounter c_999; // YDoD, very bad
} codes;
struct
{
StatsCounter direct;
StatsCounter none;
StatsCounter sibling;
StatsCounter parent;
StatsCounter empty;
StatsCounter invalid;
StatsCounter other;
} hierarchies;
struct
{
StatsCounter http;
StatsCounter https;
StatsCounter none;
StatsCounter other;
} schemes;
struct
{
StatsCounter get;
StatsCounter put;
StatsCounter head;
StatsCounter post;
StatsCounter del;
StatsCounter purge;
StatsCounter options;
StatsCounter none;
StatsCounter other;
} methods;
struct
{
struct
{
StatsCounter plain;
StatsCounter xml;
StatsCounter html;
StatsCounter css;
StatsCounter javascript;
StatsCounter other;
StatsCounter total;
} text;
struct
{
StatsCounter jpeg;
StatsCounter gif;
StatsCounter png;
StatsCounter bmp;
StatsCounter other;
StatsCounter total;
} image;
struct
{
StatsCounter shockwave_flash;
StatsCounter quicktime;
StatsCounter javascript;
StatsCounter zip;
StatsCounter other;
StatsCounter total;
} application;
struct
{
StatsCounter wav;
StatsCounter mpeg;
StatsCounter other;
StatsCounter total;
} audio;
StatsCounter none;
StatsCounter other;
} content;
};
///////////////////////////////////////////////////////////////////////////////
// Equal operator for char* (for the hash_map)
struct eqstr
{
inline bool operator() (const char *s1, const char *s2) const
{
return strcmp(s1, s2) == 0;
}
};
typedef hash_map < const char *, OriginStats *, hash < const char *>, eqstr > OriginStorage;
typedef hash_set < const char *, hash < const char *>, eqstr > OriginSet;
///////////////////////////////////////////////////////////////////////////////
// Globals, holding the accumulated stats (ok, I'm lazy ...)
static OriginStats totals;
static OriginStorage origins;
static OriginSet *origin_set;
static int parse_errors;
static char *hostname;
// Command line arguments (parsing)
static struct
{
char log_file[1024];
char origin_file[1024];
char origin_list[2048];
char state_tag[1024];
ink64 min_hits;
int max_age;
int line_len;
int incremental; // Do an incremental run
int tail; // Tail the log file
int ymon; // Report in ymon format
int ysar; // Report in ysar format
int summary; // Summary only
int version;
int help;
} cl;
ArgumentDescription argument_descriptions[] = {
{"help", 'h', "Give this help", "T", &cl.help, NULL, NULL},
{"log_file", 'f', "Specific logfile to parse", "S1023", cl.log_file, NULL, NULL},
{"origin_list", 'o', "Only show stats for listed Origins", "S2047", cl.origin_list, NULL, NULL},
{"origin_file", 'O', "File listing Origins to show", "S1023", cl.origin_file, NULL, NULL},
{"incremental", 'i', "Incremental log parsing", "T", &cl.incremental, NULL, NULL},
{"statetag", 'S', "Name of the state file to use", "S1023", cl.state_tag, NULL, NULL},
{"tail", 't', "Parse the last <sec> seconds of log", "I", &cl.tail, NULL, NULL},
{"summary", 's', "Only produce the summary", "T", &cl.summary, NULL, NULL},
{"ymon", 'y', "Output is formatted for YMon/Nagios", "T", &cl.ymon, NULL, NULL},
{"ysar", 'Y', "Output is formatted for YSAR", "T", &cl.ysar, NULL, NULL},
{"min_hits", 'm', "Minimum total hits for an Origin", "L", &cl.min_hits, NULL, NULL},
{"max_age", 'a', "Max age for log entries to be considered", "I", &cl.max_age, NULL, NULL},
{"line_len", 'l', "Output line length", "I", &cl.line_len, NULL, NULL},
{"debug_tags", 'T', "Colon-Separated Debug Tags", "S1023", &error_tags, NULL, NULL},
{"version", 'V', "Print Version Id", "T", &cl.version, NULL, NULL},
};
int n_argument_descriptions = SIZE(argument_descriptions);
static char *USAGE_LINE =
"Usage: " PROGRAM_NAME " [-l logfile] [-o origin[,...]] [-O originfile] [-m minhits] [-inshv]";
// Enum for YMON return code levels.
enum YmonLevel
{
YMON_OK = 0,
YMON_WARNING = 1,
YMON_CRITICAL = 2,
YMON_UNKNOWN = 3
};
// Enum for parsing a log line
enum ParseStates
{
P_STATE_ELAPSED,
P_STATE_IP,
P_STATE_RESULT,
P_STATE_CODE,
P_STATE_SIZE,
P_STATE_METHOD,
P_STATE_URL,
P_STATE_RFC931,
P_STATE_HIERARCHY,
P_STATE_PEER,
P_STATE_TYPE,
P_STATE_END
};
// Enum for HTTP methods
enum HTTPMethod
{
METHOD_GET,
METHOD_PUT,
METHOD_HEAD,
METHOD_POST,
METHOD_PURGE,
METHOD_DELETE,
METHOD_OPTIONS,
METHOD_NONE,
METHOD_OTHER
};
// Enum for URL schemes
enum URLScheme
{
SCHEME_HTTP,
SCHEME_HTTPS,
SCHEME_NONE,
SCHEME_OTHER
};
///////////////////////////////////////////////////////////////////////////////
// Initialize the elapsed field
inline void
init_elapsed(OriginStats & stats)
{
stats.elapsed.hits.hit.min = -1;
stats.elapsed.hits.ims.min = -1;
stats.elapsed.hits.refresh.min = -1;
stats.elapsed.hits.other.min = -1;
stats.elapsed.hits.total.min = -1;
stats.elapsed.misses.miss.min = -1;
stats.elapsed.misses.ims.min = -1;
stats.elapsed.misses.refresh.min = -1;
stats.elapsed.misses.other.min = -1;
stats.elapsed.misses.total.min = -1;
}
// Update the counters for one StatsCounter
inline void
update_counter(StatsCounter & counter, int size)
{
counter.count++;
counter.bytes += size;
}
inline void
update_elapsed(ElapsedStats & stat, const int elapsed, const StatsCounter & counter)
{
int newcount, oldcount;
float oldavg, newavg, sum_of_squares;
// Skip all the "0" values.
if (elapsed == 0)
return;
if (stat.min == -1)
stat.min = elapsed;
else if (stat.min > elapsed)
stat.min = elapsed;
if (stat.max < elapsed)
stat.max = elapsed;
// update_counter should have been called on counter.count before calling
// update_elapsed.
newcount = counter.count;
// New count should never be zero, else there was a programming error.
assert(newcount);
oldcount = counter.count - 1;
oldavg = stat.avg;
newavg = (oldavg * oldcount + elapsed) / newcount;
// Now find the new standard deviation from the old one
if (oldcount != 0)
sum_of_squares = (stat.stddev * stat.stddev * oldcount);
else
sum_of_squares = 0;
//Find the old sum of squares.
sum_of_squares = sum_of_squares + 2 * oldavg * oldcount * (oldavg - newavg)
+ oldcount * (newavg * newavg - oldavg * oldavg);
//Now, find the new sum of squares.
sum_of_squares = sum_of_squares + (elapsed - newavg) * (elapsed - newavg);
stat.stddev = sqrt(sum_of_squares / newcount);
stat.avg = newavg;
}
///////////////////////////////////////////////////////////////////////////////
// Update the "result" and "elapsed" stats for a particular record
inline void
update_results_elapsed(OriginStats * stat, int result, int elapsed, int size)
{
switch (result) {
case SQUID_LOG_TCP_HIT:
update_counter(stat->results.hits.hit, size);
update_counter(stat->results.hits.total, size);
update_elapsed(stat->elapsed.hits.hit, elapsed, stat->results.hits.hit);
update_elapsed(stat->elapsed.hits.total, elapsed, stat->results.hits.total);
break;
case SQUID_LOG_TCP_MISS:
update_counter(stat->results.misses.miss, size);
update_counter(stat->results.misses.total, size);
update_elapsed(stat->elapsed.misses.miss, elapsed, stat->results.misses.miss);
update_elapsed(stat->elapsed.misses.total, elapsed, stat->results.misses.total);
break;
case SQUID_LOG_TCP_IMS_HIT:
update_counter(stat->results.hits.ims, size);
update_counter(stat->results.hits.total, size);
update_elapsed(stat->elapsed.hits.ims, elapsed, stat->results.hits.ims);
update_elapsed(stat->elapsed.hits.total, elapsed, stat->results.hits.total);
break;
case SQUID_LOG_TCP_IMS_MISS:
update_counter(stat->results.misses.ims, size);
update_counter(stat->results.misses.total, size);
update_elapsed(stat->elapsed.misses.ims, elapsed, stat->results.misses.ims);
update_elapsed(stat->elapsed.misses.total, elapsed, stat->results.misses.total);
break;
case SQUID_LOG_TCP_REFRESH_HIT:
update_counter(stat->results.hits.refresh, size);
update_counter(stat->results.hits.total, size);
update_elapsed(stat->elapsed.hits.refresh, elapsed, stat->results.hits.refresh);
update_elapsed(stat->elapsed.hits.total, elapsed, stat->results.hits.total);
break;
case SQUID_LOG_TCP_REFRESH_MISS:
update_counter(stat->results.misses.refresh, size);
update_counter(stat->results.misses.total, size);
update_elapsed(stat->elapsed.misses.refresh, elapsed, stat->results.misses.refresh);
update_elapsed(stat->elapsed.misses.total, elapsed, stat->results.misses.total);
break;
case SQUID_LOG_ERR_CLIENT_ABORT:
update_counter(stat->results.errors.client_abort, size);
update_counter(stat->results.errors.total, size);
break;
case SQUID_LOG_ERR_CONNECT_FAIL:
update_counter(stat->results.errors.connect_fail, size);
update_counter(stat->results.errors.total, size);
break;
case SQUID_LOG_ERR_INVALID_REQ:
update_counter(stat->results.errors.invalid_req, size);
update_counter(stat->results.errors.total, size);
break;
case SQUID_LOG_ERR_UNKNOWN:
update_counter(stat->results.errors.unknown, size);
update_counter(stat->results.errors.total, size);
break;
case SQUID_LOG_TCP_DISK_HIT:
case SQUID_LOG_TCP_MEM_HIT:
case SQUID_LOG_TCP_REF_FAIL_HIT:
case SQUID_LOG_UDP_HIT:
case SQUID_LOG_UDP_WEAK_HIT:
case SQUID_LOG_UDP_HIT_OBJ:
update_counter(stat->results.hits.other, size);
update_counter(stat->results.hits.total, size);
update_elapsed(stat->elapsed.hits.other, elapsed, stat->results.hits.other);
update_elapsed(stat->elapsed.hits.total, elapsed, stat->results.hits.total);
break;
case SQUID_LOG_TCP_EXPIRED_MISS:
case SQUID_LOG_TCP_WEBFETCH_MISS:
case SQUID_LOG_UDP_MISS:
update_counter(stat->results.misses.other, size);
update_counter(stat->results.misses.total, size);
update_elapsed(stat->elapsed.misses.other, elapsed, stat->results.misses.other);
update_elapsed(stat->elapsed.misses.total, elapsed, stat->results.misses.total);
break;
default:
if ((result >= SQUID_LOG_ERR_READ_TIMEOUT) && (result <= SQUID_LOG_ERR_UNKNOWN)) {
update_counter(stat->results.errors.other, size);
update_counter(stat->results.errors.total, size);
} else
update_counter(stat->results.other, size);
break;
}
}
///////////////////////////////////////////////////////////////////////////////
// Update the "codes" stats for a particular record
inline void
update_codes(OriginStats * stat, int code, int size)
{
// Special case for "200", most common.
if (code == 200)
update_counter(stat->codes.c_200, size);
else if ((code > 199) && (code < 299)) {
if (code == 204)
update_counter(stat->codes.c_204, size);
else if (code == 206)
update_counter(stat->codes.c_206, size);
else
update_counter(stat->codes.c_2xx, size);
} else if ((code > 299) && (code < 399)) {
if (code == 301)
update_counter(stat->codes.c_301, size);
else if (code == 302)
update_counter(stat->codes.c_302, size);
else if (code == 304)
update_counter(stat->codes.c_304, size);
else
update_counter(stat->codes.c_3xx, size);
} else if ((code > 399) && (code < 499)) {
if (code == 400)
update_counter(stat->codes.c_400, size);
else if (code == 403)
update_counter(stat->codes.c_403, size);
else if (code == 404)
update_counter(stat->codes.c_404, size);
else
update_counter(stat->codes.c_4xx, size);
} else if ((code > 499) && (code < 599)) {
if (code == 501)
update_counter(stat->codes.c_501, size);
else if (code == 502)
update_counter(stat->codes.c_502, size);
else if (code == 503)
update_counter(stat->codes.c_503, size);
else
update_counter(stat->codes.c_5xx, size);
} else if (code == 999)
update_counter(stat->codes.c_999, size);
else if (code == 0)
update_counter(stat->codes.c_000, size);
}
///////////////////////////////////////////////////////////////////////////////
// Update the "methods" stats for a particular record
inline void
update_methods(OriginStats * stat, int method, int size)
{
// We're so loppsides on GETs, so makes most sense to test 'out of order'.
if (method == METHOD_GET)
update_counter(stat->methods.get, size);
else if (method == METHOD_PUT)
update_counter(stat->methods.put, size);
else if (method == METHOD_HEAD)
update_counter(stat->methods.head, size);
else if (method == METHOD_POST)
update_counter(stat->methods.post, size);
else if (method == METHOD_DELETE)
update_counter(stat->methods.del, size);
else if (method == METHOD_PURGE)
update_counter(stat->methods.purge, size);
else if (method == METHOD_OPTIONS)
update_counter(stat->methods.options, size);
else if (method == METHOD_NONE)
update_counter(stat->methods.none, size);
else
update_counter(stat->methods.other, size);
}
///////////////////////////////////////////////////////////////////////////////
// Update the "schemes" stats for a particular record
inline void
update_schemes(OriginStats * stat, int scheme, int size)
{
if (scheme == SCHEME_HTTP)
update_counter(stat->schemes.http, size);
else if (scheme == SCHEME_HTTPS)
update_counter(stat->schemes.https, size);
else if (scheme == SCHEME_NONE)
update_counter(stat->schemes.none, size);
else
update_counter(stat->schemes.other, size);
}
///////////////////////////////////////////////////////////////////////////////
// Parse a log buffer
int
parse_log_buff(LogBufferHeader * buf_header, bool summary = false)
{
static char *str_buf = NULL;
static LogFieldList *fieldlist = NULL;
LogEntryHeader *entry;
LogBufferIterator buf_iter(buf_header);
LogField *field;
OriginStorage::iterator o_iter;
ParseStates state;
char *read_from;
char *tok;
char *ptr;
int tok_len;
int flag = 0; // Flag used in state machine to carry "state" forward
// Parsed results
int http_code = 0, size = 0, result = 0, hier = 0, elapsed = 0;
OriginStats *o_stats;
char *o_server;
HTTPMethod method;
URLScheme scheme;
// Initialize some "static" variables.
if (str_buf == NULL) {
str_buf = (char *) xmalloc(LOG_MAX_FORMATTED_LINE);
if (str_buf == NULL)
return 0;
}
if (!fieldlist) {
fieldlist = NEW(new LogFieldList);
ink_assert(fieldlist != NULL);
bool agg = false;
LogFormat::parse_symbol_string(buf_header->fmt_fieldlist(), fieldlist, &agg);
}
// Loop over all entries
while ((entry = buf_iter.next())) {
read_from = (char *) entry + sizeof(LogEntryHeader);
// We read and skip over the first field, which is the timestamp.
if ((field = fieldlist->first()))
read_from += MIN_ALIGN;
else // This shouldn't happen, buffer must be messed up.
break;
state = P_STATE_ELAPSED;
o_stats = NULL;
o_server = NULL;
method = METHOD_OTHER;
scheme = SCHEME_OTHER;
while ((field = fieldlist->next(field))) {
switch (state) {
case P_STATE_ELAPSED:
state = P_STATE_IP;
elapsed = ntohl(*((LOG_INT *) (read_from)));
read_from += MIN_ALIGN;
break;
case P_STATE_IP:
state = P_STATE_RESULT;
// Just skip the IP, we no longer assume it's always the same.
//
// TODO address IP logged in text format (that's not good)
// Warning: This is maybe not IPv6 safe.
read_from += LogAccess::strlen(read_from);
break;
case P_STATE_RESULT:
state = P_STATE_CODE;
result = ntohl(*((LOG_INT *) (read_from)));
read_from += MIN_ALIGN;
if ((result<32) || (result> 255)) {
flag = 1;
state = P_STATE_END;
}
break;
case P_STATE_CODE:
state = P_STATE_SIZE;
http_code = ntohl(*((LOG_INT *) (read_from)));
read_from += MIN_ALIGN;
if ((http_code<0) || (http_code> 999)) {
flag = 1;
state = P_STATE_END;
}
//printf("CODE == %d\n", http_code);
break;
case P_STATE_SIZE:
// Warning: This is not 64-bit safe, when converting the log format,
// this needs to be fixed as well.
state = P_STATE_METHOD;
size = ntohl(*((LOG_INT *) (read_from)));
read_from += MIN_ALIGN;
//printf("Size == %d\n", size)
break;
case P_STATE_METHOD:
state = P_STATE_URL;
//printf("METHOD == %s\n", read_from);
flag = 0;
// Small optimization for common (3-4 char) cases
switch (*reinterpret_cast < int *>(read_from)) {
case GET_AS_INT:
method = METHOD_GET;
read_from += LogAccess::round_strlen(3 + 1);
break;
case PUT_AS_INT:
method = METHOD_PUT;
read_from += LogAccess::round_strlen(3 + 1);
break;
case HEAD_AS_INT:
method = METHOD_HEAD;
read_from += LogAccess::round_strlen(4 + 1);
break;
case POST_AS_INT:
method = METHOD_POST;
read_from += LogAccess::round_strlen(4 + 1);
break;
default:
tok_len = strlen(read_from);
if ((tok_len == 5) && (strncmp(read_from, "PURGE", 5) == 0))
method = METHOD_PURGE;
else if ((tok_len == 6) && (strncmp(read_from, "DELETE", 6) == 0))
method = METHOD_DELETE;
else if ((tok_len == 7) && (strncmp(read_from, "OPTIONS", 7) == 0))
method = METHOD_OPTIONS;
else if ((tok_len == 1) && (*read_from == '-')) {
method = METHOD_NONE;
flag = 1; // No method, so no need to parse the URL
} else {
ptr = read_from;
while (*ptr && isupper(*ptr))
++ptr;
// Skip URL if it doesn't look like an HTTP method
if (*ptr != '\0')
flag = 1;
}
read_from += LogAccess::round_strlen(tok_len + 1);
break;
}
break;
case P_STATE_URL:
state = P_STATE_RFC931;
//printf("URL == %s\n", tok);
// TODO check for read_from being empty string
if (flag == 0) {
tok = read_from;
if (*reinterpret_cast < int *>(tok) == HTTP_AS_INT) {
tok += 4;
if (*tok == ':') {
scheme = SCHEME_HTTP;
tok += 3;
tok_len = strlen(tok) + 7;
} else if (*tok == 's') {
scheme = SCHEME_HTTPS;
tok += 4;
tok_len = strlen(tok) + 8;
} else
tok_len = strlen(tok) + 4;
} else {
if (*tok == '/')
scheme = SCHEME_NONE;
tok_len = strlen(tok);
}
//printf("SCHEME = %d\n", scheme);
if (*tok == '/') // This is to handle crazy stuff like http:///origin.com
tok++;
ptr = strchr(tok, '/');
if (ptr && !summary) // Find the origin
{
*ptr = '\0';
if (origin_set ? (origin_set->find(tok) != origin_set->end()) : 1) {
//printf("ORIGIN = %s\n", tok);
o_iter = origins.find(tok);
if (o_iter == origins.end()) {
o_stats = (OriginStats *) xmalloc(sizeof(OriginStats));
init_elapsed(*o_stats);
o_server = xstrdup(tok);
if (o_stats && o_server) {
o_stats->server = o_server;
origins[o_server] = o_stats;
}
} else
o_stats = o_iter->second;
}
}
} else {
// No method given
if (*read_from == '/')
scheme = SCHEME_NONE;
tok_len = strlen(read_from);
}
read_from += LogAccess::round_strlen(tok_len + 1);
// Update the stats so far, since now we have the Origin (maybe)
update_results_elapsed(&totals, result, elapsed, size);
update_codes(&totals, http_code, size);
update_methods(&totals, method, size);
update_schemes(&totals, scheme, size);
update_counter(totals.total, size);
if (o_stats != NULL) {
update_results_elapsed(o_stats, result, elapsed, size);
update_codes(o_stats, http_code, size);
update_methods(o_stats, method, size);
update_schemes(o_stats, scheme, size);
update_counter(o_stats->total, size);
}
break;
case P_STATE_RFC931:
state = P_STATE_HIERARCHY;
if (*read_from == '-')
read_from += LogAccess::round_strlen(1 + 1);
else
read_from += LogAccess::strlen(read_from);
break;
case P_STATE_HIERARCHY:
state = P_STATE_PEER;
hier = ntohl(*((LOG_INT *) (read_from)));
switch (hier) {
case SQUID_HIER_NONE:
update_counter(totals.hierarchies.none, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.none, size);
break;
case SQUID_HIER_DIRECT:
update_counter(totals.hierarchies.direct, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.direct, size);
break;
case SQUID_HIER_SIBLING_HIT:
update_counter(totals.hierarchies.sibling, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.sibling, size);
break;
case SQUID_HIER_PARENT_HIT:
update_counter(totals.hierarchies.parent, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.direct, size);
break;
case SQUID_HIER_EMPTY:
update_counter(totals.hierarchies.empty, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.empty, size);
break;
default:
if ((hier >= SQUID_HIER_EMPTY) && (hier < SQUID_HIER_INVALID_ASSIGNED_CODE)) {
update_counter(totals.hierarchies.other, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.other, size);
} else {
update_counter(totals.hierarchies.invalid, size);
if (o_stats != NULL)
update_counter(o_stats->hierarchies.invalid, size);
}
break;
}
read_from += MIN_ALIGN;
break;
case P_STATE_PEER:
state = P_STATE_TYPE;
if (*read_from == '-')
read_from += LogAccess::round_strlen(1 + 1);
else
read_from += LogAccess::strlen(read_from);
break;
case P_STATE_TYPE:
state = P_STATE_END;
//printf("TYPE == %s\n", read_from);
if (*reinterpret_cast < int *>(read_from) == IMAG_AS_INT) {
update_counter(totals.content.image.total, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.total, size);
tok = read_from + 6;
//printf("SUBTYPE == %s\n", tok);
switch (*reinterpret_cast < int *>(tok)) {
case JPEG_AS_INT:
tok_len = 10;
update_counter(totals.content.image.jpeg, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.jpeg, size);
break;
case JPG_AS_INT:
tok_len = 9;
update_counter(totals.content.image.jpeg, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.jpeg, size);
break;
case GIF_AS_INT:
tok_len = 9;
update_counter(totals.content.image.gif, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.gif, size);
break;
case PNG_AS_INT:
tok_len = 9;
update_counter(totals.content.image.png, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.png, size);
break;
case BMP_AS_INT:
tok_len = 9;
update_counter(totals.content.image.bmp, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.bmp, size);
break;
default:
tok_len = 6 + strlen(tok);
update_counter(totals.content.image.other, size);
if (o_stats != NULL)
update_counter(o_stats->content.image.other, size);
break;
}
} else if (*reinterpret_cast < int *>(read_from) == TEXT_AS_INT) {
tok = read_from + 5;
//printf("SUBTYPE == %s\n", tok);
update_counter(totals.content.text.total, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.total, size);
switch (*reinterpret_cast < int *>(tok)) {
case JAVA_AS_INT:
// TODO verify if really "javascript"
tok_len = 15;
update_counter(totals.content.text.javascript, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.javascript, size);
break;
case CSS_AS_INT:
tok_len = 8;
update_counter(totals.content.text.css, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.css, size);
break;
case XML_AS_INT:
tok_len = 8;
update_counter(totals.content.text.xml, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.xml, size);
break;
case HTML_AS_INT:
tok_len = 9;
update_counter(totals.content.text.html, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.html, size);
break;
case PLAI_AS_INT:
tok_len = 10;
update_counter(totals.content.text.plain, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.plain, size);
break;
default:
tok_len = 5 + strlen(tok);;
update_counter(totals.content.text.other, size);
if (o_stats != NULL)
update_counter(o_stats->content.text.other, size);
break;
}
} else if (strncmp(read_from, "application", 11) == 0) {
// TODO optimize token acquisition
tok = read_from + 12;
//printf("SUBTYPE == %s\n", tok);
update_counter(totals.content.application.total, size);
if (o_stats != NULL)
update_counter(o_stats->content.application.total, size);
if (strcmp(tok, "x-shockwave-flash") == 0) {
tok_len = 29;
update_counter(totals.content.application.shockwave_flash, size);
if (o_stats != NULL)
update_counter(o_stats->content.application.shockwave_flash, size);
} else if (strcmp(tok, "x-javascript") == 0) {
tok_len = 24;
update_counter(totals.content.application.javascript, size);
if (o_stats != NULL)
update_counter(o_stats->content.application.javascript, size);
} else if (strcmp(tok, "x-quicktimeplayer") == 0) {
tok_len = 29;
update_counter(totals.content.application.quicktime, size);
if (o_stats != NULL)
update_counter(o_stats->content.application.quicktime, size);
} else if (*reinterpret_cast < int *>(tok) == ZIP_AS_INT) {
tok_len = 15;
update_counter(totals.content.application.zip, size);
if (o_stats != NULL)
update_counter(o_stats->content.application.zip, size);
} else {
tok_len = 12 + strlen(tok);
update_counter(totals.content.application.other, size);
if (o_stats != NULL)
update_counter(o_stats->content.application.other, size);
}
} else if (strncmp(read_from, "audio", 5) == 0) {
// TODO use strcmp()
tok = read_from + 6;
tok_len = 6 + strlen(tok);
update_counter(totals.content.audio.total, size);
if (o_stats != NULL)
update_counter(o_stats->content.audio.total, size);
//printf("SUBTYPE == %s\n", tok);
if ((strcmp(tok, "x-wav") == 0) || (strcmp(tok, "wav") == 0)) {
update_counter(totals.content.audio.wav, size);
if (o_stats != NULL)
update_counter(o_stats->content.audio.wav, size);
} else if ((strcmp(tok, "x-mpeg") == 0) || (strcmp(tok, "mpeg") == 0)) {
update_counter(totals.content.audio.mpeg, size);
if (o_stats != NULL)
update_counter(o_stats->content.audio.mpeg, size);
} else {
update_counter(totals.content.audio.other, size);
if (o_stats != NULL)
update_counter(o_stats->content.audio.other, size);
}
} else if (*read_from == '-') {
tok_len = 1;
update_counter(totals.content.none, size);
if (o_stats != NULL)
update_counter(o_stats->content.none, size);
} else {
tok_len = strlen(read_from);
update_counter(totals.content.other, size);
if (o_stats != NULL)
update_counter(o_stats->content.other, size);
}
read_from += LogAccess::round_strlen(tok_len + 1);
flag = 0; // We exited this state without errors
break;
case P_STATE_END:
// Nothing to do really
if (flag) {
parse_errors++;
}
break;
}
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Process a file (FD)
int
process_file(int in_fd, off_t offset, unsigned max_age)
{
char buffer[MAX_LOGBUFFER_SIZE];
int nread, buffer_bytes;
while (true) {
Debug("logcat", "Reading buffer ...");
buffer[0] = '\0';
unsigned first_read_size = 2 * sizeof(unsigned);
LogBufferHeader *header = (LogBufferHeader *) & buffer[0];
// Find the next log header, aligning us properly. This is not
// particularly optimal, but we shouldn't only have to do this
// once, and hopefully we'll be aligned immediately.
if (offset > 0) {
while (true) {
if (lseek(in_fd, offset, SEEK_SET) < 0)
return 1;
// read the first 8 bytes of the header, which will give us the
// cookie and the version number.
nread = read(in_fd, buffer, first_read_size);
if (!nread || nread == EOF) {
return 0;
}
// ensure that this is a valid logbuffer header
if (header->cookie && (header->cookie == LOG_SEGMENT_COOKIE)) {
offset = 0;
break;
}
offset++;
}
if (!header->cookie) {
return 0;
}
} else {
nread = read(in_fd, buffer, first_read_size);
if (!nread || nread == EOF || !header->cookie)
return 0;
// ensure that this is a valid logbuffer header
if (header->cookie != LOG_SEGMENT_COOKIE)
return 1;
}
Debug("logstats", "LogBuffer version %d, current = %d", header->version, LOG_SEGMENT_VERSION);
if (header->version != LOG_SEGMENT_VERSION)
return 1;
// read the rest of the header
unsigned second_read_size = sizeof(LogBufferHeader) - first_read_size;
nread = read(in_fd, &buffer[first_read_size], second_read_size);
if (!nread || nread == EOF)
return 1;
// read the rest of the buffer
if (header->byte_count > sizeof(buffer))
return 1;
buffer_bytes = header->byte_count - sizeof(LogBufferHeader) + 1;
if (buffer_bytes <= 0 || (unsigned int) buffer_bytes > (sizeof(buffer) - sizeof(LogBufferHeader)))
return 1;
nread = read(in_fd, &buffer[sizeof(LogBufferHeader)], buffer_bytes);
if (!nread || nread == EOF)
return 1;
// Possibly skip too old entries (the entire buffer is skipped)
if (header->high_timestamp >= max_age)
if (parse_log_buff(header, cl.summary != 0) != 0)
return 1;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Determine if this "stat" (Origin Server) is worthwhile to produce a
// report for.
inline int
use_origin(const OriginStats * stat)
{
return ((stat->total.count > cl.min_hits) && (strchr(stat->server, '.') != NULL) &&
(strchr(stat->server, '%') == NULL));
}
///////////////////////////////////////////////////////////////////////////////
// Produce a nicely formatted output for a stats collection on a stream
inline void
format_center(const char *str, std::ostream & out)
{
out << std::setfill(' ') << std::setw((cl.line_len - strlen(str)) / 2 + strlen(str)) << str << std::endl << std::endl;
}
inline void
format_int(ink64 num, std::ostream & out)
{
if (num > 0) {
ink64 mult = (ink64) pow(10, (int) (log10(num) / 3) * 3);
ink64 div;
std::stringstream ss;
ss.fill('0');
while (mult > 0) {
div = num / mult;
ss << div << std::setw(3);
num -= (div * mult);
if (mult /= 1000)
ss << std::setw(0) << ',' << std::setw(3);
}
out << ss.str();
} else
out << '0';
}
void
format_elapsed_header(std::ostream & out)
{
out << std::left << std::setw(20) << "Elapsed time stats";
out << std::right << std::setw(6) << "Min" << std::setw(10) << "Max";
out << std::right << std::setw(20) << "Avg" << std::setw(24) << "Std Deviation" << std::endl;
out << std::setw(cl.line_len) << std::setfill('-') << '-' << std::setfill(' ') << std::endl;
}
inline void
format_elapsed_line(const char *desc, const ElapsedStats & stat, std::ostream & out)
{
static char buf[64];
out << std::left << std::setw(20) << desc;
out << std::right << std::setw(6);
format_int(stat.min, out);
out << std::right << std::setw(10);
format_int(stat.max, out);
snprintf(buf, sizeof(buf), "%20.10f", stat.avg);
out << std::right << buf;
snprintf(buf, sizeof(buf), "%24.12f", stat.stddev);
out << std::right << buf << std::endl;
}
void
format_detail_header(const char *desc, std::ostream & out)
{
out << std::left << std::setw(29) << desc;
out << std::right << std::setw(15) << "Count" << std::setw(11) << "Percent";
out << std::right << std::setw(12) << "Bytes" << std::setw(11) << "Percent" << std::endl;
out << std::setw(cl.line_len) << std::setfill('-') << '-' << std::setfill(' ') << std::endl;
}
inline void
format_line(const char *desc, const StatsCounter & stat, const StatsCounter & total, std::ostream & out)
{
static char metrics[] = "KKMGTP";
static char buf[64];
int ix = (stat.bytes > 1024 ? (int) (log10(stat.bytes) / LOG10_1024) : 1);
out << std::left << std::setw(29) << desc;
out << std::right << std::setw(15);
format_int(stat.count, out);
snprintf(buf, sizeof(buf), "%10.2f%%", ((double) stat.count / total.count * 100));
out << std::right << buf;
snprintf(buf, sizeof(buf), "%10.2f%cB", stat.bytes / pow(1024, ix), metrics[ix]);
out << std::right << buf;
snprintf(buf, sizeof(buf), "%10.2f%%", ((double) stat.bytes / total.bytes * 100));
out << std::right << buf << std::endl;
}
// Little "helpers" for the vector we use to sort the Origins.
typedef pair<const char *, OriginStats *>OriginPair;
inline bool
operator<(const OriginPair & a, const OriginPair & b)
{
return a.second->total.count > b.second->total.count;
}
void
print_detail_stats(const OriginStats * stat, std::ostream & out)
{
// Cache hit/misses etc.
format_detail_header("Request Result", out);
format_line("Cache hit", stat->results.hits.hit, stat->total, out);
format_line("Cache hit IMS", stat->results.hits.ims, stat->total, out);
format_line("Cache hit refresh", stat->results.hits.refresh, stat->total, out);
format_line("Cache hit other", stat->results.hits.other, stat->total, out);
format_line("Cache hit total", stat->results.hits.total, stat->total, out);
out << std::endl;
format_line("Cache miss", stat->results.misses.miss, stat->total, out);
format_line("Cache miss IMS", stat->results.misses.ims, stat->total, out);
format_line("Cache miss refresh", stat->results.misses.refresh, stat->total, out);
format_line("Cache miss other", stat->results.misses.other, stat->total, out);
format_line("Cache miss total", stat->results.misses.total, stat->total, out);
out << std::endl;
format_line("Client aborted", stat->results.errors.client_abort, stat->total, out);
format_line("Connect failed", stat->results.errors.connect_fail, stat->total, out);
format_line("Invalid request", stat->results.errors.invalid_req, stat->total, out);
format_line("Unknown error(99)", stat->results.errors.unknown, stat->total, out);
format_line("Other errors", stat->results.errors.other, stat->total, out);
format_line("Errors total", stat->results.errors.total, stat->total, out);
out << std::setw(cl.line_len) << std::setfill('.') << '.' << std::setfill(' ') << std::endl;
format_line("Total requests", stat->total, stat->total, out);
out << std::endl << std::endl;
// HTTP codes
format_detail_header("HTTP return codes", out);
format_line("200 OK", stat->codes.c_200, stat->total, out);
format_line("204 No content", stat->codes.c_204, stat->total, out);
format_line("206 Partial content", stat->codes.c_206, stat->total, out);
format_line("2xx other success", stat->codes.c_2xx, stat->total, out);
out << std::endl;
format_line("301 Moved permanently", stat->codes.c_301, stat->total, out);
format_line("302 Found", stat->codes.c_302, stat->total, out);
format_line("304 Not modified", stat->codes.c_304, stat->total, out);
format_line("3xx other redirects", stat->codes.c_3xx, stat->total, out);
out << std::endl;
format_line("400 Bad request", stat->codes.c_400, stat->total, out);
format_line("403 Forbidden", stat->codes.c_403, stat->total, out);
format_line("404 Not found", stat->codes.c_404, stat->total, out);
format_line("4xx other client errors", stat->codes.c_4xx, stat->total, out);
out << std::endl;
format_line("501 Not implemented", stat->codes.c_501, stat->total, out);
format_line("502 Bad gateway", stat->codes.c_502, stat->total, out);
format_line("503 Service unavailable", stat->codes.c_503, stat->total, out);
format_line("5xx other server errors", stat->codes.c_5xx, stat->total, out);
out << std::endl;
format_line("999 YDoD rejection", stat->codes.c_999, stat->total, out);
format_line("000 Unknown", stat->codes.c_000, stat->total, out);
out << std::endl << std::endl;
// Origin hierarchies
format_detail_header("Origin hierarchies", out);
format_line("NONE", stat->hierarchies.none, stat->total, out);
format_line("DIRECT", stat->hierarchies.direct, stat->total, out);
format_line("SIBLING", stat->hierarchies.sibling, stat->total, out);
format_line("PARENT", stat->hierarchies.parent, stat->total, out);
format_line("EMPTY", stat->hierarchies.empty, stat->total, out);
format_line("invalid", stat->hierarchies.invalid, stat->total, out);
format_line("other", stat->hierarchies.other, stat->total, out);
out << std::endl << std::endl;
// HTTP methods
format_detail_header("HTTP Methods", out);
format_line("GET", stat->methods.get, stat->total, out);
format_line("PUT", stat->methods.put, stat->total, out);
format_line("HEAD", stat->methods.head, stat->total, out);
format_line("POST", stat->methods.post, stat->total, out);
format_line("DELETE", stat->methods.del, stat->total, out);
format_line("PURGE", stat->methods.purge, stat->total, out);
format_line("OPTIONS", stat->methods.options, stat->total, out);
format_line("none (-)", stat->methods.none, stat->total, out);
format_line("other", stat->methods.other, stat->total, out);
out << std::endl << std::endl;
// URL schemes (HTTP/HTTPs)
format_detail_header("URL Schemes", out);
format_line("HTTP (port 80)", stat->schemes.http, stat->total, out);
format_line("HTTPS (port 443)", stat->schemes.https, stat->total, out);
format_line("none", stat->schemes.none, stat->total, out);
format_line("other", stat->schemes.other, stat->total, out);
out << std::endl << std::endl;
// Content types
format_detail_header("Content Types", out);
format_line("text/javascript", stat->content.text.javascript, stat->total, out);
format_line("text/css", stat->content.text.css, stat->total, out);
format_line("text/html", stat->content.text.html, stat->total, out);
format_line("text/xml", stat->content.text.xml, stat->total, out);
format_line("text/plain", stat->content.text.plain, stat->total, out);
format_line("text/ other", stat->content.text.other, stat->total, out);
format_line("text/ total", stat->content.text.total, stat->total, out);
out << std::endl;
format_line("image/jpeg", stat->content.image.jpeg, stat->total, out);
format_line("image/gif", stat->content.image.gif, stat->total, out);
format_line("image/png", stat->content.image.png, stat->total, out);
format_line("image/bmp", stat->content.image.bmp, stat->total, out);
format_line("image/ other", stat->content.image.other, stat->total, out);
format_line("image/ total", stat->content.image.total, stat->total, out);
out << std::endl;
format_line("audio/x-wav", stat->content.audio.wav, stat->total, out);
format_line("audio/x-mpeg", stat->content.audio.mpeg, stat->total, out);
format_line("audio/ other", stat->content.audio.other, stat->total, out);
format_line("audio/ total", stat->content.audio.total, stat->total, out);
out << std::endl;
format_line("application/x-shockwave", stat->content.application.shockwave_flash, stat->total, out);
format_line("application/x-javascript", stat->content.application.javascript, stat->total, out);
format_line("application/x-quicktime", stat->content.application.quicktime, stat->total, out);
format_line("application/zip", stat->content.application.zip, stat->total, out);
format_line("application/ other", stat->content.application.other, stat->total, out);
format_line("application/ total", stat->content.application.total, stat->total, out);
out << std::endl;
format_line("none", stat->content.none, stat->total, out);
format_line("other", stat->content.other, stat->total, out);
out << std::endl << std::endl;
// Elapsed time
format_elapsed_header(out);
format_elapsed_line("Cache hit", stat->elapsed.hits.hit, out);
format_elapsed_line("Cache hit IMS", stat->elapsed.hits.ims, out);
format_elapsed_line("Cache hit refresh", stat->elapsed.hits.refresh, out);
format_elapsed_line("Cache hit other", stat->elapsed.hits.other, out);
format_elapsed_line("Cache hit total", stat->elapsed.hits.total, out);
format_elapsed_line("Cache miss", stat->elapsed.misses.miss, out);
format_elapsed_line("Cache miss IMS", stat->elapsed.misses.ims, out);
format_elapsed_line("Cache miss refresh", stat->elapsed.misses.refresh, out);
format_elapsed_line("Cache miss other", stat->elapsed.misses.other, out);
format_elapsed_line("Cache miss total", stat->elapsed.misses.total, out);
out << std::endl;
out << std::setw(cl.line_len) << std::setfill('_') << '_' << std::setfill(' ') << std::endl;
}
///////////////////////////////////////////////////////////////////////////////
// Produce metrics in YMon format for a particular Origin.
inline void
format_ymon(const char *subsys, const char *desc, const char *server, const StatsCounter & stat, std::ostream & out)
{
out << subsys << ".'" << server << "'." << desc << "_cnt=" << stat.count << " ";
out << subsys << ".'" << server << "'." << desc << "_bytes=" << stat.bytes << " ";
}
// Produce "elapsed" metrics in YMon format for a particular Origin.
inline void
format_elapsed_ymon(const char *subsys, const char *desc, const char *server, const ElapsedStats & stat,
std::ostream & out)
{
out << subsys << ".'" << server << "'." << desc << "_min=" << stat.min << " ";
out << subsys << ".'" << server << "'." << desc << "_max=" << stat.max << " ";
out << subsys << ".'" << server << "'." << desc << "_avg=" << stat.avg << " ";
out << subsys << ".'" << server << "'." << desc << "_stddev=" << stat.stddev << " ";
}
void
print_ymon_metrics(const OriginStats * stat, std::ostream & out)
{
// Results (hits, misses, errors and a total
format_ymon("result", "hit", stat->server, stat->results.hits.hit, out);
format_ymon("result", "hit_ims", stat->server, stat->results.hits.ims, out);
format_ymon("result", "hit_refresh", stat->server, stat->results.hits.refresh, out);
format_ymon("result", "hit_other", stat->server, stat->results.hits.other, out);
format_ymon("result", "hit_total", stat->server, stat->results.hits.total, out);
format_ymon("result", "miss", stat->server, stat->results.misses.miss, out);
format_ymon("result", "miss_ims", stat->server, stat->results.misses.ims, out);
format_ymon("result", "miss_refresh", stat->server, stat->results.misses.refresh, out);
format_ymon("result", "miss_other", stat->server, stat->results.misses.other, out);
format_ymon("result", "miss_total", stat->server, stat->results.misses.total, out);
format_ymon("result", "err_abort", stat->server, stat->results.errors.client_abort, out);
format_ymon("result", "err_conn", stat->server, stat->results.errors.connect_fail, out);
format_ymon("result", "err_invalid", stat->server, stat->results.errors.invalid_req, out);
format_ymon("result", "err_unknown", stat->server, stat->results.errors.unknown, out);
format_ymon("result", "err_other", stat->server, stat->results.errors.other, out);
format_ymon("result", "err_total", stat->server, stat->results.errors.total, out);
format_ymon("result", "total", stat->server, stat->total, out);
// HTTP codes
format_ymon("http", "200", stat->server, stat->codes.c_200, out);
format_ymon("http", "204", stat->server, stat->codes.c_204, out);
format_ymon("http", "206", stat->server, stat->codes.c_206, out);
format_ymon("http", "2xx", stat->server, stat->codes.c_2xx, out);
format_ymon("http", "301", stat->server, stat->codes.c_301, out);
format_ymon("http", "302", stat->server, stat->codes.c_302, out);
format_ymon("http", "304", stat->server, stat->codes.c_304, out);
format_ymon("http", "3xx", stat->server, stat->codes.c_3xx, out);
format_ymon("http", "400", stat->server, stat->codes.c_400, out);
format_ymon("http", "403", stat->server, stat->codes.c_403, out);
format_ymon("http", "404", stat->server, stat->codes.c_404, out);
format_ymon("http", "4xx", stat->server, stat->codes.c_4xx, out);
format_ymon("http", "501", stat->server, stat->codes.c_501, out);
format_ymon("http", "502", stat->server, stat->codes.c_502, out);
format_ymon("http", "503", stat->server, stat->codes.c_503, out);
format_ymon("http", "5xx", stat->server, stat->codes.c_5xx, out);
format_ymon("http", "999", stat->server, stat->codes.c_999, out);
format_ymon("http", "000", stat->server, stat->codes.c_000, out);
// Origin hierarchies
format_ymon("hier", "none", stat->server, stat->hierarchies.none, out);
format_ymon("hier", "direct", stat->server, stat->hierarchies.direct, out);
format_ymon("hier", "sibling", stat->server, stat->hierarchies.sibling, out);
format_ymon("hier", "parent", stat->server, stat->hierarchies.parent, out);
format_ymon("hier", "empty", stat->server, stat->hierarchies.empty, out);
format_ymon("hier", "invalid", stat->server, stat->hierarchies.invalid, out);
format_ymon("hier", "other", stat->server, stat->hierarchies.other, out);
// HTTP methods
format_ymon("method", "get", stat->server, stat->methods.get, out);
format_ymon("method", "put", stat->server, stat->methods.put, out);
format_ymon("method", "head", stat->server, stat->methods.head, out);
format_ymon("method", "post", stat->server, stat->methods.post, out);
format_ymon("method", "delete", stat->server, stat->methods.del, out);
format_ymon("method", "purge", stat->server, stat->methods.purge, out);
format_ymon("method", "options", stat->server, stat->methods.options, out);
format_ymon("method", "none", stat->server, stat->methods.none, out);
format_ymon("method", "other", stat->server, stat->methods.other, out);
// URL schemes (HTTP/HTTPs)
format_ymon("scheme", "http", stat->server, stat->schemes.http, out);
format_ymon("scheme", "https", stat->server, stat->schemes.https, out);
format_ymon("scheme", "none", stat->server, stat->schemes.none, out);
format_ymon("scheme", "other", stat->server, stat->schemes.other, out);
// Content types
format_ymon("ctype", "text_js", stat->server, stat->content.text.javascript, out);
format_ymon("ctype", "text_css", stat->server, stat->content.text.css, out);
format_ymon("ctype", "text_html", stat->server, stat->content.text.html, out);
format_ymon("ctype", "text_xml", stat->server, stat->content.text.xml, out);
format_ymon("ctype", "text_plain", stat->server, stat->content.text.plain, out);
format_ymon("ctype", "text_other", stat->server, stat->content.text.other, out);
format_ymon("ctype", "text_total", stat->server, stat->content.text.total, out);
format_ymon("ctype", "image_jpeg", stat->server, stat->content.image.jpeg, out);
format_ymon("ctype", "image_gif", stat->server, stat->content.image.gif, out);
format_ymon("ctype", "image_png", stat->server, stat->content.image.png, out);
format_ymon("ctype", "image_bmp", stat->server, stat->content.image.bmp, out);
format_ymon("ctype", "image_other", stat->server, stat->content.image.other, out);
format_ymon("ctype", "image_total", stat->server, stat->content.image.total, out);
format_ymon("ctype", "audio_xwav", stat->server, stat->content.audio.wav, out);
format_ymon("ctype", "audio_xmpeg", stat->server, stat->content.audio.mpeg, out);
format_ymon("ctype", "audio_other", stat->server, stat->content.audio.other, out);
format_ymon("ctype", "audio_total", stat->server, stat->content.audio.total, out);
format_ymon("ctype", "app_shock", stat->server, stat->content.application.shockwave_flash, out);
format_ymon("ctype", "app_js", stat->server, stat->content.application.javascript, out);
format_ymon("ctype", "app_qt", stat->server, stat->content.application.quicktime, out);
format_ymon("ctype", "app_zip", stat->server, stat->content.application.zip, out);
format_ymon("ctype", "app_other", stat->server, stat->content.application.other, out);
format_ymon("ctype", "app_total", stat->server, stat->content.application.total, out);
format_ymon("ctype", "none", stat->server, stat->content.none, out);
format_ymon("ctype", "other", stat->server, stat->content.other, out);
// Elapsed stats
format_elapsed_ymon("elapsed", "hit", stat->server, stat->elapsed.hits.hit, out);
format_elapsed_ymon("elapsed", "hit_ims", stat->server, stat->elapsed.hits.ims, out);
format_elapsed_ymon("elapsed", "hit_refresh", stat->server, stat->elapsed.hits.refresh, out);
format_elapsed_ymon("elapsed", "hit_other", stat->server, stat->elapsed.hits.other, out);
format_elapsed_ymon("elapsed", "hit_total", stat->server, stat->elapsed.hits.total, out);
format_elapsed_ymon("elapsed", "miss", stat->server, stat->elapsed.misses.miss, out);
format_elapsed_ymon("elapsed", "miss_ims", stat->server, stat->elapsed.misses.ims, out);
format_elapsed_ymon("elapsed", "miss_refresh", stat->server, stat->elapsed.misses.refresh, out);
format_elapsed_ymon("elapsed", "miss_other", stat->server, stat->elapsed.misses.other, out);
format_elapsed_ymon("elapsed", "miss_total", stat->server, stat->elapsed.misses.total, out);
}
///////////////////////////////////////////////////////////////////////////////
// Format one value for ysar output
inline void
format_ysar(const StatsCounter & stat, const StatsCounter & tot, bool last = false)
{
if (last)
std::cout << 100 * (double) stat.count / tot.count;
else
std::cout << 100 * (double) stat.count / tot.count << ',';
}
///////////////////////////////////////////////////////////////////////////////
// Little wrapper around exit, to allow us to exit like a YMon plugin.
void
my_exit(YmonLevel status, const char *notice)
{
vector<OriginPair> vec;
if (cl.ysar) {
std::cout.precision(2);
std::cout.setf(std::ios::fixed);
format_ysar(totals.codes.c_200, totals.total);
format_ysar(totals.codes.c_204, totals.total);
format_ysar(totals.codes.c_206, totals.total);
format_ysar(totals.codes.c_2xx, totals.total);
format_ysar(totals.codes.c_301, totals.total);
format_ysar(totals.codes.c_302, totals.total);
format_ysar(totals.codes.c_304, totals.total);
format_ysar(totals.codes.c_3xx, totals.total);
format_ysar(totals.codes.c_400, totals.total);
format_ysar(totals.codes.c_403, totals.total);
format_ysar(totals.codes.c_404, totals.total);
format_ysar(totals.codes.c_4xx, totals.total);
format_ysar(totals.codes.c_501, totals.total);
format_ysar(totals.codes.c_502, totals.total);
format_ysar(totals.codes.c_503, totals.total);
format_ysar(totals.codes.c_5xx, totals.total);
format_ysar(totals.codes.c_999, totals.total);
format_ysar(totals.codes.c_000, totals.total);
format_ysar(totals.content.text.total, totals.total);
format_ysar(totals.content.image.total, totals.total);
format_ysar(totals.content.application.total, totals.total);
format_ysar(totals.content.audio.total, totals.total);
format_ysar(totals.content.other, totals.total);
format_ysar(totals.content.none, totals.total, true);
} else if (cl.ymon) {
std::cout << hostname << '\t' << "yts_origins\t" << status << '\t' << "ver. " << PACKAGE_VERSION << notice << std::
endl;
for (OriginStorage::iterator i = origins.begin(); i != origins.end(); i++) {
if (use_origin(i->second)) {
std::cout << hostname << '\t' << "yts_origins" << '\t' <<
status << '\t' << "ver. " << PACKAGE_VERSION << notice << "|";
print_ymon_metrics(i->second, std::cout);
std::cout << std::endl;
}
}
} else {
switch (status) {
case YMON_OK:
break;
case YMON_WARNING:
std::cout << "warning: " << notice << std::endl;
break;
case YMON_CRITICAL:
std::cout << "critical: " << notice << std::endl;
_exit(status);
break;
case YMON_UNKNOWN:
std::cout << "unknown: " << notice << std::endl;
_exit(status);
break;
}
if (!origins.empty()) {
// Sort the Origins by 'traffic'
for (OriginStorage::iterator i = origins.begin(); i != origins.end(); i++)
if (use_origin(i->second))
vec.push_back(*i);
sort(vec.begin(), vec.end());
// Produce a nice summary first
format_center("Traffic summary", std::cout);
std::cout << std::left << std::setw(33) << "Origin Server";
std::cout << std::right << std::setw(15) << "Hits";
std::cout << std::right << std::setw(15) << "Misses";
std::cout << std::right << std::setw(15) << "Errors" << std::endl;
std::cout << std::setw(cl.line_len) << std::setfill('-') << '-' << std::setfill(' ') << std::endl;
for (vector<OriginPair>::iterator i = vec.begin(); i != vec.end(); i++) {
std::cout << std::left << std::setw(33) << i->first;
std::cout << std::right << std::setw(15);
format_int(i->second->results.hits.total.count, std::cout);
std::cout << std::right << std::setw(15);
format_int(i->second->results.misses.total.count, std::cout);
std::cout << std::right << std::setw(15);
format_int(i->second->results.errors.total.count, std::cout);
std::cout << std::endl;
}
std::cout << std::setw(cl.line_len) << std::setfill('=') << '=' << std::setfill(' ') << std::endl;
std::cout << std::endl << std::endl << std::endl;
}
// Next the totals for all Origins
format_center("Totals (all Origins combined)", std::cout);
print_detail_stats(&totals, std::cout);
std::cout << std::endl << std::endl << std::endl;
// And finally the individual Origin Servers.
for (vector<OriginPair>::iterator i = vec.begin(); i != vec.end(); i++) {
format_center(i->first, std::cout);
print_detail_stats(i->second, std::cout);
std::cout << std::endl << std::endl << std::endl;
}
}
_exit(status);
}
///////////////////////////////////////////////////////////////////////////////
// Open the "default" log file (squid.blog), allow for it to be rotated.
int
open_main_log(char *ymon_notice, const size_t ymon_notice_size)
{
int cnt = 3;
int main_fd;
while (((main_fd = open("./squid.blog", O_RDONLY)) < 0) && --cnt) {
switch (errno) {
case ENOENT:
case EACCES:
sleep(5);
break;
default:
strncat(ymon_notice, " can't open squid.blog", ymon_notice_size - strlen(ymon_notice) - 1);
return -1;
}
}
if (main_fd < 0) {
strncat(ymon_notice, " squid.blog not enabled", ymon_notice_size - strlen(ymon_notice) - 1);
return -1;
}
posix_fadvise(main_fd, 0, 0, POSIX_FADV_DONTNEED);
return main_fd;
}
///////////////////////////////////////////////////////////////////////////////
// main
int
main(int argc, char *argv[])
{
YmonLevel ymon_status = YMON_OK;
char ymon_notice[4096];
struct utsname uts_buf;
int res, cnt;
int main_fd;
unsigned max_age;
char ts_path[PATH_NAME_MAX + 1];
// build the application information structure
appVersionInfo.setup(PACKAGE_NAME,PROGRAM_NAME, PACKAGE_VERSION, __DATE__,
__TIME__, BUILD_MACHINE, BUILD_PERSON, "");
// Initialize some globals
memset(&totals, 0, sizeof(totals)); // Make sure counters are zero
// Initialize "elapsed" field
init_elapsed(totals);
memset(&cl, 0, sizeof(cl));
memset(ymon_notice, 0, sizeof(ymon_notice));
cl.line_len = DEFAULT_LINE_LEN;
origin_set = NULL;
parse_errors = 0;
// Get TS directory
if (0 == get_ts_directory(ts_path,sizeof(ts_path))) {
ink_strncpy(system_root_dir, ts_path, sizeof(system_root_dir));
ink_snprintf(system_log_dir, sizeof(system_log_dir), "%s/var/log/trafficserver", system_root_dir);
} else {
ink_strncpy(system_log_dir, DEFAULT_LOG_DIRECTORY, sizeof(system_log_dir));
}
// process command-line arguments
process_args(argument_descriptions, n_argument_descriptions, argv, USAGE_LINE);
// Post processing
if (cl.ysar) {
cl.summary = 1;
cl.ymon = 0;
cl.incremental = 1;
if (cl.state_tag[0] == '\0')
ink_strncpy(cl.state_tag, "ysar", sizeof(cl.state_tag));
}
if (cl.ymon) {
cl.ysar = 0;
cl.summary = 0;
}
// check for the version number request
if (cl.version) {
std::cerr << appVersionInfo.FullVersionInfoStr << std::endl;
_exit(0);
}
// check for help request
if (cl.help) {
usage(argument_descriptions, n_argument_descriptions, USAGE_LINE);
_exit(0);
}
// Calculate the max age of acceptable log entries, if necessary
if (cl.max_age > 0) {
struct timeval tv;
gettimeofday(&tv, NULL);
max_age = tv.tv_sec - cl.max_age;
} else {
max_age = 0;
}
// initialize this application for standalone logging operation
init_log_standalone_basic(PROGRAM_NAME);
Log::init(Log::NO_REMOTE_MANAGEMENT | Log::LOGCAT);
// Do we have a list of Origins on the command line?
if (cl.origin_list[0] != '\0') {
char *tok;
char *sep_ptr = cl.origin_list;
if (origin_set == NULL)
origin_set = NEW(new OriginSet);
while ((tok = strsep(&sep_ptr, ",")) != NULL)
origin_set->insert(tok);
}
// Load origins from an "external" file (\n separated)
if (cl.origin_file[0] != '\0') {
std::ifstream fs;
fs.open(cl.origin_file, std::ios::in);
if (!fs.is_open()) {
std::cerr << "can't read " << cl.origin_file << std::endl;
usage(argument_descriptions, n_argument_descriptions, USAGE_LINE);
_exit(0);
}
if (origin_set == NULL)
origin_set = NEW(new OriginSet);
while (!fs.eof()) {
std::string line;
std::string::size_type start, end;
getline(fs, line);
start = line.find_first_not_of(" \t");
if (start != std::string::npos) {
end = line.find_first_of(" \t#/");
if (end == std::string::npos)
end = line.length();
if (end > start) {
char *buf;
buf = xstrdup(line.substr(start, end).c_str());
if (buf)
origin_set->insert(buf);
}
}
}
}
// Get the hostname
if (uname(&uts_buf) < 0) {
strncat(ymon_notice, " can't get hostname", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
hostname = xstrdup(uts_buf.nodename);
// Change directory to the log dir
if (chdir(system_log_dir) < 0) {
ink_snprintf(ymon_notice, sizeof(ymon_notice), "can't chdir to %s", system_log_dir);
my_exit(YMON_CRITICAL, ymon_notice);
}
if (cl.incremental) {
// Do the incremental parse of the default squid log.
std::string sf_name(system_log_dir);
struct stat stat_buf;
int state_fd;
sf_name.append("/logstats.state");
if (cl.state_tag[0] != '\0') {
sf_name.append(".");
sf_name.append(cl.state_tag);
} else {
// Default to the username
struct passwd *pwd = getpwuid(geteuid());
if (pwd) {
sf_name.append(".");
sf_name.append(pwd->pw_name);
} else {
strncat(ymon_notice, " can't get current UID", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
}
if ((state_fd = open(sf_name.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
strncat(ymon_notice, " can't open state file", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
// Get an exclusive lock, if possible. Try for up to 20 seconds.
cnt = 10;
while (((res = flock(state_fd, LOCK_EX | LOCK_NB)) < 0) && --cnt) {
switch (errno) {
case EWOULDBLOCK:
case EINTR:
sleep(2);
break;
default:
strncat(ymon_notice, " locking failure", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
break;
}
}
if (res < 0) {
strncat(ymon_notice, " can't lock state file", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
// Fetch previous state information, allow for concurrent accesses.
cnt = 10;
while (((res = read(state_fd, &last_state, sizeof(last_state))) < 0) && --cnt) {
switch (errno) {
case EINTR:
case EAGAIN:
sleep(1);
break;
default:
strncat(ymon_notice, " can't read state file", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
break;
}
}
if (res != sizeof(last_state)) {
// First time / empty file, so reset.
last_state.offset = 0;
last_state.st_ino = 0;
}
if ((main_fd = open_main_log(ymon_notice, sizeof(ymon_notice))) < 0)
my_exit(YMON_CRITICAL, ymon_notice);
// Get stat's from the main log file.
if (fstat(main_fd, &stat_buf) < 0) {
strncat(ymon_notice, " can't stat squid.blog", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
// Make sure the last_state.st_ino is sane.
if (last_state.st_ino <= 0)
last_state.st_ino = stat_buf.st_ino;
// Check if the main log file was rotated, and if so, locate
// the old file first, and parse the remaining log data.
if (stat_buf.st_ino != last_state.st_ino) {
DIR *dirp = NULL;
struct dirent *dp = NULL;
ino_t old_inode = last_state.st_ino;
// Save the current log-file's I-Node number.
last_state.st_ino = stat_buf.st_ino;
// Find the old log file.
dirp = opendir(system_log_dir);
if (dirp == NULL) {
strncat(ymon_notice, " can't read log directory", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
} else {
while ((dp = readdir(dirp)) != NULL) {
if (stat(dp->d_name, &stat_buf) < 0) {
strncat(ymon_notice, " can't stat ", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
strncat(ymon_notice, dp->d_name, sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
} else if (stat_buf.st_ino == old_inode) {
int old_fd = open(dp->d_name, O_RDONLY);
if (old_fd < 0) {
strncat(ymon_notice, " can't open ", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
strncat(ymon_notice, dp->d_name, sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
break; // Don't attempt any more files
}
// Process it
if (process_file(old_fd, last_state.offset, max_age) != 0) {
strncat(ymon_notice, " can't read ", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
strncat(ymon_notice, dp->d_name, sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
}
close(old_fd);
break; // Don't attempt any more files
}
}
}
// Make sure to read from the beginning of the freshly rotated file.
last_state.offset = 0;
} else {
// Make sure the last_state.offset is sane, stat_buf is for the main_fd.
if (last_state.offset > stat_buf.st_size)
last_state.offset = stat_buf.st_size;
}
// Process the main file (always)
if (process_file(main_fd, last_state.offset, max_age) != 0) {
strncat(ymon_notice, " can't parse log", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
ymon_status = YMON_CRITICAL;
last_state.offset = 0;
last_state.st_ino = 0;
} else {
// Save the current file offset.
last_state.offset = lseek(main_fd, 0, SEEK_CUR);
if (last_state.offset < 0) {
strncat(ymon_notice, " can't lseek squid.blog", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
last_state.offset = 0;
}
}
// Save the state, release the lock, and close the FDs.
if (lseek(state_fd, 0, SEEK_SET) < 0) {
strncat(ymon_notice, " can't lseek state file", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
} else {
if (write(state_fd, &last_state, sizeof(last_state)) == (-1)) {
strncat(ymon_notice, " can't write state_fd ", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
if (ymon_status == YMON_OK)
ymon_status = YMON_WARNING;
}
}
flock(state_fd, LOCK_UN);
close(main_fd);
close(state_fd);
} else {
if (cl.log_file[0] != '\0') {
main_fd = open(cl.log_file, O_RDONLY);
if (main_fd < 0) {
strncat(ymon_notice, " can't open log file ", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
strncat(ymon_notice, cl.log_file, sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
} else {
main_fd = open_main_log(ymon_notice, sizeof(ymon_notice));
}
if (cl.tail > 0) {
if (lseek(main_fd, 0, SEEK_END) < 0) {
strncat(ymon_notice, " can't lseek squid.blog", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
sleep(cl.tail);
}
if (process_file(main_fd, 0, max_age) != 0) {
close(main_fd);
strncat(ymon_notice, " can't parse log file ", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
strncat(ymon_notice, cl.log_file, sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(YMON_CRITICAL, ymon_notice);
}
close(main_fd);
}
// All done.
if (ymon_status == YMON_OK)
strncat(ymon_notice, " OK", sizeof(ymon_notice) - strlen(ymon_notice) - 1);
my_exit(ymon_status, ymon_notice);
}