#!/usr/bin/perl
#------------------------------------------------------------------------------
#
# PgBadger - An other PostgreSQL log analyzer that aims to replace and
#           outperforms pgFouine
#
# This program is open source, licensed under the PostgreSQL Licence.
# For license terms, see the LICENSE file.
#------------------------------------------------------------------------------
#
# You must enable SQL query logging : log_min_duration_statement = 0
# With stderr output
#  Log line prefix should be: log_line_prefix = '%t [%p]: [%l-1] '
#  Log line prefix should be: log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d '
#  Log line prefix should be: log_line_prefix = '%t [%p]: [%l-1] db=%d,user=%u '
# With syslog output
#  Log line prefix should be: log_line_prefix = 'db=%d,user=%u '
#
# Additional informations that could be collected and reported
#  log_checkpoints = on
#  log_connections = on
#  log_disconnections = on
#  log_lock_waits = on
#  log_temp_files = 0
#------------------------------------------------------------------------------
use vars qw($VERSION);

use strict qw(vars subs);

use Getopt::Long qw(:config no_ignore_case bundling);
use IO::File;
use Benchmark;
use File::Basename;
use Time::Local 'timegm_nocheck';
use POSIX qw(locale_h);
setlocale(LC_NUMERIC, '');
setlocale(LC_ALL,     'C');

$VERSION = '2.2';

$| = 1;

# Global variables overriden during install
my $JQGRAPH = 1;

# Command line options
my $zcat                    = 'zcat';
my $bzcat                   = 'bunzip2 -c';
my $ucat                    = 'unzip -p';
my $uncompress_size         = "gunzip -l %f | grep -E '^\\s*[0-9]+' | awk '{print \$2}'";
my $format                  = '';
my $outfile                 = '';
my $outdir                  = '';
my $help                    = '';
my $ver                     = '';
my @dbname                  = ();
my @dbuser                  = ();
my @dbclient                = ();
my @dbappname               = ();
my @exclude_user            = ();
my $ident                   = '';
my $top                     = 0;
my $sample                  = 0;
my $extension               = '';
my $maxlength               = 0;
my $graph                   = 1;
my $nograph                 = 0;
my $debug                   = 0;
my $nohighlight             = 0;
my $noprettify              = 0;
my $from                    = '';
my $to                      = '';
my $quiet                   = 0;
my $progress                = 1;
my $error_only              = 0;
my @exclude_query           = ();
my $exclude_file            = '';
my @include_query           = ();
my $include_file            = '';
my $disable_error           = 0;
my $disable_hourly          = 0;
my $disable_type            = 0;
my $disable_query           = 0;
my $disable_session         = 0;
my $disable_connection      = 0;
my $disable_lock            = 0;
my $disable_temporary       = 0;
my $disable_checkpoint      = 0;
my $avg_minutes             = 5;
my $last_parsed             = '';
my $report_title            = 'PgBadger: PostgreSQL log analyzer';
my $log_line_prefix         = '';
my $compiled_prefix         = '';
my $project_url             = 'http://dalibo.github.com/pgbadger/';
my $t_min                   = 0;
my $t_max                   = 0;
my $t_min_hour              = 0;
my $t_max_hour              = 0;
my $log_duration            = 0;
my $enable_log_duration     = 0;
my $remove_comment          = 0;
my $select_only             = 0;
my $enable_log_min_duration = 0;
my $tsung_queries           = 0;

my $NUMPROGRESS = 10000;
my @DIMENSIONS  = (800, 300);
my $RESRC_URL   = '';
my $img_format  = 'png';
my @log_files   = ();
my %prefix_vars = ();

# Do not display data in pie where percentage is lower than this value
# to avoid label overlaping.
my $pie_percentage_limit = 2;

# Get the decimal separator
my $n       = 5 / 2;
my $num_sep = ',';
$num_sep = ' ' if ($n =~ /,/);

# get the command line parameters
my $result = GetOptions(
	"a|average=i"              => \$avg_minutes,
	"b|begin=s"                => \$from,
	"c|client=s"               => \@dbclient,
	"C|nocomment!"             => \$remove_comment,
	"d|dbname=s"               => \@dbname,
	"e|end=s"                  => \$to,
	"f|format=s"               => \$format,
	"G|nograph!"               => \$nograph,
	"h|help!"                  => \$help,
	"i|ident=s"                => \$ident,
	"l|last-parsed=s"          => \$last_parsed,
	"m|maxlength=i"            => \$maxlength,
	"N|appname=s"              => \@dbappname,
	"n|nohighlight!"           => \$nohighlight,
	"o|outfile=s"              => \$outfile,
	"p|prefix=s"               => \$log_line_prefix,
	"P|no-prettify!"           => \$noprettify,
	"q|quiet!"                 => \$quiet,
	"s|sample=i"               => \$sample,
	"S|select-only!"           => \$select_only,
	"t|top=i"                  => \$top,
	"T|title=s"                => \$report_title,
	"u|dbuser=s"               => \@dbuser,
	"U|exclude-user=s"         => \@exclude_user,
	"v|verbose!"               => \$debug,
	"V|version!"               => \$ver,
	"w|watch-mode!"            => \$error_only,
	"x|extension=s"            => \$extension,
	"z|zcat=s"                 => \$zcat,
	"pie-limit=i"              => \$pie_percentage_limit,
	"image-format=s"           => \$img_format,
	"exclude-query=s"          => \@exclude_query,
	"exclude-file=s"           => \$exclude_file,
	"include-query=s"          => \@include_query,
	"include-file=s"           => \$include_file,
	"disable-error!"           => \$disable_error,
	"disable-hourly!"          => \$disable_hourly,
	"disable-type!"            => \$disable_type,
	"disable-query!"           => \$disable_query,
	"disable-session!"         => \$disable_session,
	"disable-connection!"      => \$disable_connection,
	"disable-lock!"            => \$disable_lock,
	"disable-temporary!"       => \$disable_temporary,
	"disable-checkpoint!"      => \$disable_checkpoint,
	"enable-log_duration!"     => \$enable_log_duration,
	"enable-log_min_duration!" => \$enable_log_min_duration,
);

if ($ver) {
	print "pgBadger version $VERSION\n";
	exit 0;
}
&usage() if ($help);

# Rewrite some command line argument as lists
&compute_arg_list();

# Log file to be parsed are passed as command line argument
if ($#ARGV >= 0) {
	foreach my $file (@ARGV) {
		if ($file ne '-') {
			die "FATAL: logfile $file must exist!\n" if (!-f $file);
			if (-z $file) {
				print "WARNING: file $file is empty\n";
				next;
			}
		}
		push(@log_files, $file);
	}
}

# Logfile is a mandatory parameter
if ($#log_files < 0) {
	print STDERR "FATAL: you must give a log file as command line parameter.\n\n";
	&usage();
}

# Quiet mode is forced with progress bar
$progress = 0 if ($quiet);

# Set the default number minutes for queries and connections average
$avg_minutes ||= 5;
$avg_minutes = 60 if ($avg_minutes > 60);
$avg_minutes = 1  if ($avg_minutes < 1);

# Set default format
$format ||= &autodetect_format($log_files[0]);

$log_duration ||= &autodetect_duration($log_files[0]);
$log_duration = 1 if ($enable_log_duration);
$log_duration = 0 if ($enable_log_min_duration);

if ($enable_log_min_duration && $enable_log_duration) {
	print STDERR "FATAL: you must choose between reports based on log_duration or log_min_duration_statement.\n\n";
	&usage();
}

# Set default top query
$top ||= 20;

# Set the default number of samples
$sample ||= 3;

# Set the default extension and output format
if (!$extension) {
	if ($outfile =~ /\.tsung/i) {
		$extension = 'tsung';
	} elsif ($outfile =~ /\.htm[l]*/i) {
		$extension = 'html';
	} elsif ($outfile) {
		$extension = 'txt';
	} else {
		$extension = 'html';
	}
}

# Set default filename of the output file
$outfile ||= 'out.' . $extension;
&logmsg('DEBUG', "Output '$extension' reports will be written to $outfile");

# Set default syslog ident name
$ident ||= 'postgres';

# Set default pie percentage limit or fix value
$pie_percentage_limit = 0   if ($pie_percentage_limit < 0);
$pie_percentage_limit = 2   if ($pie_percentage_limit eq '');
$pie_percentage_limit = 100 if ($pie_percentage_limit > 100);

# Set default download image format
$img_format = lc($img_format);
$img_format = 'jpeg' if ($img_format eq 'jpg');
$img_format = 'png' if ($img_format ne 'jpeg');

# Extract the output directory from outfile so that graphs will
# be created in the same directoty
my @infs = fileparse($outfile);
$outdir = $infs[1] . '/';

# Remove graph support if output is not html
$graph = 0 if ($extension ne 'html');
$graph = 0 if ($nograph);

my $end_top = $top - 1;

if ($extension eq 'tsung') {

	# Open filehandle
	my $fh = new IO::File ">$outfile";
	if (not defined $fh) {
		die "FATAL: can't write to $outfile, $!\n";
	}
	print $fh "<sessions>\n";
	$fh->close();
} else {

	# Test file creation before going to parse log
	my $tmpfh = new IO::File ">$outfile";
	if (not defined $tmpfh) {
		die "FATAL: can't write to $outfile, $!\n";
	}
	$tmpfh->close();
	unlink($outfile) if (-e $outfile);
}

# -w and --disable-error can't go together
if ($error_only && $disable_error) {
	die "FATAL: please choose between no event report and reporting events only.\n";
}

# Set default search pattern for database and user name in log_line_prefix
my $regex_prefix_dbname = qr/db=([^,]*)/;
my $regex_prefix_dbuser = qr/user=([^,]*)/;

# Loading excluded query from file if any
if ($exclude_file) {
	open(IN, "$exclude_file") or die "FATAL: can't read file $exclude_file: $!\n";
	my @exclq = <IN>;
	close(IN);
	chomp(@exclq);
	map {s/
//;} @exclq;
	foreach my $r (@exclq) {
		&check_regex($r, '--exclude-file');
	}
	push(@exclude_query, @exclq);
}

# Testing regex syntaxe
if ($#exclude_query >= 0) {
	foreach my $r (@exclude_query) {
		&check_regex($r, '--exclude-query');
	}
}

# Loading included query from file if any
if ($include_file) {
	open(IN, "$include_file") or die "FATAL: can't read file $include_file: $!\n";
	my @exclq = <IN>;
	close(IN);
	chomp(@exclq);
	map {s/
//;} @exclq;
	foreach my $r (@exclq) {
		&check_regex($r, '--include-file');
	}
	push(@include_query, @exclq);
}

# Testing regex syntaxe
if ($#include_query >= 0) {
	foreach my $r (@include_query) {
		&check_regex($r, '--include-query');
	}
}

my $other_syslog_line =
	qr/^(...)\s+(\d+)\s(\d+):(\d+):(\d+)(?:\s[^\s]+)?\s([^\s]+)\s([^\s\[]+)\[(\d+)\]:(?:\s\[[^\]]+\])?\s\[(\d+)\-\d+\]\s*(.*)/;
my $orphan_syslog_line = qr/^(...)\s+(\d+)\s(\d+):(\d+):(\d+)(?:\s[^\s]+)?\s([^\s]+)\s([^\s\[]+)\[(\d+)\]:/;
my $orphan_stderr_line = qr/[^']*\d+-\d+-\d+\s\d+:\d+:\d+[\.\d]*\s[^\s]+[^']*/;

# Compile custom log line prefie prefix
my @prefix_params = ();
if ($log_line_prefix) {
	@prefix_params = &build_log_line_prefix_regex();
	&check_regex($log_line_prefix, '--prefix');
	if ($format eq 'syslog') {
		$log_line_prefix =
			  '^(...)\s+(\d+)\s(\d+):(\d+):(\d+)(?:\s[^\s]+)?\s([^\s]+)\s([^\s\[]+)\[(\d+)\]:(?:\s\[[^\]]+\])?\s\[(\d+)\-\d+\]\s*'
			. $log_line_prefix
			. '\s*(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):\s+(.*)';
		$compiled_prefix = qr/$log_line_prefix/;
		unshift(@prefix_params, 't_month', 't_day', 't_hour', 't_min', 't_sec', 't_host', 't_ident', 't_pid', 't_session_line');
		push(@prefix_params, 't_loglevel', 't_query');
	} elsif ($format eq 'stderr') {
		$log_line_prefix = '^' . $log_line_prefix . '\s*(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):\s+(.*)';
		$compiled_prefix = qr/$log_line_prefix/;
		push(@prefix_params, 't_loglevel', 't_query');
	}
} elsif ($format eq 'syslog') {
	$compiled_prefix =
qr/^(...)\s+(\d+)\s(\d+):(\d+):(\d+)(?:\s[^\s]+)?\s([^\s]+)\s([^\s\[]+)\[(\d+)\]:(?:\s\[[^\]]+\])?\s\[(\d+)\-\d+\]\s*(.*?)\s*(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):\s+(.*)/;
	push(@prefix_params, 't_month', 't_day', 't_hour', 't_min', 't_sec', 't_host', 't_ident', 't_pid', 't_session_line',
		't_logprefix', 't_loglevel', 't_query');
} elsif ($format eq 'stderr') {
	$compiled_prefix =
qr/^(\d+-\d+-\d+\s\d+:\d+:\d+)[\.\d]*(?: [A-Z\d]{3,6})?\s\[(\d+)\]:\s\[(\d+)\-\d+\]\s*(.*?)\s*(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):\s+(.*)/;
	push(@prefix_params, 't_timestamp', 't_pid', 't_session_line', 't_logprefix', 't_loglevel', 't_query');
}

sub check_regex
{
	my ($pattern, $varname) = @_;

	eval {m/$pattern/i;};
	if ($@) {
		die "FATAL: '$varname' invalid regex '$pattern', $!\n";
	}
}

# Check start/end date time
if ($from) {
	if ($from =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
		$from = "$1$2$3$4$5$6";
	} elsif ($from =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
		$from = "$1$2$3" . "000000";
	} else {
		die "FATAL: bad format for begin datetime, shoud be yyyy-mm-dd hh:mm:ss\n";
	}
}
if ($to) {
	if ($to =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
		$to = "$1$2$3$4$5$6";
	} elsif ($to =~ /^(\d{4})-(\d{2})-(\d{2})$/) {
		$to = "$1$2$3" . "000000";
	} else {
		die "FATAL: bad format for ending datetime, shoud be yyyy-mm-dd hh:mm:ss\n";
	}
}

# Stores the last parsed line from log file to allow incremental parsing
my $LAST_LINE = '';

# Set the level of the data aggregator, can be minute, hour or day follow the
# size of the log file.
my $LEVEL = 'hour';

# Month names
my %month_abbr = (
	'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06',
	'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12'
);
my %abbr_month = (
	'01' => 'Jan', '02' => 'Feb', '03' => 'Mar', '04' => 'Apr', '05' => 'May', '06' => 'Jun',
	'07' => 'Jul', '08' => 'Aug', '09' => 'Sep', '10' => 'Oct', '11' => 'Nov', '12' => 'Dec'
);

# Highlight variables
my @KEYWORDS1 = (
	'ALL',     'ASC',    'AS',   'ALTER', 'AND',     'ADD', 'AUTO_INCREMENT', 'ANY', 'ANALYZE',
	'BETWEEN', 'BINARY', 'BOTH', 'BY',    'BOOLEAN', 'BEGIN',
	'CHANGE', 'CHECK', 'COLLATION', 'COLUMNS', 'COLUMN', 'CONCURRENTLY', 'CROSS', 'CREATE', 'CASE', 'COMMIT', 'COALESCE', 'CLUSTER', 'COPY',
	'DATABASES', 'DATABASE', 'DATA',    'DELAYED', 'DESCRIBE', 'DESC',   'DISTINCT', 'DELETE', 'DROP',     'DEFAULT',
	'ENCLOSED',  'ESCAPED',  'EXISTS',  'EXPLAIN', 'ELSE',     'END',    'EXCEPT',
	'FIELDS', 'FIELD', 'FLUSH', 'FOR', 'FOREIGN', 'FUNCTION', 'FROM', 'FULL',
	'GROUP',  'GRANT', 'GREATEST',
	'HAVING',
	'IGNORE', 'INDEX', 'INFILE', 'INSERT', 'INNER', 'INTO', 'IDENTIFIED', 'IN', 'IS', 'IF', 'INTERSECT', 'INHERIT',
	'JOIN',
	'KEYS', 'KILL', 'KEY',
	'LEADING', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOW_PRIORITY', 'LEFT', 'LANGUAGE', 'LEAST', 'LOGIN',
	'MODIFY',
	'NATURAL', 'NOT', 'NULL', 'NEXTVAL', 'NULLIF', 'NOSUPERUSER', 'NOCREATEDB', 'NOCREATEROLE',
	'OPTIMIZE',  'OPTION',     'OPTIONALLY', 'ORDER', 'OUTFILE', 'OR', 'OUTER', 'ON', 'OVERLAPS', 'OWNER',
	'PROCEDURE', 'PROCEDURAL', 'PRIMARY',
	'READ', 'REFERENCES', 'REGEXP', 'RENAME',        'REPLACE', 'RETURN', 'REVOKE', 'RLIKE', 'RIGHT', 'ROLE', 'ROLLBACK',
	'SHOW', 'SONAME',     'STATUS', 'STRAIGHT_JOIN', 'SELECT',  'SETVAL', 'SET',    'SOME',  'SEQUENCE',
	'TABLES', 'TEMINATED', 'TO',    'TRAILING', 'TRUNCATE', 'TABLE',    'TEMPORARY', 'TRIGGER', 'TRUSTED', 'THEN',
	'UNIQUE', 'UNLOCK',    'USE',   'USING',    'UPDATE',   'UNSIGNED', 'UNION',
	'VALUES', 'VARIABLES', 'VIEW',  'VACUUM',   'VERBOSE',
	'WITH',   'WRITE',     'WHERE', 'WHEN',
	'ZEROFILL',
	'XOR',
);
my @KEYWORDS2 = (
	'ascii',      'age',
	'bit_length', 'btrim',
	'char_length', 'character_length', 'convert', 'chr', 'current_date', 'current_time', 'current_timestamp', 'count',
	'decode',      'date_part',        'date_trunc',
	'encode',      'extract',
	'get_byte',    'get_bit',
	'initcap',       'isfinite', 'interval',
	'justify_hours', 'justify_days',
	'lower', 'length', 'lpad', 'ltrim', 'localtime', 'localtimestamp',
	'md5',
	'now',
	'octet_length', 'overlay',
	'position',     'pg_client_encoding',
	'quote_ident',  'quote_literal',
	'repeat', 'replace', 'rpad', 'rtrim',
	'substring', 'split_part', 'strpos', 'substr', 'set_byte', 'set_bit',
	'trim', 'to_ascii', 'to_hex', 'translate', 'to_char', 'to_date', 'to_timestamp', 'to_number', 'timeofday',
	'upper',
);
my @KEYWORDS3 = ('STDIN', 'STDOUT');
my %SYMBOLS = (
	'='  => '=', '<'  => '&lt;', '>' => '&gt;', '\|' => '|', ',' => ',', '\.' => '.', '\+' => '+', '\-' => '-', '\*' => '*',
	'\/' => '/', '!=' => '!='
);
my @BRACKETS = ('(', ')');
map {$_ = quotemeta($_)} @BRACKETS;

# Where statistic are stored
my %STATS               = ();
my $first_log_timestamp = '';
my $last_log_timestamp  = '';
my $first_log_date      = '';
my $last_log_date       = '';
my %overall_stat        = ();
my @top_slowest         = ();
my %normalyzed_info     = ();
my %error_info          = ();
my %logs_type           = ();
my %per_hour_info       = ();
my %per_minute_info     = ();
my %lock_info           = ();
my %tempfile_info       = ();
my %connection_info     = ();
my %session_info        = ();
my %conn_received       = ();
my %checkpoint_info     = ();
my @graph_values        = ();
my %cur_info            = ();
my $nlines              = 0;
my %last_line           = ();
my %saved_last_line     = ();
my %tsung_session       = ();

my $t0 = Benchmark->new;

# Reading last line parsed
if ($last_parsed && -e $last_parsed) {
	if (open(IN, "$last_parsed")) {
		my $line = <IN>;
		close(IN);
		($saved_last_line{datetime}, $saved_last_line{orig}) = split(/\t/, $line, 2);
	} else {
		die "FATAL: can't read last parsed line from $last_parsed, $!\n";
	}
}

# Main loop reading log files
foreach my $logfile (@log_files) {

	&logmsg('DEBUG', "Starting to parse log file: $logfile");

	my $curdate = localtime(time);

	# Syslog do not have year information, so take care of year overlapping
	my ($gsec, $gmin, $ghour, $gmday, $gmon, $gyear, $gwday, $gyday, $gisdst) = localtime(time);
	$gyear += 1900;
	my $CURRENT_DATE = $gyear . sprintf("%02d", $gmon + 1) . sprintf("%02d", $gmday);

	# Get size of the file
	my $totalsize = (stat("$logfile"))[7] || 0;
	my $cursize = 0;

	if ($format eq 'csv') {
		require Text::CSV_XS;
		my $csv = Text::CSV_XS->new({binary => 1, eol => $/});
		my $io = undef;
		if ($logfile !~ /\.(gz|bz2|zip)/i) {
			open($io, "<", $logfile) or die "FATAL: cannot read csvlog file $logfile. $!\n";
		} else {
			my $uncompress = $zcat;
			if (($logfile =~ /\.bz2/i) && ($zcat =~ /^zcat$/)) {
				$uncompress = $bzcat;
			} elsif (($logfile =~ /\.zip/i) && ($zcat =~ /^zcat$/)) {
				$uncompress = $ucat;
			}
			&logmsg('DEBUG', "Compressed file found, will used command: $uncompress $logfile");
			open($io, "$uncompress $logfile |") or die "FATAL: cannot open pipe to $uncompress $logfile. $!\n";

			# Real size of the file is unknow, try to find it
			my $cmd_file_size = $uncompress_size;
			$cmd_file_size =~ s/\%f/$logfile/g;
			$totalsize = `$cmd_file_size`;
			chomp($totalsize);
			$totalsize ||= 0;
		}

		# Parse csvlog lines
		my $getout = 0;
		while (my $row = $csv->getline($io)) {

			# Set progress statistics
			$cursize += length(join(',', @$row));
			$nlines++;
			if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
				if ($totalsize) {
					print progress_bar($cursize, $totalsize, 25, '=');
				} else {
					print ".";
				}
			}

			# Process only relevant lines
			next if ($row->[11] !~ /^(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT)$/);

			# Extract the date
			$row->[0] =~ m/^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)\.(\d+)/;
			my $milli = $7 || 0;
			($prefix_vars{'t_year'}, $prefix_vars{'t_month'}, $prefix_vars{'t_day'}, $prefix_vars{'t_hour'}, $prefix_vars{'t_min'},
				$prefix_vars{'t_sec'}) = ($1, $2, $3, $4, $5, $6);
			$prefix_vars{'t_date'} =
				  $prefix_vars{'t_year'}
				. $prefix_vars{'t_month'}
				. $prefix_vars{'t_day'}
				. $prefix_vars{'t_hour'}
				. $prefix_vars{'t_min'}
				. $prefix_vars{'t_sec'};
			$prefix_vars{'t_timestamp'} =
"$prefix_vars{'t_year'}-$prefix_vars{'t_month'}-$prefix_vars{'t_day'} $prefix_vars{'t_hour'}:$prefix_vars{'t_min'}:$prefix_vars{'t_sec'}";

			# Skip unwanted lines
			next if ($from && ($from > $prefix_vars{'t_date'}));
			$getout = 1, last if ($to && ($to < $prefix_vars{'t_date'}));

			# Jump to the last line parsed if required
			next if (!&check_incremental_position($prefix_vars{'t_date'}, join(',', @$row)));

			# Set query parameters as global variables
			$prefix_vars{'t_dbuser'}  = $row->[1] || '';
			$prefix_vars{'t_dbname'}  = $row->[2] || '';
			$prefix_vars{'t_appname'} = $row->[2] || '';
			$prefix_vars{'t_client'}  = $row->[2] || '';
			$prefix_vars{'t_host'}    = 'csv';
			$prefix_vars{'t_pid'}     = $row->[3];
			$prefix_vars{'t_session_line'} = $row->[5];
			$prefix_vars{'t_session_line'} =~ s/\..*//;
			$prefix_vars{'t_loglevel'} = $row->[11];
			$prefix_vars{'t_query'}    = $row->[13];

			# Store the current timestamp of the log line
			if (!$first_log_date || ($first_log_date > $prefix_vars{'t_date'})) {
				$first_log_timestamp = $prefix_vars{'t_timestamp'};
				$first_log_date      = $prefix_vars{'t_date'};
			}
			if (!$last_log_date || ($last_log_date < $prefix_vars{'t_date'})) {
				$last_log_timestamp = $prefix_vars{'t_timestamp'};
				$last_log_date      = $prefix_vars{'t_date'};
			}

			# Parse the query now
			&parse_query();
			if ($row->[14]) {
				if ($row->[11] eq 'LOG') {
					if ($row->[13] =~ /^(duration: \d+\.\d+ ms)/) {
						$row->[14] = "$1  $row->[14]";
					}
				}
				$prefix_vars{'t_loglevel'} = 'DETAIL';
				$prefix_vars{'t_query'}    = $row->[14];
				&parse_query();
			}
			if ($row->[15]) {
				$prefix_vars{'t_query'}    = $row->[15];
				$prefix_vars{'t_loglevel'} = 'HINT';
				&parse_query();
			}
		}
		if (!$getout) {
			$csv->eof or warn "FATAL: cannot use CSV, " . $csv->error_diag() . "\n";
		}
		close $io;

	} else {

		# Open log file for reading
		my $lfile = new IO::File;
		if ($logfile !~ /\.(gz|bz2|zip)/i) {
			$lfile->open($logfile) || die "FATAL: cannot read log file $logfile. $!\n";
		} else {

			my $uncompress = $zcat;
			if (($logfile =~ /\.bz2/i) && ($zcat =~ /^zcat$/)) {
				$uncompress = $bzcat;
			} elsif (($logfile =~ /\.zip/i) && ($zcat =~ /^zcat$/)) {
				$uncompress = $ucat;
			}
			&logmsg('DEBUG', "Compressed log file, will used command: $uncompress $logfile");

			# Open a pipe to zcat program for compressed log
			$lfile->open("$uncompress $logfile |") || die "FATAL: cannot read from pipe to $uncompress $logfile. $!\n";

			# Real size of the file is unknow, try to find it
			my $cmd_file_size = $uncompress_size;
			$cmd_file_size =~ s/\%f/$logfile/g;
			$totalsize = `$cmd_file_size`;
			chomp($totalsize);
			$totalsize ||= 0;
		}

		my $time_pattern = qr/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;

		&logmsg('DEBUG', "Starting reading file...");
		my $cur_pid = '';
		while (my $line = <$lfile>) {
			$cursize += length($line);
			chomp($line);
			$line =~ s/
//;
			$nlines++;
			next if (!$line);

			if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
				if ($totalsize) {
					print progress_bar($cursize, $totalsize, 25, '=');
				} else {
					print ".";
				}
			}

			# Parse syslog lines
			if ($format =~ /syslog/) {

				my @matches = ($line =~ $compiled_prefix);

				if ($#matches >= 0) {

					for (my $i = 0 ; $i <= $#prefix_params ; $i++) {
						$prefix_vars{$prefix_params[$i]} = $matches[$i];
					}

					# skip non postgresql lines
					next if ($prefix_vars{'t_ident'} ne $ident);

					# Syslog do not have year information, so take care of year overlapping
					$prefix_vars{'t_year'}  = $gyear;
					$prefix_vars{'t_day'}   = sprintf("%02d", $prefix_vars{'t_day'});
					$prefix_vars{'t_month'} = $month_abbr{$prefix_vars{'t_month'}};
					if ("$prefix_vars{'t_year'}$prefix_vars{'t_month'}$prefix_vars{'t_day'}" > $CURRENT_DATE) {
						$prefix_vars{'t_year'} = substr($CURRENT_DATE, 1, 4) - 1;
					}

					$prefix_vars{'t_date'} =
						  $prefix_vars{'t_year'}
						. $prefix_vars{'t_month'}
						. $prefix_vars{'t_day'}
						. $prefix_vars{'t_hour'}
						. $prefix_vars{'t_min'}
						. $prefix_vars{'t_sec'};
					$prefix_vars{'t_timestamp'} =
"$prefix_vars{'t_year'}-$prefix_vars{'t_month'}-$prefix_vars{'t_day'} $prefix_vars{'t_hour'}:$prefix_vars{'t_min'}:$prefix_vars{'t_sec'}";

					# Skip unwanted lines
					next if ($from && ($from > $prefix_vars{'t_date'}));
					last if ($to   && ($to < $prefix_vars{'t_date'}));

					# Jump to the last line parsed if required
					next if (!&check_incremental_position($prefix_vars{'t_date'}, $line));
					$cur_pid = $prefix_vars{'t_pid'};

					# Store the current timestamp of the log line
					if (!$first_log_date || ($first_log_date > $prefix_vars{'t_date'})) {
						$first_log_timestamp = $prefix_vars{'t_timestamp'};
						$first_log_date      = $prefix_vars{'t_date'};
					}
					if (!$last_log_date || ($last_log_date < $prefix_vars{'t_date'})) {
						$last_log_timestamp = $prefix_vars{'t_timestamp'};
						$last_log_date      = $prefix_vars{'t_date'};
					}

					# Extract information from log line prefix
					if (!$log_line_prefix) {
						&parse_log_prefix($prefix_vars{'t_logprefix'});
					}

					# Check if the log line shoud be exclude from the report
					if (&validate_log_line($prefix_vars{'t_pid'})) {

						# Process the log line
						&parse_query();
					}

				} elsif ($line =~ $other_syslog_line) {

					$cur_pid = $8;
					my $t_query = $10;
					$t_query = $11 if ($format eq 'syslog-ng');
					$t_query =~ s/#011/\t/g;
					next if ($t_query eq "\t");
					if ($cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $t_query;
					} elsif ($cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $t_query;
					} elsif ($cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $t_query;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $t_query;
					}

					# Collect orphans lines of multiline queries
				} elsif ($line !~ $orphan_syslog_line) {

					if ($cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $line;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $line;
					}

				} else {
					&logmsg('DEBUG', "Unknown syslog line format: $line");
				}

			} elsif ($format eq 'stderr') {

				%prefix_vars = ();
				my @matches = ($line =~ $compiled_prefix);
				if ($#matches >= 0) {
					for (my $i = 0 ; $i <= $#prefix_params ; $i++) {
						$prefix_vars{$prefix_params[$i]} = $matches[$i];
					}
					if (!$prefix_vars{'t_timestamp'} && $prefix_vars{'t_mtimestamp'}) {
						$prefix_vars{'t_timestamp'} = $prefix_vars{'t_mtimestamp'};
					} elsif (!$prefix_vars{'t_timestamp'} && $prefix_vars{'t_session_timestamp'}) {
						$prefix_vars{'t_timestamp'} = $prefix_vars{'t_session_timestamp'};
					}
					($prefix_vars{'t_year'}, $prefix_vars{'t_month'}, $prefix_vars{'t_day'}, $prefix_vars{'t_hour'},
						$prefix_vars{'t_min'}, $prefix_vars{'t_sec'}) = ($prefix_vars{'t_timestamp'} =~ $time_pattern);
					$prefix_vars{'t_date'} =
						  $prefix_vars{'t_year'}
						. $prefix_vars{'t_month'}
						. $prefix_vars{'t_day'}
						. $prefix_vars{'t_hour'}
						. $prefix_vars{'t_min'}
						. $prefix_vars{'t_sec'};

					# Skip unwanted lines
					next if ($from && ($from > $prefix_vars{'t_date'}));
					last if ($to   && ($to < $prefix_vars{'t_date'}));

					# Jump to the last line parsed if required
					next if (!&check_incremental_position($prefix_vars{'t_date'}, $line));
					$cur_pid = $prefix_vars{'t_pid'};

					# Store the current timestamp of the log line
					if (!$first_log_date || ($first_log_date > $prefix_vars{'t_date'})) {
						$first_log_timestamp = $prefix_vars{'t_timestamp'};
						$first_log_date      = $prefix_vars{'t_date'};
					}
					if (!$last_log_date || ($last_log_date < $prefix_vars{'t_date'})) {
						$last_log_timestamp = $prefix_vars{'t_timestamp'};
						$last_log_date      = $prefix_vars{'t_date'};
					}

					# Extract information from log line prefix
					if (!$log_line_prefix) {
						&parse_log_prefix($prefix_vars{'t_logprefix'});
					}

					# Check if the log line shoud be exclude from the report
					if (&validate_log_line($prefix_vars{'t_pid'})) {
						$prefix_vars{'t_host'} = 'stderr';

						# Process the log line
						&parse_query();
					}

					# Collect orphans lines of multiline queries
				} elsif ($line !~ $orphan_stderr_line) {

					if ($cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $line;
					} elsif ($cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $line;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $line;
					}

				} elsif ($cur_info{$cur_pid}{query}) {
					$cur_info{$cur_pid}{query} .= "\n" . $line;
				}

			} else {

				# unknown format
				&logmsg('DEBUG', "Unknown line format: $line");
			}
		}
		$lfile->close();
	}

	# Get stats from all pending temporary storage
	foreach my $pid (sort {$cur_info{$a}{date} <=> $cur_info{$b}{date}} %cur_info) {
		&store_queries($pid);
	}
	if ($extension eq 'tsung') {
		foreach my $pid (sort {$a <=> $b} keys %tsung_session) {
			&store_tsung_session($pid);
		}
	}
	%cur_info = ();

	if ($progress) {
		if ($totalsize) {
			print progress_bar($cursize, $totalsize, 25, '=');
		}
		print STDERR "\n";
	}

}

# Save last line parsed
if ($last_parsed && scalar keys %last_line) {
	if (open(OUT, ">$last_parsed")) {
		print OUT "$last_line{datetime}\t$last_line{orig}\n";
		close(OUT);
	} else {
		&logmsg('ERROR', "can't save last parsed line into $last_parsed, $!");
	}
}

my $t1 = Benchmark->new;
my $td = timediff($t1, $t0);
&logmsg('DEBUG', "the log statistics gathering tooks:" . timestr($td));

&logmsg('DEBUG', "Ok, generating $extension report...");

# Open filehandle
my $fh = undef;
if ($extension ne 'tsung') {
	$fh = new IO::File ">$outfile";
	if (not defined $fh) {
		die "FATAL: can't write to $outfile, $!\n";
	}
	if (($extension eq 'text') || ($extension eq 'txt')) {
		if ($error_only) {
			&dump_error_as_text();
		} else {
			&dump_as_text();
		}
	} else {
		if ($error_only) {
			&dump_error_as_html();
		} else {
			&dump_as_html();
		}
	}
	$fh->close;
} else {

	# Open filehandle
	$fh = new IO::File ">>$outfile";
	if (not defined $fh) {
		die "FATAL: can't write to $outfile, $!\n";
	}
	print $fh "</sessions>\n";
	$fh->close();
}

my $t2 = Benchmark->new;
$td = timediff($t2, $t1);
&logmsg('DEBUG', "the generating of reports tooks:" . timestr($td));
$td = timediff($t2, $t0);
&logmsg('DEBUG', "the total execution time tooks:" . timestr($td));

exit 0;

#-------------------------------------------------------------------------------

# Show PgBadger command line usage
sub usage
{
	print qq{
Usage: pgbadger [options] logfile [...]

	PostgreSQL log analyzer with fully detailed reports and graphs.

Arguments:

    logfile can be a single log file, a list of files or a shell command 
    returning a list of file. If you want to pass log content from stdin
    use - as filename. Note that input from stdin will not work with csvlog.

Options:

    -a | --average minutes : number of minutes to build the average graphs of
			     queries and connections.
    -b | --begin datetime  : start date/time for the data to be parsed in log.
    -c | --dbclient host   : only report what concern the given client host.
    -C | --nocomment       : remove comments like /* ... */ from queries.
    -d | --dbname database : only report what concern the given database.
    -e | --end datetime    : end date/time for the data to be parsed in log.
    -f | --format logtype  : possible values: syslog,stderr,csv. Default: stderr.
    -G | --nograph         : disable graphs on HTML output. Enable by default.
    -h | --help            : show this message and exit.
    -i | --ident name      : programname used as syslog ident. Default: postgres
    -l | --last-parsed file: allow incremental log parsing by registering the
			     last datetime and line parsed. Useful if you want
			     to watch errors since last run or if you want one
			     report per day with a log rotated each week.
    -m | --maxlength size  : maximum length of a query, it will be cutted above
			     the given size. Default: no truncate
    -n | --nohighlight     : disable SQL code highlighting.
    -N | --appname name    : only report what concern the given application name
    -o | --outfile filename: define the filename for the output. Default depends
			     of the output format: out.html, out.txt or out.tsung.
			     To dump output to stdout use - as filename.
    -p | --prefix string   : give here the value of your custom log_line_prefix
			     defined in your postgresql.conf. You may used it only
			     if don't have the default allowed formats ot to use
			     other custom variables like client ip or application
			     name. See examples below.
    -P | --no-prettify     : disable SQL queries prettify formatter.
    -q | --quiet           : don't print anything to stdout, even not a progress bar.
    -s | --sample number   : number of query sample to store/display. Default: 3
    -s | --select-only     : use it if you want reports on select queries only.
    -t | --top number      : number of query to store/display. Default: 20
    -T | --title string    : change title of the HTML page report.
    -u | --dbuser username : only report what concern the given user
    -U | --exclude-user username : make report with exclude specified user.
    -v | --verbose         : enable verbose or debug mode. Disabled by default.
    -V | --version         : show pgBadger version and exit.
    -w | --watch-mode      : only report events/errors just like logwatch could do.
    -x | --extension       : output format. Values: text, html or tsung. Default: html
    -z | --zcat exec_path  : set the full path to the zcat program. Use it if
			     zcat or bzcat or unzip is not on your path.
    --pie-limit num        : pie data lower than num% will show a sum instead.
    --exclude-query regex  : any query matching the given regex will be excluded
			     from the report. For example: "^(VACUUM|COMMIT)"
			     you can use this option multiple time.
    --exclude-file filename: path of the file which contains all the regex to use
			     to exclude queries from the report. One regex per line.
    --include-query regex  : any query that do not match the given regex will be
			     excluded from the report. For example: "(table_1|table_2)"
			     you can use this option multiple time.
    --include-file filename: path of the file which contains all the regex of the
			     queries to include from the report. One regex per line.
    --disable-error        : do not generate error report.
    --disable-hourly       : do not generate hourly reports.
    --disable-type         : do not generate query type report.
    --disable-query        : do not generate queries reports (slowest, most
			     frequent, ...).
    --disable-session      : do not generate session report.
    --disable-connection   : do not generate connection report.
    --disable-lock         : do not generate lock report.
    --disable-temporary    : do not generate temporary report.
    --disable-checkpoint   : do not generate checkpoint report.
    --enable-log_duration  : force pgbadger to use log_duration even if
			     log_min_duration_statement format is autodetected
    --enable-log_min_duration: force pgbadger to use log_min_duration even if
			     log_duration format is autodetected.

Examples:

	pgbadger /var/log/postgresql.log
	pgbadger /var/log/postgres.log.2.gz /var/log/postgres.log.1.gz \
		       /var/log/postgres.log
	pgbadger /var/log/postgresql/postgresql-2012-05-*
	pgbadger --exclude-query="^(COPY|COMMIT)" /var/log/postgresql.log
	cat /var/log/postgres.log | pgbadger -
	# log prefix with stderr log output
	perl pgbadger --prefix '%t [%p]: [%l-1] user=%u,db=%d,client=%h' \
			/pglog/postgresql-2012-08-21*
	perl pgbadger --prefix '%m %u@%d %p %r %a : ' /pglog/postgresql.log
	# Log line prefix with syslog log output
	perl pgbadger --prefix 'user=%u,db=%d,client=%h,appname=%a' \
			/pglog/postgresql-2012-08-21*

Generate Tsung sessions XML file with select queries only:

    perl pgbadger -S -o sessions.tsung --prefix '%t [%p]: [%l-1] user=%u,db=%d ' /pglog/postgresql-9.1.log

Reporting errors every week by cron job:

    30 23 * * 1 /usr/bin/pgbadger -q -w /var/log/postgresql.log -o /var/reports/pg_errors.html

Generate report every week using incremental behavior:

    0 4 * * 1 /usr/bin/pgbadger -q `find /var/log/ -mtime -7 -name "postgresql.log*"` \
	-o /var/reports/pg_errors-`date +%F`.html -l /var/reports/pgbadger_incremental_file.dat

This supposes that your log file and HTML report are also rotated every weeks.

};

	exit 0;
}

# Method used to check if we have already reach the last parsing position in incremental mode
# This position should have been saved in the incremental file and read in the $last_parsed at
# start up.
sub check_incremental_position
{
	my ($cur_date, $line) = @_;

	if ($last_parsed) {
		if ($saved_last_line{datetime}) {
			if ($cur_date < $saved_last_line{datetime}) {
				return 0;
			} elsif (!$last_line{datetime} && ($cur_date == $saved_last_line{datetime})) {
				return 0 if ($line ne $saved_last_line{orig});
			}
		}
		$last_line{datetime} = $cur_date;
		$last_line{orig}     = $line;
	}

	return 1;
}

# Display message following the log level
sub logmsg
{
	my ($level, $str) = @_;

	return if ($quiet && ($level ne 'FATAL'));
	return if (!$debug && ($level eq 'DEBUG'));

	if ($level =~ /(\d+)/) {
		print STDERR "\t" x $1;
	}

	print STDERR "$level: $str\n";
}

# Normalyze SQL queries by removing parameters
sub normalize_query
{
	my $orig_query = shift;

	return if (!$orig_query);

	$orig_query = lc($orig_query);

	# Remove extra space, new line and tab caracters by a single space
	$orig_query =~ s/[\t\s\r\n]+/ /gs;

	# Remove string content
	$orig_query =~ s/\\'//g;
	$orig_query =~ s/'[^']*'/''/g;
	$orig_query =~ s/''('')+/''/g;

	# Remove NULL parameters
	$orig_query =~ s/=\s*NULL/=''/g;

	# Remove numbers
	$orig_query =~ s/([^a-z_\$-])-?([0-9]+)/${1}0/g;

	# Remove hexadecimal numbers
	$orig_query =~ s/([^a-z_\$-])0x[0-9a-f]{1,10}/${1}0x/g;

	# Remove IN values
	$orig_query =~ s/in\s*\([\'0x,\s]*\)/in (...)/g;

	return $orig_query;
}

# Format numbers with comma for better reading
sub comma_numbers
{
	return 0 if ($#_ < 0);

	my $text = reverse $_[0];

	$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1$num_sep/g;

	return scalar reverse $text;
}

# Format duration
sub convert_time
{
	my $time = shift;

	return '0s' if (!$time);

	my $days = int($time / 86400000);
	$time -= ($days * 86400000);
	my $hours = int($time / 3600000);
	$time -= ($hours * 3600000);
	my $minutes = int($time / 60000);
	$time -= ($minutes * 60000);
	my $seconds = sprintf("%0.2f", $time / 1000);

	$days    = $days < 1    ? '' : $days . 'd';
	$hours   = $hours < 1   ? '' : $hours . 'h';
	$minutes = $minutes < 1 ? '' : $minutes . 'm';
	$time    = $days . $hours . $minutes . $seconds . 's';

	return $time;
}

# Stores the top N slowest queries
sub set_top_slowest
{
	my ($q, $dt, $date) = @_;

	push(@top_slowest, [($dt, $date, $q)]);

	@top_slowest = (sort {$b->[0] <=> $a->[0]} @top_slowest)[0 .. $end_top];

}

# Stores top N slowest sample queries
sub set_top_sample
{
	my ($norm, $q, $dt, $date) = @_;

	$normalyzed_info{$norm}{samples}{$dt}{query} = $q;
	$normalyzed_info{$norm}{samples}{$dt}{date}  = $date;

	my $i = 1;
	foreach my $k (sort {$b <=> $a} keys %{$normalyzed_info{$norm}{samples}}) {
		if ($i > $sample) {
			delete $normalyzed_info{$norm}{samples}{$k};
		}
		$i++;
	}
}

# Stores top N error sample queries
sub set_top_error_sample
{
	my ($q, $date, $real_error, $detail, $context, $statement, $hint) = @_;

	# Stop when we have our number of samples
	if (!exists $error_info{$q}{date} || ($#{$error_info{$q}{date}} < $sample)) {
		if (($q =~ /deadlock detected/) || !grep(/\Q$real_error\E/, @{$error_info{$q}{error}})) {
			push(@{$error_info{$q}{date}},      $date);
			push(@{$error_info{$q}{detail}},    $detail);
			push(@{$error_info{$q}{context}},   $context);
			push(@{$error_info{$q}{statement}}, $statement);
			push(@{$error_info{$q}{hint}},      $hint);
			push(@{$error_info{$q}{error}},     $real_error);
		}
	}
}

sub dump_as_text
{

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
$report_title

- Global informations --------------------------------------------------

Generated on $curdate
Log file: $logfile_str
Parsed $fmt_nlines log entries in $total_time
Log start from $first_log_timestamp to $last_log_timestamp
};

	# Overall statistics
	my $fmt_unique  = &comma_numbers(scalar keys %normalyzed_info)    || 0;
	my $fmt_queries = &comma_numbers($overall_stat{'queries_number'}) || 0;
	if ((scalar keys %normalyzed_info == 1) && ($overall_stat{'queries_number'} > 1)) {
		$fmt_unique                     = 'none';
		$overall_stat{'first_query_ts'} = $first_log_timestamp;
		$overall_stat{'last_query_ts'}  = $last_log_timestamp;
	}
	my $fmt_duration = &convert_time($overall_stat{'queries_duration'}) || 0;
	print $fh qq{

- Overall statistics ---------------------------------------------------

Number of unique normalized queries: $fmt_unique
Number of queries: $fmt_queries
Total query duration: $fmt_duration
First query: $overall_stat{'first_query_ts'}
Last query: $overall_stat{'last_query_ts'}
};
	foreach (sort {$overall_stat{'query_peak'}{$b} <=> $overall_stat{'query_peak'}{$a}} keys %{$overall_stat{'query_peak'}}) {
		print $fh "Query peak: ", &comma_numbers($overall_stat{'query_peak'}{$_}), " queries/s at $_";
		last;
	}
	if (!$disable_error) {
		my $fmt_errors = &comma_numbers($overall_stat{'errors_number'}) || 0;
		my $fmt_unique_error = &comma_numbers(scalar keys %{$overall_stat{'unique_normalized_errors'}}) || 0;
		print $fh qq{
Number of events: $fmt_errors
Number of unique normalized events: $fmt_unique_error
};
	}
	if ($tempfile_info{count}) {
		my $fmt_temp_maxsise = &comma_numbers($tempfile_info{maxsize}) || 0;
		my $fmt_temp_avsize = &comma_numbers(sprintf("%.2f", ($tempfile_info{maxsize} / $tempfile_info{count})));
		print $fh qq{Number temporary file: $tempfile_info{count}
Max size of temporary file: $fmt_temp_maxsise
Average size of temporary file: $fmt_temp_avsize
};
	}
	if (!$disable_session && $session_info{count}) {
		my $avg_session_duration = &convert_time($session_info{duration} / $session_info{count});
		my $tot_session_duration = &convert_time($session_info{duration});
		print $fh qq{Total number of sessions: $session_info{count}
Total duration of sessions: $tot_session_duration
Average duration of sessions: $avg_session_duration
};
	}
	if (!$disable_connection && $connection_info{count}) {
		print $fh "Total number of connections: $connection_info{count}\n";
	}
	if (!$disable_hourly && $overall_stat{'queries_number'}) {
		print $fh qq{

- Hourly statistics ----------------------------------------------------

Report not supported by text format

};
	}

	if (!$disable_type && (!$log_duration || $enable_log_min_duration)) {

		# INSERT/DELETE/UPDATE/SELECT repartition
		my $totala = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
		my $total = $overall_stat{'queries_number'} || 1;
		print $fh "\n- Queries by type ------------------------------------------------------\n\n";
		print $fh "Type     Count     Percentage\n";
		print $fh "SELECT: ", &comma_numbers($overall_stat{'SELECT'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'SELECT'} * 100) / $total), "%\n";
		print $fh "INSERT: ", &comma_numbers($overall_stat{'INSERT'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'INSERT'} * 100) / $total), "%\n";
		print $fh "UPDATE: ", &comma_numbers($overall_stat{'UPDATE'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'UPDATE'} * 100) / $total), "%\n";
		print $fh "DELETE: ", &comma_numbers($overall_stat{'DELETE'}) || 0, " ",
			sprintf("%0.2f", ($overall_stat{'DELETE'} * 100) / $total), "%\n";
		print $fh "OTHERS: ", &comma_numbers($total - $totala) || 0, " ", sprintf("%0.2f", (($total - $totala) * 100) / $total), "%\n"
			if (($total - $totala) > 0);
		print $fh "\n";
	}

	if (!$disable_lock && scalar keys %lock_info > 0) {
		print $fh "\n- Locks by type ------------------------------------------------------\n\n";
		print $fh "Type     Object     Count     Total Duration     Av. duration (s)\n";
		my $total_count    = 0;
		my $total_duration = 0;
		foreach my $t (sort keys %lock_info) {
			print $fh "$t\t\t", &comma_numbers($lock_info{$t}{count}), " ", &convert_time($lock_info{$t}{duration}), " ",
				&convert_time($lock_info{$t}{duration} / $lock_info{$t}{count}), "\n";
			foreach my $o (sort keys %{$lock_info{$t}}) {
				next if (($o eq 'count') || ($o eq 'duration') || ($o eq 'chronos'));
				print $fh "\t$o\t", &comma_numbers($lock_info{$t}{$o}{count}), " ", &convert_time($lock_info{$t}{$o}{duration}), " ",
					&convert_time($lock_info{$t}{$o}{duration} / $lock_info{$t}{$o}{count}), "\n";
			}
			$total_count    += $lock_info{$t}{count};
			$total_duration += $lock_info{$t}{duration};
		}
		print $fh "Total:\t\t\t", &comma_numbers($total_count), " ", &convert_time($total_duration), " ",
			&convert_time($total_duration / ($total_count || 1)), "\n";
	}

	# Show session per database statistics
	if (!$disable_session && exists $session_info{database}) {
		print $fh "\n- Sessions per database ------------------------------------------------------\n\n";
		print $fh "Database     Count     Total Duration     Av. duration (s)\n";
		foreach my $d (sort keys %{$session_info{database}}) {
			print $fh "$d - ", &comma_numbers($session_info{database}{$d}{count}), " ",
				&convert_time($session_info{database}{$d}{duration}), " ",
				&convert_time($session_info{database}{$d}{duration} / $session_info{database}{$d}{count}), "\n";
		}
	}

	# Show session per user statistics
	if (!$disable_session && exists $session_info{user}) {
		print $fh "\n- Sessions per user ------------------------------------------------------\n\n";
		print $fh "User     Count     Total Duration     Av. duration (s)\n";
		foreach my $d (sort keys %{$session_info{user}}) {
			print $fh "$d - ", &comma_numbers($session_info{user}{$d}{count}), " ", &convert_time($session_info{user}{$d}{duration}),
				" ", &convert_time($session_info{user}{$d}{duration} / $session_info{user}{$d}{count}), "\n";
		}
	}

	# Show session per host statistics
	if (!$disable_session && exists $session_info{host}) {
		print $fh "\n- Sessions per host ------------------------------------------------------\n\n";
		print $fh "User     Count     Total Duration     Av. duration (s)\n";
		foreach my $d (sort keys %{$session_info{host}}) {
			print $fh "$d - ", &comma_numbers($session_info{host}{$d}{count}), " ", &convert_time($session_info{host}{$d}{duration}),
				" ", &convert_time($session_info{host}{$d}{duration} / $session_info{host}{$d}{count}), "\n";
		}
	}

	# Show connection per database statistics
	if (!$disable_connection && exists $connection_info{database}) {
		print $fh "\n- Connections per database ------------------------------------------------------\n\n";
		print $fh "Database     User     Count\n";
		foreach my $d (sort keys %{$connection_info{database}}) {
			print $fh "$d - ", &comma_numbers($connection_info{database}{$d}), "\n";
			foreach my $u (sort keys %{$connection_info{user}}) {
				next if (!exists $connection_info{database_user}{$d}{$u});
				print $fh "\t$u ", &comma_numbers($connection_info{database_user}{$d}{$u}), "\n";
			}
		}
	}

	# Show connection per user statistics
	if (!$disable_connection && exists $connection_info{user}) {
		print $fh "\n- Connections per user ------------------------------------------------------\n\n";
		print $fh "User     Count\n";
		foreach my $d (sort keys %{$connection_info{user}}) {
			print $fh "$d - ", &comma_numbers($connection_info{user}{$d}), "\n";
		}
	}

	# Show connection per host statistics
	if (!$disable_connection && exists $connection_info{host}) {
		print $fh "\n- Connections per host ------------------------------------------------------\n\n";
		print $fh "User     Count\n";
		foreach my $d (sort keys %{$connection_info{host}}) {
			print $fh "$d - ", &comma_numbers($connection_info{host}{$d}), "\n";
		}
	}

	# Show top informations
	if (!$disable_query && (!$log_duration || $enable_log_min_duration)) {
		print $fh "\n- Slowest queries ------------------------------------------------------\n\n";
		print $fh "Rank     Duration (s)     Query\n";
		for (my $i = 0 ; $i <= $#top_slowest ; $i++) {
			print $fh $i + 1, ") " . &convert_time($top_slowest[$i]->[0]) . " - $top_slowest[$i]->[2]\n";
			print $fh "--\n";
		}
		@top_slowest = ();

		print $fh "\n- Queries that took up the most time (N) -------------------------------\n\n";
		print $fh "Rank     Total duration      Times executed     Av. duration (s)     Query\n";
		my $idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{duration} <=> $normalyzed_info{$a}{duration}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach (keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$_}{query};
					last;
				}
			}
			$normalyzed_info{$k}{average} = $normalyzed_info{$k}{duration} / $normalyzed_info{$k}{count};
			print $fh "$idx) "
				. &convert_time($normalyzed_info{$k}{duration}) . " - "
				. &comma_numbers($normalyzed_info{$k}{count}) . " - "
				. &convert_time($normalyzed_info{$k}{average})
				. " - $q\n";
			print $fh "--\n";
			my $i = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
				print $fh "\t- Example $i: ", &convert_time($d), " - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}

		print $fh "\n- Most frequent queries (N) --------------------------------------------\n\n";
		print $fh "Rank     Times executed     Total duration     Av. duration (s)     Query\n";
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{count} <=> $normalyzed_info{$a}{count}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach (keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$_}{query};
					last;
				}
			}
			print $fh "$idx) "
				. &comma_numbers($normalyzed_info{$k}{count}) . " - "
				. &convert_time($normalyzed_info{$k}{duration}) . " - "
				. &convert_time($normalyzed_info{$k}{duration} / $normalyzed_info{$k}{count})
				. " - $q\n";
			print $fh "--\n";
			my $i = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
				print $fh "\tExample $i: ", &convert_time($d), " - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}

		print $fh "\n- Slowest queries (N) --------------------------------------------------\n\n";
		print $fh "Rank     Av. duration (s)     Times executed     Total duration     Query\n";
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{average} <=> $normalyzed_info{$a}{average}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach (keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$_}{query};
					last;
				}
			}
			print $fh "$idx) "
				. &convert_time($normalyzed_info{$k}{average}) . " - "
				. &comma_numbers($normalyzed_info{$k}{count}) . " - "
				. &convert_time($normalyzed_info{$k}{duration})
				. " - $q\n";
			print $fh "--\n";
			my $i = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
				print $fh "\tExample $i: ", &convert_time($d), " - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}
	}

	if (!$disable_error) {
		&show_error_as_text();
	}

	print $fh "\n\n";
	print $fh "Report generated by PgBadger $VERSION ($project_url).\n";

}

sub dump_error_as_text
{

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
$report_title

- Global informations --------------------------------------------------

Generated on $curdate
Log file: $logfile_str
Parsed $fmt_nlines log entries in $total_time
Log start from $first_log_timestamp to $last_log_timestamp
};

	&show_error_as_text();

	print $fh "\n\n";
	print $fh "Report generated by pgBadger $VERSION ($project_url).\n";
}

sub show_error_as_text
{
	return if (scalar keys %error_info == 0);

	print $fh "\n- Most frequent events (N) ---------------------------------------------\n\n";
	my $idx = 1;
	foreach my $k (sort {$error_info{$b}{count} <=> $error_info{$a}{count}} keys %error_info) {
		next if (!$error_info{$k}{count});
		last if ($idx > $top);
		if ($error_info{$k}{count} > 1) {
			my $msg = $k;
			$msg =~ s/HINT:  (parameter "[^"]+" changed to)/LOG:  $1/;
			$msg =~ s/HINT:  (database system was shut down)/LOG:  $1/;
			print $fh "$idx) " . &comma_numbers($error_info{$k}{count}) . " - $msg\n";
			print $fh "--\n";
			my $j = 1;
			for (my $i = 0 ; $i <= $#{$error_info{$k}{date}} ; $i++) {
				if (   ($error_info{$k}{error}[$i] =~ s/HINT:  (parameter "[^"]+" changed to)/LOG:  $1/)
					|| ($error_info{$k}{error}[$i] =~ s/HINT:  (database system was shut down)/LOG:  $1/))
				{
					$logs_type{HINT}--;
					$logs_type{LOG}++;
				}
				print $fh "\t- Example $j: $error_info{$k}{date}[$i] - $error_info{$k}{error}[$i]\n";
				print $fh "\t\tDetail: $error_info{$k}{detail}[$i]\n"       if ($error_info{$k}{detail}[$i]);
				print $fh "\t\tContext: $error_info{$k}{context}[$i]\n"     if ($error_info{$k}{context}[$i]);
				print $fh "\t\tHint: $error_info{$k}{hint}[$i]\n"           if ($error_info{$k}{hint}[$i]);
				print $fh "\t\tStatement: $error_info{$k}{statement}[$i]\n" if ($error_info{$k}{statement}[$i]);
				$j++;
			}
		} else {
			if (   ($error_info{$k}{error}[0] =~ s/HINT:  (parameter "[^"]+" changed to)/LOG:  $1/)
				|| ($error_info{$k}{error}[0] =~ s/HINT:  (database system was shut down)/LOG:  $1/))
			{
				$logs_type{HINT}--;
				$logs_type{LOG}++;
			}
			print $fh "$idx) " . &comma_numbers($error_info{$k}{count}) . " - $error_info{$k}{error}[0]\n";
			print $fh "--\n";
			print $fh "\t- Date: $error_info{$k}{date}[0]\n";
			print $fh "\t\tDetail: $error_info{$k}{detail}[0]\n"       if ($error_info{$k}{detail}[0]);
			print $fh "\t\tContext: $error_info{$k}{context}[0]\n"     if ($error_info{$k}{context}[0]);
			print $fh "\t\tHint: $error_info{$k}{hint}[0]\n"           if ($error_info{$k}{hint}[0]);
			print $fh "\t\tStatement: $error_info{$k}{statement}[0]\n" if ($error_info{$k}{statement}[0]);
		}
		$idx++;
	}

	if (scalar keys %logs_type > 0) {
		print $fh "\n- Logs per type ---------------------------------------------\n\n";

		my $total_logs = 0;
		foreach my $d (keys %logs_type) {
			$total_logs += $logs_type{$d};
		}
		print $fh "Logs type	Count	Percentage\n";
		foreach my $d (sort keys %logs_type) {
			next if (!$logs_type{$d});
			print $fh "$d\t\t", &comma_numbers($logs_type{$d}), "\t", sprintf("%0.2f", ($logs_type{$d} * 100) / $total_logs), "%\n";
		}
	}
}

sub html_header
{
	my $date = localtime(time);
	print $fh qq{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>$report_title</title>
<meta NAME="robots" CONTENT="noindex,nofollow">
<meta HTTP-EQUIV="Expires" CONTENT="$date">
<meta HTTP-EQUIV="Generator" CONTENT="PgBadger v$VERSION">
<meta HTTP-EQUIV="Date" CONTENT="$date">
<style type="text/css">
body { background-color: #dedede; font-family: Verdana ;font-size: 14px;font-style: normal;font-weight: normal;text-transform: normal;letter-spacing: normal;line-height: 17px;padding: 0px; color: rgb(69,54,37); }
div, p, th, td { font-size:12px; }
a {color:rgb(180, 80, 80);text-decoration:underline}
a:hover {color:rgb(180, 80, 80);text-decoration:none}
h1 { border-left:8px #0c57a3 solid; color: #0c57a3; font: lighter 30px Arial, Helvetica, sans-serif; margin: 0px; padding: 7px;border-top: 1px solid #CCCCCC;}
h1 a, h1 a:visited { color: #007DFC; text-decoration: none; }
h1 a:hover { color: #888888; text-decoration: none; }
h2 { border-bottom: 1px solid #CCCCCC; color: #0c57a3; font: lighter 18px Arial, Helvetica, sans-serif; letter-spacing: -1px; margin: 0px 0px 2px; padding-bottom: 3px; padding-top:7px;}
h2 a, h2 a:visited { color: #007dfc; text-decoration: none; }
h2 a:hover { color: #6f6e6e; text-decoration: none; }

div.menu {background-color:#EFEFEF; margin-bottom:15px; padding:4px; -moz-border-radius:8px; padding-left:10px; color:#8F8F8F; border:1px solid #D8D8D8; text-align: left; width: 1050px; -moz-box-shadow: 3px 3px 5px #000000; -webkit-box-shadow: 3px 3px 5px #000000; -khtml-box-shadow: 3px 3px 5px #000000; -o-box-shadow: 3px 3px 5px #000000; box-shadow: 3px 3px 5px #000000; border-color: #DBDBCC; }
div.menu a { color: #007dfc; text-decoration: none; }
div.menu a:hover { color: #6f6e6e; text-decoration: none; }
div.menu a:visited { color: #007dfc; text-decoration: none; }

div.information {
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
	width: 1035px;
}

input {
	background:#F3F2ED;
	border:4px double white;
	padding:2 2px;
	margin:3px 3px 3px 3px;
	border-radius:6px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
}

ul { padding-left: 14px; padding-top: 0px; padding-bottom: 0px; margin-bottom: 0px; margin-top: 0px; }
ul li { list-style-type: square; }
div.reports { padding:4px; }

table { border-collapse:collapse;}
table th.left { text-align:left !important; }
table tr.row0 td { background-color: #FFFFFF; border: Opx solid #E7E7E7;}
table tr.row1 td { background-color: #E7E7E7; border: Opx solid #FFFFFF;}
table td.top { vertical-align:top; }
table td.right { text-align:right; }
table td.center { text-align:center; }
table td.relevantInformation { font-weight:bold; }

table.HourStatsTable {
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	/*css3*/
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
	width: 1065px;
}

table.HourStatsTable th { background-color: #770818; border:1px solid #FEE3C4; color: white; font-weight: normal;}
table.SmallTableList {
        background:#F3F2ED;
        border:4px double white;
        padding:0 10px;
        margin:30px 0 30px 0;
        /*css3*/
        border-radius:10px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
        box-shadow:3px 3px 6px 2px #A9A9A9;
        -moz-box-shadow:3px 3px 6px 2px #A9A9A9;
        -webkit-box-shadow:3px 3px 6px #A9A9A9;
        width: 520px;
}
table.SmallTableList th { background-color: #770818; border:1px solid #FEE3C4; color: white; font-weight: normal;}

table.queryList {
        background:#F3F2ED;
        border:4px double white;
        padding:2 2px;
        margin:3px 3px 3px 3px;
        /*css3*/
        border-radius:6px;
        -moz-border-radius:10px;
        -webkit-border-radius:10px;
        box-shadow:3px 3px 6px 2px #A9A9A9;
        -moz-box-shadow:3px 3px 6px 2px #A9A9A9;
        -webkit-box-shadow:3px 3px 6px #A9A9A9;
}
table.queryList th { background-color: #770818; border:1px solid #FEE3C4; color: white; font-weight: normal;}
table.queryList td, table.queryList th { padding: 2px;}
table.queryList div.sql { width: 800px; overflow: auto; }
table.queryList div.examples { background-color:#E7E7E7; border:1px solid #FFFFFF; -moz-border-radius:10px; padding:6px; margin:5px;}
table.queryList div.examples div.example0 { padding:2px; }
table.queryList div.examples div.example1 { background-color:#FFFFFF; padding:2px; border:1px solid #EBF0FC; -moz-border-radius:5px; }
table.queryList div.error { color: #D53131; font-weight:bold; }
table.queryList div.errorInformation0 { background-color:#E7E7E7; color: #8D8D8D; font-style:italic; }
table.queryList div.errorInformation1 { background-color:#FFFFFF; color: #8D8D8D; font-style:italic; }
table.queryList input { border:1px solid black; background-color:#FFFFFF; padding:1px; font-size:11px; }

div.tooltipLink { position:relative; cursor:pointer; }
div.tooltipLink span.information { border-bottom:1px dotted gray; z-index:10; }
div.tooltipLink div.tooltip { display:none; background-color:#EBF0FC; border:1px solid #FFFFFF; -moz-border-radius:10px; padding:6px; width:250px; }
div.tooltipLink div.tooltip table { background-color:white; width:250px; }
div.tooltipLink div.tooltip table tr.row0 td { background-color: #FFFFFF; border: 1px solid #EEEEEE; }
div.tooltipLink div.tooltip table tr.row1 td { background-color: #EEEEEE; border: 1px solid #EEEEEE; }
div.tooltipLink div.tooltip th { font-size:10px; }
div.tooltipLink div.tooltip td { font-size:9px; font-weight:normal; padding:1px; }
div.tooltipLink:hover div.tooltip { display:block; z-index:20; position:absolute; top:1.5em; left:2em; }

div.footer { font: 14px Helvetica, Arial, sans-serif;clear: both; height:30px; color: #dddddd; padding:13px 0px 0 0;margin-left: auto; margin-right: auto;  text-align: center; background-color: #0c57a3; }
div.footer a strong { color: #eeeeee; font-weight: bold;}
div.footer a, #footer a:visited { color: #eeeeee; }
div.footer a:hover { color: #eeeeee; }

div#littleToc { display:none; }
html>body div#littleToc { display:block; background-color:white; color:black; position:fixed; bottom:10px; right:10px; width:160px; font-size:11px; text-align:left; border:1px dotted #BBBBBB; }
div#littleToc div#littleTocContent { display:none; padding:2px; }
div#littleToc:hover { width:205px; }
div#littleToc:hover div#littleTocContent { display:block; border-right:5px solid #BBBBBB; }

div#littleToc div#littleTocTitle { font-weight:bold; text-align:center;padding:2px; }
div#littleToc:hover div#littleTocTitle { display:none; }

div#littleToc ul { padding:0px; text-indent:0px; margin:0px; }
div#littleToc li { font-size:11px; list-style-type:none; padding:0px; text-indent:0px; margin:0px; }

div#littleToc a { color:#000000; padding:2px; margin:2px; display:block; text-decoration:none; border:1px solid #CCCCCC; }
div#littleToc a:hover { text-decoration:none; background-color:#DDDDDD; }

.sql  {font-family:monospace;}
.sql .imp {font-weight: bold; color: red;}
.sql .kw1 {color: #993333; font-weight: bold; text-transform: uppercase;}
.sql .kw2 {color: #993333; font-style: italic;}
.sql .kw3 {color: #993333; text-transform: uppercase;}
.sql .co1 {color: #808080; font-style: italic;}
.sql .co2 {color: #808080; font-style: italic;}
.sql .coMULTI {color: #808080; font-style: italic;}
.sql .es0 {color: #000099; font-weight: bold;}
.sql .br0 {color: #66cc66;}
.sql .sy0 {color: #000000;}
.sql .st0 {color: #ff0000;}
.sql .nu0 {color: #cc66cc;}
.sql span.xtra { display:block; }

#queriespersecond_graph, #connectionspersecond_graph, #allqueries_graph, #checkpointwritebuffers_graph, #checkpointfiles_graph, #temporaryfile_graph, #selectqueries_graph, #writequeries_graph {
	width : 1025px;
	height: 400px;
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
}
#queriesbytype_graph, #lockbytype_graph, #databasesessions_graph, #usersessions_graph, #hostsessions_graph, #databaseconnections_graph, #userconnections_graph, #hostconnections_graph, #logstype_graph {
	width : 500px;
	height: 250px;
	background:#F3F2ED;
	border:4px double white;
	padding:0 10px;
	margin:30px 0 30px 0;
	/*css3*/
	border-radius:10px;
	-moz-border-radius:10px;
	-webkit-border-radius:10px;
	box-shadow:3px 3px 6px 2px #A9A9A9;
	-moz-box-shadow:3px 3px 6px 2px #A9A9A9;
	-webkit-box-shadow:3px 3px 6px #A9A9A9;
}

</style>
<script type="text/javascript">
 /* <![CDATA[ */
function toggle(idButton, idDiv, label) {
	if(document.getElementById(idDiv)) {
		if(document.getElementById(idDiv).style.display == 'none') {
			document.getElementById(idDiv).style.display = 'block';
			document.getElementById(idButton).value = 'Hide '+label;
		} else {
			document.getElementById(idDiv).style.display = 'none';
			document.getElementById(idButton).value = 'Show '+label;
		}
	}
}


/* ]]> */
</script>
};
	if ($JQGRAPH) {
		my @jslib = <DATA>;
		print $fh <<EOF;
@jslib
EOF
	}
	print $fh qq{
</head>
<body>
<div id="content">

<h1 id="top">$report_title</h1>
};
	print $fh qq{
<div class="menu">
<a href="#OverallStatsReport">Overall statistics</a> | 
};
	if (!$error_only) {
		if (!$disable_hourly && $overall_stat{'queries_number'}) {
			print $fh qq{<a href="#HourlyStatsReport">Hourly statistics</a> | };
		}
		if (!$disable_type && (!$log_duration || $enable_log_min_duration)) {
			print $fh qq{<a href="#QueriesByTypeReport">Queries by type</a> | };
		}
		if (!$disable_query && (!$log_duration || $enable_log_min_duration)) {
			print $fh qq{
<a href="#SlowestQueriesReport">Slowest queries</a> | 
<a href="#NormalizedQueriesMostTimeReport">Queries that took up the most time (N)</a> |
<a href="#NormalizedQueriesMostFrequentReport">Most frequent queries (N)</a> |
<a href="#NormalizedQueriesSlowestAverageReport">Slowest queries (N)</a><br />
		}
		}
		if (!$disable_lock && scalar keys %lock_info > 0) {
			print $fh qq{<a href="#LocksByTypeReport">Locks by type</a> |};
		}
		if (!$disable_session) {
			if (exists $session_info{database}) {
				print $fh qq{<a href="#SessionsDatabaseReport">Sessions per database</a> |};
			}
			if (exists $session_info{user}) {
				print $fh qq{<a href="#SessionsUserReport">Sessions per user</a> |};
			}
			if (exists $session_info{host}) {
				print $fh qq{<a href="#SessionsHostReport">Sessions per host</a> |};
			}
		}
		if (!$disable_connection) {
			if (exists $connection_info{database}) {
				print $fh qq{<a href="#ConnectionsDatabaseReport">Connections per database</a> |};
			}
			if (exists $connection_info{user}) {
				print $fh qq{<a href="#ConnectionsUserReport">Connections per user</a> |};
			}
			if (exists $connection_info{host}) {
				print $fh qq{<a href="#ConnectionsHostReport">Connections per host</a><br>};
			}
		}
	}
	if (!$disable_error && (scalar keys %error_info > 0)) {
		print $fh qq{<a href="#NormalizedErrorsMostFrequentReport">Most frequent events (N)</a> | };
		print $fh qq{<a href="#LogsTypeReport">Logs per type</a>};
	}
	print $fh "</div>\n";
	print $fh "<p>Normalized reports are marked with a \"(N)\".</p>\n";

}

sub html_footer
{
	print $fh qq{
	<p>&nbsp;</p>
	<div class="footer">
		Report generated by <a href="$project_url" target="_new">pgBadger</a> $VERSION.
	</div>
</div>
};
	print $fh qq{
<div id="littleToc">
	<div id="littleTocContent">

		<ul>
			<li><a href="#top">^ Back to top</a></li><li><a href="#OverallStatsReport">Overall statistics</a></li>
};
	if (!$error_only) {
		if (!$disable_hourly && $overall_stat{'queries_number'}) {
			print $fh qq{<li><a href="#HourlyStatsReport">Hourly statistics</a></li>};
		}
		if (!$disable_type && (!$log_duration || $enable_log_min_duration)) {
			print $fh qq{<li><a href="#QueriesByTypeReport">Queries by type</a></li>};
		}
		if (!$disable_lock && scalar keys %lock_info > 0) {
			print $fh qq{<li><a href="#LocksByTypeReport">Locks by type</a></li>};
		}
		if (!$disable_session) {
			if (exists $session_info{database}) {
				print $fh qq{<li><a href="#SessionsDatabaseReport">Sessions per database</a></li>};
			}
			if (exists $session_info{user}) {
				print $fh qq{<li><a href="#SessionsUserReport">Sessions per user</a></li>};
			}
			if (exists $session_info{host}) {
				print $fh qq{<li><a href="#SessionsHostReport">Sessions per host</a></li><li>};
			}
		}
		if (!$disable_connection) {
			if (exists $connection_info{database}) {
				print $fh qq{<li><a href="#ConnectionsDatabaseReport">Connections per database</a></li>};
			}
			if (exists $connection_info{user}) {
				print $fh qq{<li><a href="#ConnectionsUserReport">Connections per user</a></li>};
			}
			if (exists $connection_info{host}) {
				print $fh qq{<li><a href="#ConnectionsHostReport">Connections per host</a></li><li>};
			}
		}
		if (!$disable_query && (!$log_duration || $enable_log_min_duration)) {
			print $fh
qq{<a href="#SlowestQueriesReport">Slowest queries</a></li><li><a href="#NormalizedQueriesMostTimeReport">Queries that took up the most time (N)</a></li><li><a href="#NormalizedQueriesMostFrequentReport">Most frequent queries (N)</a></li><li><a href="#NormalizedQueriesSlowestAverageReport">Slowest queries (N)</a></li>};
		}
	}
	if (!$disable_error && (scalar keys %error_info > 0)) {
		print $fh "<li><a href=\"#NormalizedErrorsMostFrequentReport\">Most frequent events (N)</a></li>\n";
		print $fh qq{<li><a href="#LogsTypeReport">Logs per type</a></li>\n};
	}
	print $fh qq{</ul>
	</div>
	<div id="littleTocTitle">Table of contents</div>
</div>
};
	print $fh qq{
</body>
</html>
};

}

sub dump_as_html
{

	# Dump the html header
	&html_header();

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
<div class="information">
<ul>
<li>Generated on $curdate</li>
<li>Log file: $logfile_str</li>
<li>Parsed $fmt_nlines log entries in $total_time</li>
<li>Log start from $first_log_timestamp to $last_log_timestamp</li>
</ul>
</div>
};

	# Overall statistics
	my $fmt_unique  = &comma_numbers(scalar keys %normalyzed_info)    || 0;
	my $fmt_queries = &comma_numbers($overall_stat{'queries_number'}) || 0;
	if ((scalar keys %normalyzed_info == 1) && ($overall_stat{'queries_number'} > 1)) {
		$fmt_unique                     = 'none';
		$overall_stat{'first_query_ts'} = $first_log_timestamp;
		$overall_stat{'last_query_ts'}  = $last_log_timestamp;
	}
	my $fmt_duration = &convert_time($overall_stat{'queries_duration'}) || 0;
	print $fh qq{
<div class="reports">
<h2 id="OverallStatsReport">Overall statistics <a href="#top" title="Back to top">^</a></h2>
<div class="information">
<table><tr><td valign="top">
<ul>
<li>Number of unique normalized queries: $fmt_unique</li>
<li>Number of queries: $fmt_queries</li>
<li>Total query duration: $fmt_duration</li>
<li>First query: $overall_stat{'first_query_ts'}</li>
<li>Last query: $overall_stat{'last_query_ts'}</li>
};
	foreach (sort {$overall_stat{'query_peak'}{$b} <=> $overall_stat{'query_peak'}{$a}} keys %{$overall_stat{'query_peak'}}) {
		print $fh "<li>Query peak: ", &comma_numbers($overall_stat{'query_peak'}{$_}), " queries/s at $_</li>";
		last;
	}
	if (!$disable_error) {
		my $fmt_errors = &comma_numbers($overall_stat{'errors_number'}) || 0;
		my $fmt_unique_error = &comma_numbers(scalar keys %{$overall_stat{'unique_normalized_errors'}}) || 0;
		print $fh qq{
<li>Number of events: $fmt_errors</li>
<li>Number of unique normalized events: $fmt_unique_error</li>
</ul>
</td><td valign="top">
<ul>
};
	}
	if ($tempfile_info{count}) {
		my $fmt_temp_maxsise = &comma_numbers($tempfile_info{maxsize}) || 0;
		my $fmt_temp_avsize = &comma_numbers(sprintf("%.2f", $tempfile_info{maxsize} / $tempfile_info{count}));
		print $fh qq{
<li>Number temporary file: $tempfile_info{count}</li>
<li>Max size of temporary file: $fmt_temp_maxsise</li>
<li>Average size of temporary file: $fmt_temp_avsize</li>
};
	}
	if (!$disable_session && $session_info{count}) {
		my $avg_session_duration = &convert_time($session_info{duration} / $session_info{count});
		my $tot_session_duration = &convert_time($session_info{duration});
		print $fh qq{
<li>Total number of sessions: $session_info{count}</li>
<li>Total duration of sessions: $tot_session_duration</li>
<li>Average duration of sessions: $avg_session_duration</li>
};
	}
	if (!$disable_connection && $connection_info{count}) {
		print $fh qq{
<li>Total number of connections: $connection_info{count}</li>
}
	}
	print $fh qq{
</ul>
</td></tr></table>
</div>
};

	if (!$disable_hourly && $overall_stat{'queries_number'}) {
		print $fh qq{
<h2 id="HourlyStatsReport">Hourly statistics <a href="#top" title="Back to top">^</a></h2>

<table class="HourStatsTable">
	<tr>
		<th rowspan="2">Day</th>
		<th rowspan="2">Hour</th>
		<th colspan="2">Queries</th>
		<th colspan="2">SELECT queries</th>
		<th colspan="4">Write queries</th>
};
		if (exists $connection_info{chronos}) {
			print $fh "		<th colspan=\"2\">Connections</th>\n";
		}
		if (exists $session_info{chronos}) {
			print $fh "		<th colspan=\"2\">Sessions</th>\n";
		}
		print $fh qq{
	</tr>
	<tr>
		<th>Count</th>
		<th>Av.&nbsp;duration&nbsp;</th>
		<th>Count</th>
		<th>Av.&nbsp;duration&nbsp;</th>
		<th>INSERT</th>
		<th>UPDATE</th>
		<th>DELETE</th>
		<th>Av.&nbsp;duration&nbsp;</th>
};
		if (exists $connection_info{chronos}) {
			print $fh "		<th>Count</th><th>Av./s</th>\n";
		}
		if (exists $session_info{chronos}) {
			print $fh "		<th>Count</th><th>Av.&nbsp;duration&nbsp;</th>\n";
		}
		print $fh qq{
	</tr>
};

		foreach my $d (sort {$a <=> $b} keys %per_hour_info) {
			my $c = 1;
			$d =~ /^\d{4}(\d{2})(\d{2})$/;
			my $zday = "$abbr_month{$1} $2";
			foreach my $h (sort {$a <=> $b} keys %{$per_hour_info{$d}}) {
				my $colb = $c % 2;
				$zday = "&nbsp;" if ($c > 1);
				$per_hour_info{$d}{$h}{average} = $per_hour_info{$d}{$h}{duration} / ($per_hour_info{$d}{$h}{count} || 1);
				$per_hour_info{$d}{$h}{'SELECT'}{average} =
					$per_hour_info{$d}{$h}{'SELECT'}{duration} / ($per_hour_info{$d}{$h}{'SELECT'}{count} || 1);
				my $write_average = (
					(
						$per_hour_info{$d}{$h}{'INSERT'}{duration} +
							$per_hour_info{$d}{$h}{'UPDATE'}{duration} +
							$per_hour_info{$d}{$h}{'DELETE'}{duration}
					)
						|| 0
					) / (
					(
						$per_hour_info{$d}{$h}{'INSERT'}{count} +
							$per_hour_info{$d}{$h}{'UPDATE'}{count} +
							$per_hour_info{$d}{$h}{'DELETE'}{count}
					)
						|| 1
					);
				print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{count}),  "</td><td class=\"right\">",
					&convert_time($per_hour_info{$d}{$h}{average}), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'SELECT'}{count}  || 0), "</td><td class=\"right\">",
					&convert_time($per_hour_info{$d}{$h}{'SELECT'}{average} || 0), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'INSERT'}{count}  || 0), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'UPDATE'}{count}  || 0), "</td><td class=\"right\">",
					&comma_numbers($per_hour_info{$d}{$h}{'DELETE'}{count}  || 0), "</td><td class=\"right\">",
					&convert_time($write_average), "</td>";
				if (exists $connection_info{chronos}) {
					print $fh "<td class=\"right\">", &comma_numbers($connection_info{chronos}{"$d"}{"$h"}{count} || 0),
						"</td><td class=\"right\">",
						&comma_numbers(sprintf("%0.2f", $connection_info{chronos}{"$d"}{"$h"}{count} / 3600)), "/s</td>";
				}
				if (exists $session_info{chronos}) {
					$per_hour_info{$d}{$h}{'session'}{average} =
						$session_info{chronos}{"$d"}{"$h"}{duration} / ($session_info{chronos}{"$d"}{"$h"}{count} || 1);
					print $fh "<td class=\"right\">", &comma_numbers($session_info{chronos}{"$d"}{"$h"}{count} || 0),
						"</td><td  class=\"right\">", &convert_time($per_hour_info{$d}{$h}{'session'}{average}), "</td>";
				}
				print $fh "</tr>\n";
				$c++;
			}
		}

		print $fh "</table>\n";

		if ($graph) {
			$first_log_timestamp =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/;
			$t_min = timegm_nocheck(0, $5, $4, $3, $2 - 1, $1) * 1000;
			$t_min -= ($avg_minutes * 60000);
			$t_min_hour = timegm_nocheck(0, 0, $4, $3, $2 - 1, $1) * 1000;
			$last_log_timestamp =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/;
			$t_max = timegm_nocheck(59, $5, $4, $3, $2 - 1, $1) * 1000;
			$t_max += ($avg_minutes * 60000);
			$t_max_hour = timegm_nocheck(0, 0, $4, $3, $2 - 1, $1) * 1000;
			my @labels = ();
			my @data1  = ();
			my @data2  = ();
			my @data3  = ();
			my $d1     = '';
			my $d2     = '';
			my $d3     = '';
			my @avgs   = ();

			for (my $i = 0 ; $i < 59 ; $i += $avg_minutes) {
				push(@avgs, sprintf("%02d", $i));
			}
			push(@avgs, 59);
			foreach my $tm (sort {$a <=> $b} keys %{$per_minute_info{query}}) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my %dataavg = ();
					foreach my $m ("00" .. "59") {
						my $rd = &average_per_minutes($m, $avg_minutes);
						if (exists $per_minute_info{query}{$tm}{$h}{$m}) {

							# Average per minute
							$dataavg{average}{"$rd"} += $per_minute_info{query}{$tm}{$h}{$m}{count};

							# Search minimum and maximum during this minute
							foreach my $s (keys %{$per_minute_info{query}{$tm}{$h}{$m}{second}}) {
								$dataavg{max}{"$rd"} = $per_minute_info{query}{$tm}{$h}{$m}{second}{$s}
									if ($per_minute_info{query}{$tm}{$h}{$m}{second}{$s} > $dataavg{max}{"$rd"});
								$dataavg{min}{"$rd"} = $per_minute_info{query}{$tm}{$h}{$m}{second}{$s}
									if (not exists $dataavg{min}{"$rd"}
									|| ($per_minute_info{query}{$tm}{$h}{$m}{second}{$s} < $dataavg{min}{"$rd"}));
							}
						}
					}
					foreach my $rd (@avgs) {
						my $t = timegm_nocheck(0, $rd, $h, $d, $mo, $y) * 1000;

						next if ($t < $t_min);
						last if ($t > $t_max);

						# Average per minutes
						$d2 .= "[$t, " . int(($dataavg{average}{"$rd"} || 0) / (60 * $avg_minutes)) . "],";

						# Maxi per minute
						$d1 .= "[$t, " . ($dataavg{max}{"$rd"} || 0) . "],";

						# Mini per minute
						$d3 .= "[$t, " . ($dataavg{min}{"$rd"} || 0) . "],";
					}
				}
			}
			delete $per_minute_info{query};
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			$d3 =~ s/,$//;
			&flotr2_graph(
				1, 'queriespersecond_graph', $d1, $d2, $d3, 'Queries per second (' . $avg_minutes . ' minutes average)',
				'Queries per second', 'Maximum', 'Average', 'Minimum'
			);
			$d1 = '';
			$d2 = '';
			$d3 = '';

			if (exists $per_minute_info{connection}) {
				foreach my $tm (sort {$a <=> $b} keys %{$per_minute_info{connection}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my %dataavg = ();
						foreach my $m ("00" .. "59") {
							my $rd = &average_per_minutes($m, $avg_minutes);
							if (exists $per_minute_info{connection}{$tm}{$h}{$m}) {

								# Average per minute
								$dataavg{average}{"$rd"} += $per_minute_info{connection}{$tm}{$h}{$m}{count};

								# Search minimum and maximum during this minute
								foreach my $s (keys %{$per_minute_info{connection}{$tm}{$h}{$m}{second}}) {
									$dataavg{max}{"$rd"} = $per_minute_info{connection}{$tm}{$h}{$m}{second}{$s}
										if ($per_minute_info{connection}{$tm}{$h}{$m}{second}{$s} > $dataavg{max}{"$rd"});
									$dataavg{min}{"$rd"} = $per_minute_info{connection}{$tm}{$h}{$m}{second}{$s}
										if (not exists $dataavg{min}{"$rd"}
										|| ($per_minute_info{connection}{$tm}{$h}{$m}{second}{$s} < $dataavg{min}{"$rd"}));
								}
							}
						}
						foreach my $rd (@avgs) {
							my $t = timegm_nocheck(0, $rd, $h, $d, $mo, $y) * 1000;

							next if ($t < $t_min);
							last if ($t > $t_max);

							# Average per minutes
							$d2 .= "[$t, " . int(($dataavg{average}{"$rd"} || 0) / (60 * $avg_minutes)) . "],";

							# Maxi per minute
							$d1 .= "[$t, " . ($dataavg{max}{"$rd"} || 0) . "],";

							# Mini per minute
							$d3 .= "[$t, " . ($dataavg{min}{"$rd"} || 0) . "],";
						}
					}
				}
				delete $per_minute_info{connection};
				$d1 =~ s/,$//;
				$d2 =~ s/,$//;
				$d3 =~ s/,$//;
				&flotr2_graph(
					2, 'connectionspersecond_graph', $d1, $d2, $d3, 'Connections per second (' . $avg_minutes . ' minutes average)',
					'Connections per second', 'Maximum', 'Average', 'Minimum'
				);
				$d1 = '';
				$d2 = '';
				$d3 = '';
			}

			# All queries
			foreach my $tm (sort {$a <=> $b} keys %per_hour_info) {
				$tm =~ /(\d{4})(\d{2})(\d{2})/;
				my $y  = $1 - 1900;
				my $mo = $2 - 1;
				my $d  = $3;
				foreach my $h ("00" .. "23") {
					my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
					next if ($t < $t_min_hour);
					last if ($t > $t_max_hour);
					$d1 .= "[$t, " . ($per_hour_info{$tm}{$h}{count} || 0) . "],";
					$d2 .= "[$t, "
						. sprintf("%.2f", (($per_hour_info{$tm}{$h}{duration} || 0) / ($per_hour_info{$tm}{$h}{count} || 1)) / 1000)
						. "],";
				}
			}
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			&flotr2_graph(
				3, 'allqueries_graph', $d1, '', '', 'All queries',
				'Queries', 'Number of queries', '', '', 'Duration', $d2, 'Average duration (s)'
			);
			$d1 = '';
			$d2 = '';

			if (!$disable_query && (!$log_duration || $enable_log_min_duration)) {

				# Select queries
				foreach my $tm (sort {$a <=> $b} keys %per_hour_info) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						next if ($t < $t_min_hour);
						last if ($t > $t_max_hour);
						$d1 .= "[$t, " . ($per_hour_info{$tm}{$h}{'SELECT'}{count} || 0) . "],";
						$d2 .= "[$t, "
							. sprintf(
							"%.2f",
							(($per_hour_info{$tm}{$h}{'SELECT'}{duration} || 0) / ($per_hour_info{$tm}{$h}{'SELECT'}{count} || 1)) /
								1000
							) . "],";
					}
				}
				$d1 =~ s/,$//;
				$d2 =~ s/,$//;
				&flotr2_graph(
					4, 'selectqueries_graph', $d1, '', '', 'SELECT queries',
					'Queries', 'Number of queries', '', '', 'Duration', $d2, 'Average duration (s)'
				);
				$d1 = '';
				$d2 = '';

				# Write queries
				if (!$select_only) {
					my $d4 = '';
					foreach my $tm (sort {$a <=> $b} keys %per_hour_info) {
						$tm =~ /(\d{4})(\d{2})(\d{2})/;
						my $y  = $1 - 1900;
						my $mo = $2 - 1;
						my $d  = $3;
						foreach my $h ("00" .. "23") {
							my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
							next if ($t < $t_min_hour);
							last if ($t > $t_max_hour);
							my $wcount =
								$per_hour_info{$tm}{$h}{'UPDATE'}{count} +
								$per_hour_info{$tm}{$h}{'DELETE'}{count} +
								$per_hour_info{$tm}{$h}{'INSERT'}{count};
							my $wduration =
								$per_hour_info{$tm}{$h}{'UPDATE'}{duration} +
								$per_hour_info{$tm}{$h}{'DELETE'}{duration} +
								$per_hour_info{$tm}{$h}{'INSERT'}{duration};
							$d1 .= "[$t, " . ($per_hour_info{$tm}{$h}{'DELETE'}{count} || 0) . "],";
							$d2 .= "[$t, " . ($per_hour_info{$tm}{$h}{'INSERT'}{count} || 0) . "],";
							$d3 .= "[$t, " . ($per_hour_info{$tm}{$h}{'UPDATE'}{count} || 0) . "],";
							$d4 .= "[$t, " . sprintf("%.2f", (($wduration || 0) / ($wcount || 1)) / 1000) . "],";
						}
					}
					$d1 =~ s/,$//;
					$d2 =~ s/,$//;
					$d3 =~ s/,$//;
					$d4 =~ s/,$//;
					&flotr2_graph(
						5, 'writequeries_graph', $d1, $d2, $d3, 'Write queries',
						'Queries', 'DELETE queries', 'INSERT queries', 'UPDATE queries', 'Duration', $d4, 'Average duration (s)'
					);
					$d1 = '';
					$d2 = '';
					$d3 = '';
					$d4 = '';
				}
			}

			if ($tempfile_info{count} || exists $checkpoint_info{chronos}) {
				print $fh qq{<table class="HourStatsTable"><tr><th rowspan="2">Day</th><th rowspan="2">Hour</th>};
			}
			if ($tempfile_info{count}) {
				print $fh qq{<th colspan="2">Temporary files</th>};
			}
			if (exists $checkpoint_info{chronos}) {
				print $fh qq{<th colspan="7">Checkpoints</th>};
			}
			if (exists $checkpoint_info{warning}) {
				print $fh qq{<th colspan="2">Checkpoint warning</th>};
			}
			if ($tempfile_info{count} || exists $checkpoint_info{chronos}) {
				print $fh qq{</tr><tr>};
			}
			if ($tempfile_info{count}) {
				print $fh qq{<th>Count</th><th>Av.&nbsp;size</th>};
			}
			if (exists $checkpoint_info{chronos}) {
				print $fh
qq{<th>Wrote buffers</th><th>Added</th><th>Removed</th><th>Recycled</th><th>Write time (sec)</th><th>Sync time (sec)</th><th>Total time (sec)</th>};
			}
			if (exists $checkpoint_info{warning}) {
				print $fh qq{<th>Count</th><th>Av. time (sec)</th>};
			}
			if ($tempfile_info{count} || exists $checkpoint_info{chronos}) {
				print $fh qq{</tr>};
				foreach my $d (sort {$a <=> $b} keys %per_hour_info) {
					my $c = 1;
					$d =~ /^\d{4}(\d{2})(\d{2})$/;
					my $zday = "$abbr_month{$1} $2";
					foreach my $h (sort {$a <=> $b} keys %{$per_hour_info{$d}}) {
						my $colb = $c % 2;
						$zday = "&nbsp;" if ($c > 1);
						print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td>";
						if ($tempfile_info{count}) {
							my $temp_average = '0';
							if ($tempfile_info{chronos}{$d}{$h}{count}) {
								$temp_average = &comma_numbers(
									sprintf("%.2f", $tempfile_info{chronos}{$d}{$h}{size} / $tempfile_info{chronos}{$d}{$h}{count}));
							}
							print $fh "<td class=\"right\">", &comma_numbers($tempfile_info{chronos}{$d}{$h}{count} || 0),
								"</td><td class=\"right\">$temp_average</td>";
						}
						if (exists $checkpoint_info{chronos}) {
							if (exists $checkpoint_info{chronos}{$d}{$h}) {
								print $fh "<td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{wbuffer}) || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{file_added})   || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{file_removed}) || 0,
									"</td><td class=\"right\">",
									&comma_numbers($checkpoint_info{chronos}{$d}{$h}{file_recycled}) || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{write}) || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{sync})  || 0,
									"</td><td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{total}) || 0,
									"</td>";
								if ($checkpoint_info{warning}) {
									print $fh "<td class=\"right\">", &comma_numbers($checkpoint_info{chronos}{$d}{$h}{warning}) || 0,
										"</td><td class=\"right\">",
										&comma_numbers(
										sprintf(
											"%.2f",
											($checkpoint_info{chronos}{$d}{$h}{warning_seconds} || 0) /
												($checkpoint_info{chronos}{$d}{$h}{warning} || 1)
											   )
										) || 0, "</td>";
								}
							} else {
								print $fh
"<td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td><td class=\"right\">0</td>";
								if ($checkpoint_info{warning}) {
									print $fh "<td class=\"right\">0</td><td class=\"right\">0</td>";
								}
							}
						}
						print $fh "</tr>\n";
						$c++;
					}
				}
				print $fh "</table>\n";
			}

			# checkpoint size
			if (exists $checkpoint_info{chronos}) {
				foreach my $tm (sort {$a <=> $b} keys %{$checkpoint_info{chronos}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						next if ($t < $t_min_hour);
						last if ($t > $t_max_hour);
						$d1 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{wbuffer} || 0) . "],";
					}
				}
				$d1 =~ s/,$//;
				&flotr2_graph(
					6,         'checkpointwritebuffers_graph', $d1, '', '', 'Checkpoint write buffers',
					'Buffers', 'Write buffers',                '',  ''
				);
				$d1 = '';

				foreach my $tm (sort {$a <=> $b} keys %{$checkpoint_info{chronos}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						next if ($t < $t_min_hour);
						last if ($t > $t_max_hour);
						$d1 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{file_added}    || 0) . "],";
						$d2 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{file_removed}  || 0) . "],";
						$d3 .= "[$t, " . ($checkpoint_info{chronos}{$tm}{$h}{file_recycled} || 0) . "],";
					}
				}
				$d1 =~ s/,$//;
				$d2 =~ s/,$//;
				$d3 =~ s/,$//;
				&flotr2_graph(
					7,                 'checkpointfiles_graph', $d1,       $d2, $d3, 'Checkpoint Wal files usage',
					'Number of files', 'Added',                 'Removed', 'Recycled'
				);
				$d1 = '';
				$d2 = '';
				$d3 = '';
			}

			# Temporary file size
			if (exists $tempfile_info{chronos}) {
				foreach my $tm (sort {$a <=> $b} keys %{$tempfile_info{chronos}}) {
					$tm =~ /(\d{4})(\d{2})(\d{2})/;
					my $y  = $1 - 1900;
					my $mo = $2 - 1;
					my $d  = $3;
					foreach my $h ("00" .. "23") {
						my $t = timegm_nocheck(0, 0, $h, $d, $mo, $y) * 1000;
						next if ($t < $t_min_hour);
						last if ($t > $t_max_hour);
						$d1 .= "[$t, " . ($tempfile_info{chronos}{$tm}{$h}{size}  || 0) . "],";
						$d2 .= "[$t, " . ($tempfile_info{chronos}{$tm}{$h}{count} || 0) . "],";
					}
				}
				$d1 =~ s/,$//;
				$d2 =~ s/,$//;
				&flotr2_graph(
					8, 'temporaryfile_graph', $d1, '', '', 'Temporary files',
					'Size of files', 'Size of files', '', '', 'Number of files', $d2, 'Number of files'
				);
				$d1 = '';
				$d2 = '';
			}
		}
	}

	# INSERT/DELETE/UPDATE/SELECT repartition
	if (!$disable_type && (!$log_duration || $enable_log_min_duration)) {
		print $fh qq{
<h2 id="QueriesByTypeReport">Queries by type <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Type</th>

		<th>Count</th>
		<th>Percentage</th>
	</tr>
};
		$overall_stat{'SELECT'} ||= 0;
		$overall_stat{'INSERT'} ||= 0;
		$overall_stat{'UPDATE'} ||= 0;
		$overall_stat{'DELETE'} ||= 0;
		my $totala = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
		my $total = $overall_stat{'queries_number'} || 1;

		print $fh "<tr class=\"row0\"><td>SELECT</td><td class=\"right\">", &comma_numbers($overall_stat{'SELECT'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'SELECT'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row1\"><td>INSERT</td><td class=\"right\">", &comma_numbers($overall_stat{'INSERT'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'INSERT'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row0\"><td>UPDATE</td><td class=\"right\">", &comma_numbers($overall_stat{'UPDATE'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'UPDATE'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row1\"><td>DELETE</td><td class=\"right\">", &comma_numbers($overall_stat{'DELETE'}),
			"</td><td class=\"right\">", sprintf("%0.2f", ($overall_stat{'DELETE'} * 100) / $total), "%</td></tr>\n";
		print $fh "<tr class=\"row0\"><td>OTHERS</td><td class=\"right\">", &comma_numbers($total - $totala),
			"</td><td class=\"right\">", sprintf("%0.2f", (($total - $totala) * 100) / $total), "%</td></tr>\n"
			if (($total - $totala) > 0);
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";

		if ($graph && $totala) {
			my %data = ();
			foreach my $t ('SELECT', 'INSERT', 'UPDATE', 'DELETE') {
				if ((($overall_stat{$t} * 100) / $total) > $pie_percentage_limit) {
					$data{$t} = $overall_stat{$t} || 0;
				} else {
					$data{"Sum types < $pie_percentage_limit%"} += $overall_stat{$t} || 0;
				}
			}
			if (((($total - $totala) * 100) / $total) > $pie_percentage_limit) {
				$data{'Others'} = $total - $totala;
			} else {
				$data{"Sum types < $pie_percentage_limit%"} += $total - $totala;
			}
			&flotr2_piegraph(9, 'queriesbytype_graph', 'Type of queries', %data);
		}
		print $fh "</td></tr></table>\n";
	}

	# Lock stats per type
	if (!$disable_lock && scalar keys %lock_info > 0) {
		print $fh qq{
<h2 id="LocksByTypeReport">Locks by type <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Type</th>
		<th>Object</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count    = 0;
		my $total_duration = 0;
		foreach my $t (sort keys %lock_info) {
			print $fh "<tr class=\"row1\"><td colspan=\"2\">$t</td><td class=\"right\">", &comma_numbers($lock_info{$t}{count}),
				"</td><td class=\"right\">", &convert_time($lock_info{$t}{duration}), "</td><td class=\"right\">",
				&convert_time($lock_info{$t}{duration} / $lock_info{$t}{count}), "</td></tr>\n";
			foreach my $o (sort keys %{$lock_info{$t}}) {
				next if (($o eq 'count') || ($o eq 'duration') || ($o eq 'chronos'));
				print $fh "<tr class=\"row0\"><td  class=\"right\" colspan=\"2\">$o</td><td class=\"right\">",
					&comma_numbers($lock_info{$t}{$o}{count}),   "</td><td class=\"right\">",
					&convert_time($lock_info{$t}{$o}{duration}), "</td><td class=\"right\">",
					&convert_time($lock_info{$t}{$o}{duration} / $lock_info{$t}{$o}{count}), "</td></tr>\n";
			}
			$total_count    += $lock_info{$t}{count};
			$total_duration += $lock_info{$t}{duration};
		}
		print $fh "<tr class=\"row1\"><td colspan=\"2\"><b>Total</b></td><td class=\"right\">", &comma_numbers($total_count),
			"</td><td class=\"right\">", &convert_time($total_duration), "</td><td class=\"right\">",
			&convert_time($total_duration / ($total_count || 1)), "</td></tr>\n";
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %locktype = ();
			my @small    = ();
			foreach my $d (sort keys %lock_info) {
				if ((($lock_info{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$locktype{$d} = $lock_info{$d}{count} || 0;
				} else {
					$locktype{"Sum types < $pie_percentage_limit%"} += $lock_info{$d}{count} || 0;
					push(@small, $d);

				}
			}
			if ($#small == 0) {
				$locktype{$small[0]} = $locktype{"Sum types < $pie_percentage_limit%"};
				delete $locktype{"Sum types < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(10, 'lockbytype_graph', 'Type of locks', %locktype);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show session per database statistics
	if (!$disable_session && exists $session_info{database}) {
		print $fh qq{
<h2 id="SessionsDatabaseReport">Sessions per database <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Database</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count = 0;
		my $c           = 0;
		foreach my $d (sort keys %{$session_info{database}}) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($session_info{database}{$d}{count}),
				"</td><td class=\"right\">", &convert_time($session_info{database}{$d}{duration}), "</td><td class=\"right\">",
				&convert_time($session_info{database}{$d}{duration} / $session_info{database}{$d}{count}), "</td></tr>\n";
			$total_count += $session_info{database}{$d}{count};
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$session_info{database}}) {
				if ((($session_info{database}{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $session_info{database}{$d}{count} || 0;
				} else {
					$infos{"Sum sessions < $pie_percentage_limit%"} += $session_info{database}{$d}{count} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum sessions < $pie_percentage_limit%"};
				delete $infos{"Sum sessions < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(11, 'databasesessions_graph', 'Sessions per database', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show session per user statistics
	if (!$disable_session && exists $session_info{user}) {
		print $fh qq{
<h2 id="SessionsUserReport">Sessions per user <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>User</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count = 0;
		my $c           = 0;
		foreach my $d (sort keys %{$session_info{user}}) {
			my $colb = $c % 2;
			$total_count += $session_info{user}{$d}{count};
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($session_info{user}{$d}{count}),
				"</td><td class=\"right\">", &convert_time($session_info{user}{$d}{duration}), "</td><td class=\"right\">",
				&convert_time($session_info{user}{$d}{duration} / $session_info{user}{$d}{count}), "</td></tr>\n";
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$session_info{user}}) {
				if ((($session_info{user}{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $session_info{user}{$d}{count} || 0;
				} else {
					$infos{"Sum sessions < $pie_percentage_limit%"} += $session_info{user}{$d}{count} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum sessions < $pie_percentage_limit%"};
				delete $infos{"Sum sessions < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(12, 'usersessions_graph', 'Sessions per user', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show session per host statistics
	if (!$disable_session && exists $session_info{host}) {
		print $fh qq{
<h2 id="SessionsHostReport">Sessions per host <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Host</th>
		<th>Count</th>
		<th>Total Duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
	</tr>
};
		my $total_count = 0;
		my $c           = 0;
		foreach my $d (sort keys %{$session_info{host}}) {
			my $colb = $c % 2;
			$total_count += $session_info{host}{$d}{count};
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($session_info{host}{$d}{count}),
				"</td><td class=\"right\">", &convert_time($session_info{host}{$d}{duration}), "</td><td class=\"right\">",
				&convert_time($session_info{host}{$d}{duration} / $session_info{host}{$d}{count}), "</td></tr>\n";
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$session_info{host}}) {
				if ((($session_info{host}{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $session_info{host}{$d}{count} || 0;
				} else {
					$infos{"Sum sessions < $pie_percentage_limit%"} += $session_info{host}{$d}{count} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum sessions < $pie_percentage_limit%"};
				delete $infos{"Sum sessions < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(13, 'hostsessions_graph', 'Sessions per host', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show connection per database statistics
	if (!$disable_connection && exists $connection_info{database}) {
		print $fh qq{
<h2 id="ConnectionsDatabaseReport">Connections per database <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Database</th>
		<th>User</th>
		<th>Count</th>
	</tr>
};
		my $total_count = 0;
		foreach my $d (sort keys %{$connection_info{database}}) {
			print $fh "<tr class=\"row1\"><td colspan=\"2\">$d</td><td class=\"right\">",
				&comma_numbers($connection_info{database}{$d}), "</td></tr>\n";
			$total_count += $connection_info{database}{$d};
			foreach my $u (sort keys %{$connection_info{user}}) {
				next if (!exists $connection_info{database_user}{$d}{$u});
				print $fh "<tr class=\"row0\"><td colspan=\"2\" class=\"right\">$u</td><td class=\"right\">",
					&comma_numbers($connection_info{database_user}{$d}{$u}), "</td></tr>\n";
			}
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$connection_info{database}}) {
				if ((($connection_info{database}{$d} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $connection_info{database}{$d} || 0;
				} else {
					$infos{"Sum connections < $pie_percentage_limit%"} += $connection_info{database}{$d} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum connections < $pie_percentage_limit%"};
				delete $infos{"Sum connections < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(14, 'databaseconnections_graph', 'Connections per database', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show connection per user statistics
	if (!$disable_connection && exists $connection_info{user}) {
		print $fh qq{
<h2 id="ConnectionsUserReport">Connections per user <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>User</th>
		<th>Count</th>
	</tr>
};

		my $total_count = 0;
		my $c           = 0;
		foreach my $u (sort keys %{$connection_info{user}}) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$u</td><td class=\"right\">", &comma_numbers($connection_info{user}{$u}),
				"</td></tr>\n";
			$total_count += $connection_info{user}{$u};
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$connection_info{user}}) {
				if ((($connection_info{user}{$d} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $connection_info{user}{$d} || 0;
				} else {
					$infos{"Sum connections < $pie_percentage_limit%"} += $connection_info{user}{$d} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum connections < $pie_percentage_limit%"};
				delete $infos{"Sum connections < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(15, 'userconnections_graph', 'Connections per user', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show connection per host statistics
	if (!$disable_connection && exists $connection_info{host}) {
		print $fh qq{
<h2 id="ConnectionsHostReport">Connections per host <a href="#top" title="Back to top">^</a></h2>
<table>
<tr><td width="500" align="left" valign="top">
<table class="SmallTableList">
	<tr>
		<th>Host</th>
		<th>Count</th>
	</tr>
};

		my $total_count = 0;
		my $c           = 0;
		foreach my $h (sort keys %{$connection_info{host}}) {
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$h</td><td class=\"right\">", &comma_numbers($connection_info{host}{$h}),
				"</td></tr>\n";
			$total_count += $connection_info{host}{$h};
			$c++;
		}
		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_count) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %{$connection_info{host}}) {
				if ((($connection_info{host}{$d} * 100) / $total_count) > $pie_percentage_limit) {
					$infos{$d} = $connection_info{host}{$d} || 0;
				} else {
					$infos{"Sum connections < $pie_percentage_limit%"} += $connection_info{host}{$d} || 0;
					push(@small, $d);
				}
			}
			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum connections < $pie_percentage_limit%"};
				delete $infos{"Sum connections < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(16, 'hostconnections_graph', 'Connections per host', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

	# Show top informations
	if (!$disable_query && (!$log_duration || $enable_log_min_duration)) {
		print $fh qq{
<h2 id="SlowestQueriesReport">Slowest queries <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>

		<th>Duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		for (my $i = 0 ; $i <= $#top_slowest ; $i++) {
			my $col = $i % 2;
			my $ttl = $top_slowest[$i]->[1] || '';
			print $fh "<tr class=\"row$col\"><td class=\"center top\">", $i + 1, "</td><td class=\"relevantInformation top center\">",
				&convert_time($top_slowest[$i]->[0]),
				"</td><td title=\"$ttl\"><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($top_slowest[$i]->[2]), "</div></td></tr>\n";
		}
		print $fh "</table>\n";
		@top_slowest = ();

		print $fh qq{
<h2 id="NormalizedQueriesMostTimeReport">Queries that took up the most time (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>
		<th>Total duration</th>
		<th>Times executed</th>

		<th>Av.&nbsp;duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		my $idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{duration} <=> $normalyzed_info{$a}{duration}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$d}{query};
					last;
				}
			}
			$normalyzed_info{$k}{average} = $normalyzed_info{$k}{duration} / $normalyzed_info{$k}{count};
			my $col = $idx % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\">",
				&convert_time($normalyzed_info{$k}{duration}),
				"</td><td class=\"top center\"><div class=\"tooltipLink\"><span class=\"information\">",
				&comma_numbers($normalyzed_info{$k}{count}),
"</span><div class=\"tooltip\"><table><tr><th>Day</th><th>Hour</th><th>Count</th><th>Duration</th><th>Av.&nbsp;Duration</th></tr>";
			foreach my $d (sort keys %{$normalyzed_info{$k}{chronos}}) {
				my $c = 1;
				$d =~ /^\d{4}(\d{2})(\d{2})$/;
				my $zday = "$abbr_month{$1} $2";
				foreach my $h (sort keys %{$normalyzed_info{$k}{chronos}{$d}}) {
					$normalyzed_info{$k}{chronos}{$d}{$h}{average} =
						$normalyzed_info{$k}{chronos}{$d}{$h}{duration} / $normalyzed_info{$k}{chronos}{$d}{$h}{count};
					my $colb = $c % 2;
					$zday = "&nbsp;" if ($c > 1);
					print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
						&comma_numbers($normalyzed_info{$k}{chronos}{$d}{$h}{count}),   "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{duration}), "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{average}),  "</td></tr>";
					$c++;
				}
			}
			print $fh "</table></div></div></td>";
			print $fh "<td class=\"top center\">", &convert_time($normalyzed_info{$k}{average}),
				"</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($q), "</div>";

			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedQueriesMostTimeReport_$idx\" name=\"button_NormalizedQueriesMostTimeReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedQueriesMostTimeReport_$idx', 'examples_NormalizedQueriesMostTimeReport_$idx', 'examples');\" /><div id=\"examples_NormalizedQueriesMostTimeReport_$idx\" class=\"examples\" style=\"display:none;\">";
				my $i = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $i % 2;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$i++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";

		print $fh qq{
<h2 id="NormalizedQueriesMostFrequentReport">Most frequent queries (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>
		<th>Times executed</th>
		<th>Total duration</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{count} <=> $normalyzed_info{$a}{count}} keys %normalyzed_info) {
			next if (!$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$d}{query};
					last;
				}
			}
			my $col = $idx % 2;
			print $fh
"<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\"><div class=\"tooltipLink\"><span class=\"information\">",
				&comma_numbers($normalyzed_info{$k}{count}),
"</span><div class=\"tooltip\"><table><tr><th>Day</th><th>Hour</th><th>Count</th><th>Duration</th><th>Av.&nbsp;Duration</th></tr>";
			foreach my $d (sort keys %{$normalyzed_info{$k}{chronos}}) {
				my $c = 1;
				$d =~ /^\d{4}(\d{2})(\d{2})$/;
				my $zday = "$abbr_month{$1} $2";
				foreach my $h (sort keys %{$normalyzed_info{$k}{chronos}{$d}}) {
					$normalyzed_info{$k}{chronos}{$d}{$h}{average} =
						$normalyzed_info{$k}{chronos}{$d}{$h}{duration} / $normalyzed_info{$k}{chronos}{$d}{$h}{count};
					my $colb = $c % 2;
					$zday = "&nbsp;" if ($c > 1);
					print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
						&comma_numbers($normalyzed_info{$k}{chronos}{$d}{$h}{count}),   "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{duration}), "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{average}),  "</td></tr>";
					$c++;
				}
			}
			print $fh "</table></div></div></td>";
			print $fh "<td class=\"top center\">", &convert_time($normalyzed_info{$k}{duration}), "</td><td class=\"top center\">",
				&convert_time($normalyzed_info{$k}{average}), "</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($q), "</div>";

			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedQueriesMostFrequentReport_$idx\" name=\"button_NormalizedQueriesMostFrequentReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedQueriesMostFrequentReport_$idx', 'examples_NormalizedQueriesMostFrequentReport_$idx', 'examples');\" /><div id=\"examples_NormalizedQueriesMostFrequentReport_$idx\" class=\"examples\" style=\"display:none;\">";
				my $i = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $i % 2;
					print $fh
"<div class=\"example$d\" title=\"$normalyzed_info{$k}{samples}{$d}{date}\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$i++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";

		print $fh qq{
<h2 id="NormalizedQueriesSlowestAverageReport">Slowest queries (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
	<tr>
		<th>Rank</th>
		<th>Av.&nbsp;duration&nbsp;(s)</th>

		<th>Times executed</th>
		<th>Total duration</th>
		<th>Query</th>
	</tr>
};
		$idx = 1;
		foreach my $k (sort {$normalyzed_info{$b}{average} <=> $normalyzed_info{$a}{average}} keys %normalyzed_info) {
			next if (!$k || !$normalyzed_info{$k}{count});
			last if ($idx > $top);
			my $q = $k;
			if ($normalyzed_info{$k}{count} == 1) {
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					$q = $normalyzed_info{$k}{samples}{$d}{query};
					last;
				}
			}
			my $col = $idx % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\">",
				&convert_time($normalyzed_info{$k}{average}),
				"</td><td class=\"top center\"><div class=\"tooltipLink\"><span class=\"information\">",
				&comma_numbers($normalyzed_info{$k}{count}),
"</span><div class=\"tooltip\"><table><tr><th>Day</th><th>Hour</th><th>Count</th><th>Duration</th><th>Av.&nbsp;Duration</th></tr>";
			foreach my $d (sort keys %{$normalyzed_info{$k}{chronos}}) {
				my $c = 1;
				$d =~ /^\d{4}(\d{2})(\d{2})$/;
				my $zday = "$abbr_month{$1} $2";
				foreach my $h (sort keys %{$normalyzed_info{$k}{chronos}{$d}}) {
					$normalyzed_info{$k}{chronos}{$d}{$h}{average} =
						$normalyzed_info{$k}{chronos}{$d}{$h}{duration} / $normalyzed_info{$k}{chronos}{$d}{$h}{count};
					my $colb = $c % 2;
					$zday = "&nbsp;" if ($c > 1);
					print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
						&comma_numbers($normalyzed_info{$k}{chronos}{$d}{$h}{count}),   "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{duration}), "</td><td>",
						&convert_time($normalyzed_info{$k}{chronos}{$d}{$h}{average}),  "</td></tr>";
					$c++;
				}
			}
			print $fh "</table></div></div></td>";
			print $fh "<td class=\"top center\">", &convert_time($normalyzed_info{$k}{duration}),
				"</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($q), "</div>";
			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedQueriesSlowestAverageReport_$idx\" name=\"button_NormalizedQueriesSlowestAverageReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedQueriesSlowestAverageReport_$idx', 'examples_NormalizedQueriesSlowestAverageReport_$idx', 'examples');\" /><div id=\"examples_NormalizedQueriesSlowestAverageReport_$idx\" class=\"examples\" style=\"display:none;\">";
				my $i = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $i % 2;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$i++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";
	}

	if (!$disable_error) {
		&show_error_as_html();
	}

	# Dump the html footer
	&html_footer();

}

sub dump_error_as_html
{

	# Dump the html header
	&html_header();

	# Global informations
	my $curdate    = localtime(time);
	my $fmt_nlines = &comma_numbers($nlines);
	my $total_time = timestr($td);
	$total_time =~ s/^([\.0-9]+) wallclock.*/$1/;
	$total_time = &convert_time($total_time * 1000);
	my $logfile_str = $log_files[0];
	if ($#log_files > 0) {
		$logfile_str .= ', ..., ' . $log_files[-1];
	}
	print $fh qq{
<div class="information">
<ul>
<li>Generated on $curdate</li>
<li>Log file: $logfile_str</li>
<li>Parsed $fmt_nlines log entries in $total_time</li>
<li>Log start from $first_log_timestamp to $last_log_timestamp</li>
</ul>
</div>
};
	my $fmt_errors = &comma_numbers($overall_stat{'errors_number'}) || 0;
	my $fmt_unique_error = &comma_numbers(scalar keys %{$overall_stat{'unique_normalized_errors'}}) || 0;
	print $fh qq{
<div class="reports">
<h2 id="OverallStatsReport">Overall statistics <a href="#top" title="Back to top">^</a></h2>
<div class="information">
<table><tr><td valign="top">
<ul>
<li>Number of events: $fmt_errors</li>
<li>Number of unique normalized events: $fmt_unique_error</li>
</ul>
</td></tr></table>
</div>
};

	&show_error_as_html();

	# Dump the html footer
	&html_footer();
}

sub show_error_as_html
{
	return if (scalar keys %error_info == 0);

	print $fh qq{
<h2 id="NormalizedErrorsMostFrequentReport">Most frequent events (N) <a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
<tr>
	<th>Rank</th>
	<th>Times reported</th>
	<th>Error</th>

</tr>
};
	my $idx = 1;
	foreach my $k (sort {$error_info{$b}{count} <=> $error_info{$a}{count}} keys %error_info) {
		next if (!$error_info{$k}{count});
		last if ($idx > $top);
		my $col = $idx % 2;
		print $fh
"<tr class=\"row$col\"><td class=\"center top\">$idx</td><td class=\"relevantInformation top center\"><div class=\"tooltipLink\"><span class=\"information\">",
			&comma_numbers($error_info{$k}{count}), "</span>";
		print $fh "<div class=\"tooltip\"><table><tr><th>Day</th><th>Hour</th><th>Count</th></tr>";
		foreach my $d (sort keys %{$error_info{$k}{chronos}}) {
			my $c = 1;
			$d =~ /^\d{4}(\d{2})(\d{2})$/;
			my $zday = "$abbr_month{$1} $2";
			foreach my $h (sort keys %{$error_info{$k}{chronos}{$d}}) {
				my $colb = $c % 2;
				$zday = "&nbsp;" if ($c > 1);
				print $fh "<tr class=\"row$colb\"><td>$zday</td><td>$h</td><td>",
					&comma_numbers($error_info{$k}{chronos}{$d}{$h}{count}), "</td></tr>";
				$c++;
			}
		}
		print $fh "</table></div></div></td>\n";
		if ($error_info{$k}{count} > 1) {
			my $msg = $k;
			$msg =~ s/HINT:  (parameter "[^"]+" changed to)/LOG:  $1/;
			$msg =~ s/HINT:  (database system was shut down)/LOG:  $1/;
			print $fh "<td><div class=\"error\">$msg</div>";
			print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_NormalizedErrorsMostFrequentReport_$idx\" name=\"button_NormalizedErrorsMostFrequentReport_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_NormalizedErrorsMostFrequentReport_$idx', 'examples_NormalizedErrorsMostFrequentReport_$idx', 'examples');\" /><div id=\"examples_NormalizedErrorsMostFrequentReport_$idx\" class=\"examples\" style=\"display:none;\">";
			for (my $i = 0 ; $i <= $#{$error_info{$k}{date}} ; $i++) {
				if (   ($error_info{$k}{error}[$i] =~ s/HINT:  (parameter "[^"]+" changed to)/LOG:  $1/)
					|| ($error_info{$k}{error}[$i] =~ s/HINT:  (database system was shut down)/LOG:  $1/))
				{
					$logs_type{HINT}--;
					$logs_type{LOG}++;
				}
				my $c = $i % 2;
				print $fh "<div class=\"example$c\" title=\"$error_info{$k}{date}[$i]\">$error_info{$k}{error}[$i]</div>\n";
				print $fh "<div class=\"errorInformation$c\">Detail: $error_info{$k}{detail}[$i]</div>\n"
					if ($error_info{$k}{detail}[$i]);
				print $fh "<div class=\"errorInformation$c\">Context: $error_info{$k}{context}[$i]</div>\n"
					if ($error_info{$k}{context}[$i]);
				print $fh "<div class=\"errorInformation$c\">Hint: $error_info{$k}{hint}[$i]</div>\n" if ($error_info{$k}{hint}[$i]);
				print $fh "<div class=\"errorInformation$c\">Statement: $error_info{$k}{statement}[$i]</div>\n"
					if ($error_info{$k}{statement}[$i]);
			}
			print $fh "</div>";
		} else {
			if (   ($error_info{$k}{error}[0] =~ s/HINT:  (parameter "[^"]+" changed to)/LOG:  $1/)
				|| ($error_info{$k}{error}[0] =~ s/HINT:  (database system was shut down)/LOG:  $1/))
			{
				$logs_type{HINT}--;
				$logs_type{LOG}++;
			}
			print $fh "<td><div class=\"error\" title=\"$error_info{$k}{date}[0]\">$error_info{$k}{error}[0]</div>";
			print $fh "<div class=\"errorInformation\">Detail: $error_info{$k}{detail}[0]</div>\n"   if ($error_info{$k}{detail}[0]);
			print $fh "<div class=\"errorInformation\">Context: $error_info{$k}{context}[0]</div>\n" if ($error_info{$k}{context}[0]);
			print $fh "<div class=\"errorInformation\">Hint: $error_info{$k}{hint}[0]</div>\n"       if ($error_info{$k}{hint}[0]);
			print $fh "<div class=\"errorInformation\">Statement: $error_info{$k}{statement}[0]</div>\n"
				if ($error_info{$k}{statement}[0]);
		}
		print $fh "</td></tr>\n";
		$idx++;
	}
	print $fh "</table>\n";

	if (scalar keys %logs_type > 0) {

		# Show log' types
		print $fh qq{
	<h2 id="LogsTypeReport">Logs per type <a href="#top" title="Back to top">^</a></h2>
	<table>
	<tr><td width="500" align="left" valign="top">
	<table class="SmallTableList">
	  <tr>
	    <th>Type</th>
	    <th>Count</th>
	    <th>Percentage</th>
	  </tr>
	};

		my $total_logs = 0;
		foreach my $d (sort keys %logs_type) {
			$total_logs += $logs_type{$d};
		}

		my $c = 0;

		foreach my $d (sort keys %logs_type) {
			next if (!$logs_type{$d});
			my $colb = $c % 2;
			print $fh "<tr class=\"row$colb\"><td>$d</td><td class=\"right\">", &comma_numbers($logs_type{$d}),
				"</td><td class=\"right\">", sprintf("%0.2f", ($logs_type{$d} * 100) / $total_logs), "%</td></tr>\n";
			$c++;
		}

		print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
		if ($graph && $total_logs) {
			my %infos = ();
			my @small = ();
			foreach my $d (sort keys %logs_type) {
				if ((($logs_type{$d} * 100) / $total_logs) > $pie_percentage_limit) {
					$infos{$d} = $logs_type{$d} || 0;
				} else {
					$infos{"Sum log types < $pie_percentage_limit%"} += $logs_type{$d} || 0;
					push(@small, $d);
				}
			}

			if ($#small == 0) {
				$infos{$small[0]} = $infos{"Sum log types < $pie_percentage_limit%"};
				delete $infos{"Sum log types < $pie_percentage_limit%"};
			}
			&flotr2_piegraph(17, 'logstype_graph', 'Logs per type', %infos);
		}
		print $fh "</td></tr></table>\n";
	}

}

# Highlight SQL code
sub highlight_code
{
	my $code = shift;

	# prettify SQL query
	if (!$noprettify) {
		my $sql = SQL::Beautify->new;
		$sql->query($code);
		$code = $sql->beautify;
	}

	return $code if ($nohighlight);

	foreach my $x (keys %SYMBOLS) {
		$code =~ s/$x/\$\$CLASSSY0A\$\$$SYMBOLS{$x}\$\$CLASSSY0B\$\$/gs;
	}

	#$code =~ s/("[^"]*")/<span class="st0">$1<\/span>/igs;
	$code =~ s/('[^']*')/<span class="st0">$1<\/span>/gs;
	$code =~ s/(`[^`]*`)/<span class="st0">$1<\/span>/gs;

	for (my $x = 0 ; $x <= $#KEYWORDS1 ; $x++) {

		#$code =~ s/\b($KEYWORDS1[$x])\b/<span class="kw1">$1<\/span>/igs;
		$code =~ s/\b$KEYWORDS1[$x]\b/<span class="kw1">$KEYWORDS1[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#KEYWORDS2 ; $x++) {

		#$code =~ s/\b($KEYWORDS2[$x])\b/<span class="kw2">$1<\/span>/igs;
		$code =~ s/\b$KEYWORDS2[$x]\b/<span class="kw2">$KEYWORDS2[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#KEYWORDS3 ; $x++) {

		#$code =~ s/\b($KEYWORDS3[$x])\b/<span class="kw3">$1<\/span>/igs;
		$code =~ s/\b$KEYWORDS3[$x]\b/<span class="kw3">$KEYWORDS3[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#BRACKETS ; $x++) {
		$code =~ s/($BRACKETS[$x])/<span class="br0">$1<\/span>/igs;
	}
	$code =~ s/\$\$CLASSSY0A\$\$([^\$]+)\$\$CLASSSY0B\$\$/<span class="sy0">$1<\/span>/gs;

	$code =~ s/\b(\d+)\b/<span class="nu0">$1<\/span>/igs;

	return $code;
}

sub compute_arg_list
{

	# Some command lines arguments can be used multiple time or be written
	# as a coma separated list.
	# For example: --dbuser=postgres --dbuser=joe or --dbuser=postgres,joe
	# So we have to aggregate all the possible value
	my @tmp = ();
	foreach my $v (@exclude_user) {
		push(@tmp, split(/,/, $v));
	}
	@exclude_user = ();
	push(@exclude_user, @tmp);

	@tmp = ();
	foreach my $v (@dbname) {
		push(@tmp, split(/,/, $v));
	}
	@dbname = ();
	push(@dbname, @tmp);

	@tmp = ();
	foreach my $v (@dbuser) {
		push(@tmp, split(/,/, $v));
	}
	@dbuser = ();
	push(@dbuser, @tmp);

	@tmp = ();
	foreach my $v (@dbclient) {
		push(@tmp, split(/,/, $v));
	}
	@dbclient = ();
	push(@dbclient, @tmp);

	@tmp = ();
	foreach my $v (@dbappname) {
		push(@tmp, split(/,/, $v));
	}
	@dbappname = ();
	push(@dbappname, @tmp);
}

sub validate_log_line
{
	my ($t_pid) = @_;

	# Check user and/or database if require
	if ($#dbname >= 0) {

		# Log line do not match the required dbname
		if (!$prefix_vars{'t_dbname'} || !grep(/^$prefix_vars{'t_dbname'}$/i, @dbname)) {
			delete $cur_info{$t_pid};
			return 0;
		}
	}
	if ($#dbuser >= 0) {

		# Log line do not match the required dbuser
		if (!$prefix_vars{'t_dbuser'} || !grep(/^$prefix_vars{'t_dbuser'}$/i, @dbuser)) {
			delete $cur_info{$t_pid};
			return 0;
		}
	}
	if ($#dbclient >= 0) {

		# Log line do not match the required dbclient
		$prefix_vars{'t_client'} ||= $prefix_vars{'t_hostport'};
		if (!$prefix_vars{'t_client'} || !grep(/^$prefix_vars{'t_dbclient'}$/i, @dbclient)) {
			delete $cur_info{$t_pid};
			return 0;
		}
	}
	if ($#dbappname >= 0) {

		# Log line do not match the required dbname
		if (!$prefix_vars{'t_appname'} || !grep(/^$prefix_vars{'t_appname'}$/i, @dbappname)) {
			delete $cur_info{$t_pid};
			return 0;
		}
	}
	if ($#exclude_user >= 0) {

		# Log line match the excluded dbuser
		if ($prefix_vars{'t_dbuser'} && grep(/^$prefix_vars{'t_dbuser'}$/i, @exclude_user)) {
			delete $cur_info{$t_pid};
			return 0;
		}
	}
	return 1;
}

sub parse_log_prefix
{
	my ($t_logprefix) = @_;

	# Extract user and database information from the logprefix part
	if ($t_logprefix) {

		# Search for database user
		if ($t_logprefix =~ $regex_prefix_dbuser) {
			$prefix_vars{'t_dbuser'} = $1;
		}

		# Search for database name
		if ($t_logprefix =~ $regex_prefix_dbname) {
			$prefix_vars{'t_dbname'} = $1;
		}
	}
}

sub parse_query
{

	my $t_pid = $prefix_vars{'t_pid'};

	# Force parameter change to be a hint message so that it can appear
	# in the event/error/warning messages report part.
	if ($prefix_vars{'t_loglevel'} eq 'LOG') {
		if ($prefix_vars{'t_query'} =~ /parameter "[^"]+" changed to "[^"]+"/) {
			$prefix_vars{'t_loglevel'} = 'HINT';
		} elsif ($prefix_vars{'t_query'} =~ /database system was shut down at /) {
			$prefix_vars{'t_loglevel'} = 'HINT';
		}
	}

	# Do not parse lines that are not an error like message
	if ($error_only && ($prefix_vars{'t_loglevel'} !~ /(WARNING|ERROR|FATAL|PANIC|DETAIL|HINT|STATEMENT|CONTEXT)/)) {
		if (exists $cur_info{$t_pid} && ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session})) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
		return;
	}

	# Do not parse lines that are an error like message
	if ($disable_error && ($prefix_vars{'t_loglevel'} =~ /WARNING|ERROR|FATAL|PANIC|HINT|CONTEXT|DETAIL|STATEMENT/)) {
		if (exists $cur_info{$t_pid} && ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session})) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
		return;
	}

	# Store a counter of logs type
	$logs_type{$prefix_vars{'t_loglevel'}}++;

	# Replace syslog tablulation rewrite
	$prefix_vars{'t_query'} =~ s/#011/\t/g if ($format =~ /syslog/);

	my $date_part = "$prefix_vars{'t_year'}$prefix_vars{'t_month'}$prefix_vars{'t_day'}";

	# Stores lock activity
	if (($prefix_vars{'t_loglevel'} eq 'LOG') && ($prefix_vars{'t_query'} =~ /acquired ([^\s]+) on ([^\s]+) .* after ([0-9\.]+) ms/))
	{
		return if ($disable_lock);
		$lock_info{$1}{count}++;
		$lock_info{$1}{duration} += $3;
		$lock_info{$1}{$2}{count}++;
		$lock_info{$1}{$2}{duration} += $3;
		$lock_info{$1}{chronos}{$date_part}{$prefix_vars{'t_hour'}}{count}++;
		$lock_info{$1}{chronos}{$date_part}{$prefix_vars{'t_hour'}}{duration}++;
		return;
	}

	# Stores temporary files activity
	if (($prefix_vars{'t_loglevel'} eq 'LOG') && ($prefix_vars{'t_query'} =~ /temporary file: path .*, size (\d+)/)) {
		return if ($disable_temporary);
		$tempfile_info{count}++;
		$tempfile_info{size} += $1;
		$tempfile_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{count}++;
		$tempfile_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{size} += $1;
		$tempfile_info{maxsize} = $1 if ($tempfile_info{maxsize} < $1);
		return;
	}

	# Stores pre connection activity
	if (($prefix_vars{'t_loglevel'} eq 'LOG') && ($prefix_vars{'t_query'} =~ /connection received: host=([^\s]+) port=(\d+)/)) {
		return if ($disable_connection);
		$conn_received{$t_pid} = $1;
		return;
	}

	# Stores connection activity
	if (   ($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~ /connection authorized: user=([^\s]+) database=([^\s]+)/))
	{
		return if ($disable_connection);
		my $usr = $1;
		my $db  = $2;
		if ($extension eq 'tsung') {
			$tsung_session{$prefix_vars{'t_pid'}}{connection}{database} = $db;
			$tsung_session{$prefix_vars{'t_pid'}}{connection}{user}     = $usr;
			$tsung_session{$prefix_vars{'t_pid'}}{connection}{date}     = $prefix_vars{'t_date'};
			return;

		}
		$connection_info{count}++;
		$connection_info{user}{$usr}++;
		$connection_info{database}{$db}++;
		$connection_info{database_user}{$db}{$usr}++;
		$connection_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{count}++;
		$connection_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{user}{$usr}++;
		$connection_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{database}{$db}++;
		$connection_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{database_user}{$db}{$usr}++;

		if ($graph) {
			$per_minute_info{connection}{$date_part}{$prefix_vars{'t_hour'}}{"$prefix_vars{'t_min'}"}{count}++;
			$per_minute_info{connection}{$date_part}{$prefix_vars{'t_hour'}}{"$prefix_vars{'t_min'}"}{second}
				{$prefix_vars{'t_sec'}}++;
		}
		if (exists $conn_received{$t_pid}) {
			$connection_info{host}{$conn_received{$t_pid}}++;
			$connection_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{host}{$conn_received{$t_pid}}++;
			delete $conn_received{$t_pid};
		}
		return;
	}

	# Stores session duration
	if (($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~
			/disconnection: session time: ([^\s]+) user=([^\s]+) database=([^\s]+) host=([^\s]+) port=(\d+)/))
	{
		return if ($disable_session);
		if ($extension eq 'tsung') {
			$tsung_session{$prefix_vars{'t_pid'}}{disconnection}{date} = $prefix_vars{'t_date'};
		}
		my $time = $1;
		my $usr  = $2;
		my $db   = $3;
		my $host = $4;

		if ($extension eq 'tsung') {
			&store_tsung_session($prefix_vars{'t_pid'});
			return;
		}

		# Store time in millisecond
		$time =~ /(\d+):(\d+):(\d+\.\d+)/;
		$time = ($3 * 1000) + ($2 * 60 * 1000) + ($1 * 60 * 60 * 1000);
		$session_info{count}++;
		$session_info{duration} += $time;
		$session_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{count}++;
		$session_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{duration} += $time;
		$session_info{database}{$db}{count}++;
		$session_info{database}{$db}{duration} += $time;
		$session_info{user}{$usr}{count}++;
		$session_info{user}{$usr}{duration} += $time;
		$session_info{host}{$host}{count}++;
		$session_info{host}{$host}{duration} += $time;
		return;
	}

	# Store checkpoint information
	if (
		($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~
/checkpoint complete: wrote (\d+) buffers \(([^\)]+)\); (\d+) transaction log file\(s\) added, (\d+) removed, (\d+) recycled; write=([0-9\.]+) s, sync=([0-9\.]+) s, total=([0-9\.]+) s/
		   )
	   )
	{
		return if ($disable_checkpoint);
		$checkpoint_info{wbuffer} += $1;

		#$checkpoint_info{percent_wbuffer} += $2;
		$checkpoint_info{file_added}    += $3;
		$checkpoint_info{file_removed}  += $4;
		$checkpoint_info{file_recycled} += $5;
		$checkpoint_info{write}         += $6;
		$checkpoint_info{sync}          += $7;
		$checkpoint_info{total}         += $8;

		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{wbuffer} += $1;

		#$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{percent_wbuffer} += $2;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{file_added}    += $3;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{file_removed}  += $4;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{file_recycled} += $5;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{write}         += $6;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{sync}          += $7;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{total}         += $8;
		return;
	}
	if (   ($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~ /checkpoints are occurring too frequently \((\d+) seconds apart\)/))
	{
		return if ($disable_checkpoint);
		$checkpoint_info{warning}++;
		$checkpoint_info{warning_seconds} += $1;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{warning}++;
		$checkpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{warning_seconds} += $1;
		return;
	}

	# Store the detail of the error
	if ($cur_info{$t_pid}{loglevel} =~ /WARNING|ERROR|FATAL|PANIC/) {
		if ($prefix_vars{'t_loglevel'} =~ /(DETAIL|STATEMENT|CONTEXT|HINT)/) {
			$cur_info{$t_pid}{"\L$1\E"} .= $prefix_vars{'t_query'};
			return;
		}
	}

	# Process current query following context
	if ($cur_info{$t_pid}{query}) {

		# Remove obsolete connexion storage
		delete $conn_received{$cur_info{$t_pid}{pid}};

		# The query is complete but we are missing some debug/info/bind parameter logs
		if ($cur_info{$t_pid}{loglevel} eq 'LOG') {

			# Apply bind parameters if any
			if (($prefix_vars{'t_loglevel'} eq 'DETAIL') && ($prefix_vars{'t_query'} =~ /parameters: (.*)/)) {
				my @t_res = split(/[,\s]*\$(\d+)\s=\s/, $1);
				shift(@t_res);
				for (my $i = 0 ; $i < $#t_res ; $i += 2) {
					$cur_info{$t_pid}{query} =~ s/\$$t_res[$i]\b/$t_res[$i+1]/s;
				}
				&store_queries($t_pid);
				delete $cur_info{$t_pid};
			}
		}

		# When we are ready to overwrite the last storage, add it to the global stats
		if (   ($prefix_vars{'t_loglevel'} =~ /LOG|FATAL|PANIC|ERROR|WARNING|HINT/)
			&& exists $cur_info{$t_pid}
			&& (($format eq 'csv') || ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session})))
		{
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
	}

	# Extract the duration and the query parts from the entry
	my $duration_required = 1;
	if ($error_only || ($disable_hourly && $disable_query)) {
		$duration_required = 0;
	}
	my $t_action   = '';
	my $t_duration = '';
	if ($log_duration) {
		if ($prefix_vars{'t_query'} =~ s/duration: ([0-9\.]+) ms$//s) {
			$t_duration             = $1;
			$t_action               = 'statement';
			$prefix_vars{'t_query'} = 'No log_min_duration';
		} elsif (!$enable_log_min_duration) {
			if (exists $cur_info{$t_pid} && ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session})) {
				&store_queries($t_pid);
				delete $cur_info{$t_pid};
			}
			return;
		}
	}
	if (!$log_duration || $enable_log_min_duration) {
		if (($extension eq 'tsung') && !exists $tsung_session{$prefix_vars{'t_pid'}}{connection} && $prefix_vars{'t_dbname'}) {
			$tsung_session{$prefix_vars{'t_pid'}}{connection}{database} = $prefix_vars{'t_dbname'};
			$tsung_session{$prefix_vars{'t_pid'}}{connection}{user}     = $prefix_vars{'t_dbuser'};
			$tsung_session{$prefix_vars{'t_pid'}}{connection}{date}     = $prefix_vars{'t_date'};
		}
		if ($prefix_vars{'t_query'} =~ s/duration: ([0-9\.]+) ms  (query|statement): //is) {
			$t_duration = $1;
			$t_action   = $2;
		} elsif ($prefix_vars{'t_query'} =~ s/duration: ([0-9\.]+) ms  (prepare|parse|bind|execute|execute from fetch)\s+[^:]+:\s//is)
		{
			$t_duration = $1;
			$t_action   = $2;

			# Skiping parse and bind logs
			return if ($t_action !~ /query|statement|execute|bind/);
		} elsif (!$duration_required && ($prefix_vars{'t_query'} =~ s/(query|statement): //is)) {
			$t_action = $1;
		} elsif (!$duration_required && ($prefix_vars{'t_query'} =~ s/(prepare|parse|bind|execute|execute from fetch)\s+[^:]+:\s//is))
		{
			$t_action = $1;

			# Skiping parse and bind logs
			return if ($t_action !~ /query|statement|execute|bind/);
		} elsif ($prefix_vars{'t_loglevel'} eq 'LOG') {
			if ($prefix_vars{'t_query'} !~
/incomplete startup packet|connection|receive|unexpected EOF|still waiting for [^\s]+Lock|checkpoint starting:|could not send data to client|parameter .*configuration file|autovacuum launcher|automatic (analyze|vacuum)|detected deadlock while waiting for|database system was shut down/
			   )
			{
				&logmsg('DEBUG', "Unrecognized line: $prefix_vars{'t_loglevel'}: $prefix_vars{'t_query'} at line $nlines");
			}
			if (exists $cur_info{$t_pid} && ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session})) {
				&store_queries($t_pid);
				delete $cur_info{$t_pid};
			}
			return;
		}
	}
	$cur_info{$t_pid}{year}      = $prefix_vars{'t_year'};
	$cur_info{$t_pid}{month}     = $prefix_vars{'t_month'};
	$cur_info{$t_pid}{day}       = $prefix_vars{'t_day'};
	$cur_info{$t_pid}{hour}      = $prefix_vars{'t_hour'};
	$cur_info{$t_pid}{min}       = $prefix_vars{'t_min'};
	$cur_info{$t_pid}{sec}       = $prefix_vars{'t_sec'};
	$cur_info{$t_pid}{date}      = $prefix_vars{'t_date'};
	$cur_info{$t_pid}{ident}     = $prefix_vars{'t_ident'};
	$cur_info{$t_pid}{query}     = $prefix_vars{'t_query'};
	$cur_info{$t_pid}{duration}  = $t_duration;
	$cur_info{$t_pid}{pid}       = $prefix_vars{'t_pid'};
	$cur_info{$t_pid}{session}   = $prefix_vars{'t_session_line'};
	$cur_info{$t_pid}{loglevel}  = $prefix_vars{'t_loglevel'};
	$cur_info{$t_pid}{dbname}    = $prefix_vars{'t_dbname'};
	$cur_info{$t_pid}{dbuser}    = $prefix_vars{'t_dbuser'};
	$cur_info{$t_pid}{dbclient}  = $prefix_vars{'t_client'};
	$cur_info{$t_pid}{dbappname} = $prefix_vars{'t_appname'};
	$cur_info{$t_pid}{action}    = $t_action;

	return 1;
}

sub store_tsung_session
{
	my $pid = shift;

	return if ($#{$tsung_session{$pid}{dates}} < 0);

	# Open filehandle
	my $fh = new IO::File ">>$outfile";
	if (not defined $fh) {
		die "FATAL: can't write to $outfile, $!\n";
	}
	if ($pid) {
		print $fh "  <session probability=\"100\" name=\"pgbadger-$pid\" type=\"ts_pgsql\">\n";
		if (exists $tsung_session{$pid}{connection}{database}) {
			print $fh qq{    <transaction name="connection">
      <request><pgsql type="connect" database="$tsung_session{$pid}{connection}{database}" username="$tsung_session{$pid}{connection}{user}" /></request>
      <request><pgsql type="authenticate" password="changeme"/></request>
    </transaction>
};
		}
		if ($#{$tsung_session{$pid}{dates}} >= 0) {
			my $sec = 0;
			if ($tsung_session{$pid}{connection}{date}) {
				$sec = $tsung_session{$pid}{dates}[0] - $tsung_session{$pid}{connection}{date};
			}
			print $fh "    <thinktime value=\"$sec\" random=\"true\"></thinktime>\n" if ($sec > 0);
			print $fh "    <transaction name=\"requests\">\n";
			for (my $i = 0 ; $i <= $#{$tsung_session{$pid}{queries}} ; $i++) {
				$tsung_queries++;
				$sec = 0;
				if ($i > 0) {
					$sec = $tsung_session{$pid}{dates}[$i] - $tsung_session{$pid}{dates}[$i - 1];
					print $fh "    <thinktime value=\"$sec\" random=\"true\"></thinktime>\n" if ($sec > 0);
				}
				print $fh "    <request><pgsql type='sql'><![CDATA[$tsung_session{$pid}{queries}[$i];]]></pgsql></request>\n";
			}
			print $fh "    </transaction>\n";
		}
		if ($#{$tsung_session{$pid}{dates}} >= 0) {
			my $sec = $tsung_session{$pid}{disconnection}{date} - $tsung_session{$pid}{dates}[-1];
			print $fh "    <thinktime value=\"$sec\" random=\"true\"></thinktime>\n" if ($sec > 0);
		}
		if (exists $tsung_session{$pid}{connection}{database}) {
			print $fh "    <request><pgsql type=\"close\"></pgsql></request>\n";
		}
		print $fh "  </session>\n\n";
		delete $tsung_session{$pid};
	}
	$fh->close;
}

sub store_queries
{
	my $t_pid = shift;

	# Remove comments if required
	if ($remove_comment) {
		$cur_info{$t_pid}{query} =~ s/\/\*(.*?)\*\///gs;
	}

	# Cleanup and normalize the current query
	$cur_info{$t_pid}{query} =~ s/^[\t\s\r\n]+//s;
	$cur_info{$t_pid}{query} =~ s/[\t\s\r\n;]+$//s;

	# Should we just want select queries
	if ($select_only) {
		return if (($cur_info{$t_pid}{query} !~ /^SELECT/is) || ($cur_info{$t_pid}{query} =~ /FOR UPDATE/is));
	}

	# Should we have to exclude some queries
	if ($#exclude_query >= 0) {
		foreach (@exclude_query) {
			if ($cur_info{$t_pid}{query} =~ /$_/i) {
				$cur_info{$t_pid}{query} = '';
				return;
			}
		}
	}

	# Should we have to include only some queries
	if ($#include_query >= 0) {
		foreach (@include_query) {
			if ($cur_info{$t_pid}{query} !~ /$_/i) {
				$cur_info{$t_pid}{query} = '';
				return;
			}
		}
	}

	# Truncate the query if requested by the user
	$cur_info{$t_pid}{query} = substr($cur_info{$t_pid}{query}, 0, $maxlength) . '[...]'
		if (($maxlength > 0) && (length($cur_info{$t_pid}{query}) > $maxlength));

	# Dump queries as tsung request and return
	if ($extension eq 'tsung') {
		if ($cur_info{$t_pid}{loglevel} eq 'LOG') {
			push(@{$tsung_session{$t_pid}{queries}}, $cur_info{$t_pid}{query});
			push(@{$tsung_session{$t_pid}{dates}},   $cur_info{$t_pid}{date});
			if (!exists $tsung_session{$t_pid}{connection} && $cur_info{$t_pid}{t_dbname}) {
				$tsung_session{$t_pid}{connection}{database} = $cur_info{$t_pid}{t_dbname};
				$tsung_session{$t_pid}{connection}{user}     = $cur_info{$t_pid}{t_dbuser};
				$tsung_session{$t_pid}{connection}{date}     = $cur_info{$t_pid}{date};
			}
		}
		return;
	}

	# Store the collected informations into global statistics
	if ($cur_info{$t_pid}{loglevel} =~ /WARNING|ERROR|FATAL|PANIC|HINT/) {

		# Add log level at beginning of the query and normalyze it
		$cur_info{$t_pid}{query} = $cur_info{$t_pid}{loglevel} . ":  " . $cur_info{$t_pid}{query};
		my $normalized_error = &normalize_error($cur_info{$t_pid}{query});

		# Stores total and normalyzed error count
		$overall_stat{'errors_number'}++;
		$overall_stat{'unique_normalized_errors'}{"$normalized_error"}++;
		$error_info{$normalized_error}{count}++;

		# Stores normalyzed error count per time
		$error_info{$normalized_error}{chronos}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
			{"$cur_info{$t_pid}{hour}"}{count}++;

		# Stores normalyzed query samples
		my $cur_last_log_timestamp =
"$cur_info{$t_pid}{year}-$cur_info{$t_pid}{month}-$cur_info{$t_pid}{day} $cur_info{$t_pid}{hour}:$cur_info{$t_pid}{min}:$cur_info{$t_pid}{sec}";
		&set_top_error_sample(
			$normalized_error,          $cur_last_log_timestamp,      $cur_info{$t_pid}{query}, $cur_info{$t_pid}{detail},
			$cur_info{$t_pid}{context}, $cur_info{$t_pid}{statement}, $cur_info{$t_pid}{hint}
		);

	} elsif ($cur_info{$t_pid}{loglevel} eq 'LOG') {

		# Add a semi-colon at end of the query
		$cur_info{$t_pid}{query} .= ';' if (substr($cur_info{$t_pid}{query}, -1, 1) ne ';');

		# Normalyze query
		my $normalized = &normalize_query($cur_info{$t_pid}{query});

		# Stores global statistics
		my $cur_last_log_timestamp =
"$cur_info{$t_pid}{year}-$cur_info{$t_pid}{month}-$cur_info{$t_pid}{day} $cur_info{$t_pid}{hour}:$cur_info{$t_pid}{min}:$cur_info{$t_pid}{sec}";
		my $cur_last_log_date =
"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}$cur_info{$t_pid}{hour}$cur_info{$t_pid}{min}$cur_info{$t_pid}{sec}";
		$overall_stat{'queries_number'}++;
		$overall_stat{'queries_duration'} += $cur_info{$t_pid}{duration};
		if (!$overall_stat{'first_query_date'} || ($overall_stat{'first_query_date'} > $cur_last_log_date)) {
			$overall_stat{'first_query_ts'}   = $cur_last_log_timestamp;
			$overall_stat{'first_query_date'} = $cur_last_log_date;
		}
		if (!$overall_stat{'last_query_date'} || ($overall_stat{'last_query_date'} < $cur_last_log_date)) {
			$overall_stat{'last_query_ts'} = $cur_last_log_timestamp;
		}
		$overall_stat{'query_peak'}{$cur_last_log_timestamp}++;
		$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}{count}++;
		$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
			{duration} += $cur_info{$t_pid}{duration};

		if ($graph) {
			$per_minute_info{query}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{$cur_info{$t_pid}{hour}}
				{$cur_info{$t_pid}{min}}{count}++;
			$per_minute_info{query}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{$cur_info{$t_pid}{hour}}
				{$cur_info{$t_pid}{min}}{second}{$cur_info{$t_pid}{sec}}++;
			$per_minute_info{query}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{$cur_info{$t_pid}{hour}}
				{$cur_info{$t_pid}{min}}{duration} += $cur_info{$t_pid}{duration};
		}
		if ($normalized =~ /\bdelete from\b/) {
			$overall_stat{'DELETE'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'DELETE'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'DELETE'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{delete}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{$cur_info{$t_pid}{hour}}{$cur_info{$t_pid}{min}}{count}++;
				$per_minute_info{delete}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{$cur_info{$t_pid}{hour}}{$cur_info{$t_pid}{min}}{duration} += $cur_info{$t_pid}{duration};
			}
		} elsif ($normalized =~ /\binsert into\b/) {
			$overall_stat{'INSERT'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'INSERT'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'INSERT'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{insert}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{count}++;
				$per_minute_info{insert}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{duration} += $cur_info{$t_pid}{duration};
			}
		} elsif ($normalized =~ /\bupdate\b.*\bset\b/) {
			$overall_stat{'UPDATE'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'UPDATE'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'UPDATE'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{update}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{count}++;
				$per_minute_info{update}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{duration} += $cur_info{$t_pid}{duration};
			}
		} elsif ($normalized =~ /\bselect\b/is) {
			$overall_stat{'SELECT'}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'SELECT'}{count}++;
			$per_hour_info{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}{"$cur_info{$t_pid}{hour}"}
				{'SELECT'}{duration} += $cur_info{$t_pid}{duration};
			if ($graph) {
				$per_minute_info{select}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{count}++;
				$per_minute_info{select}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
					{"$cur_info{$t_pid}{hour}"}{"$cur_info{$t_pid}{min}"}{duration} += $cur_info{$t_pid}{duration};
			}
		}
		&set_top_slowest($cur_info{$t_pid}{query}, $cur_info{$t_pid}{duration}, $cur_last_log_timestamp);

		# Store normalyzed query count
		$normalyzed_info{$normalized}{count}++;

		# Store normalyzed query total duration
		$normalyzed_info{$normalized}{duration} += $cur_info{$t_pid}{duration};

		# Store normalyzed query count and duration per time
		$normalyzed_info{$normalized}{chronos}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
			{"$cur_info{$t_pid}{hour}"}{count}++;
		$normalyzed_info{$normalized}{chronos}{"$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}"}
			{"$cur_info{$t_pid}{hour}"}{duration} += $cur_info{$t_pid}{duration};

		# Store normalyzed query samples
		&set_top_sample($normalized, $cur_info{$t_pid}{query}, $cur_info{$t_pid}{duration}, $last_log_timestamp);
	}
}

# Normalyze error messages
sub normalize_error
{
	my $orig_query = shift;

	return if (!$orig_query);

	# Remove character position
	$orig_query =~ s/ at character \d+//;

	# Remove encoding detail
	$orig_query =~ s/(byte sequence for encoding).*/$1/;

	# Replace changing parameter by ...
	$orig_query =~ s/"[^"]*"/"..."/g;
	$orig_query =~ s/\(.*\)/\(...\)/g;
	$orig_query =~ s/column .* does not exist/column "..." does not exist/;
	$orig_query =~ s/(database system was shut down at).*/$1 .../;

	# Need more normalization stuff here

	return $orig_query;
}

sub average_per_minutes
{
	my $val = shift;
	my $idx = shift;

	my @avgs = ();
	for (my $i = 0 ; $i < 59 ; $i += $idx) {
		push(@avgs, sprintf("%02d", $i));
	}
	push(@avgs, 59);

	for (my $i = 0 ; $i <= $#avgs ; $i++) {
		if ($val == $avgs[$i]) {
			return "$avgs[$i]";
		} elsif ($avgs[$i] == $avgs[-1]) {
			return "$avgs[$i-1]";
		} elsif (($val > $avgs[$i]) && ($val < $avgs[$i + 1])) {
			return "$avgs[$i]";
		}
	}
	return $val;
}

sub autodetect_format
{
	my $file = shift;

	# Open log file for reading
	my $nfound = 0;
	my $nline  = 0;
	my $fmt    = '';
	my $tfile  = new IO::File;
	if ($file !~ /\.(gz|bz2|zip)/i) {
		$tfile->open($file) || die "FATAL: cannot read logfile $file. $!\n";
	} else {
		my $uncompress = $zcat;
		if (($file =~ /\.bz2/i) && ($zcat =~ /^zcat$/)) {
			$uncompress = $bzcat;
		} elsif (($file =~ /\.zip/i) && ($zcat =~ /^zcat$/)) {
			$uncompress = $ucat;
		}

		&logmsg('DEBUG', "Detect compressed file, will used command: $uncompress $file");

		# Open a pipe to zcat program for compressed log
		$tfile->open("$uncompress $file |") || die "FATAL: cannot read from pipe to $uncompress $file. $!\n";
	}
	my $duration = 'duration:';
	if ($error_only || ($disable_hourly && $disable_query)) {
		$duration = '';
	}
	my %ident_name = ();
	while (my $line = <$tfile>) {
		chomp($line);
		$line =~ s/
//;
		next if (!$line);
		$nline++;

		# Is syslog lines ?
		if ($line =~
/^[A-Z][a-z]{2}\s+\d+\s\d+:\d+:\d+(?:\s[^\s]+)?\s[^\s]+\s([^\s\[]+)\[\d+\]:(?:\s\[[^\]]+\])?\s\[\d+\-\d+\].*?(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):\s+$duration/
		   )
		{
			$fmt = 'syslog';
			$nfound++;
			$ident_name{$1}++;

			# Is stderr lines
		} elsif (
			(
				$line =~
				/^\d+-\d+-\d+ \d+:\d+:\d+\.\d+(?: [A-Z\d]{3,6})?,.*,(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT),/
			)
			&& ($line =~ tr/,/,/ >= 12)
			)
		{
			$fmt = 'csv';
			$nfound++;
		} elsif ($line =~
/\d+-\d+-\d+ \d+:\d+:\d+[\.0-9]*(?: [A-Z\d]{3,6})?(.*?)(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):\s+$duration/
			)
		{
			$fmt = 'stderr';
			$nfound++;
		}
		last if (($nfound > 10) || ($nline > 5000));
	}
	$tfile->close();
	if (!$fmt || ($nfound < 10)) {
		die "FATAL: unable to detect log file format from $file, please use -f option.\n";
	}

	if (($fmt =~ /syslog/) && !$ident && (scalar keys %ident_name == 1)) {
		$ident = (keys %ident_name)[0];
	}

	&logmsg('DEBUG', "Autodetected log format '$fmt' from $file");

	return $fmt;
}

sub autodetect_duration
{
	my $file = shift;

	# Open log file for reading
	my $nfound = 0;
	my $nline  = 0;
	my $tfile  = new IO::File;
	if ($file !~ /\.(gz|bz2|zip)/i) {
		$tfile->open($file) || die "FATAL: cannot read logfile $file. $!\n";
	} else {
		my $uncompress = $zcat;
		if (($file =~ /\.bz2/i) && ($zcat =~ /^zcat$/)) {
			$uncompress = $bzcat;
		} elsif (($file =~ /\.zip/i) && ($zcat =~ /^zcat$/)) {
			$uncompress = $ucat;
		}

		&logmsg('DEBUG', "Found compressed file, will used command: $uncompress $file");

		# Open a pipe to zcat program for compressed log
		$tfile->open("$uncompress $file |") || die "FATAL: cannot read from pipe to $uncompress $file. $!\n";
	}
	my %ident_name = ();
	while (my $line = <$tfile>) {
		chomp($line);
		$line =~ s/
//;
		next if (!$line);
		$nline++;
		if (   ($line =~ /\s+duration: \d+\.\d+ ms\s+(query|statement|prepare|parse|bind|execute)/)
			|| ($line =~ /,"duration: \d+\.\d+ ms\s+(query|statement|prepare|parse|bind|execute)[^"]+"/))
		{
			$nfound++;
		}
		last if (($nfound > 10) || ($nline > 25000));
	}
	$tfile->close();

	if ($nfound) {
		&logmsg('DEBUG', "Autodetecting log duration format from $file: log_duration is disabled.");
		return 0;
	}

	&logmsg('DEBUG', "Autodetecting log duration format from $file: log_duration is enabled.");

	return 1;
}

sub progress_bar
{
	my ($got, $total, $width, $char) = @_;
	$width ||= 25;
	$char  ||= '=';
	my $num_width = length $total;
	if ($extension eq 'tsung') {
		sprintf(
			"[%-${width}s] Parsed %${num_width}s bytes of %s (%.2f%%), queries: %d\r",
			$char x (($width - 1) * $got / $total) . '>',
			$got, $total, 100 * $got / +$total, $tsung_queries
		);
	} else {
		sprintf(
			"[%-${width}s] Parsed %${num_width}s bytes of %s (%.2f%%), queries: %d, events: %d\r",
			$char x (($width - 1) * $got / $total) . '>',
			$got, $total, 100 * $got / +$total, $overall_stat{'queries_number'}, $overall_stat{'errors_number'}
		);
	}
}

sub flotr2_graph
{
	my ($buttonid, $divid, $data1, $data2, $data3, $title, $ytitle, $legend1, $legend2, $legend3, $ytitle2, $data4, $legend4) = @_;

	$data1 = "var d1 = [$data1];" if ($data1);
	$data2 = "var d2 = [$data2];" if ($data2);
	$data3 = "var d3 = [$data3];" if ($data3);
	$data4 = "var d4 = [$data4];" if ($data4);

	$legend1 = "{ data: d1, label: \"$legend1\" },"          if ($legend1);
	$legend2 = "{ data: d2, label: \"$legend2\" },"          if ($legend2);
	$legend3 = "{ data: d3, label: \"$legend3\" },"          if ($legend3);
	$legend4 = "{ data: d4, label: \"$legend4\",yaxis: 2 }," if ($legend4);

	my $yaxis2 = '';
	if ($ytitle2) {
		$yaxis2 = "y2axis: { title: \"$ytitle2\", min: 0, color: \"#4DA74D\" },";
	}

	my $min = $t_min;
	my $max = $t_max;
	if ($divid !~ /persecond/) {
		$min = $t_min_hour;
		$max = $t_max_hour;
	}
	print $fh <<EOF;
<div id="$divid"></div>
<script type="text/javascript">
(function mouse_zoom(container) {

document.writeln('<table align="center"><tr><td><input type="button" class="dldButton" value="To Image" id="toimage$buttonid" onclick="return false;">'+
	'<input type="button" class="dldButton" value="Download" id="download$buttonid" onclick="return false;">' +
	'<input type="button" class="dldButton" value="Reset" id="reset$buttonid" onclick="return false;"></td></tr><tr><td>&nbsp;</td></tr></table>'
	);
    $data1
    $data2
    $data3
    $data4
    var options = {
        xaxis: {
            min: $min,
            max: $max,
            tickDecimals: 0,
            noTicks: 20,
            mode: "time",
            labelsAngle: 45
        },
        yaxis: {
            mode: "normal",
            title: "$ytitle",
        },
	$yaxis2
        selection: {
            mode: "x",
            fps: 30
        },
        title: "$title",
        legend: {
            position: "nw",
            backgroundColor: "#D2E8FF"
        },
	mouse: {
            track: true,
	    trackFormatter: function(obj){ return dateTracker(obj) },
            relative: true
        },
        HtmlText: false,
    };

    function drawGraph(opts) {
        var o = Flotr._.extend(Flotr._.clone(options), opts );
        return Flotr.draw(
        	container,
        	[
        		$legend1
        		$legend2
        		$legend3
			$legend4
    		],
    		o
    	);
    }

    var graph = drawGraph();
    Flotr.EventAdapter.observe(container, "flotr:select", function(area) {
        f = drawGraph({
            xaxis: {
                mode: "time",
                labelsAngle: 45,
                min: area.x1,
                max: area.x2
            },
            yaxis: {
                min: area.y1,
                max: area.y2
            }
        });
    });
    Flotr.EventAdapter.observe(container, "flotr:click", function() {
        drawGraph();
    });
    document.getElementById('reset$buttonid').onclick = function() {
      graph.download.restoreCanvas();
    };
    document.getElementById('download$buttonid').onclick = function(){
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format');
    };
    document.getElementById('toimage$buttonid').onclick = function() {
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format', null, null, true);
    };

})(document.getElementById("$divid"));
</script>
EOF

}

sub flotr2_piegraph
{
	my ($buttonid, $divid, $title, %data) = @_;

	my @datadef = ();
	my @contdef = ();
	my $i       = 1;
	foreach my $k (sort keys %data) {
		push(@datadef, "var d$i = [ [0,$data{$k}] ];\n");
		push(@contdef, "{ data: d$i, label: \"$k\" },\n");
		$i++;
	}
	print $fh <<EOF;
<div id="$divid"></div>
<script type="text/javascript">
(function basic_pie(container) {


document.writeln('<input type="button" class="dldButton" value="To Image" id="toimage$buttonid" onclick="return false;">'+
	'<input type="button" class="dldButton" value="Download" id="download$buttonid" onclick="return false;">' +
	'<input type="button" class="dldButton" value="Reset" id="reset$buttonid" onclick="return false;">'
	);

    @datadef
    var graph = Flotr.draw(container, [
    @contdef
    ], {
        title: "$title",
        HtmlText: false,
        grid: {
            verticalLines: false,
            horizontalLines: false,
	    backgroundColor: '#ffffff',
	    outline: '',
        },
        xaxis: {
            showLabels: false
        },
        yaxis: {
            showLabels: false
        },
        pie: {
            show: true,
	    explode: 6
        },
        mouse: {
            track: true,
	    trackFormatter: function(obj){ return obj.y },
        },
        legend: {
            position: "sw",
            backgroundColor: "#D2E8FF"
        }
    });
    document.getElementById('reset$buttonid').onclick = function() {
      graph.download.restoreCanvas();
    };
    document.getElementById('download$buttonid').onclick = function(){
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format');
    };
    document.getElementById('toimage$buttonid').onclick = function() {
	if (Flotr.isIE && Flotr.isIE < 9) {
		alert(
		"Your browser doesn't allow you to get a bitmap image from the plot, " +
		"you can only get a VML image that you can use in Microsoft Office.<br />"
		);
	}
      graph.download.saveImage('$img_format', null, null, true);
    };


})(document.getElementById("$divid"));
</script>
EOF

}

sub build_log_line_prefix_regex
{
	my %regex_map = (
		'%a' => [('t_appname',      '([0-9a-zA-Z\.\-\_\/\[\]]*)')],                                   # application name
		'%u' => [('t_dbuser',       '([0-9a-zA-Z\_\[\]]*)')],                                         # user name
		'%d' => [('t_dbname',       '([0-9a-zA-Z\_\[\]]*)')],                                         # database name
		'%r' => [('t_hostport',     '(\[local\]|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?[\(\d\)]*')],     # remote host and port
		'%h' => [('t_client',       '(\[local\]|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?')],              # remote host
		'%p' => [('t_pid',          '(\d+)')],                                                        # process ID
		'%t' => [('t_timestamp',    '(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})(?: [A-Z\d]{3,6})?')],      # timestamp without milliseconds
		'%m' => [('t_mtimestamp',   '(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\.\d+(?: [A-Z\d]{3,6})?')], # timestamp with milliseconds
		'%l' => [('t_session_line', '(\d+)')],                                                        # session line number
		'%s' => [('t_session_timestamp', '(\d{4}-\d{2}-\d{2} \d{2}):\d{2}:\d{2})(?: [A-Z\d]{3,6})?')],    # session start timestamp
		'%c' => [('t_session_id',        '([0-9a-f\.]*)')],                                               # session ID
		'%v' => [('t_virtual_xid',       '([0-9a-f\.\/]*)')],                                             # virtual transaction ID
		'%x' => [('t_xid',               '([0-9a-f\.\/]*)')],                                             # transaction ID
		'%i' => [('t_command',           '([0-9a-zA-Z\.\-\_]*)')],                                        # command tag
		'%e' => [('t_sqlstate',          '([0-9a-zA-Z]+)')],                                              # SQL state
	);
	my @param_list = ();
	$log_line_prefix =~ s/([\[\]\|\(\)\{\}])/\\$1/g;
	$log_line_prefix =~ s/\%l([^\d])\d+/\%l$1\\d\+/;
	while ($log_line_prefix =~ s/(\%[audrhptmlscvxie])/$regex_map{"$1"}->[1]/) {
		push(@param_list, $regex_map{"$1"}->[0]);
	}
	return @param_list;
}

# Inclusion of package SQL::Beautify
{

	package SQL::Beautify;

	use strict;
	use warnings;

	our $VERSION = 0.04;

	use Carp;

	# Keywords from SQL-92, SQL-99 and SQL-2003.
	use constant KEYWORDS => qw(
		ABSOLUTE ACTION ADD AFTER ALL ALLOCATE ALTER AND ANY ARE ARRAY AS ASC
		ASENSITIVE ASSERTION ASYMMETRIC AT ATOMIC AUTHORIZATION AVG BEFORE BEGIN
		BETWEEN BIGINT BINARY BIT BIT_LENGTH BLOB BOOLEAN BOTH BREADTH BY CALL
		CALLED CASCADE CASCADED CASE CAST CATALOG CHAR CHARACTER CHARACTER_LENGTH
		CHAR_LENGTH CHECK CLOB CLOSE COALESCE COLLATE COLLATION COLUMN COMMIT
		CONDITION CONNECT CONNECTION CONSTRAINT CONSTRAINTS CONSTRUCTOR CONTAINS
		CONTINUE CONVERT CORRESPONDING COUNT CREATE CROSS CUBE CURRENT CURRENT_DATE
		CURRENT_DEFAULT_TRANSFORM_GROUP CURRENT_PATH CURRENT_ROLE CURRENT_TIME
		CURRENT_TIMESTAMP CURRENT_TRANSFORM_GROUP_FOR_TYPE CURRENT_USER CURSOR
		CYCLE DATA DATE DAY DEALLOCATE DEC DECIMAL DECLARE DEFAULT DEFERRABLE
		DEFERRED DELETE DEPTH DEREF DESC DESCRIBE DESCRIPTOR DETERMINISTIC
		DIAGNOSTICS DISCONNECT DISTINCT DO DOMAIN DOUBLE DROP DYNAMIC EACH ELEMENT
		ELSE ELSEIF END EPOCH EQUALS ESCAPE EXCEPT EXCEPTION EXEC EXECUTE EXISTS
		EXIT EXTERNAL EXTRACT FALSE FETCH FILTER FIRST FLOAT FOR FOREIGN FOUND FREE
		FROM FULL FUNCTION GENERAL GET GLOBAL GO GOTO GRANT GROUP GROUPING HANDLER
		HAVING HOLD HOUR IDENTITY IF IMMEDIATE IN INDICATOR INITIALLY INNER INOUT
		INPUT INSENSITIVE INSERT INT INTEGER INTERSECT INTERVAL INTO IS ISOLATION
		ITERATE JOIN KEY LANGUAGE LARGE LAST LATERAL LEADING LEAVE LEFT LEVEL LIKE
		LIMIT LOCAL LOCALTIME LOCALTIMESTAMP LOCATOR LOOP LOWER MAP MATCH MAX
		MEMBER MERGE METHOD MIN MINUTE MODIFIES MODULE MONTH MULTISET NAMES
		NATIONAL NATURAL NCHAR NCLOB NEW NEXT NO NONE NOT NULL NULLIF NUMERIC
		OBJECT OCTET_LENGTH OF OLD ON ONLY OPEN OPTION OR ORDER ORDINALITY OUT
		OUTER OUTPUT OVER OVERLAPS PAD PARAMETER PARTIAL PARTITION PATH POSITION
		PRECISION PREPARE PRESERVE PRIMARY PRIOR PRIVILEGES PROCEDURE PUBLIC RANGE
		READ READS REAL RECURSIVE REF REFERENCES REFERENCING RELATIVE RELEASE
		REPEAT RESIGNAL RESTRICT RESULT RETURN RETURNS REVOKE RIGHT ROLE ROLLBACK
		ROLLUP ROUTINE ROW ROWS SAVEPOINT SCHEMA SCOPE SCROLL SEARCH SECOND SECTION
		SELECT SENSITIVE SESSION SESSION_USER SET SETS SIGNAL SIMILAR SIZE SMALLINT
		SOME SPACE SPECIFIC SPECIFICTYPE SQL SQLCODE SQLERROR SQLEXCEPTION SQLSTATE
		SQLWARNING START STATE STATIC SUBMULTISET SUBSTRING SUM SYMMETRIC SYSTEM
		SYSTEM_USER TABLE TABLESAMPLE TEMPORARY TEXT THEN TIME TIMESTAMP
		TIMEZONE_HOUR TIMEZONE_MINUTE TINYINT TO TRAILING TRANSACTION TRANSLATE
		TRANSLATION TREAT TRIGGER TRIM TRUE UNDER UNDO UNION UNIQUE UNKNOWN UNNEST
		UNTIL UPDATE UPPER USAGE USER USING VALUE VALUES VARCHAR VARYING VIEW WHEN
		WHENEVER WHERE WHILE WINDOW WITH WITHIN WITHOUT WORK WRITE YEAR ZONE
		);

	sub tokenize_sql
	{
		my ($query, $remove_white_tokens) = @_;

		my $re = qr{
    (
        (?:--|\#)[\ \t\S]*      # single line comments
        |
        (?:<>|<=>|>=|<=|==|=|!=|!|<<|>>|<|>|\|\||\||&&|&|-|\+|\*(?!/)|/(?!\*)|\%|~|\^|\?)
                                # operators and tests
        |
        [\[\]\(\),;.]            # punctuation (parenthesis, comma)
        |
        \'\'(?!\')              # empty single quoted string
        |
        \"\"(?!\"")             # empty double quoted string
        |
        "(?>(?:(?>[^"\\]+)|""|\\.)*)+"
                                # anything inside double quotes, ungreedy
        |
        `(?>(?:(?>[^`\\]+)|``|\\.)*)+`
                                # anything inside backticks quotes, ungreedy
        |
        '(?>(?:(?>[^'\\]+)|''|\\.)*)+'
                                # anything inside single quotes, ungreedy.
        |
        /\*[\ \t\r\n\S]*?\*/      # C style comments
        |
        (?:[\w:@]+(?:\.(?:\w+|\*)?)*)
                                # words, standard named placeholders, db.table.*, db.*
        |
        (?: \$_\$ | \$\d+ | \${1,2} )
                                # dollar expressions - eg $_$ $3 $$
        |
        \n                      # newline
        |
        [\t\ ]+                 # any kind of white spaces
    )
}smx;

		my @query = ();
		@query = $query =~ m{$re}smxg;

		if ($remove_white_tokens) {
			@query = grep(!/^[\s\n\r]*$/, @query);
		}

		return wantarray ? @query : \@query;
	}

	sub new
	{
		my ($class, %options) = @_;

		my $self = bless {%options}, $class;

		# Set some defaults.
		$self->{query}       = ''   unless defined($self->{query});
		$self->{spaces}      = 4    unless defined($self->{spaces});
		$self->{space}       = ' '  unless defined($self->{space});
		$self->{break}       = "\n" unless defined($self->{break});
		$self->{wrap}        = {}   unless defined($self->{wrap});
		$self->{keywords}    = []   unless defined($self->{keywords});
		$self->{rules}       = {}   unless defined($self->{rules});
		$self->{uc_keywords} = 0    unless defined $self->{uc_keywords};

		push @{$self->{keywords}}, KEYWORDS;

		# Initialize internal stuff.
		$self->{_level} = 0;

		return $self;
	}

	# Add more SQL.
	sub add
	{
		my ($self, $addendum) = @_;

		$addendum =~ s/^\s*/ /;

		$self->{query} .= $addendum;
	}

	# Set SQL to beautify.
	sub query
	{
		my ($self, $query) = @_;

		$self->{query} = $query if (defined($query));

		return $self->{query};
	}

	# Beautify SQL.
	sub beautify
	{
		my ($self) = @_;

		$self->{_output}      = '';
		$self->{_level_stack} = [];
		$self->{_new_line}    = 1;

		my $last;
		$self->{_tokens} = [tokenize_sql($self->query, 1)];

		while (defined(my $token = $self->_token)) {
			my $rule = $self->_get_rule($token);

			# Allow custom rules to override defaults.
			if ($rule) {
				$self->_process_rule($rule, $token);
			}

			elsif ($token eq '(') {
				$self->_add_token($token);
				$self->_new_line;
				push @{$self->{_level_stack}}, $self->{_level};
				$self->_over unless $last and uc($last) eq 'WHERE';
			}

			elsif ($token eq ')') {
				$self->_new_line;
				$self->{_level} = pop(@{$self->{_level_stack}}) || 0;
				$self->_add_token($token);
				$self->_new_line;
			}

			elsif ($token eq ',') {
				$self->_add_token($token);
				$self->_new_line;
			}

			elsif ($token eq ';') {
				$self->_add_token($token);
				$self->_new_line;

				# End of statement; remove all indentation.
				@{$self->{_level_stack}} = ();
				$self->{_level} = 0;
			}

			elsif ($token =~ /^(?:SELECT|FROM|WHERE|HAVING)$/i) {
				$self->_back unless $last and $last eq '(';
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line if ($self->_next_token and $self->_next_token ne '(');
				$self->_over;
			}

			elsif ($token =~ /^(?:GROUP|ORDER|LIMIT)$/i) {
				$self->_back;
				$self->_new_line;
				$self->_add_token($token);
			}

			elsif ($token =~ /^(?:BY)$/i) {
				$self->_add_token($token);
				$self->_new_line;
				$self->_over;
			}

			elsif ($token =~ /^(?:UNION|INTERSECT|EXCEPT)$/i) {
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line;
			}

			elsif ($token =~ /^(?:LEFT|RIGHT|INNER|OUTER|CROSS)$/i) {
				$self->_back;
				$self->_new_line;
				$self->_add_token($token);
				$self->_over;
			}

			elsif ($token =~ /^(?:JOIN)$/i) {
				if ($last and $last !~ /^(?:LEFT|RIGHT|INNER|OUTER|CROSS)$/) {
					$self->_new_line;
				}

				$self->_add_token($token);
			}

			elsif ($token =~ /^(?:AND|OR)$/i) {
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line;
			}

			else {
				$self->_add_token($token, $last);
			}

			$last = $token;
		}

		$self->_new_line;

		$self->{_output};
	}

	# Add a token to the beautified string.
	sub _add_token
	{
		my ($self, $token, $last_token) = @_;

		if ($self->{wrap}) {
			my $wrap;
			if ($self->_is_keyword($token)) {
				$wrap = $self->{wrap}->{keywords};
			} elsif ($self->_is_constant($token)) {
				$wrap = $self->{wrap}->{constants};
			}

			if ($wrap) {
				$token = $wrap->[0] . $token . $wrap->[1];
			}
		}

		my $last_is_dot = defined($last_token) && $last_token eq '.';

		if (!$self->_is_punctuation($token) and !$last_is_dot) {
			$self->{_output} .= $self->_indent;
		}

		# uppercase keywords
		$token = uc $token
			if $self->_is_keyword($token)
				and $self->{uc_keywords};

		$self->{_output} .= $token;

		# This can't be the beginning of a new line anymore.
		$self->{_new_line} = 0;
	}

	# Increase the indentation level.
	sub _over
	{
		my ($self) = @_;

		++$self->{_level};
	}

	# Decrease the indentation level.
	sub _back
	{
		my ($self) = @_;

		--$self->{_level} if ($self->{_level} > 0);
	}

	# Return a string of spaces according to the current indentation level and the
	# spaces setting for indenting.
	sub _indent
	{
		my ($self) = @_;

		if ($self->{_new_line}) {
			return $self->{space} x ($self->{spaces} * $self->{_level});
		} else {
			return $self->{space};
		}
	}

	# Add a line break, but make sure there are no empty lines.
	sub _new_line
	{
		my ($self) = @_;

		$self->{_output} .= $self->{break} unless ($self->{_new_line});
		$self->{_new_line} = 1;
	}

	# Have a look at the token that's coming up next.
	sub _next_token
	{
		my ($self) = @_;

		return @{$self->{_tokens}} ? $self->{_tokens}->[0] : undef;
	}

	# Get the next token, removing it from the list of remaining tokens.
	sub _token
	{
		my ($self) = @_;

		return shift @{$self->{_tokens}};
	}

	# Check if a token is a known SQL keyword.
	sub _is_keyword
	{
		my ($self, $token) = @_;

		return ~~ grep {$_ eq uc($token)} @{$self->{keywords}};
	}

	# Add new keywords to highlight.
	sub add_keywords
	{
		my $self = shift;

		for my $keyword (@_) {
			push @{$self->{keywords}}, ref($keyword) ? @{$keyword} : $keyword;
		}
	}

	# Add new rules.
	sub add_rule
	{
		my ($self, $format, $token) = @_;

		my $rules = $self->{rules}    ||= {};
		my $group = $rules->{$format} ||= [];

		push @{$group}, ref($token) ? @{$token} : $token;
	}

	# Find custom rule for a token.
	sub _get_rule
	{
		my ($self, $token) = @_;

		values %{$self->{rules}};    # Reset iterator.

		while (my ($rule, $list) = each %{$self->{rules}}) {
			return $rule if (grep {uc($token) eq uc($_)} @$list);
		}

		return undef;
	}

	sub _process_rule
	{
		my ($self, $rule, $token) = @_;

		my $format = {
			break => sub {$self->_new_line},
			over  => sub {$self->_over},
			back  => sub {$self->_back},
			token => sub {$self->_add_token($token)},
			push  => sub {push @{$self->{_level_stack}}, $self->{_level}},
			pop   => sub {$self->{_level} = pop(@{$self->{_level_stack}}) || 0},
			reset => sub {$self->{_level} = 0; @{$self->{_level_stack}} = ();},
		};

		for (split /-/, lc $rule) {
			&{$format->{$_}} if ($format->{$_});
		}
	}

	# Check if a token is a constant.
	sub _is_constant
	{
		my ($self, $token) = @_;

		return ($token =~ /^\d+$/ or $token =~ /^(['"`]).*\1$/);
	}

	# Check if a token is punctuation.
	sub _is_punctuation
	{
		my ($self, $token) = @_;
		return ($token =~ /^[,;.]$/);
	}

}

__DATA__

<script type="text/javascript">
function sql_format (obj)
{
	if (obj.style == undefined || obj.style.whiteSpace == 'pre') {
		obj.style.whiteSpace ='normal';
	} else {
		obj.style.whiteSpace = 'pre';
	}
}

function dateTracker(obj) 
{
	var dateToDisplay = new Date(parseInt(obj.x)); 
	return '('+dateToDisplay.toGMTString()+' , '+obj.y+')';
}

/*!
  * bean.js - copyright Jacob Thornton 2011
  * https://github.com/fat/bean
  * MIT License
  * special thanks to:
  * dean edwards: http://dean.edwards.name/
  * dperini: https://github.com/dperini/nwevents
  * the entire mootools team: github.com/mootools/mootools-core
  *//*global module:true, define:true*/
!function(a,b,c){typeof module!="undefined"?module.exports=c(a,b):typeof define=="function"&&typeof define.amd=="object"?define(c):b[a]=c(a,b)}("bean",this,function(a,b){var c=window,d=b[a],e=/over|out/,f=/[^\.]*(?=\..*)\.|.*/,g=/\..*/,h="addEventListener",i="attachEvent",j="removeEventListener",k="detachEvent",l=document||{},m=l.documentElement||{},n=m[h],o=n?h:i,p=Array.prototype.slice,q=/click|mouse|menu|drag|drop/i,r=/^touch|^gesture/i,s={one:1},t=function(a,b,c){for(c=0;c<b.length;c++)a[b[c]]=1;return a}({},("click dblclick mouseup mousedown contextmenu mousewheel DOMMouseScroll mouseover mouseout mousemove selectstart selectend keydown keypress keyup orientationchange focus blur change reset select submit load unload beforeunload resize move DOMContentLoaded readystatechange error abort scroll "+(n?"show input invalid touchstart touchmove touchend touchcancel gesturestart gesturechange gestureend message readystatechange pageshow pagehide popstate hashchange offline online afterprint beforeprint dragstart dragenter dragover dragleave drag drop dragend loadstart progress suspend emptied stalled loadmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate play pause ratechange volumechange cuechange checking noupdate downloading cached updateready obsolete ":"")).split(" ")),u=function(){function a(a,b){while((b=b.parentNode)!==null)if(b===a)return!0;return!1}function b(b){var c=b.relatedTarget;return c?c!==this&&c.prefix!=="xul"&&!/document/.test(this.toString())&&!a(this,c):c===null}return{mouseenter:{base:"mouseover",condition:b},mouseleave:{base:"mouseout",condition:b},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}}}(),v=function(){var a="altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which".split(" "),b=a.concat("button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" ")),c=a.concat("char charCode key keyCode".split(" ")),d=a.concat("touches targetTouches changedTouches scale rotation".split(" ")),f="preventDefault",g=function(a){return function(){a[f]?a[f]():a.returnValue=!1}},h="stopPropagation",i=function(a){return function(){a[h]?a[h]():a.cancelBubble=!0}},j=function(a){return function(){a[f](),a[h](),a.stopped=!0}},k=function(a,b,c){var d,e;for(d=c.length;d--;)e=c[d],!(e in b)&&e in a&&(b[e]=a[e])};return function(n,o){var p={originalEvent:n,isNative:o};if(!n)return p;var s,t=n.type,u=n.target||n.srcElement;p[f]=g(n),p[h]=i(n),p.stop=j(p),p.target=u&&u.nodeType===3?u.parentNode:u;if(o){if(t.indexOf("key")!==-1)s=c,p.keyCode=n.which||n.keyCode;else if(q.test(t)){s=b,p.rightClick=n.which===3||n.button===2,p.pos={x:0,y:0};if(n.pageX||n.pageY)p.clientX=n.pageX,p.clientY=n.pageY;else if(n.clientX||n.clientY)p.clientX=n.clientX+l.body.scrollLeft+m.scrollLeft,p.clientY=n.clientY+l.body.scrollTop+m.scrollTop;e.test(t)&&(p.relatedTarget=n.relatedTarget||n[(t==="mouseover"?"from":"to")+"Element"])}else r.test(t)&&(s=d);k(n,p,s||a)}return p}}(),w=function(a,b){return!n&&!b&&(a===l||a===c)?m:a},x=function(){function a(a,b,c,d,e){this.element=a,this.type=b,this.handler=c,this.original=d,this.namespaces=e,this.custom=u[b],this.isNative=t[b]&&a[o],this.eventType=n||this.isNative?b:"propertychange",this.customType=!n&&!this.isNative&&b,this.target=w(a,this.isNative),this.eventSupport=this.target[o]}return a.prototype={inNamespaces:function(a){var b,c;if(!a)return!0;if(!this.namespaces)return!1;for(b=a.length;b--;)for(c=this.namespaces.length;c--;)if(a[b]===this.namespaces[c])return!0;return!1},matches:function(a,b,c){return this.element===a&&(!b||this.original===b)&&(!c||this.handler===c)}},a}(),y=function(){var a={},b=function(c,d,e,f,g){if(!d||d==="*")for(var h in a)h.charAt(0)==="$"&&b(c,h.substr(1),e,f,g);else{var i=0,j,k=a["$"+d],l=c==="*";if(!k)return;for(j=k.length;i<j;i++)if(l||k[i].matches(c,e,f))if(!g(k[i],k,i,d))return}},c=function(b,c,d){var e,f=a["$"+c];if(f)for(e=f.length;e--;)if(f[e].matches(b,d,null))return!0;return!1},d=function(a,c,d){var e=[];return b(a,c,d,null,function(a){return e.push(a)}),e},e=function(b){return(a["$"+b.type]||(a["$"+b.type]=[])).push(b),b},f=function(c){b(c.element,c.type,null,c.handler,function(b,c,d){return c.splice(d,1),c.length===0&&delete a["$"+b.type],!1})},g=function(){var b,c=[];for(b in a)b.charAt(0)==="$"&&(c=c.concat(a[b]));return c};return{has:c,get:d,put:e,del:f,entries:g}}(),z=n?function(a,b,c,d){a[d?h:j](b,c,!1)}:function(a,b,c,d,e){e&&d&&a["_on"+e]===null&&(a["_on"+e]=0),a[d?i:k]("on"+b,c)},A=function(a,b,d){return function(e){return e=v(e||((this.ownerDocument||this.document||this).parentWindow||c).event,!0),b.apply(a,[e].concat(d))}},B=function(a,b,d,e,f,g){return function(h){if(e?e.apply(this,arguments):n?!0:h&&h.propertyName==="_on"+d||!h)h&&(h=v(h||((this.ownerDocument||this.document||this).parentWindow||c).event,g)),b.apply(a,h&&(!f||f.length===0)?arguments:p.call(arguments,h?0:1).concat(f))}},C=function(a,b,c,d,e){return function(){a(b,c,e),d.apply(this,arguments)}},D=function(a,b,c,d){var e,f,h,i=b&&b.replace(g,""),j=y.get(a,i,c);for(e=0,f=j.length;e<f;e++)j[e].inNamespaces(d)&&((h=j[e]).eventSupport&&z(h.target,h.eventType,h.handler,!1,h.type),y.del(h))},E=function(a,b,c,d,e){var h,i=b.replace(g,""),j=b.replace(f,"").split(".");if(y.has(a,i,c))return a;i==="unload"&&(c=C(D,a,i,c,d)),u[i]&&(u[i].condition&&(c=B(a,c,i,u[i].condition,!0)),i=u[i].base||i),h=y.put(new x(a,i,c,d,j[0]&&j)),h.handler=h.isNative?A(a,h.handler,e):B(a,h.handler,i,!1,e,!1),h.eventSupport&&z(h.target,h.eventType,h.handler,!0,h.customType)},F=function(a,b,c){return function(d){var e,f,g=typeof a=="string"?c(a,this):a;for(e=d.target;e&&e!==this;e=e.parentNode)for(f=g.length;f--;)if(g[f]===e)return b.apply(e,arguments)}},G=function(a,b,c){var d,e,h,i,j,k=D,l=b&&typeof b=="string";if(l&&b.indexOf(" ")>0){b=b.split(" ");for(j=b.length;j--;)G(a,b[j],c);return a}h=l&&b.replace(g,""),h&&u[h]&&(h=u[h].type);if(!b||l){if(i=l&&b.replace(f,""))i=i.split(".");k(a,h,c,i)}else if(typeof b=="function")k(a,null,b);else for(d in b)b.hasOwnProperty(d)&&G(a,d,b[d]);return a},H=function(a,b,c,d,e){var f,g,h,i,j=c,k=c&&typeof c=="string";if(b&&!c&&typeof b=="object")for(f in b)b.hasOwnProperty(f)&&H.apply(this,[a,f,b[f]]);else{i=arguments.length>3?p.call(arguments,3):[],g=(k?c:b).split(" "),k&&(c=F(b,j=d,e))&&(i=p.call(i,1)),this===s&&(c=C(G,a,b,c,j));for(h=g.length;h--;)E(a,g[h],c,j,i)}return a},I=function(){return H.apply(s,arguments)},J=n?function(a,b,d){var e=l.createEvent(a?"HTMLEvents":"UIEvents");e[a?"initEvent":"initUIEvent"](b,!0,!0,c,1),d.dispatchEvent(e)}:function(a,b,c){c=w(c,a),a?c.fireEvent("on"+b,l.createEventObject()):c["_on"+b]++},K=function(a,b,c){var d,e,h,i,j,k=b.split(" ");for(d=k.length;d--;){b=k[d].replace(g,"");if(i=k[d].replace(f,""))i=i.split(".");if(!i&&!c&&a[o])J(t[b],b,a);else{j=y.get(a,b),c=[!1].concat(c);for(e=0,h=j.length;e<h;e++)j[e].inNamespaces(i)&&j[e].handler.apply(a,c)}}return a},L=function(a,b,c){var d=0,e=y.get(b,c),f=e.length;for(;d<f;d++)e[d].original&&H(a,e[d].type,e[d].original);return a},M={add:H,one:I,remove:G,clone:L,fire:K,noConflict:function(){return b[a]=d,this}};if(c[i]){var N=function(){var a,b=y.entries();for(a in b)b[a].type&&b[a].type!=="unload"&&G(b[a].element,b[a].type);c[k]("onunload",N),c.CollectGarbage&&c.CollectGarbage()};c[i]("onunload",N)}return M});
//     Underscore.js 1.1.7
//     (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
//     Underscore is freely distributable under the MIT license.
//     Portions of Underscore are inspired or borrowed from Prototype,
//     Oliver Steele's Functional, and John Resig's Micro-Templating.
//     For all details and documentation:
//     http://documentcloud.github.com/underscore

(function(){var a=this,b=a._,c={},d=Array.prototype,e=Object.prototype,f=Function.prototype,g=d.slice,h=d.unshift,i=e.toString,j=e.hasOwnProperty,k=d.forEach,l=d.map,m=d.reduce,n=d.reduceRight,o=d.filter,p=d.every,q=d.some,r=d.indexOf,s=d.lastIndexOf,t=Array.isArray,u=Object.keys,v=f.bind,w=function(a){return new B(a)};typeof module!="undefined"&&module.exports?(module.exports=w,w._=w):a._=w,w.VERSION="1.1.7";var x=w.each=w.forEach=function(a,b,d){if(a==null)return;if(k&&a.forEach===k)a.forEach(b,d);else if(a.length===+a.length){for(var e=0,f=a.length;e<f;e++)if(e in a&&b.call(d,a[e],e,a)===c)return}else for(var g in a)if(j.call(a,g)&&b.call(d,a[g],g,a)===c)return};w.map=function(a,b,c){var d=[];return a==null?d:l&&a.map===l?a.map(b,c):(x(a,function(a,e,f){d[d.length]=b.call(c,a,e,f)}),d)},w.reduce=w.foldl=w.inject=function(a,b,c,d){var e=c!==void 0;a==null&&(a=[]);if(m&&a.reduce===m)return d&&(b=w.bind(b,d)),e?a.reduce(b,c):a.reduce(b);x(a,function(a,f,g){e?c=b.call(d,c,a,f,g):(c=a,e=!0)});if(!e)throw new TypeError("Reduce of empty array with no initial value");return c},w.reduceRight=w.foldr=function(a,b,c,d){a==null&&(a=[]);if(n&&a.reduceRight===n)return d&&(b=w.bind(b,d)),c!==void 0?a.reduceRight(b,c):a.reduceRight(b);var e=(w.isArray(a)?a.slice():w.toArray(a)).reverse();return w.reduce(e,b,c,d)},w.find=w.detect=function(a,b,c){var d;return y(a,function(a,e,f){if(b.call(c,a,e,f))return d=a,!0}),d},w.filter=w.select=function(a,b,c){var d=[];return a==null?d:o&&a.filter===o?a.filter(b,c):(x(a,function(a,e,f){b.call(c,a,e,f)&&(d[d.length]=a)}),d)},w.reject=function(a,b,c){var d=[];return a==null?d:(x(a,function(a,e,f){b.call(c,a,e,f)||(d[d.length]=a)}),d)},w.every=w.all=function(a,b,d){var e=!0;return a==null?e:p&&a.every===p?a.every(b,d):(x(a,function(a,f,g){if(!(e=e&&b.call(d,a,f,g)))return c}),e)};var y=w.some=w.any=function(a,b,d){b=b||w.identity;var e=!1;return a==null?e:q&&a.some===q?a.some(b,d):(x(a,function(a,f,g){if(e|=b.call(d,a,f,g))return c}),!!e)};w.include=w.contains=function(a,b){var c=!1;return a==null?c:r&&a.indexOf===r?a.indexOf(b)!=-1:(y(a,function(a){if(c=a===b)return!0}),c)},w.invoke=function(a,b){var c=g.call(arguments,2);return w.map(a,function(a){return(b.call?b||a:a[b]).apply(a,c)})},w.pluck=function(a,b){return w.map(a,function(a){return a[b]})},w.max=function(a,b,c){if(!b&&w.isArray(a))return Math.max.apply(Math,a);var d={computed:-Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g>=d.computed&&(d={value:a,computed:g})}),d.value},w.min=function(a,b,c){if(!b&&w.isArray(a))return Math.min.apply(Math,a);var d={computed:Infinity};return x(a,function(a,e,f){var g=b?b.call(c,a,e,f):a;g<d.computed&&(d={value:a,computed:g})}),d.value},w.sortBy=function(a,b,c){return w.pluck(w.map(a,function(a,d,e){return{value:a,criteria:b.call(c,a,d,e)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")},w.groupBy=function(a,b){var c={};return x(a,function(a,d){var e=b(a,d);(c[e]||(c[e]=[])).push(a)}),c},w.sortedIndex=function(a,b,c){c||(c=w.identity);var d=0,e=a.length;while(d<e){var f=d+e>>1;c(a[f])<c(b)?d=f+1:e=f}return d},w.toArray=function(a){return a?a.toArray?a.toArray():w.isArray(a)?g.call(a):w.isArguments(a)?g.call(a):w.values(a):[]},w.size=function(a){return w.toArray(a).length},w.first=w.head=function(a,b,c){return b!=null&&!c?g.call(a,0,b):a[0]},w.rest=w.tail=function(a,b,c){return g.call(a,b==null||c?1:b)},w.last=function(a){return a[a.length-1]},w.compact=function(a){return w.filter(a,function(a){return!!a})},w.flatten=function(a){return w.reduce(a,function(a,b){return w.isArray(b)?a.concat(w.flatten(b)):(a[a.length]=b,a)},[])},w.without=function(a){return w.difference(a,g.call(arguments,1))},w.uniq=w.unique=function(a,b){return w.reduce(a,function(a,c,d){if(0==d||(b===!0?w.last(a)!=c:!w.include(a,c)))a[a.length]=c;return a},[])},w.union=function(){return w.uniq(w.flatten(arguments))},w.intersection=w.intersect=function(a){var b=g.call(arguments,1);return w.filter(w.uniq(a),function(a){return w.every(b,function(b){return w.indexOf(b,a)>=0})})},w.difference=function(a,b){return w.filter(a,function(a){return!w.include(b,a)})},w.zip=function(){var a=g.call(arguments),b=w.max(w.pluck(a,"length")),c=new Array(b);for(var d=0;d<b;d++)c[d]=w.pluck(a,""+d);return c},w.indexOf=function(a,b,c){if(a==null)return-1;var d,e;if(c)return d=w.sortedIndex(a,b),a[d]===b?d:-1;if(r&&a.indexOf===r)return a.indexOf(b);for(d=0,e=a.length;d<e;d++)if(a[d]===b)return d;return-1},w.lastIndexOf=function(a,b){if(a==null)return-1;if(s&&a.lastIndexOf===s)return a.lastIndexOf(b);var c=a.length;while(c--)if(a[c]===b)return c;return-1},w.range=function(a,b,c){arguments.length<=1&&(b=a||0,a=0),c=arguments[2]||1;var d=Math.max(Math.ceil((b-a)/c),0),e=0,f=new Array(d);while(e<d)f[e++]=a,a+=c;return f},w.bind=function(a,b){if(a.bind===v&&v)return v.apply(a,g.call(arguments,1));var c=g.call(arguments,2);return function(){return a.apply(b,c.concat(g.call(arguments)))}},w.bindAll=function(a){var b=g.call(arguments,1);return b.length==0&&(b=w.functions(a)),x(b,function(b){a[b]=w.bind(a[b],a)}),a},w.memoize=function(a,b){var c={};return b||(b=w.identity),function(){var d=b.apply(this,arguments);return j.call(c,d)?c[d]:c[d]=a.apply(this,arguments)}},w.delay=function(a,b){var c=g.call(arguments,2);return setTimeout(function(){return a.apply(a,c)},b)},w.defer=function(a){return w.delay.apply(w,[a,1].concat(g.call(arguments,1)))};var z=function(a,b,c){var d;return function(){var e=this,f=arguments,g=function(){d=null,a.apply(e,f)};c&&clearTimeout(d);if(c||!d)d=setTimeout(g,b)}};w.throttle=function(a,b){return z(a,b,!1)},w.debounce=function(a,b){return z(a,b,!0)},w.once=function(a){var b=!1,c;return function(){return b?c:(b=!0,c=a.apply(this,arguments))}},w.wrap=function(a,b){return function(){var c=[a].concat(g.call(arguments));return b.apply(this,c)}},w.compose=function(){var a=g.call(arguments);return function(){var b=g.call(arguments);for(var c=a.length-1;c>=0;c--)b=[a[c].apply(this,b)];return b[0]}},w.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}},w.keys=u||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[];for(var c in a)j.call(a,c)&&(b[b.length]=c);return b},w.values=function(a){return w.map(a,w.identity)},w.functions=w.methods=function(a){var b=[];for(var c in a)w.isFunction(a[c])&&b.push(c);return b.sort()},w.extend=function(a){return x(g.call(arguments,1),function(b){for(var c in b)b[c]!==void 0&&(a[c]=b[c])}),a},w.defaults=function(a){return x(g.call(arguments,1),function(b){for(var c in b)a[c]==null&&(a[c]=b[c])}),a},w.clone=function(a){return w.isArray(a)?a.slice():w.extend({},a)},w.tap=function(a,b){return b(a),a},w.isEqual=function(a,b){if(a===b)return!0;var c=typeof a,d=typeof b;if(c!=d)return!1;if(a==b)return!0;if(!a&&b||a&&!b)return!1;a._chain&&(a=a._wrapped),b._chain&&(b=b._wrapped);if(a.isEqual)return a.isEqual(b);if(b.isEqual)return b.isEqual(a);if(w.isDate(a)&&w.isDate(b))return a.getTime()===b.getTime();if(w.isNaN(a)&&w.isNaN(b))return!1;if(w.isRegExp(a)&&w.isRegExp(b))return a.source===b.source&&a.global===b.global&&a.ignoreCase===b.ignoreCase&&a.multiline===b.multiline;if(c!=="object")return!1;if(a.length&&a.length!==b.length)return!1;var e=w.keys(a),f=w.keys(b);if(e.length!=f.length)return!1;for(var g in a)if(!(g in b)||!w.isEqual(a[g],b[g]))return!1;return!0},w.isEmpty=function(a){if(w.isArray(a)||w.isString(a))return a.length===0;for(var b in a)if(j.call(a,b))return!1;return!0},w.isElement=function(a){return!!a&&a.nodeType==1},w.isArray=t||function(a){return i.call(a)==="[object Array]"},w.isObject=function(a){return a===Object(a)},w.isArguments=function(a){return!!a&&!!j.call(a,"callee")},w.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)},w.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)},w.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)},w.isNaN=function(a){return a!==a},w.isBoolean=function(a){return a===!0||a===!1},w.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)},w.isRegExp=function(a){return!(!(a&&a.test&&a.exec)||!a.ignoreCase&&a.ignoreCase!==!1)},w.isNull=function(a){return a===null},w.isUndefined=function(a){return a===void 0},w.noConflict=function(){return a._=b,this},w.identity=function(a){return a},w.times=function(a,b,c){for(var d=0;d<a;d++)b.call(c,d)},w.mixin=function(a){x(w.functions(a),function(b){D(b,w[b]=a[b])})};var A=0;w.uniqueId=function(a){var b=A++;return a?a+b:b},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g},w.template=function(a,b){var c=w.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(c.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(c.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj",d);return b?e(b):e};var B=function(a){this._wrapped=a};w.prototype=B.prototype;var C=function(a,b){return b?w(a).chain():a},D=function(a,b){B.prototype[a]=function(){var a=g.call(arguments);return h.call(a,this._wrapped),C(b.apply(w,a),this._chain)}};w.mixin(w),x(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=d[a];B.prototype[a]=function(){return b.apply(this._wrapped,arguments),C(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(a){var b=d[a];B.prototype[a]=function(){return C(b.apply(this._wrapped,arguments),this._chain)}}),B.prototype.chain=function(){return this._chain=!0,this},B.prototype.value=function(){return this._wrapped}})();
/**
 * Flotr2 (c) 2012 Carl Sutherland
 * MIT License
 * Special thanks to:
 * Flotr: http://code.google.com/p/flotr/ (fork)
 * Flot: https://github.com/flot/flot (original fork)
 */
(function(){var a=this,b=this.Flotr,c;c={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(a,b){c.graphTypes[a]=b,c.defaultOptions[a]=b.options||{},c.defaultOptions.defaultType=c.defaultOptions.defaultType||a},addPlugin:function(a,b){c.plugins[a]=b,c.defaultOptions[a]=b.options||{}},draw:function(a,b,d,e){return e=e||c.Graph,new e(a,b,d)},merge:function(a,b){var d,e,f=b||{};for(d in a)e=a[d],e&&typeof e=="object"?e.constructor===Array?f[d]=this._.clone(e):e.constructor!==RegExp&&!this._.isElement(e)?f[d]=c.merge(e,b?b[d]:undefined):f[d]=e:f[d]=e;return f},clone:function(a){return c.merge(a,{})},getTickSize:function(a,b,d,e){var f=(d-b)/a,g=c.getMagnitude(f),h=10,i=f/g;return i<1.5?h=1:i<2.25?h=2:i<3?h=e===0?2:2.5:i<7.5&&(h=5),h*g},defaultTickFormatter:function(a,b){return a+""},defaultTrackFormatter:function(a){return"("+a.x+", "+a.y+")"},engineeringNotation:function(a,b,c){var d=["Y","Z","E","P","T","G","M","k",""],e=["y","z","a","f","p","n","Âµ","m",""],f=d.length;c=c||1e3,b=Math.pow(10,b||2);if(a===0)return 0;if(a>1)while(f--&&a>=c)a/=c;else{d=e,f=d.length;while(f--&&a<1)a*=c}return Math.round(a*b)/b+d[f]},getMagnitude:function(a){return Math.pow(10,Math.floor(Math.log(a)/Math.LN10))},toPixel:function(a){return Math.floor(a)+.5},toRad:function(a){return-a*(Math.PI/180)},floorInBase:function(a,b){return b*Math.floor(a/b)},drawText:function(a,b,d,e,f){if(!a.fillText){a.drawText(b,d,e,f);return}f=this._.extend({size:c.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},f),a.save(),a.translate(d,e),a.rotate(f.angle),a.fillStyle=f.color,a.font=(f.weight>1?"bold ":"")+f.size*1.3+"px sans-serif",a.textAlign=f.textAlign,a.textBaseline=f.textBaseline,a.fillText(b,0,0),a.restore()},getBestTextAlign:function(a,b){return b=b||{textAlign:"center",textBaseline:"middle"},a+=c.getTextAngleFromAlign(b),Math.abs(Math.cos(a))>.01&&(b.textAlign=Math.cos(a)>0?"right":"left"),Math.abs(Math.sin(a))>.01&&(b.textBaseline=Math.sin(a)>0?"top":"bottom"),b},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(a){return c.alignTable[a.textAlign+" "+a.textBaseline]||0},noConflict:function(){return a.Flotr=b,this}},a.Flotr=c})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function b(a,b,c,d){this.rgba=["r","g","b","a"];var e=4;while(-1<--e)this[this.rgba[e]]=arguments[e]||(e==3?1:0);this.normalize()}var a=Flotr._,c={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};b.prototype={scale:function(b,c,d,e){var f=4;while(-1<--f)a.isUndefined(arguments[f])||(this[this.rgba[f]]*=arguments[f]);return this.normalize()},alpha:function(b){return!a.isUndefined(b)&&!a.isNull(b)&&(this.a=b),this.normalize()},clone:function(){return new b(this.r,this.b,this.g,this.a)},limit:function(a,b,c){return Math.max(Math.min(a,c),b)},normalize:function(){var a=this.limit;return this.r=a(parseInt(this.r,10),0,255),this.g=a(parseInt(this.g,10),0,255),this.b=a(parseInt(this.b,10),0,255),this.a=a(this.a,0,1),this},distance:function(a){if(!a)return;a=new b.parse(a);var c=0,d=3;while(-1<--d)c+=Math.abs(this[this.rgba[d]]-a[this.rgba[d]]);return c},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var a=1-(.299*this.r+.587*this.g+.114*this.b)/255;return a<.5?"#000000":"#ffffff"}},a.extend(b,{parse:function(a){if(a instanceof b)return a;var d;if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return new b(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16));if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10));if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return new b(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16));if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]));if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55);if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(a))return new b(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]));var e=(a+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return e=="transparent"?new b(255,255,255,0):(d=c[e])?new b(d[0],d[1],d[2]):new b(0,0,0,0)},processColor:function(c,d){var e=d.opacity;if(!c)return"rgba(0, 0, 0, 0)";if(c instanceof b)return c.alpha(e).toString();if(a.isString(c))return b.parse(c).alpha(e).toString();var f=c.colors?c:{colors:c};if(!d.ctx)return a.isArray(f.colors)?b.parse(a.isArray(f.colors[0])?f.colors[0][1]:f.colors[0]).alpha(e).toString():"rgba(0, 0, 0, 0)";f=a.extend({start:"top",end:"bottom"},f),/top/i.test(f.start)&&(d.x1=0),/left/i.test(f.start)&&(d.y1=0),/bottom/i.test(f.end)&&(d.x2=0),/right/i.test(f.end)&&(d.y2=0);var g,h,i,j=d.ctx.createLinearGradient(d.x1,d.y1,d.x2,d.y2);for(g=0;g<f.colors.length;g++)h=f.colors[g],a.isArray(h)?(i=h[0],h=h[1]):i=g/(f.colors.length-1),j.addColorStop(i,b.parse(h).alpha(e));return j}}),Flotr.Color=b}(),Flotr.Date={set:function(a,b,c,d){c=c||"UTC",b="set"+(c==="UTC"?"UTC":"")+b,a[b](d)},get:function(a,b,c){return c=c||"UTC",b="get"+(c==="UTC"?"UTC":"")+b,a[b]()},format:function(a,b,c){function f(a){return a+="",a.length==1?"0"+a:a}if(!a)return;var d=this.get,e={h:d(a,"Hours",c).toString(),H:f(d(a,"Hours",c)),M:f(d(a,"Minutes",c)),S:f(d(a,"Seconds",c)),s:d(a,"Milliseconds",c),d:d(a,"Date",c).toString(),m:(d(a,"Month")+1).toString(),y:d(a,"FullYear").toString(),b:Flotr.Date.monthNames[d(a,"Month",c)]},g=[],h,i=!1;for(var j=0;j<b.length;++j)h=b.charAt(j),i?(g.push(e[h]||h),i=!1):h=="%"?i=!0:g.push(h);return g.join("")},getFormat:function(a,b){var c=Flotr.Date.timeUnits;return a<c.second?"%h:%M:%S.%s":a<c.minute?"%h:%M:%S":a<c.day?b<2*c.day?"%h:%M":"%b %d %h:%M":a<c.month?"%b %d":a<c.year?b<c.year?"%b":"%b %y":"%y"},formatter:function(a,b){var c=b.options,d=Flotr.Date.timeUnits[c.timeUnit],e=new Date(a*d);if(b.options.timeFormat)return Flotr.Date.format(e,c.timeFormat,c.timeMode);var f=(b.max-b.min)*d,g=b.tickSize*Flotr.Date.timeUnits[b.tickUnit];return Flotr.Date.format(e,Flotr.Date.getFormat(g,f),c.timeMode)},generator:function(a){function s(a){b(q,a,g,Flotr.floorInBase(c(q,a,g),m))}var b=this.set,c=this.get,d=this.timeUnits,e=this.spec,f=a.options,g=f.timeMode,h=d[f.timeUnit],i=a.min*h,j=a.max*h,k=(j-i)/f.noTicks,l=[],m=a.tickSize,n,o,p;o=f.tickFormatter===Flotr.defaultTickFormatter?this.formatter:f.tickFormatter;for(p=0;p<e.length-1;++p){var q=e[p][0]*d[e[p][1]];if(k<(q+e[p+1][0]*d[e[p+1][1]])/2&&q>=m)break}m=e[p][0],n=e[p][1],n=="year"&&(m=Flotr.getTickSize(f.noTicks*d.year,i,j,0),m==.5&&(n="month",m=6)),a.tickUnit=n,a.tickSize=m;var q=new Date(i),r=m*d[n];switch(n){case"millisecond":s("Milliseconds");break;case"second":s("Seconds");break;case"minute":s("Minutes");break;case"hour":s("Hours");break;case"month":s("Month");break;case"year":s("FullYear")}r>=d.second&&b(q,"Milliseconds",g,0),r>=d.minute&&b(q,"Seconds",g,0),r>=d.hour&&b(q,"Minutes",g,0),r>=d.day&&b(q,"Hours",g,0),r>=d.day*4&&b(q,"Date",g,1),r>=d.year&&b(q,"Month",g,0);var t=0,u=NaN,v;do{v=u,u=q.getTime(),l.push({v:u/h,label:o(u/h,a)});if(n=="month")if(m<1){b(q,"Date",g,1);var w=q.getTime();b(q,"Month",g,c(q,"Month",g)+1);var x=q.getTime();q.setTime(u+t*d.hour+(x-w)*m),t=c(q,"Hours",g),b(q,"Hours",g,0)}else b(q,"Month",g,c(q,"Month",g)+m);else n=="year"?b(q,"FullYear",g,c(q,"FullYear",g)+m):q.setTime(u+r)}while(u<j&&u!=v);return l},timeUnits:{millisecond:1,second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,year:31556952e3},spec:[[1,"millisecond"],[20,"millisecond"],[50,"millisecond"],[100,"millisecond"],[200,"millisecond"],[500,"millisecond"],[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},function(){var a=Flotr._;Flotr.DOM={addClass:function(b,c){var d=b.className?b.className:"";if(a.include(d.split(/\s+/g),c))return;b.className=(d?d+" ":"")+c},create:function(a){return document.createElement(a)},node:function(a){var b=Flotr.DOM.create("div"),c;return b.innerHTML=a,c=b.children[0],b.innerHTML="",c},empty:function(a){a.innerHTML=""},hide:function(a){Flotr.DOM.setStyles(a,{display:"none"})},insert:function(b,c){a.isString(c)?b.innerHTML+=c:a.isElement(c)&&b.appendChild(c)},opacity:function(a,b){a.style.opacity=b},position:function(a,b){return a.offsetParent?(b=this.position(a.offsetParent),b.left+=a.offsetLeft,b.top+=a.offsetTop,b):{left:a.offsetLeft||0,top:a.offsetTop||0}},removeClass:function(b,c){var d=b.className?b.className:"";b.className=a.filter(d.split(/\s+/g),function(a){if(a!=c)return!0}).join(" ")},setStyles:function(b,c){a.each(c,function(a,c){b.style[c]=a})},show:function(a){Flotr.DOM.setStyles(a,{display:""})},size:function(a){return{height:a.offsetHeight,width:a.offsetWidth}}}}(),function(){var a=Flotr,b=a.bean;a.EventAdapter={observe:function(a,c,d){return b.add(a,c,d),this},fire:function(a,c,d){return b.fire(a,c,d),typeof Prototype!="undefined"&&Event.fire(a,c,d),this},stopObserving:function(a,c,d){return b.remove(a,c,d),this},eventPointer:function(b){if(!a._.isUndefined(b.touches)&&b.touches.length>0)return{x:b.touches[0].pageX,y:b.touches[0].pageY};if(!a._.isUndefined(b.changedTouches)&&b.changedTouches.length>0)return{x:b.changedTouches[0].pageX,y:b.changedTouches[0].pageY};if(b.pageX||b.pageY)return{x:b.pageX,y:b.pageY};if(b.clientX||b.clientY){var c=document,d=c.body,e=c.documentElement;return{x:b.clientX+d.scrollLeft+e.scrollLeft,y:b.clientY+d.scrollTop+e.scrollTop}}}}}(),function(){var a=Flotr,b=a.DOM,c=a._,d=function(a){this.o=a};d.prototype={dimensions:function(a,b,c,d){return a?this.o.html?this.html(a,this.o.element,c,d):this.canvas(a,b):{width:0,height:0}},canvas:function(b,c){if(!this.o.textEnabled)return;c=c||{};var d=this.measureText(b,c),e=d.width,f=c.size||a.defaultOptions.fontSize,g=c.angle||0,h=Math.cos(g),i=Math.sin(g),j=2,k=6,l;return l={width:Math.abs(h*e)+Math.abs(i*f)+j,height:Math.abs(i*e)+Math.abs(h*f)+k},l},html:function(a,c,d,e){var f=b.create("div");return b.setStyles(f,{position:"absolute",top:"-10000px"}),b.insert(f,'<div style="'+d+'" class="'+e+' flotr-dummy-div">'+a+"</div>"),b.insert(this.o.element,f),b.size(f)},measureText:function(b,d){var e=this.o.ctx,f;return!e.fillText||a.isIphone&&e.measure?{width:e.measure(b,d)}:(d=c.extend({size:a.defaultOptions.fontSize,weight:1,angle:0},d),e.save(),e.font=(d.weight>1?"bold ":"")+d.size*1.3+"px sans-serif",f=e.measureText(b),e.restore(),f)}},Flotr.Text=d}(),function(){function e(a,c,d){return b.observe.apply(this,arguments),this._handles.push(arguments),this}var a=Flotr.DOM,b=Flotr.EventAdapter,c=Flotr._,d=Flotr;Graph=function(a,e,f){this._setEl(a),this._initMembers(),this._initPlugins(),b.fire(this.el,"flotr:beforeinit",[this]),this.data=e,this.series=d.Series.getSeries(e),this._initOptions(f),this._initGraphTypes(),this._initCanvas(),this._text=new d.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),b.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(c.bind(function(){b.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){b.fire(this.el,"flotr:destroy"),c.each(this._handles,function(a){b.stopObserving.apply(this,a)}),this._handles=[],this.el.graph=null},observe:e,_observe:e,processColor:function(a,b){var e={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return c.extend(e,b),d.Color.processColor(a,e)},findDataRanges:function(){var a=this.axes,b,e,f;c.each(this.series,function(a){f=a.getRange(),f&&(b=a.xaxis,e=a.yaxis,b.datamin=Math.min(f.xmin,b.datamin),b.datamax=Math.max(f.xmax,b.datamax),e.datamin=Math.min(f.ymin,e.datamin),e.datamax=Math.max(f.ymax,e.datamax),b.used=b.used||f.xused,e.used=e.used||f.yused)},this),!a.x.used&&!a.x2.used&&(a.x.used=!0),!a.y.used&&!a.y2.used&&(a.y.used=!0),c.each(a,function(a){a.calculateRange()});var g=c.keys(d.graphTypes),h=!1;c.each(this.series,function(a){if(a.hide)return;c.each(g,function(b){a[b]&&a[b].show&&(this.extendRange(b,a),h=!0)},this),h||this.extendRange(this.options.defaultType,a)},this)},extendRange:function(a,b){this[a].extendRange&&this[a].extendRange(b,b.data,b[a],this[a]),this[a].extendYRange&&this[a].extendYRange(b.yaxis,b.data,b[a],this[a]),this[a].extendXRange&&this[a].extendXRange(b.xaxis,b.data,b[a],this[a])},calculateSpacing:function(){var a=this.axes,b=this.options,d=this.series,e=b.grid.labelMargin,f=this._text,g=a.x,h=a.x2,i=a.y,j=a.y2,k=b.grid.outlineWidth,l,m,n,o;c.each(a,function(a){a.calculateTicks(),a.calculateTextDimensions(f,b)}),o=f.dimensions(b.title,{size:b.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=o.height,o=f.dimensions(b.subtitle,{size:b.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=o.height;for(m=0;m<b.length;++m)d[m].points.show&&(k=Math.max(k,d[m].points.radius+d[m].points.lineWidth/2));var p=this.plotOffset;g.options.margin===!1?(p.bottom=0,p.top=0):(p.bottom+=(b.grid.circular?0:g.used&&g.options.showLabels?g.maxLabel.height+e:0)+(g.used&&g.options.title?g.titleSize.height+e:0)+k,p.top+=(b.grid.circular?0:h.used&&h.options.showLabels?h.maxLabel.height+e:0)+(h.used&&h.options.title?h.titleSize.height+e:0)+this.subtitleHeight+this.titleHeight+k),i.options.margin===!1?(p.left=0,p.right=0):(p.left+=(b.grid.circular?0:i.used&&i.options.showLabels?i.maxLabel.width+e:0)+(i.used&&i.options.title?i.titleSize.width+e:0)+k,p.right+=(b.grid.circular?0:j.used&&j.options.showLabels?j.maxLabel.width+e:0)+(j.used&&j.options.title?j.titleSize.width+e:0)+k),p.top=Math.floor(p.top),this.plotWidth=this.canvasWidth-p.left-p.right,this.plotHeight=this.canvasHeight-p.bottom-p.top,g.length=h.length=this.plotWidth,i.length=j.length=this.plotHeight,i.offset=j.offset=this.plotHeight,g.setScale(),h.setScale(),i.setScale(),j.setScale()},draw:function(a){var c=this.ctx,d;b.fire(this.el,"flotr:beforedraw",[this.series,this]);if(this.series.length){c.save(),c.translate(this.plotOffset.left,this.plotOffset.top);for(d=0;d<this.series.length;d++)this.series[d].hide||this.drawSeries(this.series[d]);c.restore(),this.clip()}b.fire(this.el,"flotr:afterdraw",[this.series,this]),a&&a()},drawSeries:function(a){function b(a,b){var c=this.getOptions(a,b);this[b].draw(c)}var e=!1;a=a||this.series,c.each(d.graphTypes,function(c,d){a[d]&&a[d].show&&this[d]&&(e=!0,b.call(this,a,d))},this),e||b.call(this,a,this.options.defaultType)},getOptions:function(a,b){var e=a[b],f=this[b],g={context:this.ctx,width:this.plotWidth,height:this.plotHeight,fontSize:this.options.fontSize,fontColor:this.options.fontColor,textEnabled:this.textEnabled,htmlText:this.options.HtmlText,text:this._text,element:this.el,data:a.data,color:a.color,shadowSize:a.shadowSize,xScale:c.bind(a.xaxis.d2p,a.xaxis),yScale:c.bind(a.yaxis.d2p,a.yaxis)};return g=d.merge(e,g),g.fillStyle=this.processColor(e.fillColor||a.color,{opacity:e.fillOpacity}),g},getEventPosition:function(c){var d=document,e=d.body,f=d.documentElement,g=this.axes,h=this.plotOffset,i=this.lastMousePos,j=b.eventPointer(c),k=j.x-i.pageX,l=j.y-i.pageY,m,n,o;return"ontouchstart"in this.el?(m=a.position(this.overlay),n=j.x-m.left-h.left,o=j.y-m.top-h.top):(m=this.overlay.getBoundingClientRect(),n=c.clientX-m.left-h.left-e.scrollLeft-f.scrollLeft,o=c.clientY-m.top-h.top-e.scrollTop-f.scrollTop),{x:g.x.p2d(n),x2:g.x2.p2d(n),y:g.y.p2d(o),y2:g.y2.p2d(o),relX:n,relY:o,dX:k,dY:l,absX:j.x,absY:j.y,pageX:j.x,pageY:j.y}},clickHandler:function(a){if(this.ignoreClick)return this.ignoreClick=!1,this.ignoreClick;b.fire(this.el,"flotr:click",[this.getEventPosition(a),this])},mouseMoveHandler:function(a){if(this.mouseDownMoveHandler)return;var c=this.getEventPosition(a);b.fire(this.el,"flotr:mousemove",[a,c,this]),this.lastMousePos=c},mouseDownHandler:function(a){if(this.mouseUpHandler)return;this.mouseUpHandler=c.bind(function(a){b.stopObserving(document,"mouseup",this.mouseUpHandler),b.stopObserving(document,"mousemove",this.mouseDownMoveHandler),this.mouseDownMoveHandler=null,this.mouseUpHandler=null,b.fire(this.el,"flotr:mouseup",[a,this])},this),this.mouseDownMoveHandler=c.bind(function(c){var d=this.getEventPosition(c);b.fire(this.el,"flotr:mousemove",[a,d,this]),this.lastMousePos=d},this),b.observe(document,"mouseup",this.mouseUpHandler),b.observe(document,"mousemove",this.mouseDownMoveHandler),b.fire(this.el,"flotr:mousedown",[a,this]),this.ignoreClick=!1},drawTooltip:function(b,c,d,e){var f=this.getMouseTrack(),g="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;",h=e.position,i=e.margin,j=this.plotOffset;c!==null&&d!==null?(e.relative?(h.charAt(0)=="n"?g+="bottom:"+(i-j.top-d+this.canvasHeight)+"px;top:auto;":h.charAt(0)=="s"&&(g+="top:"+(i+j.top+d)+"px;bottom:auto;"),h.charAt(1)=="e"?g+="left:"+(i+j.left+c)+"px;right:auto;":h.charAt(1)=="w"&&(g+="right:"+(i-j.left-c+this.canvasWidth)+"px;left:auto;")):(h.charAt(0)=="n"?g+="top:"+(i+j.top)+"px;bottom:auto;":h.charAt(0)=="s"&&(g+="bottom:"+(i+j.bottom)+"px;top:auto;"),h.charAt(1)=="e"?g+="right:"+(i+j.right)+"px;left:auto;":h.charAt(1)=="w"&&(g+="left:"+(i+j.left)+"px;right:auto;")),f.style.cssText=g,a.empty(f),a.insert(f,b),a.show(f)):a.hide(f)},clip:function(){var a=this.ctx,b=this.plotOffset,c=this.canvasWidth,e=this.canvasHeight;d.isIE&&d.isIE<9?(a.save(),a.fillStyle=this.processColor(this.options.ieBackgroundColor),a.fillRect(0,0,c,b.top),a.fillRect(0,0,b.left,e),a.fillRect(0,e-b.bottom,c,b.bottom),a.fillRect(c-b.right,0,b.right,e),a.restore()):(a.clearRect(0,0,c,b.top),a.clearRect(0,0,b.left,e),a.clearRect(0,e-b.bottom,c,b.bottom),a.clearRect(c-b.right,0,b.right,e))},_initMembers:function(){this._handles=[],this.lastMousePos={pageX:null,pageY:null},this.plotOffset={left:0,right:0,top:0,bottom:0},this.ignoreClick=!0,this.prevHit=null},_initGraphTypes:function(){c.each(d.graphTypes,function(a,b){this[b]=d.clone(a)},this)},_initEvents:function(){var a=this.el,d,e,f;"ontouchstart"in a?(d=c.bind(function(c){f=!0,b.stopObserving(document,"touchend",d),b.fire(a,"flotr:mouseup",[event,this]),this.multitouches=null,e||this.clickHandler(c)},this),this.observe(this.overlay,"touchstart",c.bind(function(c){e=!1,f=!1,this.ignoreClick=!1,c.touches&&c.touches.length>1&&(this.multitouches=c.touches),b.fire(a,"flotr:mousedown",[event,this]),this.observe(document,"touchend",d)},this)),this.observe(this.overlay,"touchmove",c.bind(function(c){var d=this.getEventPosition(c);c.preventDefault(),e=!0,this.multitouches||c.touches&&c.touches.length>1?this.multitouches=c.touches:f||b.fire(a,"flotr:mousemove",[event,d,this]),this.lastMousePos=d},this))):this.observe(this.overlay,"mousedown",c.bind(this.mouseDownHandler,this)).observe(a,"mousemove",c.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",c.bind(this.clickHandler,this)).observe(a,"mouseout",function(){b.fire(a,"flotr:mouseout")})},_initCanvas:function(){function k(e,f){return e||(e=a.create("canvas"),typeof FlashCanvas!="undefined"&&typeof e.getContext=="function"&&FlashCanvas.initElement(e),e.className="flotr-"+f,e.style.cssText="position:absolute;left:0px;top:0px;",a.insert(b,e)),c.each(i,function(b,c){a.show(e);if(f=="canvas"&&e.getAttribute(c)===b)return;e.setAttribute(c,b*d.resolution),e.style[c]=b+"px"}),e.context_=null,e}function l(a){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(a);var b=a.getContext("2d");return window.G_vmlCanvasManager||b.scale(d.resolution,d.resolution),b}var b=this.el,d=this.options,e=b.children,f=[],g,h,i,j;for(h=e.length;h--;)g=e[h],!this.canvas&&g.className==="flotr-canvas"?this.canvas=g:!this.overlay&&g.className==="flotr-overlay"?this.overlay=g:f.push(g);for(h=f.length;h--;)b.removeChild(f[h]);a.setStyles(b,{position:"relative"}),i={},i.width=b.clientWidth,i.height=b.clientHeight;if(i.width<=0||i.height<=0||d.resolution<=0)throw"Invalid dimensions for plot, width = "+i.width+", height = "+i.height+", resolution = "+d.resolution;this.canvas=k(this.canvas,"canvas"),this.overlay=k(this.overlay,"overlay"),this.ctx=l(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=l(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=i.height,this.canvasWidth=i.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){c.each(d.plugins,function(a,b){c.each(a.callbacks,function(a,b){this.observe(this.el,b,c.bind(a,this))},this),this[b]=d.clone(a),c.each(this[b],function(a,d){c.isFunction(a)&&(this[b][d]=c.bind(a,this))},this)},this)},_initOptions:function(a){var e=d.clone(d.defaultOptions);e.x2axis=c.extend(c.clone(e.xaxis),e.x2axis),e.y2axis=c.extend(c.clone(e.yaxis),e.y2axis),this.options=d.merge(a||{},e),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),b.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=d.Axis.getAxes(this.options);var f=[],g=[],h=this.series.length,i=this.series.length,j=this.options.colors,k=[],l=0,m,n,o,p;for(n=i-1;n>-1;--n)m=this.series[n].color,m&&(--i,c.isNumber(m)?f.push(m):k.push(d.Color.parse(m)));for(n=f.length-1;n>-1;--n)i=Math.max(i,f[n]+1);for(n=0;g.length<i;){m=j.length==n?new d.Color(100,100,100):d.Color.parse(j[n]);var q=l%2==1?-1:1,r=1+q*Math.ceil(l/2)*.2;m.scale(r,r,r),g.push(m),++n>=j.length&&(n=0,++l)}for(n=0,o=0;n<h;++n){p=this.series[n],p.color?c.isNumber(p.color)&&(p.color=g[p.color].toString()):p.color=g[o++].toString(),p.xaxis||(p.xaxis=this.axes.x),p.xaxis==1?p.xaxis=this.axes.x:p.xaxis==2&&(p.xaxis=this.axes.x2),p.yaxis||(p.yaxis=this.axes.y),p.yaxis==1?p.yaxis=this.axes.y:p.yaxis==2&&(p.yaxis=this.axes.y2);for(var s in d.graphTypes)p[s]=c.extend(c.clone(this.options[s]),p[s]);p.mouse=c.extend(c.clone(this.options.mouse),p.mouse),c.isUndefined(p.shadowSize)&&(p.shadowSize=this.options.shadowSize)}},_setEl:function(a){if(!a)throw"The target container doesn't exist";if(a.graph instanceof Graph)a.graph.destroy();else if(!a.clientWidth)throw"The target container must be visible";a.graph=this,this.el=a}},Flotr.Graph=Graph}(),function(){function c(b){this.orientation=1,this.offset=0,this.datamin=Number.MAX_VALUE,this.datamax=-Number.MAX_VALUE,a.extend(this,b),this._setTranslations()}function d(a){return this.offset+this.orientation*(a-this.min)*this.scale}function e(a){return(this.offset+this.orientation*a)/this.scale+this.min}function f(a){return this.offset+this.orientation*(h(a,this.options.base)-h(this.min,this.options.base))*this.scale}function g(a){return j((this.offset+this.orientation*a)/this.scale+h(this.min,this.options.base),this.options.base)}function h(a,b){return a=Math.log(Math.max(a,Number.MIN_VALUE)),b!==Math.E&&(a/=Math.log(b)),a}function j(a,b){return b===Math.E?Math.exp(a):Math.pow(b,a)}var a=Flotr._,b="logarithmic";c.prototype={setScale:function(){var a=this.length;this.options.scaling==b?this.scale=a/(h(this.max,this.options.base)-h(this.min,this.options.base)):this.scale=a/(this.max-this.min)},calculateTicks:function(){var a=this.options;this.ticks=[],this.minorTicks=[],a.ticks?(this._cleanUserTicks(a.ticks,this.ticks),this._cleanUserTicks(a.minorTicks||[],this.minorTicks)):a.mode=="time"?this._calculateTimeTicks():a.scaling==="logarithmic"?this._calculateLogTicks():this._calculateTicks()},calculateRange:function(){if(!this.used)return;var a=this,b=a.options,c=b.min!==null?b.min:a.datamin,d=b.max!==null?b.max:a.datamax,e=b.autoscaleMargin;b.scaling=="logarithmic"&&(c<=0&&(c=a.datamin),d<=0&&(d=c));if(d==c){var f=d?.01:1;b.min===null&&(c-=f),b.max===null&&(d+=f)}if(b.scaling==="logarithmic"){c<0&&(c=d/b.base);var g=Math.log(d);b.base!=Math.E&&(g/=Math.log(b.base)),g=Math.ceil(g);var h=Math.log(c);b.base!=Math.E&&(h/=Math.log(b.base)),h=Math.ceil(h),a.tickSize=Flotr.getTickSize(b.noTicks,h,g,b.tickDecimals===null?0:b.tickDecimals),b.minorTickFreq===null&&(g-h>10?b.minorTickFreq=0:g-h>5?b.minorTickFreq=2:b.minorTickFreq=5)}else a.tickSize=Flotr.getTickSize(b.noTicks,c,d,b.tickDecimals);a.min=c,a.max=d,b.min===null&&b.autoscale&&(a.min-=a.tickSize*e,a.min<0&&a.datamin>=0&&(a.min=0),a.min=a.tickSize*Math.floor(a.min/a.tickSize)),b.max===null&&b.autoscale&&(a.max+=a.tickSize*e,a.max>0&&a.datamax<=0&&a.datamax!=a.datamin&&(a.max=0),a.max=a.tickSize*Math.ceil(a.max/a.tickSize)),a.min==a.max&&(a.max=a.min+1)},calculateTextDimensions:function(a,b){var c="",d,e;if(this.options.showLabels)for(e=0;e<this.ticks.length;++e)d=this.ticks[e].label.length,d>c.length&&(c=this.ticks[e].label);this.maxLabel=a.dimensions(c,{size:b.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=a.dimensions(this.options.title,{size:b.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(b,c){var d=this,e=this.options,f,g,h,i;a.isFunction(b)&&(b=b({min:d.min,max:d.max}));for(g=0;g<b.length;++g)i=b[g],typeof i=="object"?(f=i[0],h=i.length>1?i[1]:e.tickFormatter(f,{min:d.min,max:d.max})):(f=i,h=e.tickFormatter(f,{min:this.min,max:this.max})),c[g]={v:f,label:h}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var a=this,b=a.options,c,d,e=Math.log(a.max);b.base!=Math.E&&(e/=Math.log(b.base)),e=Math.ceil(e);var f=Math.log(a.min);b.base!=Math.E&&(f/=Math.log(b.base)),f=Math.ceil(f);for(i=f;i<e;i+=a.tickSize){d=b.base==Math.E?Math.exp(i):Math.pow(b.base,i);var g=d*(b.base==Math.E?Math.exp(a.tickSize):Math.pow(b.base,a.tickSize)),h=(g-d)/b.minorTickFreq;a.ticks.push({v:d,label:b.tickFormatter(d,{min:a.min,max:a.max})});for(c=d+h;c<g;c+=h)a.minorTicks.push({v:c,label:b.tickFormatter(c,{min:a.min,max:a.max})})}d=b.base==Math.E?Math.exp(i):Math.pow(b.base,i),a.ticks.push({v:d,label:b.tickFormatter(d,{min:a.min,max:a.max})})},_calculateTicks:function(){var a=this,b=a.options,c=a.tickSize,d=a.min,e=a.max,f=c*Math.ceil(d/c),g,h,i,j,k,l;b.minorTickFreq&&(h=c/b.minorTickFreq);for(k=0;(i=j=f+k*c)<=e;++k){g=b.tickDecimals,g===null&&(g=1-Math.floor(Math.log(c)/Math.LN10)),g<0&&(g=0),i=i.toFixed(g),a.ticks.push({v:i,label:b.tickFormatter(i,{min:a.min,max:a.max})});if(b.minorTickFreq)for(l=0;l<b.minorTickFreq&&k*c+l*h<e;++l)i=j+l*h,a.minorTicks.push({v:i,label:b.tickFormatter(i,{min:a.min,max:a.max})})}},_setTranslations:function(a){this.d2p=a?f:d,this.p2d=a?g:e}},a.extend(c,{getAxes:function(a){return{x:new c({options:a.xaxis,n:1,length:this.plotWidth}),x2:new c({options:a.x2axis,n:2,length:this.plotWidth}),y:new c({options:a.yaxis,n:1,length:this.plotHeight,offset:this.plotHeight,orientation:-1}),y2:new c({options:a.y2axis,n:2,length:this.plotHeight,offset:this.plotHeight,orientation:-1})}}}),Flotr.Axis=c}(),function(){function b(b){a.extend(this,b)}var a=Flotr._;b.prototype={getRange:function(){var a=this.data,b=a.length,c=Number.MAX_VALUE,d=Number.MAX_VALUE,e=-Number.MAX_VALUE,f=-Number.MAX_VALUE,g=!1,h=!1,i,j,k;if(b<0||this.hide)return!1;for(k=0;k<b;k++)i=a[k][0],j=a[k][1],i<c&&(c=i,g=!0),i>e&&(e=i,g=!0),j<d&&(d=j,h=!0),j>f&&(f=j,h=!0);return{xmin:c,xmax:e,ymin:d,ymax:f,xused:g,yused:h}}},a.extend(b,{getSeries:function(c){return a.map(c,function(c){var d;return c.data?(d=new b,a.extend(d,c)):d=new b({data:c}),d})}}),Flotr.Series=b}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize,e;b.save(),b.lineJoin="round",d&&(b.lineWidth=d/2,e=c/2+b.lineWidth/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,e+d/2,!1),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,e,!1)),b.lineWidth=c,b.strokeStyle=a.color,this.plot(a,0,!0),b.restore()},plot:function(a,b,c){var d=a.context,e=a.width,f=a.height,g=a.xScale,h=a.yScale,i=a.data,j=a.stacked?this.stack:!1,k=i.length-1,l=null,m=null,n=h(0),o,p,q,r,s,t,u;if(k<1)return;d.beginPath();for(u=0;u<k;++u){if(i[u][1]===null||i[u+1][1]===null)continue;o=g(i[u][0]),p=g(i[u+1][0]),j?(s=j.values[i[u][0]]||0,t=j.values[i[u+1][0]]||j.values[i[u][0]]||0,q=h(i[u][1]+s),r=h(i[u+1][1]+t),c&&(j.values[i[u][0]]=i[u][1]+s,u==k-1&&(j.values[i[u+1][0]]=i[u+1][1]+t))):(q=h(i[u][1]),r=h(i[u+1][1]));if(q>f&&r>f||q<0&&r<0||o<0&&p<0||o>e&&p>e)continue;(l!=o||m!=q+b)&&d.moveTo(o,q+b),l=p,m=r+b,a.steps?(d.lineTo(l+b/2,q+b),d.lineTo(l+b/2,m)):d.lineTo(l,m)}(!a.fill||a.fill&&!a.fillBorder)&&d.stroke(),!b&&a.fill&&(o=g(i[0][0]),d.fillStyle=a.fillStyle,d.lineTo(p,n),d.lineTo(o,n),d.lineTo(o,h(i[0][1])),d.fill(),a.fillBorder&&d.stroke()),d.closePath()},extendYRange:function(a,b,c,d){var e=a.options;if(c.stacked&&(!e.max&&e.max!==0||!e.min&&e.min!==0)){var f=a.max,g=a.min,h=d.positiveSums||{},i=d.negativeSums||{},j,k;for(k=0;k<b.length;k++)j=b[k][0]+"",b[k][1]>0?(h[j]=(h[j]||0)+b[k][1],f=Math.max(f,h[j])):(i[j]=(i[j]||0)+b[k][1],g=Math.min(g,i[j]));d.negativeSums=i,d.positiveSums=h,a.max=f,a.min=g}c.steps&&(this.hit=function(a){var b=a.data,c=a.args,d=a.yScale,e=c[0],f=b.length,g=c[1],h=e.x,i=e.relY,j;for(j=0;j<f-1;j++)if(h>=b[j][0]&&h<=b[j+1][0]){Math.abs(d(b[j][1])-i)<8&&(g.x=b[j][0],g.y=b[j][1],g.index=j,g.seriesIndex=a.index);break}},this.drawHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=c.index,g=e(c.x),h=a.yScale(c.y),i;d.length-1>f&&(i=a.xScale(d[f+1][0]),b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,b.beginPath(),b.moveTo(g,h),b.lineTo(i,h),b.stroke(),b.closePath(),b.restore())},this.clearHit=function(a){var b=a.context,c=a.args,d=a.data,e=a.xScale,f=a.lineWidth,g=c.index,h=e(c.x),i=a.yScale(c.y),j;d.length-1>g&&(j=a.xScale(d[g+1][0]),b.clearRect(h-f,i-f,j-h+2*f,2*f))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,a.fill&&(b.fillStyle=a.fillStyle),this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.shadowSize,e,f,g,h,i,j;if(b.length<1)return;this.translate(c,a.horizontal);for(e=0;e<b.length;e++){f=this.getBarGeometry(b[e][0],b[e][1],a);if(f===null)continue;g=f.left,h=f.top,i=f.width,j=f.height,a.fill&&c.fillRect(g,h,i,j),d&&(c.save(),c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(g+d,h+d,i,j),c.restore()),a.lineWidth&&c.strokeRect(g,h,i,j)}},translate:function(a,b){b&&(a.rotate(-Math.PI/2),a.scale(-1,1))},getBarGeometry:function(a,b,c){var d=c.horizontal,e=c.barWidth,f=c.centered,g=c.stacked?this.stack:!1,h=c.lineWidth,i=f?e/2:0,j=d?c.yScale:c.xScale,k=d?c.xScale:c.yScale,l=d?b:a,m=d?a:b,n=0,o,p,q,r,s;return g&&(o=m>0?g.positive:g.negative,n=o[l]||n,o[l]=n+m),p=j(l-i),q=j(l+e-i),r=k(m+n),s=k(n),s<0&&(s=0),a===null||b===null?null:{x:l,y:m,xScale:j,yScale:k,top:r,left:Math.min(p,q)-h/2,width:Math.abs(q-p)-h,height:s-r}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h=this.getBarGeometry(f,g,a),i=h.width/2,j=h.left,k,l;for(l=b.length;l--;)k=this.getBarGeometry(b[l][0],b[l][1],a),k.y>h.y&&Math.abs(j-k.left)<i&&(e.x=b[l][0],e.y=b[l][1],e.index=l,e.seriesIndex=a.index)},drawHit:function(a){var b=a.context,c=a.args,d=this.getBarGeometry(c.x,c.y,a),e=d.left,f=d.top,g=d.width,h=d.height;b.save(),b.strokeStyle=a.color,b.lineWidth=a.lineWidth,this.translate(b,a.horizontal),b.beginPath(),b.moveTo(e,f+h),b.lineTo(e,f),b.lineTo(e+g,f),b.lineTo(e+g,f+h),a.fill&&(b.fillStyle=a.fillStyle,b.fill()),b.stroke(),b.closePath(),b.restore()},clearHit:function(a){var b=a.context,c=a.args,d=this.getBarGeometry(c.x,c.y,a),e=d.left,f=d.width,g=d.top,h=d.height,i=2*a.lineWidth;b.save(),this.translate(b,a.horizontal),b.clearRect(e-i,Math.min(g,g+h)-i,f+2*i,Math.abs(h)+2*i),b.restore()},extendXRange:function(a,b,c,d){this._extendRange(a,b,c,d)},extendYRange:function(a,b,c,d){this._extendRange(a,b,c,d)},_extendRange:function(a,b,c,d){var e=a.options.max;if(_.isNumber(e)||_.isString(e))return;var f=a.min,g=a.max,h=c.horizontal,i=a.orientation,j=this.positiveSums||{},k=this.negativeSums||{},l,m,n,o;(i==1&&!h||i==-1&&h)&&c.centered&&(g=Math.max(a.datamax+c.barWidth,g),f=Math.min(a.datamin-c.barWidth,f));if(c.stacked&&(i==1&&h||i==-1&&!h))for(o=b.length;o--;)l=b[o][i==1?1:0]+"",m=b[o][i==1?0:1],m>0?(j[l]=(j[l]||0)+m,g=Math.max(g,j[l])):(k[l]=(k[l]||0)+m,f=Math.min(f,k[l]));(i==1&&h||i==-1&&!h)&&c.topPadding&&(a.max===a.datamax||c.stacked&&this.stackMax!==g)&&(g+=c.topPadding*(g-f)),this.stackMin=f,this.stackMax=g,this.negativeSums=k,this.positiveSums=j,a.max=g,a.min=f}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e,f,g,h,i;b=b||0;for(f=0;f<c.length;++f)e=this.getGeometry(c[f],a),d.beginPath(),d.arc(e.x+b,e.y+b,e.z,0,2*Math.PI,!0),d.stroke(),a.fill&&d.fill(),d.closePath()},getGeometry:function(a,b){return{x:b.xScale(a[0]),y:b.yScale(a[1]),z:a[2]*b.baseRadius}},hit:function(a){var b=a.data,c=a.args,d=c[0],e=c[1],f=d.x,g=d.y,h,j,k;for(i=b.length;i--;)h=this.getGeometry(b[i],a),j=h.x-a.xScale(f),k=h.y-a.yScale(g),Math.sqrt(j*j+k*k)<h.z&&(e.x=b[i][0],e.y=b[i][1],e.index=i,e.seriesIndex=a.index)},drawHit:function(a){var b=a.context,c=this.getGeometry(a.data[a.args.index],a);b.save(),b.lineWidth=a.lineWidth,b.fillStyle=a.fillStyle,b.strokeStyle=a.color,b.beginPath(),b.arc(c.x,c.y,c.z,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath(),b.restore()},clearHit:function(a){var b=a.context,c=this.getGeometry(a.data[a.args.index],a),d=c.z+a.lineWidth;b.save(),b.clearRect(c.x-d,c.y-d,2*d,2*d),b.restore()}}),Flotr.addType("candles",{options:{show:!1,lineWidth:1,wickLineWidth:1,candleWidth:.6,fill:!0,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:.5,barcharts:!1},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineCap="butt",b.lineWidth=a.wickLineWidth||a.lineWidth,this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.xScale,e=a.yScale,f=a.candleWidth/2,g=a.shadowSize,h=a.lineWidth,i=a.wickLineWidth,j=i%2/2,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y;if(b.length<1)return;for(y=0;y<b.length;y++){l=b[y],m=l[0],o=l[1],p=l[2],q=l[3],r=l[4],s=d(m-f),t=d(m+f),u=e(q),v=e(p),w=e(Math.min(o,r)),x=e(Math.max(o,r)),k=a[o>r?"downFillColor":"upFillColor"],a.fill&&!a.barcharts&&(c.fillStyle="rgba(0,0,0,0.05)",c.fillRect(s+g,x+g,t-s,w-x),c.save(),c.globalAlpha=a.fillOpacity,c.fillStyle=k,c.fillRect(s,x+h,t-s,w-x),c.restore());if(h||i)m=Math.floor((s+t)/2)+j,c.strokeStyle=k,c.beginPath(),a.barcharts?(c.moveTo(m,Math.floor(v+f)),c.lineTo(m,Math.floor(u+f)),n=Math.floor(o+f)+.5,c.moveTo(Math.floor(s)+j,n),c.lineTo(m,n),n=Math.floor(r+f)+.5,c.moveTo(Math.floor(t)+j,n),c.lineTo(m,n)):(c.strokeRect(s,x+h,t-s,w-x),c.moveTo(m,Math.floor(x+h)),c.lineTo(m,Math.floor(v+h)),c.moveTo(m,Math.floor(w+h)),c.lineTo(m,Math.floor(u+h))),c.closePath(),c.stroke()}},extendXRange:function(a,b,c){a.options.max===null&&(a.max=Math.max(a.datamax+.5,a.max),a.min=Math.min(a.datamin-.5,a.min))}}),Flotr.addType("gantt",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=this.ctx,c=a.gantt.barWidth,d=Math.min(a.gantt.lineWidth,c);b.save(),b.translate(this.plotOffset.left,this.plotOffset.top),b.lineJoin="miter",b.lineWidth=d,b.strokeStyle=a.color,b.save(),this.gantt.plotShadows(a,c,0,a.gantt.fill),b.restore();if(a.gantt.fill){var e=a.gantt.fillColor||a.color;b.fillStyle=this.processColor(e,{opacity:a.gantt.fillOpacity})}this.gantt.plot(a,c,0,a.gantt.fill),b.restore()},plot:function(a,b,c,d){var e=a.data;if(e.length<1)return;var f=a.xaxis,g=a.yaxis,h=this.ctx,i;for(i=0;i<e.length;i++){var j=e[i][0],k=e[i][1],l=e[i][2],m=!0,n=!0,o=!0;if(k===null||l===null)continue;var p=k,q=k+l,r=j-(a.gantt.centered?b/2:0),s=j+b-(a.gantt.centered?b/2:0);if(q<f.min||p>f.max||s<g.min||r>g.max)continue;p<f.min&&(p=f.min,m=!1),q>f.max&&(q=f.max,f.lastSerie!=a&&(n=!1)),r<g.min&&(r=g.min),s>g.max&&(s=g.max,g.lastSerie!=a&&(n=!1)),d&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h.lineTo(f.d2p(p),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(s)+c),h.lineTo(f.d2p(q),g.d2p(r)+c),h.fill(),h.closePath()),a.gantt.lineWidth&&(m||o||n)&&(h.beginPath(),h.moveTo(f.d2p(p),g.d2p(r)+c),h[m?"lineTo":"moveTo"](f.d2p(p),g.d2p(s)+c),h[n?"lineTo":"moveTo"](f.d2p(q),g.d2p(s)+c),h[o?"lineTo":"moveTo"](f.d2p(q),g.d2p(r)+c),h.stroke(),h.closePath())}},plotShadows:function(a,b,c){var d=a.data;if(d.length<1)return;var e,f,g,h,i=a.xaxis,j=a.yaxis,k=this.ctx,l=this.options.shadowSize;for(e=0;e<d.length;e++){f=d[e][0],g=d[e][1],h=d[e][2];if(g===null||h===null)continue;var m=g,n=g+h,o=f-(a.gantt.centered?b/2:0),p=f+b-(a.gantt.centered?b/2:0);if(n<i.min||m>i.max||p<j.min||o>j.max)continue;m<i.min&&(m=i.min),n>i.max&&(n=i.max),o<j.min&&(o=j.min),p>j.max&&(p=j.max);var q=i.d2p(n)-i.d2p(m)-(i.d2p(n)+l<=this.plotWidth?0:l),r=j.d2p(o)-j.d2p(p)-(j.d2p(o)+l<=this.plotHeight?0:l);k.fillStyle="rgba(0,0,0,0.05)",k.fillRect(Math.min(i.d2p(m)+l,this.plotWidth),Math.min(j.d2p(p)+l,this.plotHeight),q,r)}},extendXRange:function(a){if(a.options.max===null){var b=a.min,c=a.max,d,e,f,g,h,i={},j={},k=null;for(d=0;d<this.series.length;++d){g=this.series[d],h=g.gantt;if(h.show&&g.xaxis==a){for(e=0;e<g.data.length;e++)h.show&&(y=g.data[e][0]+"",i[y]=Math.max(i[y]||0,g.data[e][1]+g.data[e][2]),k=g);for(e in i)c=Math.max(i[e],c)}}a.lastSerie=k,a.max=c,a.min=b}},extendYRange:function(a){if(a.options.max===null){var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d,e,f,g,h={},i={},j=null;for(d=0;d<this.series.length;++d){f=this.series[d],g=f.gantt;if(g.show&&!f.hide&&f.yaxis==a){var k=Number.MIN_VALUE,l=Number.MAX_VALUE;for(e=0;e<f.data.length;e++)k=Math.max(k,f.data[e][0]),l=Math.min(l,f.data[e][0]);g.centered?(b=Math.max(k+.5,b),c=Math.min(l-.5,c)):(b=Math.max(k+1,b),c=Math.min(l,c)),g.barWidth+k>b&&(b=a.max+g.barWidth)}}a.lastSerie=j,a.max=b,a.min=c,a.tickSize=Flotr.getTickSize(a.options.noTicks,c,b,a.options.tickDecimals)}}}),function(){function a(a){return typeof a=="object"&&a.constructor&&(Image?!0:a.constructor===Image)}Flotr.defaultMarkerFormatter=function(a){return Math.round(a.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(a){function m(a,b){return g=d.negative[a]||0,f=d.positive[a]||0,b>0?(d.positive[a]=g+b,g+b):(d.negative[a]=f+b,f+b)}var b=a.data,c=a.context,d=a.stacked?a.stack:!1,e=a.stackingType,f,g,h,i,j,k,l;c.save(),c.lineJoin="round",c.lineWidth=a.lineWidth,c.strokeStyle="rgba(0,0,0,0.5)",c.fillStyle=a.fillStyle;for(i=0;i<b.length;++i)j=b[i][0],k=b[i][1],d&&(e=="b"?a.horizontal?k=m(k,j):j=m(j,k):e=="a"&&(h=d.values[j]||0,d.values[j]=h+k,k=h+k)),l=a.labelFormatter({x:j,y:k,index:i,data:b}),this.plot(a.xScale(j),a.yScale(k),l,a);c.restore()},plot:function(b,c,d,e){var f=e.context;if(a(d)&&!d.complete)throw"Marker image not loaded.";this._plot(b,c,d,e)},_plot:function(b,c,d,e){var f=e.context,g=2,h=b,i=c,j;a(d)?j={height:d.height,width:d.width}:j=e.text.canvas(d),j.width=Math.floor(j.width+g*2),j.height=Math.floor(j.height+g*2),e.position.indexOf("c")!=-1?h-=j.width/2+g:e.position.indexOf("l")!=-1&&(h-=j.width),e.position.indexOf("m")!=-1?i-=j.height/2+g:e.position.indexOf("t")!=-1?i-=j.height+e.verticalMargin:i+=e.verticalMargin,h=Math.floor(h)+.5,i=Math.floor(i)+.5,e.fill&&f.fillRect(h,i,j.width,j.height),e.stroke&&f.strokeRect(h,i,j.width,j.height),a(d)?f.drawImage(d,h+g,i+g):Flotr.drawText(f,d,h+g,i+g,{textBaseline:"top",textAlign:"left",size:e.fontSize,color:e.color})}})}(),function(){var a=Flotr._;Flotr.defaultPieLabelFormatter=function(a,b){return(100*b/a).toFixed(2)+"%"},Flotr.addType("pie",{options:{show:!1,lineWidth:1,fill:!0,fillColor:null,fillOpacity:.6,explode:6,sizeRatio:.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:!1,pie3DviewAngle:Math.PI/2*.8,pie3DspliceThickness:20},draw:function(a){var b=a.data,c=a.context,d=c.canvas,e=a.lineWidth,f=a.shadowSize,g=a.sizeRatio,h=a.height,i=a.width,j=a.explode,k=a.color,l=a.fill,m=a.fillStyle,n=Math.min(d.width,d.height)*g/2,o=b[0][1],p=[],q=1,r=Math.PI*2*o/this.total,s=this.startAngle||2*Math.PI*a.startAngle,t=s+r,u=s+r/2,v=a.labelFormatter(this.total,o),w=j+n+4,x=Math.cos(u)*w,y=Math.sin(u)*w,z=x<0?"right":"left",A=y>0?"top":"bottom",B,C,D,x,y;c.save(),c.translate(i/2,h/2),c.scale(1,q),C=Math.cos(u)*j,D=Math.sin(u)*j,f>0&&(this.plotSlice(C+f,D+f,n,s,t,c),l&&(c.fillStyle="rgba(0,0,0,0.1)",c.fill())),this.plotSlice(C,D,n,s,t,c),l&&(c.fillStyle=m,c.fill()),c.lineWidth=e,c.strokeStyle=k,c.stroke(),B={size:a.fontSize*1.2,color:a.fontColor,weight:1.5},v&&(a.htmlText||!a.textEnabled?(divStyle="position:absolute;"+A+":"+(h/2+(A==="top"?y:-y))+"px;",divStyle+=z+":"+(i/2+(z==="right"?-x:x))+"px;",p.push('<div style="',divStyle,'" class="flotr-grid-label">',v,"</div>")):(B.textAlign=z,B.textBaseline=A,Flotr.drawText(c,v,x,y,B)));if(a.htmlText||!a.textEnabled){var E=Flotr.DOM.node('<div style="color:'+a.fontColor+'" class="flotr-labels"></div>');Flotr.DOM.insert(E,p.join("")),Flotr.DOM.insert(a.element,E)}c.restore(),this.startAngle=t,this.slices=this.slices||[],this.slices.push({radius:Math.min(d.width,d.height)*g/2,x:C,y:D,explode:j,start:s,end:t})},plotSlice:function(a,b,c,d,e,f){f.beginPath(),f.moveTo(a,b),f.arc(a,b,c,d,e,!1),f.lineTo(a,b),f.closePath()},hit:function(a){var b=a.data[0],c=a.args,d=a.index,e=c[0],f=c[1],g=this.slices[d],h=e.relX-a.width/2,i=e.relY-a.height/2,j=Math.sqrt(h*h+i*i),k=Math.atan(i/h),l=Math.PI*2,m=g.explode||a.explode,n=g.start%l,o=g.end%l;h<0?k+=Math.PI:h>0&&i<0&&(k+=l),j<g.radius+m&&j>m&&(n>o&&(k<o||k>n)||k>n&&k<o)&&(f.x=b[0],f.y=b[1],f.sAngle=n,f.eAngle=o,f.index=0,f.seriesIndex=d,f.fraction=b[1]/this.total)},drawHit:function(a){var b=a.context,c=this.slices[a.args.seriesIndex];b.save(),b.translate(a.width/2,a.height/2),this.plotSlice(c.x,c.y,c.radius,c.start,c.end,b),b.stroke(),b.restore()},clearHit:function(a){var b=a.context,c=this.slices[a.args.seriesIndex],d=2*a.lineWidth,e=c.radius+d;b.save(),b.translate(a.width/2,a.height/2),b.clearRect(c.x-e,c.y-e,2*e+d,2*e+d),b.restore()},extendYRange:function(a,b){this.total=(this.total||0)+b[0][1]}})}(),Flotr.addType("points",{options:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#FFFFFF",fillOpacity:.4},draw:function(a){var b=a.context,c=a.lineWidth,d=a.shadowSize;b.save(),d>0&&(b.lineWidth=d/2,b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,d/2+b.lineWidth/2),b.strokeStyle="rgba(0,0,0,0.2)",this.plot(a,b.lineWidth/2)),b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillColor||a.color,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=a.xScale,f=a.yScale,g,h,i;for(g=c.length-1;g>-1;--g){i=c[g][1];if(i===null)continue;h=e(c[g][0]),i=f(i);if(h<0||h>a.width||i<0||i>a.height)continue;d.beginPath(),b?d.arc(h,i+b,a.radius,0,Math.PI,!1):(d.arc(h,i,a.radius,0,2*Math.PI,!0),a.fill&&d.fill()),d.stroke(),d.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9},draw:function(a){var b=a.context,c=a.shadowSize;b.save(),b.translate(a.width/2,a.height/2),b.lineWidth=a.lineWidth,b.fillStyle="rgba(0,0,0,0.05)",b.strokeStyle="rgba(0,0,0,0.05)",this.plot(a,c/2),b.strokeStyle="rgba(0,0,0,0.1)",this.plot(a,c/4),b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a,b){var c=a.data,d=a.context,e=Math.min(a.height,a.width)*a.radiusRatio/2,f=2*Math.PI/c.length,g=-Math.PI/2,h,i;b=b||0,d.beginPath();for(h=0;h<c.length;++h)i=c[h][1]/this.max,d[h===0?"moveTo":"lineTo"](Math.cos(h*f+g)*e*i+b,Math.sin(h*f+g)*e*i+b);d.closePath(),a.fill&&d.fill(),d.stroke()},extendYRange:function(a,b){this.max=Math.max(a.max,this.max||-Number.MAX_VALUE)}}),Flotr.addType("timeline",{options:{show:!1,lineWidth:1,barWidth:.2,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(a){var b=a.context;b.save(),b.lineJoin="miter",b.lineWidth=a.lineWidth,b.strokeStyle=a.color,b.fillStyle=a.fillStyle,this.plot(a),b.restore()},plot:function(a){var b=a.data,c=a.context,d=a.xScale,e=a.yScale,f=a.barWidth,g=a.lineWidth,h;Flotr._.each(b,function(a){var b=a[0],h=a[1],i=a[2],j=f,k=Math.ceil(d(b)),l=Math.ceil(d(b+i))-k,m=Math.round(e(h)),n=Math.round(e(h-j))-m,o=k-g/2,p=Math.round(m-n/2)-g/2;c.strokeRect(o,p,l,n),c.fillRect(o,p,l,n)})},extendRange:function(a){var b=a.data,c=a.xaxis,d=a.yaxis,e=a.timeline.barWidth;c.options.min===null&&(c.min=c.datamin-e/2);if(c.options.max===null){var f=c.max;Flotr._.each(b,function(a){f=Math.max(f,a[0]+a[2])},this),c.max=f+e/2}d.options.min===null&&(d.min=d.datamin-e),d.options.min===null&&(d.max=d.datamax+e)}}),function(){var a=Flotr.DOM;Flotr.addPlugin("crosshair",{options:{mode:null,color:"#FF0000",hideCursor:!0},callbacks:{"flotr:mousemove":function(a,b){this.options.crosshair.mode&&(this.crosshair.clearCrosshair(),this.crosshair.drawCrosshair(b))}},drawCrosshair:function(b){var c=this.octx,d=this.options.crosshair,e=this.plotOffset,f=e.left+b.relX+.5,g=e.top+b.relY+.5;if(b.relX<0||b.relY<0||b.relX>this.plotWidth||b.relY>this.plotHeight){this.el.style.cursor=null,a.removeClass(this.el,"flotr-crosshair");return}d.hideCursor&&(this.el.style.cursor="none",a.addClass(this.el,"flotr-crosshair")),c.save(),c.strokeStyle=d.color,c.lineWidth=1,c.beginPath(),d.mode.indexOf("x")!=-1&&(c.moveTo(f,e.top),c.lineTo(f,e.top+this.plotHeight)),d.mode.indexOf("y")!=-1&&(c.moveTo(e.left,g),c.lineTo(e.left+this.plotWidth,g)),c.stroke(),c.restore()},clearCrosshair:function(){var a=this.plotOffset,b=this.lastMousePos,c=this.octx;b&&(c.clearRect(b.relX+a.left,a.top,1,this.plotHeight+1),c.clearRect(a.left,b.relY+a.top,this.plotWidth+1,1))}})}(),function(){function c(a,b,c,d){var e="image/"+a,f=b.toDataURL(e),g=new Image;return g.src=f,g}var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("download",{saveImage:function(d,e,f,g){var h=null;if(Flotr.isIE&&Flotr.isIE<9)return h="<html><body>"+this.canvas.firstChild.innerHTML+"</body></html>",window.open().document.write(h);if(d!=="jpeg"&&d!=="png")return;h=c(d,this.canvas,e,f);if(b.isElement(h)&&g)this.download.restoreCanvas(),a.hide(this.canvas),a.hide(this.overlay),a.setStyles({position:"absolute"}),a.insert(this.el,h),this.saveImageElement=h;else return window.open(h.src)},restoreCanvas:function(){a.show(this.canvas),a.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var a=Flotr.EventAdapter,b=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function p(a){for(n=0;n<a.length;++n){var b=a[n].v/l.max;for(o=0;o<=u;++o)c[o===0?"moveTo":"lineTo"](Math.cos(o*v+w)*t*b,Math.sin(o*v+w)*t*b)}}function q(a,d){b.each(b.pluck(a,"v"),function(a){if(a<=l.min||a>=l.max||(a==l.min||a==l.max)&&e.outlineWidth)return;d(Math.floor(l.d2p(a))+c.lineWidth/2)})}function r(a){c.moveTo(a,0),c.lineTo(a,j)}function s(a){c.moveTo(0,a),c.lineTo(k,a)}var c=this.ctx,d=this.options,e=d.grid,f=e.verticalLines,g=e.horizontalLines,h=e.minorVerticalLines,i=e.minorHorizontalLines,j=this.plotHeight,k=this.plotWidth,l,m,n,o;(f||h||g||i)&&a.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,d,this]),c.save(),c.lineWidth=1,c.strokeStyle=e.tickColor;if(e.circular){c.translate(this.plotOffset.left+k/2,this.plotOffset.top+j/2);var t=Math.min(j,k)*d.radar.radiusRatio/2,u=this.axes.x.ticks.length,v=2*(Math.PI/u),w=-Math.PI/2;c.beginPath(),l=this.axes.y,g&&p(l.ticks),i&&p(l.minorTicks),f&&b.times(u,function(a){c.moveTo(0,0),c.lineTo(Math.cos(a*v+w)*t,Math.sin(a*v+w)*t)}),c.stroke()}else c.translate(this.plotOffset.left,this.plotOffset.top),e.backgroundColor&&(c.fillStyle=this.processColor(e.backgroundColor,{x1:0,y1:0,x2:k,y2:j}),c.fillRect(0,0,k,j)),c.beginPath(),l=this.axes.x,f&&q(l.ticks,r),h&&q(l.minorTicks,r),l=this.axes.y,g&&q(l.ticks,s),i&&q(l.minorTicks,s),c.stroke();c.restore(),(f||h||g||i)&&a.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,d,this])},drawOutline:function(){var a=this,b=a.options,c=b.grid,d=c.outline,e=a.ctx,f=c.backgroundImage,g=a.plotOffset,h=g.left,j=g.top,k=a.plotWidth,l=a.plotHeight,m,n,o,p,q,r;if(!c.outlineWidth)return;e.save();if(c.circular){e.translate(h+k/2,j+l/2);var s=Math.min(l,k)*b.radar.radiusRatio/2,t=this.axes.x.ticks.length,u=2*(Math.PI/t),v=-Math.PI/2;e.beginPath(),e.lineWidth=c.outlineWidth,e.strokeStyle=c.color,e.lineJoin="round";for(i=0;i<=t;++i)e[i===0?"moveTo":"lineTo"](Math.cos(i*u+v)*s,Math.sin(i*u+v)*s);e.stroke()}else{e.translate(h,j);var w=c.outlineWidth,x=.5-w+(w+1)%2/2,y="lineTo",z="moveTo";e.lineWidth=w,e.strokeStyle=c.color,e.lineJoin="miter",e.beginPath(),e.moveTo(x,x),k-=w/2%1,l+=w/2,e[d.indexOf("n")!==-1?y:z](k,x),e[d.indexOf("e")!==-1?y:z](k,l),e[d.indexOf("s")!==-1?y:z](x,l),e[d.indexOf("w")!==-1?y:z](x,x),e.stroke(),e.closePath()}e.restore(),f&&(o=f.src||f,p=(parseInt(f.left,10)||0)+g.left,q=(parseInt(f.top,10)||0)+g.top,n=new Image,n.onload=function(){e.save(),f.alpha&&(e.globalAlpha=f.alpha),e.globalCompositeOperation="destination-over",e.drawImage(n,0,0,n.width,n.height,p,q,k,l),e.restore()},n.src=o)}})}(),function(){var a=Flotr.DOM,b=Flotr._,c=Flotr,d="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(a,b){this.hit.track(b)},"flotr:click":function(a){this.hit.track(a)},"flotr:mouseout":function(){this.hit.clearHit()}},track:function(a){(this.options.mouse.track||b.any(this.series,function(a){return a.mouse&&a.mouse.track}))&&this.hit.hit(a)},executeOnType:function(a,d,e){function h(a,h){b.each(b.keys(c.graphTypes),function(b){a[b]&&a[b].show&&this[b][d]&&(g=this.getOptions(a,b),g.fill=!!a.mouse.fillColor,g.fillStyle=this.processColor(a.mouse.fillColor||"#ffffff",{opacity:a.mouse.fillOpacity}),g.color=a.mouse.lineColor,g.context=this.octx,g.index=h,e&&(g.args=e),this[b][d].call(this[b],g),f=!0)},this)}var f=!1,g;return b.isArray(a)||(a=[a]),b.each(a,h,this),f},drawHit:function(a){var b=this.octx,c=a.series;if(c.mouse.lineColor){b.save(),b.lineWidth=c.points?c.points.lineWidth:1,b.strokeStyle=c.mouse.lineColor,b.fillStyle=this.processColor(c.mouse.fillColor||"#ffffff",{opacity:c.mouse.fillOpacity}),b.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(c,"drawHit",a)){var d=a.xaxis,e=a.yaxis;b.beginPath(),b.arc(d.d2p(a.x),e.d2p(a.y),c.points.radius||c.mouse.radius,0,2*Math.PI,!0),b.fill(),b.stroke(),b.closePath()}b.restore()}this.prevHit=a},clearHit:function(){var b=this.prevHit,c=this.octx,d=this.plotOffset;c.save(),c.translate(d.left,d.top);if(b){if(!this.hit.executeOnType(b.series,"clearHit",this.prevHit)){var e=b.series,f=e.points?e.points.lineWidth:1;offset=(e.points.radius||e.mouse.radius)+f,c.clearRect(b.xaxis.d2p(b.x)-offset,b.yaxis.d2p(b.y)-offset,offset*2,offset*2)}a.hide(this.mouseTrack),this.prevHit=null}c.restore()},hit:function(a){var c=this.options,d=this.prevHit,e,f,g,h,i,j,k,l;if(this.series.length===0)return;n={relX:a.relX,relY:a.relY,absX:a.absX,absY:a.absY};if(c.mouse.trackY&&!c.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[a,n]))b.isUndefined(n.seriesIndex)||(i=this.series[n.seriesIndex],n.series=i,n.mouse=i.mouse,n.xaxis=i.xaxis,n.yaxis=i.yaxis);else{e=this.hit.closest(a);if(e){e=c.mouse.trackY?e.point:e.x,h=e.seriesIndex,i=this.series[h],k=i.xaxis,l=i.yaxis,f=2*i.mouse.sensibility;if(c.mouse.trackAll||e.distanceX<f/k.scale&&(!c.mouse.trackY||e.distanceY<f/l.scale))n.series=i,n.xaxis=i.xaxis,n.yaxis=i.yaxis,n.mouse=i.mouse,n.x=e.x,n.y=e.y,n.dist=e.distance,n.index=e.dataIndex,n.seriesIndex=h}}if(!d||d.index!==n.index||d.seriesIndex!==n.seriesIndex)this.hit.clearHit(),n.series&&n.mouse&&n.mouse.track&&(this.hit.drawMouseTrack(n),this.hit.drawHit(n),Flotr.EventAdapter.fire(this.el,"flotr:hit",[n,this]))},closest:function(a){function t(a){a.distance=m,a.distanceX=n,a.distanceY=o,a.seriesIndex=r,a.dataIndex=s,a.x=p,a.y=q}var b=this.series,c=this.options,d=a.x,e=a.y,f=Number.MAX_VALUE,g=Number.MAX_VALUE,h={},i={},j=!1,k,l,m,n,o,p,q,r,s;for(r=0;r<b.length;r++){k=b[r],l=k.data,l.length&&(j=!0);for(s=l.length;s--;){p=l[s][0],q=l[s][1];if(p===null||q===null)continue;if(p<k.xaxis.min||p>k.xaxis.max)continue;n=Math.abs(p-d),o=Math.abs(q-e),m=n*n+o*o,m<f&&(f=m,t(h)),n<g&&(g=n,t(i))}}return j?{point:h,x:i}:!1},drawMouseTrack:function(b){var c="",e=b.series,f=b.mouse.position,g=b.mouse.margin,h=d,i=this.mouseTrack,j=this.plotOffset,k=j.left,l=j.right,m=j.bottom,n=j.top,o=b.mouse.trackDecimals,p=this.options;i||(i=a.node('<div class="flotr-mouse-value"></div>'),this.mouseTrack=i,a.insert(this.el,i));if(!b.mouse.relative)f.charAt(0)=="n"?c+="top:"+(g+n)+"px;bottom:auto;":f.charAt(0)=="s"&&(c+="bottom:"+(g+m)+"px;top:auto;"),f.charAt(1)=="e"?c+="right:"+(g+l)+"px;left:auto;":f.charAt(1)=="w"&&(c+="left:"+(g+k)+"px;right:auto;");else if(e.bars.show)c+="bottom:"+(g-n-b.yaxis.d2p(b.y/2)+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+b.xaxis.d2p(b.x-p.bars.barWidth/2))+"px;right:auto;";else if(e.pie.show){var q={x:this.plotWidth/2,y:this.plotHeight/2},r=Math.min(this.canvasWidth,this.canvasHeight)*e.pie.sizeRatio/2,s=b.sAngle<b.eAngle?(b.sAngle+b.eAngle)/2:(b.sAngle+b.eAngle+2*Math.PI)/2;c+="bottom:"+(g-n-q.y-Math.sin(s)*r/2+this.canvasHeight)+"px;top:auto;",c+="left:"+(g+k+q.x+Math.cos(s)*r/2)+"px;right:auto;"}else f.charAt(0)=="n"?c+="bottom:"+(g-n-b.yaxis.d2p(b.y)+this.canvasHeight)+"px;top:auto;":f.charAt(0)=="s"&&(c+="top:"+(g+n+b.yaxis.d2p(b.y))+"px;bottom:auto;"),f.charAt(1)=="e"?c+="left:"+(g+k+b.xaxis.d2p(b.x))+"px;right:auto;":f.charAt(1)=="w"&&(c+="right:"+(g-k-b.xaxis.d2p(b.x)+this.canvasWidth)+"px;left:auto;");h+=c,i.style.cssText=h;if(!o||o<0)o=0;i.innerHTML=b.mouse.trackFormatter({x:b.x.toFixed(o),y:b.y.toFixed(o),series:b.series,index:b.index,nearest:b,fraction:b.fraction}),a.show(i)}})}(),function(){function a(a,b){return a.which?a.which===1:a.button===0||a.button===1}function b(a,b){return Math.min(Math.max(0,a),b.plotWidth-1)}function c(a,b){return Math.min(Math.max(0,a),b.plotHeight)}var d=Flotr.DOM,e=Flotr.EventAdapter,f=Flotr._;Flotr.addPlugin("selection",{options:{pinchOnly:null,mode:null,color:"#B6D9FF",fps:20},callbacks:{"flotr:mouseup":function(a){var b=this.options.selection,c=this.selection,d=this.getEventPosition(a);if(!b||!b.mode)return;c.interval&&clearInterval(c.interval),this.multitouches?c.updateSelection():b.pinchOnly||c.setSelectionPos(c.selection.second,d),c.clearSelection(),c.selecting&&c.selectionIsSane()&&(c.drawSelection(),c.fireSelectEvent(),this.ignoreClick=!0)},"flotr:mousedown":function(b){var c=this.options.selection,d=this.selection,e=this.getEventPosition(b);if(!c||!c.mode)return;if(!c.mode||!a(b)&&f.isUndefined(b.touches))return;c.pinchOnly||d.setSelectionPos(d.selection.first,e),d.interval&&clearInterval(d.interval),this.lastMousePos.pageX=null,d.selecting=!1,d.interval=setInterval(f.bind(d.updateSelection,this),1e3/c.fps)},"flotr:destroy":function(a){clearInterval(this.selection.interval)}},getArea:function(){var a=this.selection.selection,b=a.first,c=a.second;return{x1:Math.min(b.x,c.x),x2:Math.max(b.x,c.x),y1:Math.min(b.y,c.y),y2:Math.max(b.y,c.y)}},selection:{first:{x:-1,y:-1},second:{x:-1,y:-1}},prevSelection:null,interval:null,fireSelectEvent:function(a){var b=this.axes,c=this.selection.selection,d,f,g,h;a=a||"select",d=b.x.p2d(c.first.x),f=b.x.p2d(c.second.x),g=b.y.p2d(c.first.y),h=b.y.p2d(c.second.y),e.fire(this.el,"flotr:"+a,[{x1:Math.min(d,f),y1:Math.min(g,h),x2:Math.max(d,f),y2:Math.max(g,h),xfirst:d,xsecond:f,yfirst:g,ysecond:h},this])},setSelection:function(a,d){var e=this.options,f=this.axes.x,g=this.axes.y,h=g.scale,i=f.scale,j=e.selection.mode.indexOf("x")!=-1,k=e.selection.mode.indexOf("y")!=-1,l=this.selection.selection;this.selection.clearSelection(),l.first.y=c(j&&!k?0:(g.max-a.y1)*h,this),l.second.y=c(j&&!k?this.plotHeight-1:(g.max-a.y2)*h,this),l.first.x=b(k&&!j?0:a.x1,this),l.second.x=b(k&&!j?this.plotWidth:a.x2,this),this.selection.drawSelection(),d||this.selection.fireSelectEvent()},setSelectionPos:function(a,d){var e=this.options.selection.mode,f=this.selection.selection;e.indexOf("x")==-1?a.x=a==f.first?0:this.plotWidth:a.x=b(d.relX,this),e.indexOf("y")==-1?a.y=a==f.first?0:this.plotHeight-1:a.y=c(d.relY,this)},drawSelection:function(){this.selection.fireSelectEvent("selecting");var a=this.selection.selection,b=this.octx,c=this.options,d=this.plotOffset,e=this.selection.prevSelection;if(e&&a.first.x==e.first.x&&a.first.y==e.first.y&&a.second.x==e.second.x&&a.second.y==e.second.y)return;b.save(),b.strokeStyle=this.processColor(c.selection.color,{opacity:.8}),b.lineWidth=1,b.lineJoin="miter",b.fillStyle=this.processColor(c.selection.color,{opacity:.4}),this.selection.prevSelection={first:{x:a.first.x,y:a.first.y},second:{x:a.second.x,y:a.second.y}};var f=Math.min(a.first.x,a.second.x),g=Math.min(a.first.y,a.second.y),h=Math.abs(a.second.x-a.first.x),i=Math.abs(a.second.y-a.first.y);b.fillRect(f+d.left+.5,g+d.top+.5,h,i),b.strokeRect(f+d.left+.5,g+d.top+.5,h,i),b.restore()},updateSelection:function(){if(!this.lastMousePos.pageX)return;this.selection.selecting=!0;if(this.multitouches)this.selection.setSelectionPos(this.selection.selection.first,this.getEventPosition(this.multitouches[0])),this.selection.setSelectionPos(this.selection.selection.second,this.getEventPosition(this.multitouches[1]));else{if(this.options.selection.pinchOnly)return;this.selection.setSelectionPos(this.selection.selection.second,this.lastMousePos)}this.selection.clearSelection(),this.selection.selectionIsSane()&&this.selection.drawSelection()},clearSelection:function(){if(!this.selection.prevSelection)return;var a=this.selection.prevSelection,b=1,c=this.plotOffset,d=Math.min(a.first.x,a.second.x),e=Math.min(a.first.y,a.second.y),f=Math.abs(a.second.x-a.first.x),g=Math.abs(a.second.y-a.first.y);this.octx.clearRect(d+c.left-b+.5,e+c.top-b,f+2*b+.5,g+2*b+.5),this.selection.prevSelection=null},selectionIsSane:function(){var a=this.selection.selection;return Math.abs(a.second.x-a.first.x)>=5||Math.abs(a.second.y-a.first.y)>=5}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function s(a,b,d){var e=d?b.minorTicks:b.ticks,f=b.orientation===1,h=b.n===1,k,m;k={color:b.options.color||o.grid.color,angle:Flotr.toRad(b.options.labelsAngle),textBaseline:"middle"};for(l=0;l<e.length&&(d?b.options.showMinorLabels:b.options.showLabels);++l){c=e[l],c.label+="";if(!c.label||!c.label.length)continue;x=Math.cos(l*i+j)*g,y=Math.sin(l*i+j)*g,k.textAlign=f?Math.abs(x)<.1?"center":x<0?"right":"left":"left",Flotr.drawText(p,c.label,f?x:3,f?y:-(b.ticks[l].v/b.max)*(g-o.fontSize),k)}}function t(a,b,d,e){function j(a){return a.options.showLabels&&a.used}function k(a,b,c,d){return a.plotOffset.left+(b?d:c?-o.grid.labelMargin:o.grid.labelMargin+a.plotWidth)}function m(a,b,c,d){return a.plotOffset.top+(b?o.grid.labelMargin:d)+(b&&c?a.plotHeight:0)}var f=b.orientation===1,g=b.n===1,h,i;h={color:b.options.color||o.grid.color,textAlign:d,textBaseline:e,angle:Flotr.toRad(b.options.labelsAngle)},h=Flotr.getBestTextAlign(h.angle,h);for(l=0;l<b.ticks.length&&j(b);++l){c=b.ticks[l];if(!c.label||!c.label.length)continue;i=b.d2p(c.v);if(i<0||i>(f?a.plotWidth:a.plotHeight))continue;Flotr.drawText(p,c.label,k(a,f,g,i),m(a,f,g,i),h),!f&&!g&&(p.save(),p.strokeStyle=h.color,p.beginPath(),p.moveTo(a.plotOffset.left+a.plotWidth-8,a.plotOffset.top+b.d2p(c.v)),p.lineTo(a.plotOffset.left+a.plotWidth,a.plotOffset.top+b.d2p(c.v)),p.stroke(),p.restore())}}function u(a,b){var d=b.orientation===1,e=b.n===1,g="",h,i,j,k=a.plotOffset;!d&&!e&&(p.save(),p.strokeStyle=b.options.color||o.grid.color,p.beginPath());if(b.options.showLabels&&(e?!0:b.used))for(l=0;l<b.ticks.length;++l){c=b.ticks[l];if(!c.label||!c.label.length||(d?k.left:k.top)+b.d2p(c.v)<0||(d?k.left:k.top)+b.d2p(c.v)>(d?a.canvasWidth:a.canvasHeight))continue;j=k.top+(d?(e?1:-1)*(a.plotHeight+o.grid.labelMargin):b.d2p(c.v)-b.maxLabel.height/2),h=d?k.left+b.d2p(c.v)-f/2:0,g="",l===0?g=" first":l===b.ticks.length-1&&(g=" last"),g+=d?" flotr-grid-label-x":" flotr-grid-label-y",m+=['<div style="position:absolute; text-align:'+(d?"center":"right")+"; ","top:"+j+"px; ",(!d&&!e?"right:":"left:")+h+"px; ","width:"+(d?f:(e?k.left:k.right)-o.grid.labelMargin)+"px; ",b.options.color?"color:"+b.options.color+"; ":" ",'" class="flotr-grid-label'+g+'">'+c.label+"</div>"].join(" "),!d&&!e&&(p.moveTo(k.left+a.plotWidth-8,k.top+b.d2p(c.v)),p.lineTo(k.left+a.plotWidth,k.top+b.d2p(c.v)))}}var b,c,d,e,f,g,h,i,j,k,l,m="",n=0,o=this.options,p=this.ctx,q=this.axes,r={size:o.fontSize};for(l=0;l<q.x.ticks.length;++l)q.x.ticks[l].label&&++n;f=this.plotWidth/n,o.grid.circular&&(p.save(),p.translate(this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+this.plotHeight/2),g=this.plotHeight*o.radar.radiusRatio/2+o.fontSize,h=this.axes.x.ticks.length,i=2*(Math.PI/h),j=-Math.PI/2,s(this,q.x,!1),s(this,q.x,!0),s(this,q.y,!1),s(this,q.y,!0),p.restore()),!o.HtmlText&&this.textEnabled?(t(this,q.x,"center","top"),t(this,q.x2,"center","bottom"),t(this,q.y,"right","middle"),t(this,q.y2,"left","middle")):(q.x.options.showLabels||q.x2.options.showLabels||q.y.options.showLabels||q.y2.options.showLabels)&&!o.grid.circular&&(m="",u(this,q.x),u(this,q.x2),u(this,q.y),u(this,q.y2),p.stroke(),p.restore(),k=a.create("div"),a.setStyles(k,{fontSize:"smaller",color:o.grid.color}),k.className="flotr-labels",a.insert(this.el,k),a.insert(k,m))}})}(),function(){var a=Flotr.DOM,b=Flotr._;Flotr.addPlugin("legend",{options:{show:!0,noColumns:1,labelFormatter:function(a){return a},labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,labelBoxOpacity:.4,container:null,position:"nw",margin:5,backgroundColor:null,backgroundOpacity:.85},callbacks:{"flotr:afterinit":function(){this.legend.insertLegend()}},insertLegend:function(){if(!this.options.legend.show)return;var c=this.series,d=this.plotOffset,e=this.options,f=e.legend,g=[],h=!1,i=this.ctx,j=b.filter(c,function(a){return a.label&&!a.hide}).length,k=f.position,l=f.margin,m,n,o;if(j)if(!e.HtmlText&&this.textEnabled&&!f.container){var p={size:e.fontSize*1.1,color:e.grid.color},q=f.labelBoxWidth,r=f.labelBoxHeight,s=f.labelBoxMargin,t=d.left+l,u=d.top+l,v=0;for(m=c.length-1;m>-1;--m){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),v=Math.max(v,this._text.measureText(n,p).width)}var w=Math.round(q+s*3+v),x=Math.round(j*(s+r)+s);k.charAt(0)=="s"&&(u=d.top+this.plotHeight-(l+x)),k.charAt(1)=="e"&&(t=d.left+this.plotWidth-(l+w)),o=this.processColor(f.backgroundColor||"rgb(240,240,240)",{opacity:f.backgroundOpacity||.1}),i.fillStyle=o,i.fillRect(t,u,w,x),i.strokeStyle=f.labelBoxBorderColor,i.strokeRect(Flotr.toPixel(t),Flotr.toPixel(u),w,x);var y=t+s,z=u+s;for(m=0;m<c.length;m++){if(!c[m].label||c[m].hide)continue;n=f.labelFormatter(c[m].label),i.fillStyle=c[m].color,i.fillRect(y,z,q-1,r-1),i.strokeStyle=f.labelBoxBorderColor,i.lineWidth=1,i.strokeRect(Math.ceil(y)-1.5,Math.ceil(z)-1.5,q+2,r+2),Flotr.drawText(i,n,y+q+s,z+r,p),z+=r+s}}else{for(m=0;m<c.length;++m){if(!c[m].label||c[m].hide)continue;m%f.noColumns===0&&(g.push(h?"</tr><tr>":"<tr>"),h=!0);var A=c[m],B=f.labelBoxWidth,C=f.labelBoxHeight,E=A.bars?A.bars.fillOpacity:f.labelBoxOpacity,F="opacity:"+E+";filter:alpha(opacity="+E*100+");";n=f.labelFormatter(A.label),o="background-color:"+(A.bars&&A.bars.show&&A.bars.fillColor&&A.bars.fill?A.bars.fillColor:A.color)+";",g.push('<td class="flotr-legend-color-box">','<div style="border:1px solid ',f.labelBoxBorderColor,';padding:1px">','<div style="width:',B-1,"px;height:",C-1,"px;border:1px solid ",c[m].color,'">','<div style="width:',B,"px;height:",C,"px;","opacity:.4;",o,'"></div>',"</div>","</div>","</td>",'<td class="flotr-legend-label">',n,"</td>")}h&&g.push("</tr>");if(g.length>0){var G='<table style="font-size:smaller;color:'+e.grid.color+'">'+g.join("")+"</table>";if(f.container)a.insert(f.container,G);else{var H={position:"absolute","z-index":2};k.charAt(0)=="n"?(H.top=l+d.top+"px",H.bottom="auto"):k.charAt(0)=="s"&&(H.bottom=l+d.bottom+"px",H.top="auto"),k.charAt(1)=="e"?(H.right=l+d.right+"px",H.left="auto"):k.charAt(1)=="w"&&(H.left=l+d.left+"px",H.right="auto");var I=a.create("div"),J;I.className="flotr-legend",a.setStyles(I,H),a.insert(I,G),a.insert(this.el,I);if(!f.backgroundOpacity)return;var K=f.backgroundColor||e.grid.backgroundColor||"#ffffff";b.extend(H,a.size(I),{backgroundColor:K,"z-index":1}),H.width+="px",H.height+="px",I=a.create("div"),I.className="flotr-legend-bg",a.setStyles(I,H),a.opacity(I,f.backgroundOpacity),a.insert(I," "),a.insert(this.el,I)}}}}})}(),function(){function a(a){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(a);var b=c.find(this.axes.x.ticks,function(b){return b.v==a});return b?b.label:a}var b=Flotr.DOM,c=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var a=this.spreadsheet,c=b.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),d=b.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+"</div>"),e=b.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+"</div>"),f;a.tabsContainer=c,a.tabs={graph:d,data:e},b.insert(c,d),b.insert(c,e),b.insert(this.el,c),f=b.size(e).height+2,this.plotOffset.bottom+=f,b.setStyles(c,{top:this.canvasHeight-f+"px"}),this.observe(d,"click",function(){a.showTab("graph")}).observe(e,"click",function(){a.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&a.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var a=this.series,b={};return c.each(a,function(a,d){c.each(a.data,function(a){var c=a[0],e=a[1],f=b[c];if(f)f[d+1]=e;else{var g=[];g[0]=c,g[d+1]=e,b[c]=g}})}),this.seriesData=c.sortBy(b,function(a,b){return parseInt(b,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var d=this.series,e=this.spreadsheet.loadDataGrid(),f=["<colgroup><col />"],g,h,i,j=['<table class="flotr-datagrid"><tr class="first-row">'];j.push("<th>&nbsp;</th>"),c.each(d,function(a,b){j.push('<th scope="col">'+(a.label||String.fromCharCode(65+b))+"</th>"),f.push("<col />")}),j.push("</tr>"),c.each(e,function(b){j.push("<tr>"),c.times(d.length+1,function(d){var e="td",f=b[d],g=c.isUndefined(f)?"":Math.round(f*1e5)/1e5;if(d===0){e="th";var h=a.call(this,g);h&&(g=h)}j.push("<"+e+(e=="th"?' scope="row"':"")+">"+g+"</"+e+">")},this),j.push("</tr>")},this),f.push("</colgroup>"),i=b.node(j.join("")),g=b.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarDownload+"</button>"),h=b.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarSelectAll+"</button>"),this.observe(g,"click",c.bind(this.spreadsheet.downloadCSV,this)).observe(h,"click",c.bind(this.spreadsheet.selectAllData,this));var k=b.node('<div class="flotr-datagrid-toolbar"></div>');b.insert(k,g),b.insert(k,h);var l=this.canvasHeight-b.size(this.spreadsheet.tabsContainer).height-2,m=b.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+this.canvasWidth+"px;height:"+l+'px;overflow:auto;z-index:10"></div>');return b.insert(m,k),b.insert(m,i),b.insert(this.el,m),this.spreadsheet.datagrid=i,this.spreadsheet.container=m,i},showTab:function(a){if(this.spreadsheet.activeTab===a)return;switch(a){case"graph":b.hide(this.spreadsheet.container),b.removeClass(this.spreadsheet.tabs.data,"selected"),b.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),b.show(this.spreadsheet.container),b.addClass(this.spreadsheet.tabs.data,"selected"),b.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+a}this.spreadsheet.activeTab=a},selectAllData:function(){if(this.spreadsheet.tabs){var a,b,c,d,e=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(c=e.ownerDocument)&&(d=c.defaultView)&&d.getSelection&&c.createRange&&(a=window.getSelection())&&a.removeAllRanges?(b=c.createRange(),b.selectNode(e),a.removeAllRanges(),a.addRange(b)):document.body&&document.body.createTextRange&&(b=document.body.createTextRange())&&(b.moveToElementText(e),b.select())},0),!0}return!1},downloadCSV:function(){var b="",d=this.series,e=this.options,f=this.spreadsheet.loadDataGrid(),g=encodeURIComponent(e.spreadsheet.csvFileSeparator);if(e.spreadsheet.decimalSeparator===e.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+e.spreadsheet.decimalSeparator+")";c.each(d,function(a,c){b+=g+'"'+(a.label||String.fromCharCode(65+c)).replace(/\"/g,'\\"')+'"'}),b+="%0D%0A",b+=c.reduce(f,function(b,c){var d=a.call(this,c[0])||"";d='"'+(d+"").replace(/\"/g,'\\"')+'"';var f=c.slice(1).join(g);return e.spreadsheet.decimalSeparator!=="."&&(f=f.replace(/\./g,e.spreadsheet.decimalSeparator)),b+d+g+f+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(b=b.replace(new RegExp(g,"g"),decodeURIComponent(g)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(b)):window.open("data:text/csv,"+b)}})}(),function(){var a=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var b,c=this.options,d=c.grid.labelMargin,e=this.ctx,f=this.axes;if(!c.HtmlText&&this.textEnabled){var g={size:c.fontSize,color:c.grid.color,textAlign:"center"};c.subtitle&&Flotr.drawText(e,c.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,g),g.weight=1.5,g.size*=1.5,c.title&&Flotr.drawText(e,c.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,g),g.weight=1.8,g.size*=.8,f.x.options.title&&f.x.used&&(g.textAlign=f.x.options.titleAlign||"center",g.textBaseline="top",g.angle=Flotr.toRad(f.x.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+f.x.maxLabel.height+this.plotHeight+2*d,g)),f.x2.options.title&&f.x2.used&&(g.textAlign=f.x2.options.titleAlign||"center",g.textBaseline="bottom",g.angle=Flotr.toRad(f.x2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-f.x2.maxLabel.height-2*d,g)),f.y.options.title&&f.y.used&&(g.textAlign=f.y.options.titleAlign||"right",g.textBaseline="middle",g.angle=Flotr.toRad(f.y.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y.options.title,this.plotOffset.left-f.y.maxLabel.width-2*d,this.plotOffset.top+this.plotHeight/2,g)),f.y2.options.title&&f.y2.used&&(g.textAlign=f.y2.options.titleAlign||"left",g.textBaseline="middle",g.angle=Flotr.toRad(f.y2.options.titleAngle),g=Flotr.getBestTextAlign(g.angle,g),Flotr.drawText(e,f.y2.options.title,this.plotOffset.left+this.plotWidth+f.y2.maxLabel.width+2*d,this.plotOffset.top+this.plotHeight/2,g))}else{b=[],c.title&&b.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;font-size:1em;font-weight:bold;text-align:center;width:",this.plotWidth,'px;" class="flotr-title">',c.title,"</div>"),c.subtitle&&b.push('<div style="position:absolute;top:',this.titleHeight,"px;left:",this.plotOffset.left,"px;font-size:smaller;text-align:center;width:",this.plotWidth,'px;" class="flotr-subtitle">',c.subtitle,"</div>"),b.push("</div>"),b.push('<div class="flotr-axis-title" style="font-weight:bold;">'),f.x.options.title&&f.x.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight+c.grid.labelMargin+f.x.titleSize.height,"px;left:",this.plotOffset.left,"px;width:",this.plotWidth,'px;text-align:center;" class="flotr-axis-title">',f.x.options.title,"</div>"),f.x2.options.title&&f.x2.used&&b.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;width:",this.plotWidth,'px;text-align:center;" class="flotr-axis-title">',f.x2.options.title,"</div>"),f.y.options.title&&f.y.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-f.y.titleSize.height/2,'px;left:0;text-align:right;" class="flotr-axis-title">',f.y.options.title,"</div>"),f.y2.options.title&&f.y2.used&&b.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-f.y.titleSize.height/2,'px;right:0;text-align:right;" class="flotr-axis-title">',f.y2.options.title,"</div>"),b=b.join("");var h=a.create("div");a.setStyles({color:c.grid.color}),h.className="flotr-titles",a.insert(this.el,h),a.insert(h,b)}}})}();
</script>

