#!/usr/bin/perl
#------------------------------------------------------------------------------
#
# pgBadger - Advanced PostgreSQL log analyzer
#
# This program is open source, licensed under the PostgreSQL Licence.
# For license terms, see the LICENSE file.
#------------------------------------------------------------------------------
#
# Settings in postgresql.conf
#
# You should enable SQL query logging with 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 information that could be collected and reported
#  log_checkpoints = on
#  log_connections = on
#  log_disconnections = on
#  log_lock_waits = on
#  log_temp_files = 0
#  log_autovacuum_min_duration = 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 Storable qw(store_fd fd_retrieve);
use Time::Local 'timegm_nocheck';
use POSIX qw(locale_h sys_wait_h _exit);
setlocale(LC_NUMERIC, '');
setlocale(LC_ALL,     'C');
use File::Spec qw/ tmpdir /;
use File::Temp qw/ tempfile /;
use IO::Handle;
use IO::Pipe;
use Time::HiRes qw/usleep/;

$VERSION = '3.3';

$SIG{'CHLD'} = 'DEFAULT';

my $TMP_DIR      = File::Spec->tmpdir() || '/tmp';
my %RUNNING_PIDS = ();
my @tempfiles    = ();
my $parent_pid   = $$;
my $interrupt    = 0;
my $tmp_last_parsed = '';

####
# method used to fork as many child as wanted
##
sub spawn
{
	my $coderef = shift;

	unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
		print "usage: spawn CODEREF";
		exit 0;
	}

	my $pid;
	if (!defined($pid = fork)) {
		print STDERR "Error: cannot fork: $!\n";
		return;
	} elsif ($pid) {
		$RUNNING_PIDS{$pid} = $pid;
		return; # the parent
	}
	# the child -- go spawn
	$< = $>;
	$( = $); # suid progs only

	exit &$coderef();
}

# Informa the parent that it should stop iterate on parsing other files
sub stop_parsing
{
	$interrupt = 1;
}

# With multiprocess we need to wait all childs
sub wait_child
{
        my $sig = shift;
        print STDERR "Received terminating signal ($sig).\n";
	if ($^O !~ /MSWin32|dos/i) {
		1 while wait != -1;
		$SIG{INT} = \&wait_child;
		$SIG{TERM} = \&wait_child;
		foreach my $f (@tempfiles) {
			unlink("$f->[1]") if (-e "$f->[1]");
		}
		unlink("$tmp_last_parsed") if ($tmp_last_parsed);
	}
	_exit(0);
}
$SIG{INT} = \&wait_child;
$SIG{TERM} = \&wait_child;
$SIG{USR2} = \&stop_parsing;

$| = 1;

# Command line options
my $zcat_cmd                = 'gunzip -c';
my $zcat                    = $zcat_cmd;
my $bzcat                   = 'bunzip2 -c';
my $ucat                    = 'unzip -p';
my $gzip_uncompress_size    = "gunzip -l %f | grep -E '^\\s*[0-9]+' | awk '{print \$2}'";
my $zip_uncompress_size     = "unzip -l %f | awk '{if (NR==4) print \$1}'";
my $format                  = '';
my $outfile                 = '';
my $outdir                  = '';
my $help                    = '';
my $ver                     = '';
my @dbname                  = ();
my @dbuser                  = ();
my @dbclient                = ();
my @dbclient2               = ();
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 $disable_autovacuum      = 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 $remove_comment          = 0;
my $select_only             = 0;
my $tsung_queries           = 0;
my $queue_size              = 0;
my $job_per_file            = 0;

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

# Do not display data in pie where percentage is lower than this value
# to avoid label overlapping.
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|dbclient=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,
	"j|jobs=i"                 => \$queue_size,
	"J|job_per_file=i"         => \$job_per_file,
	"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,
	"disable-autovacuum!"      => \$disable_autovacuum,
	"client=s"                 => \@dbclient2, # Backward compatibility
);
die "FATAL: use pgbadger --help\n" if (not $result);

push(@dbclient, @dbclient2); # Backward compatibility

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 not -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 syslog prefix regex
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 = '';

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

if ($format eq 'syslog2') {
	$other_syslog_line =
		qr/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(?:.[^\s]+)?\s([^\s]+)\s([^\s\[]+)\[(\d+)\]:(?:\s\[[^\]]+\])?\s\[(\d+)\-\d+\]\s*(.*)/;
	$orphan_syslog_line = qr/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(?:.[^\s]+)?\s([^\s]+)\s([^\s\[]+)\[(\d+)\]:/;
}

# 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 =~ /\.bin/i) {
		$extension = 'binary';
	} elsif ($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 directory
my @infs = fileparse($outfile);
$outdir = $infs[1] . '/';

# Remove graph support if output is not html
$graph = 0 unless ($extension eq 'html' or $extension eq 'binary' );
$graph = 0 if ($nograph);

# Set some default values
my $end_top = $top - 1;
$queue_size ||= 1;
$job_per_file ||= 1;

if ($^O =~ /MSWin32|dos/i) {
	if ( ($queue_size > 1) || ($job_per_file > 1) ) {
		print STDERR "WARNING: parallel processing is not supported on this platform.\n";
		$queue_size = 1;
		$job_per_file = 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/\r//;} @exclq;
	foreach my $r (@exclq) {
		&check_regex($r, '--exclude-file');
	}
	push(@exclude_query, @exclq);
}

# Testing regex syntax
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/\r//;} @exclq;
	foreach my $r (@exclq) {
		&check_regex($r, '--include-file');
	}
	push(@include_query, @exclq);
}

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

my @action_regex = (
	qr/^\s*(delete) from/is,
	qr/^\s*(insert) into/is,
	qr/^\s*(update) .*\bset\b/is,
	qr/^\s*(select) /is
);

# Compile custom log line prefix prefix
my @prefix_params = ();
if ($log_line_prefix) {
	# Build parameters name that will be extracted from the prefix regexp
	@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 'syslog2') {
		$format = 'syslog';
		$log_line_prefix =
			  '^(\d+)-(\d+)-(\d+)T\d+:\d+:\d+(?:.[^\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_year', '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') {
		$orphan_stderr_line = qr/$log_line_prefix/;
		$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 'syslog2') {
	$format = 'syslog';
	$compiled_prefix =
qr/^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(?:.[^\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_year', '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');
	$orphan_stderr_line = qr/^(\d+-\d+-\d+\s\d+:\d+:\d+)[\.\d]*(?: [A-Z\d]{3,6})?\s\[(\d+)\]:\s\[(\d+)\-\d+\]\s*(.*?)\s*/;
}

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})$/) {
		die "FATAL: bad format for begin datetime, should be yyyy-mm-dd hh:mm:ss\n";
	}
}
if ($to) {
	if ($to !~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/) {
		die "FATAL: bad format for ending datetime, should 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'
);

# Keywords variable
my @pg_keywords = qw(
        ALL ANALYSE ANALYZE AND ANY ARRAY AS ASC ASYMMETRIC AUTHORIZATION BINARY BOTH CASE
        CAST CHECK COLLATE COLLATION COLUMN CONCURRENTLY CONSTRAINT CREATE CROSS
        CURRENT_DATE CURRENT_ROLE CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER
        DEFAULT DEFERRABLE DESC DISTINCT DO ELSE END EXCEPT FALSE FETCH FOR FOREIGN FREEZE FROM
        FULL GRANT GROUP HAVING ILIKE IN INITIALLY INNER INTERSECT INTO IS ISNULL JOIN LEADING
        LEFT LIKE LIMIT LOCALTIME LOCALTIMESTAMP NATURAL NOT NOTNULL NULL ON ONLY OPEN OR
        ORDER OUTER OVER OVERLAPS PLACING PRIMARY REFERENCES RETURNING RIGHT SELECT SESSION_USER
        SIMILAR SOME SYMMETRIC TABLE THEN TO TRAILING TRUE UNION UNIQUE USER USING VARIADIC
        VERBOSE WHEN WHERE WINDOW WITH
);


# Highlight variables
my @KEYWORDS1 = qw(
        ALTER ADD AUTO_INCREMENT BETWEEN BY BOOLEAN BEGIN CHANGE COLUMNS COMMIT COALESCE CLUSTER
        COPY DATABASES DATABASE DATA DELAYED DESCRIBE DELETE DROP ENCLOSED ESCAPED EXISTS EXPLAIN
        FIELDS FIELD FLUSH FUNCTION GREATEST IGNORE INDEX INFILE INSERT IDENTIFIED IF INHERIT
        KEYS KILL KEY LINES LOAD LOCAL LOCK LOW_PRIORITY LANGUAGE LEAST LOGIN MODIFY
        NULLIF NOSUPERUSER NOCREATEDB NOCREATEROLE OPTIMIZE OPTION OPTIONALLY OUTFILE OWNER PROCEDURE
        PROCEDURAL READ REGEXP RENAME RETURN REVOKE RLIKE ROLE ROLLBACK SHOW SONAME STATUS
        STRAIGHT_JOIN SET SEQUENCE TABLES TEMINATED TRUNCATE TEMPORARY TRIGGER TRUSTED UNLOCK
        USE UPDATE UNSIGNED VALUES VARIABLES VIEW VACUUM WRITE ZEROFILL XOR
        ABORT ABSOLUTE ACCESS ACTION ADMIN AFTER AGGREGATE ALSO ALWAYS ASSERTION ASSIGNMENT AT ATTRIBUTE
        BACKWARD BEFORE BIGINT CACHE CALLED CASCADE CASCADED CATALOG CHAIN CHARACTER CHARACTERISTICS
        CHECKPOINT CLOSE COMMENT COMMENTS COMMITTED CONFIGURATION CONNECTION CONSTRAINTS CONTENT
        CONTINUE CONVERSION COST CSV CURRENT CURSOR CYCLE DAY DEALLOCATE DEC DECIMAL DECLARE DEFAULTS
        DEFERRED DEFINER DELIMITER DELIMITERS DICTIONARY DISABLE DISCARD DOCUMENT DOMAIN DOUBLE EACH
        ENABLE ENCODING ENCRYPTED ENUM ESCAPE EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXTENSION EXTERNAL
        FIRST FLOAT FOLLOWING FORCE FORWARD FUNCTIONS GLOBAL GRANTED HANDLER HEADER HOLD
        HOUR IDENTITY IMMEDIATE IMMUTABLE IMPLICIT INCLUDING INCREMENT INDEXES INHERITS INLINE INOUT INPUT
        INSENSITIVE INSTEAD INT INTEGER INVOKER ISOLATION LABEL LARGE LAST LC_COLLATE LC_CTYPE
        LEAKPROOF LEVEL LISTEN LOCATION LOOP MAPPING MATCH MAXVALUE MINUTE MINVALUE MODE MONTH MOVE NAMES
        NATIONAL NCHAR NEXT NO NONE NOTHING NOTIFY NOWAIT NULLS OBJECT OF OFF OIDS OPERATOR OPTIONS
        OUT OWNED PARSER PARTIAL PARTITION PASSING PASSWORD PLANS PRECEDING PRECISION PREPARE
        PREPARED PRESERVE PRIOR PRIVILEGES QUOTE RANGE REAL REASSIGN RECHECK RECURSIVE REF REINDEX RELATIVE
        RELEASE REPEATABLE REPLICA RESET RESTART RESTRICT RETURNS ROW ROWS RULE SAVEPOINT SCHEMA SCROLL SEARCH
        SECOND SECURITY SEQUENCES SERIALIZABLE SERVER SESSION SETOF SHARE SIMPLE SMALLINT SNAPSHOT STABLE
        STANDALONE START STATEMENT STATISTICS STORAGE STRICT SYSID SYSTEM TABLESPACE TEMP
        TEMPLATE TRANSACTION TREAT TYPE TYPES UNBOUNDED UNCOMMITTED UNENCRYPTED
        UNKNOWN UNLISTEN UNLOGGED UNTIL VALID VALIDATE VALIDATOR VALUE VARYING VOLATILE
        WHITESPACE WITHOUT WORK WRAPPER XMLATTRIBUTES XMLCONCAT XMLELEMENT XMLEXISTS XMLFOREST XMLPARSE
        XMLPI XMLROOT XMLSERIALIZE YEAR YES ZONE
);

foreach my $k (@pg_keywords) {
        push(@KEYWORDS1, $k) if (!grep(/^$k$/i, @KEYWORDS1));
}


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 statistics are stored
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 %database_info       = ();
my %application_info    = ();
my %session_info        = ();
my %conn_received       = ();
my %checkpoint_info     = ();
my %restartpoint_info   = ();
my %autovacuum_info     = ();
my %autoanalyze_info    = ();
my @graph_values        = ();
my %cur_info            = ();
my %cur_temp_info       = ();
my %cur_lock_info       = ();
my $nlines              = 0;
my %last_line           = ();
our %saved_last_line    = ();
my %tsung_session       = ();
my @top_locked_info     = ();
my @top_tempfile_info   = ();

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";
	}
}
$tmp_last_parsed = 'tmp_' . $last_parsed if ($last_parsed);
	

# Main loop reading log files
my $global_totalsize = 0;
my @given_log_files = ( @log_files );

# log files must be erase when loading stats from binary format
if ($format eq 'binary') {
	$queue_size = 1;
	$job_per_file = 1;
	@log_files = ();
}

my $pipe;

# Start parsing all given files using multiprocess
if ( ($queue_size > 1) || ($job_per_file > 1) ) {

	# Number of running process
	my $child_count = 0;
	# Set max number of parallel process
	my $parallel_process = $queue_size;
	if ($job_per_file > 1) {
		$parallel_process = $job_per_file;
	}
	# Store total size of the log files
	foreach my $logfile ( @given_log_files ) {
		$global_totalsize += &get_log_file($logfile);
	}

	# Open a pipe for interprocess communication
	my $reader = new IO::Handle;
	my $writer = new IO::Handle;
	$pipe = IO::Pipe->new($reader, $writer);
	$writer->autoflush(1);

	# Fork the logger process
	if ($progress) {
		spawn sub {
			&multiprocess_progressbar($global_totalsize);
		};
	}

	# Parse each log file following the multiprocess mode chosen (-j or -J)
	foreach my $logfile ( @given_log_files ) {

		while ($child_count >= $parallel_process) {
			my $kid = waitpid(-1, WNOHANG);
			if ($kid > 0) {
				$child_count--;
				delete $RUNNING_PIDS{$kid};
			}
			usleep(500000);
		}
		# Do not use split method with compressed files
		if ( ($queue_size > 1) && ($logfile !~ /\.(gz|bz2|zip)/i) ) {
			# Create multiple process to parse one log file by chunks of data
			my @chunks = &split_logfile($logfile);
			for (my $i = 0; $i < $#chunks; $i++) {
				while ($child_count >= $parallel_process) {
					my $kid = waitpid(-1, WNOHANG);
					if ($kid > 0) {
						$child_count--;
						delete $RUNNING_PIDS{$kid};
					}
					usleep(500000);
				}
				push(@tempfiles, [ tempfile('tmp_pgbadgerXXXX', SUFFIX => '.bin', DIR => $TMP_DIR, UNLINK => 1 ) ]);
				spawn sub {
					&process_file($logfile, $tempfiles[-1]->[0], $chunks[$i], $chunks[$i+1]);
				};
				$child_count++;
			} 

		} else {

			# Start parsing one file per parallel process
			push(@tempfiles, [ tempfile('tmp_pgbadgerXXXX', SUFFIX => '.bin', DIR => $TMP_DIR, UNLINK => 1 ) ]);
			spawn sub {
				&process_file($logfile, $tempfiles[-1]->[0]);
			};
			$child_count++;

		}
		last if ($interrupt);
	}

	my $minproc = 1;
	$minproc = 0 if (!$progress);
	# Wait for all child dies less the logger
	while (scalar keys %RUNNING_PIDS > $minproc) {
		my $kid = waitpid(-1, WNOHANG);
		if ($kid > 0) {
			delete $RUNNING_PIDS{$kid};
		}
		usleep(500000);
	}
	# Terminate the process logger
	foreach my $k (keys %RUNNING_PIDS) {
		kill(10, $k);
		%RUNNING_PIDS = ();
	}

	# Load all data gathered by all the differents processes
	&init_stats_vars();
	foreach my $f (@tempfiles) {
		next if (!-e "$f->[1]" || -z "$f->[1]");
		my $fht = new IO::File;
		$fht->open("< $f->[1]") or die "FATAL: can't open file $f->[1], $!\n";
		&load_stats($fht);
		$fht->close();
	}

	# Get last line parsed from all process
	if ($last_parsed) {
		if (open(IN, "$tmp_last_parsed") ) {
			while (my $line = <IN>) {
				chomp($line);
				my ($d, $l) = split(/\t/, $line, 2);
				if (!$last_line{datetime} || ($d gt $last_line{datetime})) {
					$last_line{datetime} = $d;
					$last_line{orig} = $l;
				}
			}
			close(IN);
		}
		unlink("$tmp_last_parsed");
	}

} else {

	# Multiprocessing disabled, parse log files one by one
	foreach my $logfile ( @given_log_files ) {
		last if (&process_file($logfile));
	}
}

# 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 took:" . timestr($td));

&logmsg('LOG', "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();
		}
	} elsif ($extension eq 'binary') {
		&dump_as_binary($fh);
	} else {
		# Create instance to prettify SQL query
		if (!$noprettify) {
			$sql_prettified = SQL::Beautify->new(keywords => \@pg_keywords);
		}
		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', "building reports took:" . timestr($td));
$td = timediff($t2, $t0);
&logmsg('DEBUG', "the total execution time took:" . 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 files. 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 on entries for the given client host.
    -C | --nocomment       : remove comments like /* ... */ from queries.
    -d | --dbname database : only report on entries for 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
    -j | --jobs number     : number of jobs to run at same time. Default is 1,
			     run as single process.
    -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 restricted to
                             the given size. Default: no truncate
    -n | --nohighlight     : disable SQL code highlighting.
    -N | --appname name    : only report on entries for given application name
    -o | --outfile filename: define the filename for the output. Default depends
                             on 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. Only use it if you
                             aren't using one of the standard prefixes specified
                             in the pgBadger documentation, such as if your prefix
                             includes additional 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 samples to store/display. Default: 3
    -S | --select-only     : use it if you want to report select queries only.
    -t | --top number      : number of queries to store/display. Default: 20
    -T | --title string    : change title of the HTML page report.
    -u | --dbuser username : only report on entries for the given user.
    -U | --exclude-user username : exclude entries for the specified user from report.
    -v | --verbose         : enable verbose or debug mode. Disabled by default.
    -V | --version         : show pgBadger version and exit.
    -w | --watch-mode      : only report 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 times.
    --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 does not match the given regex will be
                             excluded from the report. For example: "(table_1|table_2)"
                             You can use this option multiple times.
    --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 report.
    --disable-type         : do not generate query type report.
    --disable-query        : do not generate query 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/restartpoint report.
    --disable-autovacuum   : do not generate autovacuum report.

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
	pgbadger -b "2012-06-25 10:56:11" -e "2012-06-25 10:59:11" \
		       /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*
	# Use my 8 CPUs to parse my 10GB file faster, really faster
	perl pgbadger -j 8 /pglog/postgresql-9.1-main.log


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 week.

};

	exit 0;
}

sub init_stats_vars
{
	# Empty where statistics are stored
	%overall_stat        = ();
	@top_slowest         = ();
	%normalyzed_info     = ();
	%error_info          = ();
	%logs_type           = ();
	%per_hour_info       = ();
	%per_minute_info     = ();
	%lock_info           = ();
	%tempfile_info       = ();
	%connection_info     = ();
	%database_info       = ();
	%application_info    = ();
	%session_info        = ();
	%conn_received       = ();
	%checkpoint_info     = ();
	%restartpoint_info   = ();
	%autovacuum_info     = ();
	%autoanalyze_info    = ();
	@graph_values        = ();
	%cur_info            = ();
	$nlines              = 0;
	%tsung_session       = ();
}

####
# Main function called per each parser process
####
sub multiprocess_progressbar
{
	my $totalsize = shift;

	&logmsg('DEBUG', "Starting progressbar writer process");

	$0 = 'pgbadger logger';

	# Terminate the process when we doesn't read the complete file but must exit
	local $SIG{USR1} = sub {
		print STDERR "\n";
		exit 0;
	};
	my $timeout  = 3;
	my $cursize  = 0;
	my $nqueries = 0;
	my $nerrors  = 0;
	$pipe->reader();
	while (my $r = <$pipe>) {
		chomp($r);
		my @infos = split(/\s+/, $r);
		$cursize  += $infos[0];
		$nqueries += $infos[1];
		$nerrors  += $infos[2];
		$cursize = $totalsize if ($cursize > $totalsize);
		print STDERR &progress_bar($cursize, $totalsize, 25, '=', $nqueries, $nerrors);
		last if ($cursize >= $totalsize);
	}
	print STDERR "\n";

	exit 0;
}

####
# Main function called per each parser process
####
sub process_file
{
	my ($logfile, $tmpoutfile, $start_offset, $stop_offset) = @_;

	my $old_queries_count = 0;
	my $old_errors_count  = 0;
	my $current_offset    = $start_offset || 0;
	my $getout            = 0;

	$0 = 'pgbadger parser';

	&init_stats_vars() if ($tmpoutfile);

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

	my $terminate = 0;
	local $SIG{INT} = sub { $terminate = 1 };
	local $SIG{TERM} = sub { $terminate = 1 };

	my $curdate = localtime(time);

	$pipe->writer() if (defined $pipe);

	# Syslog does 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);

	my $cursize = 0;

	# Get file handle and size of the file
	my ($lfile, $totalsize) = &get_log_file($logfile);
	if ($stop_offset > 0) {
		$totalsize = $stop_offset - $start_offset;
	}

	&logmsg('DEBUG', "Starting reading file $logfile...");

	if ($format eq 'csv') {

		require Text::CSV_XS;
		my $csv = Text::CSV_XS->new({binary => 1, eol => $/});

		# Parse csvlog lines
		while (my $row = $csv->getline($lfile)) {

			# We received a signal
			last if ($terminate);

			# Set progress statistics
			$cursize += length(join(',', @$row));
			$nlines++;
			if (!$tmpoutfile) {
				if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
					if ($totalsize) {
						print STDERR &progress_bar($cursize, $totalsize, 25, '=');
					} else {
						print STDERR ".";
					}
				}
			} else {
				if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
					$pipe->print("$cursize " . ($overall_stat{'queries_number'} - $old_queries_count) . " " . ($overall_stat{'errors_number'} - $old_errors_count) . "\n");
					$old_queries_count = $overall_stat{'queries_number'};
					$old_errors_count = $overall_stat{'errors_number'};
					$cursize = 0;
				}
			}

			# 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_timestamp'} = "$1-$2-$3 $4:$5:$6";

			# Skip unwanted lines
			next if ($from && ($from gt $prefix_vars{'t_timestamp'}));
			if ($to && ($to lt $prefix_vars{'t_timestamp'})) {
				if ($tmpoutfile) {
					$pipe->print("$cursize " . ($overall_stat{'queries_number'} - $old_queries_count) . " " . ($overall_stat{'errors_number'} - $old_errors_count) . "\n");
					$old_queries_count = $overall_stat{'queries_number'};
					$old_errors_count = $overall_stat{'errors_number'};
					$cursize = 0;
				}
				$getout = 1;
				last;
			}

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

			# Store the current timestamp of the log line
			&store_current_timestamp($prefix_vars{'t_timestamp'});

			# Set query parameters as global variables
			$prefix_vars{'t_dbuser'}  = $row->[1] || '';
			$prefix_vars{'t_dbname'}  = $row->[2] || '';
			$prefix_vars{'t_appname'} = $row->[22] || '';
			$prefix_vars{'t_client'}  = $row->[4] || '';
			$prefix_vars{'t_client'}  =~ s/:.*//;
			$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];
			# Set ERROR additional informations
			$prefix_vars{'t_detail'} = $row->[14];
			$prefix_vars{'t_hint'} = $row->[15];
			$prefix_vars{'t_context'} = $row->[18];
			$prefix_vars{'t_statement'} = $row->[19];
			
			# Check if the log line should be excluded from the report
			if (&validate_log_line($prefix_vars{'t_pid'})) {

				# Parse the query now
				&parse_query();
				&store_queries($prefix_vars{'t_pid'});
				delete $cur_info{$prefix_vars{'t_pid'}};
			}
		}
		if (!$getout) {
			$csv->eof or warn "FATAL: cannot use CSV, " . $csv->error_diag() . "\n";
		}

	}
	elsif ($format eq 'binary') {
		&load_stats($lfile);
	}
	else { # Format is not CSV.

		my $time_pattern = qr/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/;
		my $cur_pid = '';
		my @matches = ();
		my $goon = 0;
		if ($start_offset) {
			$lfile->seek($start_offset, 0);
		}
		while (my $line = <$lfile>) {

			# We received a signal
			last if ($terminate);

			$cursize += length($line);
			$current_offset += length($line);
			chomp($line);
			$line =~ s/\r//;
			$nlines++;
			next if (!$line);

			if (!$tmpoutfile) {
				if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
					if ($totalsize) {
						if ($stop_offset > 0) {
							print STDERR &progress_bar($cursize - $start_offset, $stop_offset, 25, '=');
						} else {
							print STDERR &progress_bar($cursize, $totalsize, 25, '=');
						}
					} else {
						print STDERR ".";
					}
				}
			} else {
				if ($progress && (($nlines % $NUMPROGRESS) == 0)) {
					$pipe->print("$cursize " . ($overall_stat{'queries_number'} - $old_queries_count) . " " . ($overall_stat{'errors_number'} - $old_errors_count) . "\n");
					$old_queries_count = $overall_stat{'queries_number'};
					$old_errors_count = $overall_stat{'errors_number'};
					$cursize = 0;
				}
			}

			%prefix_vars = ();

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

				@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);

					# Standard syslog format does not have year information, months are
					# three letters and day are not always with 2 digit.
					if ($prefix_vars{'t_month'} !~ /\d/) {
						$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'}};
						# Take care of year overlapping
						if ("$prefix_vars{'t_year'}$prefix_vars{'t_month'}$prefix_vars{'t_day'}" > $CURRENT_DATE) {
							$prefix_vars{'t_year'} = substr($CURRENT_DATE, 0, 4) - 1;
						}
					}
					$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 gt $prefix_vars{'t_timestamp'}));
					if ($to   && ($to lt $prefix_vars{'t_timestamp'})) {
						if ($tmpoutfile) {
							$pipe->print("$cursize " . ($overall_stat{'queries_number'} - $old_queries_count) . " " . ($overall_stat{'errors_number'} - $old_errors_count) . "\n");
							$old_queries_count = $overall_stat{'queries_number'};
							$old_errors_count = $overall_stat{'errors_number'};
							$cursize = 0;
						}
						$getout = 1;
						last;
					}

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

					# Store the current timestamp of the log line
					&store_current_timestamp($prefix_vars{'t_timestamp'});

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

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

						# Process the log line
						&parse_query();
					}

				} elsif ($goon && ($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}{vacuum} && ($t_query =~ /^\t(pages|tuples|buffer usage|avg read rate|system usage):/)) {
						if ($t_query =~ /^\t(pages|tuples): (\d+) removed, (\d+) remain/) {
							$autovacuum_info{tables}{$cur_info{$cur_pid}{vacuum}}{$1}{removed} += $2;
						}
						next;
					} elsif ( $cur_info{$cur_pid}{parameters} && (($t_query =~ /[,\s]*\$(\d+)\s=\s/) || ($t_query =~ /^('[^']*')$/)) ) {
						# stores bind parameters if any
						$cur_info{$cur_pid}{parameters} .= " $t_query";
						next;
					} 
					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 ($cur_pid && ($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') {

				@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);

					# Skip unwanted lines
					next if ($from && ($from gt $prefix_vars{'t_timestamp'}));
					if ($to   && ($to lt $prefix_vars{'t_timestamp'})) {
						if ($tmpoutfile) {
							$pipe->print("$cursize " . ($overall_stat{'queries_number'} - $old_queries_count) . " " . ($overall_stat{'errors_number'} - $old_errors_count) . "\n");
							$old_queries_count = $overall_stat{'queries_number'};
							$old_errors_count = $overall_stat{'errors_number'};
							$cursize = 0;
						}
						$getout = 1;
						last;
					}

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

					# Store the current timestamp of the log line
					&store_current_timestamp($prefix_vars{'t_timestamp'});

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

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

						# Process the log line
						&parse_query();
					}

				# Collect additional query information
				} elsif ($cur_pid && ($line !~ $orphan_stderr_line)) {

					if ($cur_info{$cur_pid}{vacuum} && ($line =~ /^\t(pages|tuples|buffer usage|avg read rate|system usage):/)) {
						if ($line =~ /^\t(pages|tuples): (\d+) removed, (\d+) remain/) {
							$autovacuum_info{tables}{$cur_info{$cur_pid}{vacuum}}{$1}{removed} += $2;
						}
						next;
					} elsif ( $cur_info{$cur_pid}{parameters} && (($line =~ /[,\s]*\$(\d+)\s=\s/) || ($line =~ /^'[^']*'$/)) ) {
						# stores bind parameters if any
						$cur_info{$cur_pid}{parameters} .= " $line";
						next;
					} 
					if (exists $cur_info{$cur_pid}{statement}) {
						$cur_info{$cur_pid}{statement} .= "\n" . $line;
					} elsif (exists $cur_info{$cur_pid}{context}) {
						$cur_info{$cur_pid}{context} .= "\n" . $line;
					} elsif (exists $cur_info{$cur_pid}{detail}) {
						$cur_info{$cur_pid}{detail} .= "\n" . $line;
					} else {
						$cur_info{$cur_pid}{query} .= "\n" . $line;
					}

				# Collect orphans lines of multiline queries
				} elsif ($cur_pid && ($cur_info{$cur_pid}{query})) {

					$cur_info{$cur_pid}{detail} .= "\n" . $line;

				}

			} else {

				# unknown format
				&logmsg('DEBUG', "Unknown line format: $line");
			}
			last if (($stop_offset > 0) && ($current_offset > $stop_offset));
		}
	}
	close $lfile;

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

	if ($progress && !$getout) {
		if (!$tmpoutfile) {
			if ($totalsize) {
				if (($stop_offset > 0) && ($format ne 'csv')) {
					print STDERR &progress_bar($cursize - $start_offset, $stop_offset, 25, '=',$overall_stat{'queries_number'},$overall_stat{'errors_number'});
				} elsif ($extension eq 'tsung') {
					print STDERR &progress_bar($cursize, $totalsize, 25, '=', $logfile);
				} else {
					print STDERR &progress_bar($cursize, $totalsize, 25, '=', $overall_stat{'queries_number'},$overall_stat{'errors_number'});
				}
				print STDERR "\n";
			}
		} else {
			$pipe->print("$cursize " . ($overall_stat{'queries_number'} - $old_queries_count) . " " . ($overall_stat{'errors_number'} - $old_errors_count) . "\n");
		}
	}

	%cur_info = ();

	if ($tmpoutfile) {
		&dump_as_binary($tmpoutfile);
		$tmpoutfile->close();
	}

	# Inform the parent that it should stop parsing other files
	if ($getout) {
		kill(12, $parent_pid);
	}

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

# Store the current timestamp of the log line
sub store_current_timestamp
{
	my $t_timestamp = shift;

	$prefix_vars{'t_date'} = $t_timestamp;
	$prefix_vars{'t_date'} =~ s/\D+//g;

	if (!$overall_stat{'first_log_ts'} || ($overall_stat{'first_log_ts'} gt $t_timestamp)) {
		$overall_stat{'first_log_ts'} = $t_timestamp;
	}
	if (!$overall_stat{'last_log_ts'} || ($overall_stat{'last_log_ts'} lt $t_timestamp)) {
		$overall_stat{'last_log_ts'} = $t_timestamp;
	}
}

# 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 lt $saved_last_line{datetime}) {
				return 0;
			} elsif (!$last_line{datetime} && ($cur_date eq $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";
}

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

	return if (!$orig_query);

	# Remove comments
	$orig_query =~ s/\/\*(.*?)\*\///gs;

	$orig_query = lc($orig_query);

	# Remove extra space, new line and tab characters 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.3f", $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 queries generating the biggest temporary file
sub set_top_tempfile_info
{
	my ($q, $sz, $date, $db, $user, $remote, $app) = @_;

	push(@top_tempfile_info, [($sz, $date, $q, $db, $user, $remote, $app)]);

	my @tmp_top_tempfile_info = sort {$b->[0] <=> $a->[0]} @top_tempfile_info;
	@top_tempfile_info = ();
	for (my $i = 0; $i <= $#tmp_top_tempfile_info; $i++) {
		push(@top_tempfile_info, $tmp_top_tempfile_info[$i]);
		last if ($i == $end_top);
	}
}

# Stores the top N queries waiting the most
sub set_top_locked_info
{
	my ($q, $dt, $date, $db, $user, $remote, $app) = @_;

	push(@top_locked_info, [($dt, $date, $q, $db, $user, $remote, $app)]);

	my @tmp_top_locked_info = sort {$b->[0] <=> $a->[0]} @top_locked_info;
	@top_locked_info = ();
	for (my $i = 0; $i <= $#tmp_top_locked_info; $i++) {
		push(@top_locked_info, $tmp_top_locked_info[$i]);
		last if ($i == $end_top);
	}
}

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

	push(@top_slowest, [($dt, $date, $q, $db, $user, $remote, $app)]);

	my @tmp_top_slowest = sort {$b->[0] <=> $a->[0]} @top_slowest;
	@top_slowest = ();
	for (my $i = 0; $i <= $#tmp_top_slowest; $i++) {
		push(@top_slowest, $tmp_top_slowest[$i]);
		last if ($i == $end_top);
	}

}

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

	$normalyzed_info{$norm}{samples}{$dt}{query} = $q;
	$normalyzed_info{$norm}{samples}{$dt}{date}  = $date;
	$normalyzed_info{$norm}{samples}{$dt}{db}  = $db;
	$normalyzed_info{$norm}{samples}{$dt}{user}  = $user;
	$normalyzed_info{$norm}{samples}{$dt}{remote}  = $remote;
	$normalyzed_info{$norm}{samples}{$dt}{app}  = $app;

	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, $db) = @_;

	# 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);
			push(@{$error_info{$q}{db}},        $db);
		}
	}
}

sub dump_as_text
{

	# Global information
	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 information ---------------------------------------------------

Generated on $curdate
Log file: $logfile_str
Parsed $fmt_nlines log entries in $total_time
Log start from $overall_stat{'first_log_ts'} to $overall_stat{'last_log_ts'}
};

	# Overall statistics
	my $fmt_unique  = &comma_numbers(scalar keys %normalyzed_info)    || 0;
	my $fmt_queries = &comma_numbers($overall_stat{'queries_number'}) || 0;
	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{size} / $tempfile_info{count})));
		print $fh qq{Number temporary files: $tempfile_info{count}
Max size of temporary files: $fmt_temp_maxsise
Average size of temporary files: $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 (scalar keys %database_info > 1) {
		print $fh "Total number of databases: ", scalar keys %database_info, "\n";
	}
	if (!$disable_hourly && $overall_stat{'queries_number'}) {
		print $fh qq{

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

Report not supported by text format

};
	}

	# INSERT/DELETE/UPDATE/SELECT repartition
	my $totala = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
	if (!$disable_type && $totala) {

		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";

		# Show request per database statistics
		if (scalar keys %database_info > 1) {
			print $fh "\n- Request per database ------------------------------------------------------\n\n";
			print $fh "Database     Request type     Count\n";
			foreach my $d (sort keys %database_info) {
				print $fh "$d - ", &comma_numbers($database_info{$d}{count}), "\n";
				foreach my $r (sort keys %{$database_info{$d}}) {
					next if ($r eq 'count');
					print $fh "\t$r ", &comma_numbers($database_info{$d}{$r}), "\n";
				}
			}
		}

		# Show request per application statistics
		if (scalar keys %application_info > 1) {
			print $fh "\n- Request per application ------------------------------------------------------\n\n";
			print $fh "Application     Request type     Count\n";
			foreach my $d (sort keys %application_info) {
				print $fh "$d - ", &comma_numbers($application_info{$d}{count}), "\n";
				foreach my $r (sort keys %{$application_info{$d}}) {
					next if ($r eq 'count');
					print $fh "\t$r ", &comma_numbers($application_info{$d}{$r}), "\n";
				}
			}
		}
	}

	if (!$disable_lock && scalar keys %lock_info > 0) {
		print $fh "\n- Locks by type ------------------------------------------------------\n\n";
		print $fh "Type     Object     Count     Total Duration     Avg 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     Avg 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     Avg 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     Avg 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 lock wait detailed informations
	if (!$disable_lock && scalar keys %lock_info > 0) {

		my @top_locked_queries;
		foreach my $h (keys %normalyzed_info) {
			if (exists($normalyzed_info{$h}{locks})) {
				push (@top_locked_queries, [$h, $normalyzed_info{$h}{locks}{count}, $normalyzed_info{$h}{locks}{wait},
				$normalyzed_info{$h}{locks}{minwait}, $normalyzed_info{$h}{locks}{maxwait}]);
			}
		}

		# Most frequent waiting queries (N)
		@top_locked_queries = sort {$b->[2] <=> $a->[2]} @top_locked_queries;
		print $fh "\n- Most frequent waiting queries (N) -----------------------------------------\n\n";
		print $fh "Rank  Count    Total wait time (s)    Min/Max/Avg duration (s)    Query\n";
		for (my $i = 0 ; $i <= $#top_locked_queries ; $i++) {
			last if ($i > $end_top);
			print $fh ($i + 1), ")   ", $top_locked_queries[$i]->[1], " - ", &convert_time($top_locked_queries[$i]->[2]),
			" - ", &convert_time($top_locked_queries[$i]->[3]), "/", &convert_time($top_locked_queries[$i]->[4]), "/",
			&convert_time(($top_locked_queries[$i]->[4] / $top_locked_queries[$i]->[1])),
			" - ", $top_locked_queries[$i]->[0], "\n";
			print $fh "--\n";
			my $k = $top_locked_queries[$i]->[0];
			my $j = 1;
			foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
			my $ttl = $top_locked_info[$i]->[1] || '';
				my $db = " - $normalyzed_info{$k}{samples}{$d}{date} - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
				$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
				$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
				$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
				$db =~ s/^, / - /;
				print $fh "\t- Example $j: ", &convert_time($d), "$db - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$j++;
			}
		}
		print $fh "\n";
		@top_locked_queries = ();

		# Queries that waited the most
		@top_locked_info = sort {$b->[1] <=> $a->[1]} @top_locked_info;
		print $fh "\n- Queries that waited the mosts ---------------------------------------------\n\n";
		print $fh "Rank   Wait time (s)    Query\n";
		for (my $i = 0 ; $i <= $#top_locked_info ; $i++) {
			my $ttl = $top_locked_info[$i]->[1] || '';
			my $db = " - database: $top_locked_info[$i]->[3]" if ($top_locked_info[$i]->[3]);
			$db .= ", user: $top_locked_info[$i]->[4]" if ($top_locked_info[$i]->[4]);
			$db .= ", remote: $top_locked_info[$i]->[5]" if ($top_locked_info[$i]->[5]);
			$db .= ", app: $top_locked_info[$i]->[6]" if ($top_locked_info[$i]->[6]);
			$db =~ s/^, / - /;
			print $fh ($i + 1), ")   ", &convert_time($top_locked_info[$i]->[0]),
			"  $ttl$db - ", $top_locked_info[$i]->[2], "\n";
			print $fh "--\n";
		}
		print $fh "\n";
	}

	# Show temporary files detailed informations
	if (!$disable_temporary && scalar keys %tempfile_info > 0) {

		my @top_temporary;
		foreach my $h (keys %normalyzed_info) {
			if (exists($normalyzed_info{$h}{tempfiles})) {
				push (@top_temporary, [$h, $normalyzed_info{$h}{tempfiles}{count}, $normalyzed_info{$h}{tempfiles}{size},
				$normalyzed_info{$h}{tempfiles}{minsize}, $normalyzed_info{$h}{tempfiles}{maxsize}]);
			}
		}

		# Queries generating the most temporary files (N)
		@top_temporary = sort {$b->[1] <=> $a->[1]} @top_temporary;
		print $fh "\n- Queries generating the most temporary files (N) ---------------------------\n\n";
		print $fh "Rank   Count   Total size    Min/Max/Avg size    Query\n";
		my $idx = 1;
		for (my $i = 0 ; $i <= $#top_temporary ; $i++) {
			last if ($i > $end_top);
			print $fh $idx, ")   ",
			$top_temporary[$i]->[1], " - ", &comma_numbers($top_temporary[$i]->[2]),
			" - ", &comma_numbers($top_temporary[$i]->[3]),
			"/", &comma_numbers($top_temporary[$i]->[4]), "/",
			&comma_numbers(sprintf("%.2f", $top_temporary[$i]->[2] / $top_temporary[$i]->[1])),
			" - ", $top_temporary[$i]->[0], "\n";
			print $fh "--\n";
			my $k = $top_temporary[$i]->[0];
			if ($normalyzed_info{$k}{count} > 1) {
				my $j = 1;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $db = "$normalyzed_info{$k}{samples}{$d}{date} - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
					$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
					$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
					$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
					$db =~ s/^, / - /;
					print $fh "\t- Example $j: ", &convert_time($d), " - $db - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
					$j++;
				}
			}                             
			$idx++;
		}
		@top_temporary = ();

		# Top queries generating the largest temporary files
		@top_tempfile_info = sort {$b->[1] <=> $a->[1]} @top_tempfile_info;

		print $fh "\n- Queries generating the largest temporary files ----------------------------\n\n";
		print $fh "Rank   Size    Query\n";
		for (my $i = 0 ; $i <= $#top_tempfile_info ; $i++) {
			my $ttl = $top_tempfile_info[$i]->[1] || '';
			my $db = " - database: $top_tempfile_info[$i]->[3]" if ($top_tempfile_info[$i]->[3]);
			$db .= ", user: $top_tempfile_info[$i]->[4]" if ($top_tempfile_info[$i]->[4]);
			$db .= ", remote: $top_tempfile_info[$i]->[5]" if ($top_tempfile_info[$i]->[5]);
			$db .= ", app: $top_tempfile_info[$i]->[6]" if ($top_tempfile_info[$i]->[6]);
			$db =~ s/^, / - /;
			print $fh ($i + 1), ")   ", &comma_numbers($top_tempfile_info[$i]->[0]),
			" - $ttl$db - ", $top_tempfile_info[$i]->[2], "\n";
		}
		print $fh "\n";
	}

	# Show top information
	if (!$disable_query && ($#top_slowest >= 0)) {
		print $fh "\n- Slowest queries ------------------------------------------------------\n\n";
		print $fh "Rank     Duration (s)     Query\n";
		for (my $i = 0 ; $i <= $#top_slowest ; $i++) {
			my $db = " database: $top_slowest[$i]->[3]" if ($top_slowest[$i]->[3]);
			$db .= ", user: $top_slowest[$i]->[4]" if ($top_slowest[$i]->[4]);
			$db .= ", remote: $top_slowest[$i]->[5]" if ($top_slowest[$i]->[5]);
			$db .= ", app: $top_slowest[$i]->[6]" if ($top_slowest[$i]->[6]);
			$db =~ s/^, //;
			print $fh $i + 1, ") " . &convert_time($top_slowest[$i]->[0]) . "$db - $top_slowest[$i]->[2]\n";
			print $fh "--\n";
		}

		print $fh "\n- Queries that took up the most time (N) -------------------------------\n\n";
		print $fh "Rank     Total duration      Times executed     Min/Max/Avg 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}{min}) . "/"
				. &convert_time($normalyzed_info{$k}{max}) . "/"
				. &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}}) {
				my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
				$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
				$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
				$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
				$db =~ s/^, / - /;
				print $fh "\t- Example $i: ", &convert_time($d), "$db - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}
	}
	if (!$disable_query && (scalar keys %normalyzed_info > 0)) {
		print $fh "\n- Most frequent queries (N) --------------------------------------------\n\n";
		print $fh "Rank     Times executed     Total duration     Min/Max/Avg duration (s)     Query\n";
		my $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}{min}) . "/"
				. &convert_time($normalyzed_info{$k}{max}) . "/"
				. &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}}) {
				my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
				$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
				$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
				$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
				$db =~ s/^, / - /;
				print $fh "\tExample $i: ", &convert_time($d), "$db - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}
	}

	if (!$disable_query && ($#top_slowest >= 0)) {
		print $fh "\n- Slowest queries (N) --------------------------------------------------\n\n";
		print $fh "Rank     Min/Max/Avg duration (s)     Times executed     Total duration     Query\n";
		my $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}{min}) . "/"
				. &convert_time($normalyzed_info{$k}{max}) . "/"
				. &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}}) {
				my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
				$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
				$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
				$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
				$db =~ s/^, / - /;
				print $fh "\tExample $i: ", &convert_time($d), "$db - ", $normalyzed_info{$k}{samples}{$d}{query}, "\n";
				$i++;
			}
			$idx++;
		}
	}
	@top_slowest = ();

	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 information
	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 information ---------------------------------------------------

Generated on $curdate
Log file: $logfile_str
Parsed $fmt_nlines log entries in $total_time
Log start from $overall_stat{'first_log_ts'} to $overall_stat{'last_log_ts'}
};

	&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]);
				print $fh "\t\tDatabase: $error_info{$k}{db}[$i]\n" if ($error_info{$k}{db}[$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]);
			print $fh "\t\tDatabase: $error_info{$k}{db}[0]\n" if ($error_info{$k}{db}[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 get_page_style
{
	return qq{<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; }
#content h1 { border-left:8px #0c57a3 solid; color: #0c57a3; font: lighter 30px Arial, Helvetica, sans-serif; margin: 0px; padding: 7px;border-top: 1px solid #CCCCCC;}
#content h1 a, h1 a:visited { color: #007DFC; text-decoration: none; }
#content h1 a:hover { color: #888888; text-decoration: none; }
#content 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;}
#content h2 a, h2 a:visited { color: #007dfc; text-decoration: none; }
#content 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;
}

.examplesButton {
	background:#F3F2ED;
	border:1px solid black
	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;
}

.dldButton {
	background:#F3F2ED;
	border:1px solid black
	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;
}

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

#content table { border-collapse:collapse;}
#content table th.left { text-align:left !important; }
#content table tr.row0 td { background-color: #FFFFFF; border: Opx solid #E7E7E7;}
#content table tr.row1 td { background-color: #E7E7E7; border: Opx solid #FFFFFF;}
#content table td.top { vertical-align:top; }
#content table td.right { text-align:right; }
#content table td.center { text-align:center; }
#content table td.relevantInformation { font-weight:bold; }
#content table td.relevantInformationSmall { font-size: 11px; 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, #restartpointwritebuffers_graph, #temporaryfile_graph, #autovacuum_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, #requestsdatabases_graph, #requestsapplications_graph, #autovacuumbytable_graph, #autovacuumtuplesremoved_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>
};

}


sub html_header
{
	my $date = localtime(time);
	my $style = &get_page_style();
	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
};
	if (!$nograph) {
		my @jscode = <DATA>;
		print $fh <<EOF
@jscode
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'} || exists $connection_info{chronos})) {
			print $fh qq{<a href="#HourlyStatsReport">Hourly statistics</a> | };
		}
		my $totype = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
		if (!$disable_type && $totype) {
			print $fh qq{<a href="#QueriesByTypeReport">Queries by type</a> | };
			print $fh qq{<a href="#RequestsDatabaseReport">Queries per database</a> | } if (scalar keys %database_info > 1);
			print $fh qq{<a href="#RequestsApplicationReport">Queries per application</a> | } if (scalar keys %application_info > 1);
		}
		if (!$disable_query && ($#top_locked_info >= 0)) {
			print $fh qq{
<a href="#MostFrequentWaitingQueries">Most frequent waiting queries (N)</a> | 
<a href="#QueriesThatWaitedTheMost">Queries that waited the most</a> | 
};
		}
		if (!$disable_query && ($#top_tempfile_info >= 0)) {
			print $fh qq{
<a href="#QueriesMostTemporaryFiles">Queries generating the most temporary files (N)</a> | 
<a href="#QueriesLargestTemporaryFiles">Queries generating the largest temporary files</a> | 
};
		}
		if (!$disable_query && ($#top_slowest >= 0)) {
			print $fh qq{
<a href="#SlowestQueriesReport">Slowest queries</a> | 
<a href="#NormalizedQueriesMostTimeReport">Queries that took up the most time (N)</a> | 
<a href="#NormalizedQueriesSlowestAverageReport">Slowest queries (N)</a> | 
};
		} 
		if (!$disable_query && (scalar keys %normalyzed_info > 0)) {
			print $fh "<a href=\"#NormalizedQueriesMostFrequentReport\">Most frequent queries (N)</a> | ";
		}
		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> | };
			}
		}
	}
	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'} || exists $connection_info{chronos})) {
			print $fh qq{<li><a href="#HourlyStatsReport">Hourly statistics</a></li>};
		}
		my $totype = $overall_stat{'SELECT'} + $overall_stat{'INSERT'} + $overall_stat{'UPDATE'} + $overall_stat{'DELETE'};
		if (!$disable_type && $totype) {
			print $fh qq{<li><a href="#QueriesByTypeReport">Queries by type</a></li>};
			print $fh qq{<li><a href="#RequestsDatabaseReport">Queries per database</a></li>} if (scalar keys %database_info > 1);
			print $fh qq{<li><a href="#RequestsApplicationReport">Queries per application</a></li>} if (scalar keys %application_info > 1);
		}
		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>};
			}
		}
		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>};
			}
		}
		if (!$disable_query && ($#top_locked_info >= 0)) {
			print $fh qq{<li><a href="#MostFrequentWaitingQueries">Most frequent waiting queries (N)</a></li>
<li><a href="#QueriesThatWaitedTheMost">Queries that waited the most</a></li>};
		}
		if (!$disable_query && ($#top_tempfile_info >= 0)) {
			print $fh qq{
<li><a href="#QueriesMostTemporaryFiles">Queries generating the most temporary files (N)</a></li>
<li><a href="#QueriesLargestTemporaryFiles">Queries generating the largest temporary files</a></li>
};
		}
		if (!$disable_query && ($#top_slowest >= 0)) {
			print $fh qq{<li><a href="#SlowestQueriesReport">Slowest queries</a></li>
<li><a href="#NormalizedQueriesMostTimeReport">Queries that took up the most time (N)</a></li>
<li><a href="#NormalizedQueriesSlowestAverageReport">Slowest queries (N)</a></li>
};
		}
		if (!$disable_query && (scalar keys %normalyzed_info > 0)) {
			print $fh qq{<li><a href="#NormalizedQueriesMostFrequentReport">Most frequent 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 information
	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 $overall_stat{'first_log_ts'} to $overall_stat{'last_log_ts'}</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;
	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>
};
	}
	if ($autovacuum_info{count}) {
		print $fh qq{
<li>Total number of automatic vacuums: $autovacuum_info{count}</li>
};
	}
	if ($autoanalyze_info{count}) {
		print $fh qq{
<li>Total number of automatic analyzes: $autoanalyze_info{count}</li>
};
	}
	print $fh qq{
</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{size} / $tempfile_info{count}));
		print $fh qq{
<li>Number of temporary files: $tempfile_info{count}</li>
<li>Max size of temporary files: $fmt_temp_maxsise</li>
<li>Average size of temporary files: $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>
};
	}
	if (scalar keys %database_info > 1) {
		my $db_count = scalar keys %database_info;
		print $fh qq{
<li>Total number of databases: $db_count</li>
};
	}

	print $fh qq{
</ul>
</td></tr></table>
</div>
};

	# Declare variables used to draw graphs
	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);
	# Set graphs limits
	$overall_stat{'first_log_ts'} =~ /(\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;
	$overall_stat{'last_log_ts'} =~ /(\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;

	# Start creating hourly reports
	if (!$disable_hourly && ($overall_stat{'queries_number'} || exists $connection_info{chronos})) {
		print $fh qq{
<h2 id="HourlyStatsReport">Hourly statistics <a href="#top" title="Back to top">^</a></h2>
};
	}
	if (!$disable_hourly && $overall_stat{'queries_number'}) {
		print $fh qq{
<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>Min/Max/Avg&nbsp;duration&nbsp;</th>
		<th>Count</th>
		<th>Avg&nbsp;duration&nbsp;</th>
		<th>INSERT</th>
		<th>UPDATE</th>
		<th>DELETE</th>
		<th>Avg&nbsp;duration&nbsp;</th>
};
		if (exists $connection_info{chronos}) {
			print $fh "		<th>Count</th><th>Avg/s</th>\n";
		}
		if (exists $session_info{chronos}) {
			print $fh "		<th>Count</th><th>Avg&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}{min}),"/",&convert_time($per_hour_info{$d}{$h}{max}),"/",&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) {

			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 minute
						$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 (!$disable_hourly && $connection_info{'count'}) {
		if ($graph) {
			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 minute
							$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 = '';
			}
		}
	}

	if (!$disable_hourly && $overall_stat{'queries_number'}) {
		if ($graph) {
			# 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) {

				# 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 (!$disable_hourly && (scalar keys %per_hour_info > 0)) {
		if ($tempfile_info{count} || exists $checkpoint_info{chronos} || exists $restartpoint_info{chronos} || exists $autovacuum_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 ($checkpoint_info{wbuffer}) {
			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 ($restartpoint_info{wbuffer}) {
			if (exists $restartpoint_info{chronos}) {
				print $fh qq{<th colspan="4">Restartpoints</th>};
			}
		}
		if (exists $autovacuum_info{chronos}) {
			print $fh "		<th colspan=\"2\">Autovacuum</th>\n";
		}
		if ($tempfile_info{count} || exists $checkpoint_info{chronos} || exists $restartpoint_info{chronos}) {
			print $fh qq{</tr><tr>};
		}
		if ($tempfile_info{count}) {
			print $fh qq{<th>Count</th><th>Avg&nbsp;size</th>};
		}
		if ($checkpoint_info{wbuffer}) {
			print $fh
	qq{<th>Written 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>Avg time (sec)</th>};
		}
		if ($restartpoint_info{wbuffer}) {
			print $fh
	qq{<th>Written buffers</th><th>Write time (sec)</th><th>Sync time (sec)</th><th>Total time (sec)</th>};
		}
		if (exists $autovacuum_info{chronos}) {
			print $fh "		<th>VACUUMs</th><th>ANALYZEs</th>\n";
		}
		if ($tempfile_info{count} || exists $checkpoint_info{chronos} || exists $restartpoint_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} && $checkpoint_info{wbuffer}) {
						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>";
						} 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 (exists $checkpoint_info{chronos} && $checkpoint_info{warning}) {
						if (exists $checkpoint_info{chronos}{$d}{$h}{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>";
						}
					}
					if (exists $restartpoint_info{chronos} && $restartpoint_info{wbuffer}) {
						if (exists $restartpoint_info{chronos}{$d}{$h}) {
							print $fh "<td class=\"right\">", &comma_numbers($restartpoint_info{chronos}{$d}{$h}{wbuffer}) || 0,
								"</td><td class=\"right\">", &comma_numbers($restartpoint_info{chronos}{$d}{$h}{write}) || 0,
								"</td><td class=\"right\">", &comma_numbers($restartpoint_info{chronos}{$d}{$h}{sync})  || 0,
								"</td><td class=\"right\">", &comma_numbers($restartpoint_info{chronos}{$d}{$h}{total}) || 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>";
						}
					}
					if (exists $autovacuum_info{chronos}) {
						print $fh "<td class=\"right\">", &comma_numbers($autovacuum_info{chronos}{"$d"}{"$h"}{count} || 0), "</td>",
							  "<td class=\"right\">", &comma_numbers($autoanalyze_info{chronos}{"$d"}{"$h"}{count} || 0), "</td>";
					}
					print $fh "</tr>\n";
					$c++;
				}
			}
			print $fh "</table>\n";
		}
	}
	if (!$disable_hourly && $graph) {
		# checkpoint size
		if (exists $checkpoint_info{chronos} && $checkpoint_info{wbuffer}) {
			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 = '';
		}

		# restartpoint size
		if (exists $restartpoint_info{chronos} && $restartpoint_info{wbuffer}) {
			foreach my $tm (sort {$a <=> $b} keys %{$restartpoint_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, " . ($restartpoint_info{chronos}{$tm}{$h}{wbuffer} || 0) . "],";
				}
			}
			$d1 =~ s/,$//;
			&flotr2_graph(
				6,         'restartpointwritebuffers_graph', $d1, '', '', 'Restartpoint write buffers',
				'Buffers', 'Write buffers',                '',  ''
			);
			$d1 = '';
		}

		# 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 = '';
		}

		# VACUUMs and ANALYZEs
		if (exists $autovacuum_info{chronos}) {
			foreach my $tm (sort {$a <=> $b} keys %{$autovacuum_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, " . ($autovacuum_info{chronos}{$tm}{$h}{count}  || 0) . "],";
					$d2 .= "[$t, " . ($autoanalyze_info{chronos}{$tm}{$h}{count} || 0) . "],";
				}
			}
			$d1 =~ s/,$//;
			$d2 =~ s/,$//;
			&flotr2_graph(
				9, 'autovacuum_graph', $d1, $d2, '', 'Autovacuum actions',
				'', 'VACUUMs', 'ANALYZEs'
			);
			$d1 = '';
			$d2 = '';
		}
	}

	if ($graph) {
		# VACUUM stats per table
		if ($autovacuum_info{count} > 0) {
			print $fh qq{
	<h2 id="VacuumByTableReport">VACUUMs by table <a href="#top" title="Back to top">^</a></h2>
	<table>
	<tr><td width="500" align="left" valign="top">
	<table class="SmallTableList">
		<tr>
			<th>Table</th>
			<th>VACUUMs</th>
			<th>Index scans</th>
			<th>Tuples removed</th>
			<th>Pages removed</th>
		</tr>
	};
			my $total_count   = 0;
			my $total_idxscan = 0;
			my $total_tuples = 0;
			my $total_pages = 0;
			foreach my $t (sort keys %{$autovacuum_info{tables}}) {
				print $fh "<tr class=\"row1\"><td>", $t,
				    "</td><td class=\"right\">", $autovacuum_info{tables}{$t}{vacuums},
					"</td><td class=\"right\">", $autovacuum_info{tables}{$t}{idxscans},
					"</td><td class=\"right\">", $autovacuum_info{tables}{$t}{tuples}{removed},
					"</td><td class=\"right\">", $autovacuum_info{tables}{$t}{pages}{removed},
					"</td></tr>\n";
				$total_count   += $autovacuum_info{tables}{$t}{vacuums};
				$total_idxscan += $autovacuum_info{tables}{$t}{idxscans};
				$total_tuples += $autovacuum_info{tables}{$t}{tuples}{removed};
				$total_pages += $autovacuum_info{tables}{$t}{pages}{removed};
			}
			print $fh "<tr class=\"row1\"><td><b>Total</b></td><td class=\"right\">", $total_count,
				"</td><td class=\"right\">", $total_idxscan,
				"</td><td class=\"right\">", $total_tuples,
				"</td><td class=\"right\">", $total_pages, "</td></tr>\n";
			print $fh "</table></td><td width=\"500\" align=\"center\" valign=\"top\">\n";
			if ($graph && $total_count) {
				my %data = ();
				foreach my $t (sort keys %{$autovacuum_info{tables}}) {
					if ((($autovacuum_info{tables}{$t}{vacuums} * 100) / $total_count) > $pie_percentage_limit) {
						$data{$t} = $autovacuum_info{tables}{$t}{vacuums} || 0;
					} else {
						$data{"Others"} += $autovacuum_info{tables}{$t}{vacuums} || 0;
					}
				}
				&flotr2_piegraph(18, 'autovacuumbytable_graph', 'Autovacuum per table', %data);
				%data = ();
				if ($total_tuples) {
					print $fh "<br />\n";
					foreach my $t (sort keys %{$autovacuum_info{tables}}) {
						if ((($autovacuum_info{tables}{$t}{tuples}{removed} * 100) / $total_tuples) > $pie_percentage_limit) {
							$data{$t} = $autovacuum_info{tables}{$t}{tuples}{removed} || 0;
						} else {
							$data{"Others"} += $autovacuum_info{tables}{$t}{tuples}{removed} || 0;
						}
					}
					&flotr2_piegraph(19, 'autovacuumtuplesremoved_graph', 'Autovacuum tuples removed per table', %data);
				}
			}
			print $fh "</td></tr></table>\n";
		}

		# ANALYZE stats per table
		if ($autoanalyze_info{count} > 0) {
			print $fh qq{
	<h2 id="AnalyzeByTableReport">ANALYZEs by table <a href="#top" title="Back to top">^</a></h2>
	<table>
	<tr><td width="500" align="left" valign="top">
	<table class="SmallTableList">
		<tr>
			<th>Table</th>
			<th>ANALYZEs</th>
		</tr>
	};
			my $total_count   = 0;
			my $total_idxscan = 0;
			foreach my $t (sort keys %{$autoanalyze_info{tables}}) {
				print $fh "<tr class=\"row1\"><td>", $t,
				    "</td><td class=\"right\">", $autoanalyze_info{tables}{$t}{analyzes},
					"</td></tr>\n";
				$total_count   += $autoanalyze_info{tables}{$t}{analyzes};
			}
			print $fh "<tr class=\"row1\"><td><b>Total</b></td><td class=\"right\">", $total_count,
				"</td></tr>\n";
			print $fh "</table></td></tr></table>\n";
		}
	}

	# INSERT/DELETE/UPDATE/SELECT repartition
	$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'};
	if (!$disable_type && $totala) {
		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>
};
		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(22, 'queriesbytype_graph', 'Type of queries', %data);
		}
		print $fh "</td></tr></table>\n";

		# Show request per database statistics
		if (scalar keys %database_info > 1) {
			print $fh qq{
<h2 id="RequestsDatabaseReport">Queries 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>Request type</th>
		<th>Count</th>
	</tr>
};
			my $total_count = 0;
			foreach my $d (sort keys %database_info) {
				print $fh "<tr class=\"row1\"><td colspan=\"2\">$d</td><td class=\"right\">",
					&comma_numbers($database_info{$d}{count}), "</td></tr>\n";
				$total_count += $database_info{$d}{count};
				foreach my $r (sort keys %{$database_info{$d}}) {
					next if ($r eq 'count');
					print $fh "<tr class=\"row0\"><td colspan=\"2\" class=\"right\">$r</td><td class=\"right\">",
						&comma_numbers($database_info{$d}{$r}), "</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 %database_info) {
					if ((($database_info{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
						$infos{$d} = $database_info{$d}{count} || 0;
					} else {
						$infos{"Sum databases < $pie_percentage_limit%"} += $database_info{$d}{count} || 0;
						push(@small, $d);
					}
				}
				if ($#small == 0) {
					$infos{$small[0]} = $infos{"Sum databases < $pie_percentage_limit%"};
					delete $infos{"Sum databases < $pie_percentage_limit%"};
				}
				&flotr2_piegraph(20, 'requestsdatabases_graph', 'Queries per database', %infos);
			}
			print $fh "</td></tr></table>\n";
		}

		# Show request per application statistics
		if (scalar keys %application_info > 1) {
			print $fh qq{
<h2 id="RequestsApplicationReport">Queries per application <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>Request type</th>
		<th>Count</th>
	</tr>
};
			my $total_count = 0;
			foreach my $d (sort keys %application_info) {
				print $fh "<tr class=\"row1\"><td colspan=\"2\">$d</td><td class=\"right\">",
					&comma_numbers($application_info{$d}{count}), "</td></tr>\n";
				$total_count += $application_info{$d}{count};
				foreach my $r (sort keys %{$application_info{$d}}) {
					next if ($r eq 'count');
					print $fh "<tr class=\"row0\"><td colspan=\"2\" class=\"right\">$r</td><td class=\"right\">",
						&comma_numbers($application_info{$d}{$r}), "</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 %application_info) {
					if ((($application_info{$d}{count} * 100) / $total_count) > $pie_percentage_limit) {
						$infos{$d} = $application_info{$d}{count} || 0;
					} else {
						$infos{"Sum applications < $pie_percentage_limit%"} += $application_info{$d}{count} || 0;
						push(@small, $d);
					}
				}
				if ($#small == 0) {
					$infos{$small[0]} = $infos{"Sum applications < $pie_percentage_limit%"};
					delete $infos{"Sum applications < $pie_percentage_limit%"};
				}
				&flotr2_piegraph(21, 'requestsapplications_graph', 'Queries per application', %infos);
			}
			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>Avg&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>Avg&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>Avg&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>Avg&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 lock wait detailed informations
	if (!$disable_lock && scalar keys %lock_info > 0) {

		my @top_locked_queries;
		foreach my $h (keys %normalyzed_info) {
			if (exists($normalyzed_info{$h}{locks})) {
				push (@top_locked_queries, [$h, $normalyzed_info{$h}{locks}{count}, $normalyzed_info{$h}{locks}{wait},
				$normalyzed_info{$h}{locks}{minwait}, $normalyzed_info{$h}{locks}{maxwait}]);
			}
		}

		# Most frequent waiting queries (N)
		@top_locked_queries = sort {$b->[2] <=> $a->[2]} @top_locked_queries;
		print $fh qq{
<h2 id="MostFrequentWaitingQueries">Most frequent waiting queries (N)<a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
<tr>
<th>Rank</th>
<th>Count</th>
<th>Total&nbsp;wait&nbsp;time&nbsp;(s)</th>
<th>Min/Max/Avg&nbsp;duration&nbsp;(s)</th>
<th>Query</th>
</tr>
};
		my $idx = 1;
		for (my $i = 0 ; $i <= $#top_locked_queries ; $i++) {
			last if ($i > $end_top);
			my $col = $i % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">", $i + 1, "</td><td class=\"relevantInformation top center\">",
			$top_locked_queries[$i]->[1], "</td><td class=\"center top\">", &convert_time($top_locked_queries[$i]->[2]),
			"</td><td class=\"center top\">", &convert_time($top_locked_queries[$i]->[3]), "/", &convert_time($top_locked_queries[$i]->[4]), "/",
			&convert_time(($top_locked_queries[$i]->[4] / $top_locked_queries[$i]->[1])),
			"</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
			&highlight_code($top_locked_queries[$i]->[0]), "</div>\n";
			my $k = $top_locked_queries[$i]->[0];
			if ($normalyzed_info{$k}{count} > 1) {
				print $fh
"<input type=\"button\" class=\"examplesButton\" id=\"button_QueriesThatWaitedTheMost_$idx\" name=\"button_QueriesThatWaitedTheMost_$idx\" value=\"Show examples\" onclick=\"javascript:toggle('button_QueriesThatWaitedTheMost_$idx', 'examples_QueriesThatWaitedTheMost_$idx', 'examples');\" /><div id=\"examples_QueriesThatWaitedTheMost_$idx\" class=\"examples\" style=\"display:none;\">";
				my $j = 0;
				foreach my $d (sort {$b <=> $a} keys %{$normalyzed_info{$k}{samples}}) {
					my $colb = $j % 2;
					my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
					$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
					$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
					$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
					$db =~ s/^, / - /;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}$db\"><div class=\"sql\" onclick=\"sql_format(this)\">",
						&convert_time($d), " | ", &highlight_code($normalyzed_info{$k}{samples}{$d}{query}), "</div></div>";
					$j++;
				}
				print $fh "</div>";
			}
			print $fh "</td></tr>\n";
			$idx++;
		}
		print $fh "</table>\n";
		@top_locked_queries = ();

		# Queries that waited the most
		@top_locked_info = sort {$b->[1] <=> $a->[1]} @top_locked_info;
		print $fh qq{
<h2 id="QueriesThatWaitedTheMost">Queries that waited the most<a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
<tr>
<th>Rank</th>
<th>Wait&nbsp;time&nbsp;(s)</th>
<th>Query</th>
</tr>
};
		for (my $i = 0 ; $i <= $#top_locked_info ; $i++) {
			my $col = $i % 2;
			my $ttl = $top_locked_info[$i]->[1] || '';
			my $db = " - database: $top_locked_info[$i]->[3]" if ($top_locked_info[$i]->[3]);
			$db .= ", user: $top_locked_info[$i]->[4]" if ($top_locked_info[$i]->[4]);
			$db .= ", remote: $top_locked_info[$i]->[5]" if ($top_locked_info[$i]->[5]);
			$db .= ", app: $top_locked_info[$i]->[6]" if ($top_locked_info[$i]->[6]);
			$db =~ s/^, / - /;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">", $i + 1, "</td><td class=\"relevantInformation top center\">",
			&convert_time($top_locked_info[$i]->[0]),
			"</td><td title=\"$ttl$db\"><div class=\"sql\" onclick=\"sql_format(this)\">",
			&highlight_code($top_locked_info[$i]->[2]), "</div></td></tr>\n";
		}
		print $fh "</table>\n";
	}

	# Show temporary files detailed informations
	if (!$disable_temporary && scalar keys %tempfile_info > 0) {

		my @top_temporary;
		foreach my $h (keys %normalyzed_info) {
			if (exists($normalyzed_info{$h}{tempfiles})) {
				push (@top_temporary, [$h, $normalyzed_info{$h}{tempfiles}{count}, $normalyzed_info{$h}{tempfiles}{size},
				$normalyzed_info{$h}{tempfiles}{minsize}, $normalyzed_info{$h}{tempfiles}{maxsize}]);
			}
		}

		# Queries generating the most temporary files (N)
		@top_temporary = sort {$b->[1] <=> $a->[1]} @top_temporary;
		print $fh qq{
<h2 id="QueriesMostTemporaryFiles">Queries generating the most temporary files (N)<a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
<tr>
<th>Rank</th>
<th>Count</th>
<th>Total&nbsp;size</th>
<th>Min/Max/Avg&nbsp;size</th>
<th>Query</th>
</tr>
};
		my $idx = 1;
		for (my $i = 0 ; $i <= $#top_temporary ; $i++) {
			last if ($i > $end_top);
			my $col = $i % 2;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">", $i + 1, "</td><td class=\"relevantInformation top center\">",
			$top_temporary[$i]->[1], "</td><td class=\"center top\">", &comma_numbers($top_temporary[$i]->[2]),
			"</td><td class=\"center top\">", &comma_numbers($top_temporary[$i]->[3]),
			"/", &comma_numbers($top_temporary[$i]->[4]), "/",
			&comma_numbers(sprintf("%.2f", $top_temporary[$i]->[2] / $top_temporary[$i]->[1])),
			"</td><td><div class=\"sql\" onclick=\"sql_format(this)\">",
			&highlight_code($top_temporary[$i]->[0]), "</div>";
			my $k = $top_temporary[$i]->[0];
			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;
					my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
					$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
					$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
					$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
					$db =~ s/^, / - /;
					print $fh "<div class=\"example$d\" title=\"$normalyzed_info{$k}{samples}{$d}{date}$db\"><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";
		@top_temporary = ();

		# Top queries generating the largest temporary files
		@top_tempfile_info = sort {$b->[1] <=> $a->[1]} @top_tempfile_info;

		print $fh qq{
<h2 id="QueriesLargestTemporaryFiles">Queries generating the largest temporary files<a href="#top" title="Back to top">^</a></h2>
<table class="queryList">
<tr>
<th>Rank</th>
<th>Size</th>
<th>Query</th>
</tr>
};
		for (my $i = 0 ; $i <= $#top_tempfile_info ; $i++) {
			my $col = $i % 2;
			my $ttl = $top_tempfile_info[$i]->[1] || '';
			my $db = " - database: $top_tempfile_info[$i]->[3]" if ($top_tempfile_info[$i]->[3]);
			$db .= ", user: $top_tempfile_info[$i]->[4]" if ($top_tempfile_info[$i]->[4]);
			$db .= ", remote: $top_tempfile_info[$i]->[5]" if ($top_tempfile_info[$i]->[5]);
			$db .= ", app: $top_tempfile_info[$i]->[6]" if ($top_tempfile_info[$i]->[6]);
			$db =~ s/^, / - /;
			print $fh "<tr class=\"row$col\"><td class=\"center top\">", $i + 1, "</td><td class=\"relevantInformation top center\">",
			&comma_numbers($top_tempfile_info[$i]->[0]),
			"</td><td title=\"$ttl$db\"><div class=\"sql\" onclick=\"sql_format(this)\">",
			&highlight_code($top_tempfile_info[$i]->[2]), "</div></td></tr>\n";
		}
		print $fh "</table>\n";
	}

	# Show top information
	if (!$disable_query && ($#top_slowest >= 0)) {
		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] || '';
			my $db = " - database: $top_slowest[$i]->[3]" if ($top_slowest[$i]->[3]);
			$db .= ", user: $top_slowest[$i]->[4]" if ($top_slowest[$i]->[4]);
			$db .= ", remote: $top_slowest[$i]->[5]" if ($top_slowest[$i]->[5]);
			$db .= ", app: $top_slowest[$i]->[6]" if ($top_slowest[$i]->[6]);
			$db =~ s/^, / - /;
			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$db\"><div class=\"sql\" onclick=\"sql_format(this)\">",
				&highlight_code($top_slowest[$i]->[2]), "</div></td></tr>\n";
		}
		print $fh "</table>\n";

		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>Min/Max/Avg&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>Avg&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}{min}),"/", &convert_time($normalyzed_info{$k}{max}),"/", &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;
					my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
					$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
					$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
					$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
					$db =~ s/^, / - /;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}$db\"><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_query && (scalar keys %normalyzed_info > 0)) {

		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>Min/Max/Avg&nbsp;duration&nbsp;(s)</th>
		<th>Query</th>
	</tr>
};
		my $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>Avg&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}{min}),"/",&convert_time($normalyzed_info{$k}{max}),"/",
				&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;
					my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
					$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
					$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
					$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
					$db =~ s/^, / - /;
					print $fh
"<div class=\"example$d\" title=\"$normalyzed_info{$k}{samples}{$d}{date}$db\"><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_query && ($#top_slowest >= 0)) {
		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>Min/Max/Avg&nbsp;duration&nbsp;(s)</th>

		<th>Times executed</th>
		<th>Total duration</th>
		<th>Query</th>
	</tr>
};
		my $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=\"relevantInformationSmall top center\">",
				&convert_time($normalyzed_info{$k}{min}), "/",
				&convert_time($normalyzed_info{$k}{max}), "/",
				&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>Avg&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;
					my $db = " - database: $normalyzed_info{$k}{samples}{$d}{db}" if ($normalyzed_info{$k}{samples}{$d}{db});
					$db .= ", user: $normalyzed_info{$k}{samples}{$d}{user}" if ($normalyzed_info{$k}{samples}{$d}{user});
					$db .= ", remote: $normalyzed_info{$k}{samples}{$d}{remote}" if ($normalyzed_info{$k}{samples}{$d}{remote});
					$db .= ", app: $normalyzed_info{$k}{samples}{$d}{app}" if ($normalyzed_info{$k}{samples}{$d}{app});
					$db =~ s/^, / - /;
					print $fh
"<div class=\"example$colb\" title=\"$normalyzed_info{$k}{samples}{$d}{date}$db\"><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 information
	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 $overall_stat{'first_log_ts'} to $overall_stat{'last_log_ts'}</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 class=\"errorInformation$c\">Database: $error_info{$k}{db}[$i]</div>\n" if ($error_info{$k}{db}[$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 "<div class=\"errorInformation\">Database: $error_info{$k}{db}[0]</div>\n" if ($error_info{$k}{db}[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";
	}

}

sub load_stats
{

	my $fd = shift;
	my %stats = %{ fd_retrieve($fd) };
	my %_overall_stat = %{$stats{overall_stat}};
	my %_normalyzed_info = %{$stats{normalyzed_info}};
	my %_error_info = %{$stats{error_info}};
	my %_connection_info = %{$stats{connection_info}};
	my %_database_info = %{$stats{database_info}};
	my %_application_info = %{$stats{application_info}};
	my %_checkpoint_info = %{$stats{checkpoint_info}};
	my %_restartpoint_info = %{$stats{restartpoint_info}};
	my %_session_info = %{$stats{session_info}};
	my %_tempfile_info = %{$stats{tempfile_info}};
	my %_logs_type = %{$stats{logs_type}};
	my %_lock_info = %{$stats{lock_info}};
	my %_per_hour_info = %{$stats{per_hour_info}};
	my %_per_minute_info = %{$stats{per_minute_info}};
	my @_top_slowest = @{$stats{top_slowest}};
	my $_nlines = $stats{nlines};
	my $_first_log_timestamp = $stats{first_log_timestamp};
	my $_last_log_timestamp = $stats{last_log_timestamp};
	my @_log_files = @{$stats{log_files}};
	my %_autovacuum_info = %{$stats{autovacuum_info}};
	my %_autoanalyze_info = %{$stats{autoanalyze_info}};

	return if (!$_overall_stat{queries_number} && !$_overall_stat{'errors_number'});

	### overall_stat ###

	$overall_stat{queries_number} += $_overall_stat{queries_number};

	$overall_stat{'first_log_ts'} = $_overall_stat{'first_log_ts'}
		if not $overall_stat{'first_log_ts'}
			or $overall_stat{'first_log_ts'} gt $_overall_stat{'first_log_ts'};

	$overall_stat{'last_log_ts'} = $_overall_stat{'last_log_ts'}
		if not $overall_stat{'last_log_ts'}
			or $overall_stat{'last_log_ts'} lt $_overall_stat{'last_log_ts'};

	$overall_stat{first_query_ts} = $_overall_stat{first_query_ts}
		if not $overall_stat{first_query_ts}
			or $overall_stat{first_query_ts} gt $_overall_stat{first_query_ts};

	$overall_stat{last_query_ts} = $_overall_stat{last_query_ts}
		if not $overall_stat{last_query_ts}
			or $overall_stat{last_query_ts} lt $_overall_stat{last_query_ts};

	$overall_stat{errors_number} += $_overall_stat{errors_number};
	$overall_stat{queries_duration} += $_overall_stat{queries_duration};

	$overall_stat{DELETE} += $_overall_stat{DELETE}
		if exists $_overall_stat{DELETE};
	$overall_stat{UPDATE} += $_overall_stat{UPDATE}
		if exists $_overall_stat{UPDATE};
	$overall_stat{INSERT} += $_overall_stat{INSERT}
		if exists $_overall_stat{INSERT};
	$overall_stat{SELECT} += $_overall_stat{SELECT}
		if exists $_overall_stat{SELECT};

	foreach my $k (keys %{$_overall_stat{query_peak}}) {
		$overall_stat{query_peak}{$k} += $_overall_stat{query_peak}{$k};
	}

	# FIXME == $error_info ??
	foreach my $k (keys %{$_overall_stat{unique_normalized_errors}}) {
		$overall_stat{unique_normalized_errors}{$k} += $_overall_stat{unique_normalized_errors}{$k};
	}


	$logs_type{ERROR} += $_logs_type{ERROR} if exists $_logs_type{ERROR};
	$logs_type{LOG} += $_logs_type{LOG} if exists $_logs_type{LOG};
	$logs_type{DETAIL} += $_logs_type{DETAIL} if exists $_logs_type{DETAIL};
	$logs_type{STATEMENT} += $_logs_type{STATEMENT} if exists $_logs_type{STATEMENT};

	### database_info ###

	foreach my $db (keys %_database_info) {
		foreach my $k (keys %{ $_database_info{$db} }) {
			$database_info{$db}{$k} += $_database_info{$db}{$k};
		}
	}

	### application_info ###

	foreach my $app (keys %_application_info) {
		foreach my $k (keys %{ $_application_info{$app} }) {
			$application_info{$app}{$k} += $_application_info{$app}{$k};
		}
	}

	### connection_info ###

	foreach my $db (keys %{ $_connection_info{database} }) {
		$connection_info{database}{$db} += $_connection_info{database}{$db};
	}

	foreach my $db (keys %{ $_connection_info{database_user} }) {
		foreach my $user (keys %{ $_connection_info{database_user}{$db} }) {
			$connection_info{database_user}{$db}{$user} += $_connection_info{database_user}{$db}{$user};
		}
	}

	foreach my $user (keys %{ $_connection_info{user} }) {
		$connection_info{user}{$user} += $_connection_info{user}{$user};
	}

	foreach my $host (keys %{ $_connection_info{host} }) {
		$connection_info{host}{$host} += $_connection_info{host}{$host};
	}

	$connection_info{count} += $_connection_info{count};

	foreach my $day (keys %{ $_connection_info{chronos} }) {
		foreach my $hour (keys %{ $_connection_info{chronos}{$day} }) {

			foreach my $db (keys %{ $_connection_info{chronos}{$day}{$hour}{database} }) {
				$connection_info{chronos}{$day}{$hour}{database}{$db} += $_connection_info{chronos}{$day}{$hour}{database}{$db};
			}

			foreach my $db (keys %{ $_connection_info{chronos}{$day}{$hour}{database_user} }) {
				foreach my $user (keys %{ $_connection_info{chronos}{$day}{$hour}{database_user}{$db} }) {
					$connection_info{chronos}{$day}{$hour}{database_user}{$db}{$user} +=
						$_connection_info{chronos}{$day}{$hour}{database_user}{$db}{$user};
				}
			}

			$connection_info{chronos}{$day}{$hour}{count} += $_connection_info{chronos}{$day}{$hour}{count};

			foreach my $user (keys %{ $_connection_info{chronos}{$day}{$hour}{user} }) {
				$connection_info{chronos}{$day}{$hour}{user}{$user} +=
					$_connection_info{chronos}{$day}{$hour}{user}{$user};
			}

			foreach my $host (keys %{ $_connection_info{chronos}{$day}{$hour}{host} }) {
				$connection_info{chronos}{$day}{$hour}{host}{$host} +=
					$_connection_info{chronos}{$day}{$hour}{host}{$host};
			}
		}
	}

	### log_files ###

	foreach my $f (@_log_files) {
		push(@log_files, $f) if (!grep(m#^$f$#, @_log_files));
	}

	### per_hour_info ###

	foreach my $day (keys %_per_hour_info) {
		foreach my $hour (keys %{ $_per_hour_info{$day} }) {
			$per_hour_info{$day}{$hour}{count} += $_per_hour_info{$day}{$hour}{count};
			$per_hour_info{$day}{$hour}{duration} += $_per_hour_info{$day}{$hour}{duration};
			# Set min / max duration for this query
			if (!exists $per_hour_info{$day}{$hour}{min} || ($per_hour_info{$day}{$hour}{min} > $_per_hour_info{$day}{$hour}{min})) { 
				$per_hour_info{$day}{$hour}{min} = $_per_hour_info{$day}{$hour}{min};
			}
			if (!exists $per_hour_info{$day}{$hour}{max} || ($per_hour_info{$day}{$hour}{max} < $_per_hour_info{$day}{$hour}{max})) { 
				$per_hour_info{$day}{$hour}{max} = $_per_hour_info{$day}{$hour}{max};
			}

			if (exists $_per_hour_info{$day}{$hour}{DELETE}) {
				$per_hour_info{$day}{$hour}{DELETE}{count} += $_per_hour_info{$day}{$hour}{DELETE}{count};
				$per_hour_info{$day}{$hour}{DELETE}{duration} += $_per_hour_info{$day}{$hour}{DELETE}{duration};
			}

			if (exists $_per_hour_info{$day}{$hour}{SELECT}) {
				$per_hour_info{$day}{$hour}{SELECT}{count} += $_per_hour_info{$day}{$hour}{SELECT}{count};
				$per_hour_info{$day}{$hour}{SELECT}{duration} += $_per_hour_info{$day}{$hour}{SELECT}{duration};
			}

			if (exists $_per_hour_info{$day}{$hour}{INSERT}) {
				$per_hour_info{$day}{$hour}{INSERT}{count} += $_per_hour_info{$day}{$hour}{INSERT}{count};
				$per_hour_info{$day}{$hour}{INSERT}{duration} += $_per_hour_info{$day}{$hour}{INSERT}{duration};
			}

			if (exists $_per_hour_info{$day}{$hour}{UPDATE}) {
				$per_hour_info{$day}{$hour}{UPDATE}{count} += $_per_hour_info{$day}{$hour}{UPDATE}{count};
				$per_hour_info{$day}{$hour}{UPDATE}{duration} += $_per_hour_info{$day}{$hour}{UPDATE}{duration};
			}
		}
	}

	### error_info ###

	foreach my $q (keys %_error_info) {
		$error_info{$q}{count} += $_error_info{$q}{count};
		# Keep only the wanted sample number
		if (!exists $error_info{$q}{date} || ($#{$error_info{$q}{date}} < $sample)) {
			push(@{$error_info{$q}{date}},      @{$_error_info{$q}{date}});
			push(@{$error_info{$q}{detail}},    @{$_error_info{$q}{detail}});
			push(@{$error_info{$q}{context}},   @{$_error_info{$q}{context}});
			push(@{$error_info{$q}{statement}}, @{$_error_info{$q}{statement}});
			push(@{$error_info{$q}{hint}},      @{$_error_info{$q}{hint}});
			push(@{$error_info{$q}{error}},     @{$_error_info{$q}{error}});
			push(@{$error_info{$q}{db}},        @{$_error_info{$q}{db}});
			foreach my $day (keys %{ $_error_info{$q}{chronos} }) {
				foreach my $hour (keys %{$_error_info{$q}{chronos}{$day}}) {
					$error_info{$q}{chronos}{$day}{$hour}{count} += $_error_info{$q}{chronos}{$day}{$hour}{count};
				}
			}
		}
	}

	### per_minute_info ###

	foreach my $day (keys %{ $_per_minute_info{connection} }) {
		foreach my $hour (keys %{ $_per_minute_info{connection}{$day} }) {
			foreach my $min (keys %{ $_per_minute_info{connection}{$day}{$hour} }) {
				$per_minute_info{connection}{$day}{$hour}{$min}{count} +=
					$_per_minute_info{connection}{$day}{$hour}{$min}{count};

				foreach my $sec (keys %{ $_per_minute_info{connection}{$day}{$hour}{$min}{second} }) {
					$per_minute_info{connection}{$day}{$hour}{$min}{second}{$sec} +=
						$_per_minute_info{connection}{$day}{$hour}{$min}{second}{$sec};
				}
			}
		}
	}

	foreach my $day (keys %{ $_per_minute_info{query} }) {
		foreach my $hour (keys %{ $_per_minute_info{query}{$day} }) {
			foreach my $min (keys %{ $_per_minute_info{query}{$day}{$hour} }) {
				$per_minute_info{query}{$day}{$hour}{$min}{count} +=
					$_per_minute_info{query}{$day}{$hour}{$min}{count};

				$per_minute_info{query}{$day}{$hour}{$min}{duration} +=
					$_per_minute_info{query}{$day}{$hour}{$min}{duration};

				foreach my $sec (keys %{ $_per_minute_info{query}{$day}{$hour}{$min}{second} }) {
					$per_minute_info{query}{$day}{$hour}{$min}{second}{$sec} +=
						$_per_minute_info{query}{$day}{$hour}{$min}{second}{$sec};
				}
			}
		}
	}

	### lock_info ###

	foreach my $lock (keys %_lock_info) {
		$lock_info{$lock}{count} += $_lock_info{$lock}{count};

		foreach my $day (keys %{ $_lock_info{chronos} }) {
			foreach my $hour (keys %{ $_lock_info{chronos}{$day} }) {
				$lock_info{chronos}{$day}{$hour}{count} += $_lock_info{chronos}{$day}{$hour}{count};
				$lock_info{chronos}{$day}{$hour}{duration} += $_lock_info{chronos}{$day}{$hour}{duration};
			}
		}

		$lock_info{$lock}{duration} += $_lock_info{$lock}{duration};

		foreach my $type (keys %{$_lock_info{$lock}}) {
			next if $type =~ /^(count|chronos|duration)$/;

			$lock_info{$lock}{$type}{count} += $_lock_info{$lock}{$type}{count};
			$lock_info{$lock}{$type}{duration} += $_lock_info{$lock}{$type}{duration};
		}
	}

	### nlines ###

	$nlines += $_nlines;

	### normalyzed_info ###

	foreach my $stmt (keys %_normalyzed_info) {

		foreach my $dt (keys %{$_normalyzed_info{$stmt}{samples}} ) {
			$normalyzed_info{$stmt}{samples}{$dt} = $_normalyzed_info{$stmt}{samples}{$dt};
		}

		# Keep only the top N samples
		my $i = 1;
		foreach my $k (sort {$b <=> $a} keys %{$normalyzed_info{$stmt}{samples}}) {
			if ($i > $sample) {
				delete $normalyzed_info{$stmt}{samples}{$k};
			}
			$i++;
		}

		$normalyzed_info{$stmt}{count} += $_normalyzed_info{$stmt}{count};

		# Set min / max duration for this query
		if (!exists $normalyzed_info{$stmt}{min} || ($normalyzed_info{$stmt}{min} > $_normalyzed_info{$stmt}{min})) { 
			$normalyzed_info{$stmt}{min} = $_normalyzed_info{$stmt}{min};
		}
		if (!exists $normalyzed_info{$stmt}{max} || ($normalyzed_info{$stmt}{max} < $_normalyzed_info{$stmt}{max})) { 
			$normalyzed_info{$stmt}{max} = $_normalyzed_info{$stmt}{max};
		}

		foreach my $day (keys %{$_normalyzed_info{$stmt}{chronos}} ) {
			foreach my $hour (keys %{$_normalyzed_info{$stmt}{chronos}{$day}} ) {
				$normalyzed_info{$stmt}{chronos}{$day}{$hour}{count} +=
					$_normalyzed_info{$stmt}{chronos}{$day}{$hour}{count};
				$normalyzed_info{$stmt}{chronos}{$day}{$hour}{duration} +=
					$_normalyzed_info{$stmt}{chronos}{$day}{$hour}{duration};
			}
		}

		$normalyzed_info{$stmt}{duration} += $_normalyzed_info{$stmt}{duration};

		if (exists $_normalyzed_info{$stmt}{locks}) {
			$normalyzed_info{$stmt}{locks}{count} += $_normalyzed_info{$stmt}{locks}{count};
			$normalyzed_info{$stmt}{locks}{wait} += $_normalyzed_info{$stmt}{locks}{wait};
			if (!exists $normalyzed_info{$stmt}{locks}{minwait} || ($normalyzed_info{$stmt}{locks}{minwait} > $_normalyzed_info{$stmt}{locks}{minwait})) { 
				$normalyzed_info{$stmt}{locks}{minwait} = $_normalyzed_info{$stmt}{locks}{minwait};
			}
			if (!exists $normalyzed_info{$stmt}{locks}{maxwait} || ($normalyzed_info{$stmt}{locks}{maxwait} < $_normalyzed_info{$stmt}{locks}{maxwait})) { 
				$normalyzed_info{$stmt}{locks}{maxwait} = $_normalyzed_info{$stmt}{locks}{maxwait};
			}
		}

		if (exists $_normalyzed_info{$stmt}{tempfiles}) {
			$normalyzed_info{$stmt}{tempfiles}{count} += $_normalyzed_info{$stmt}{tempfiles}{count};
			$normalyzed_info{$stmt}{tempfiles}{size} += $_normalyzed_info{$stmt}{tempfiles}{size};
			if (!exists $normalyzed_info{$stmt}{tempfiles}{minsize} || ($normalyzed_info{$stmt}{tempfiles}{minsize} > $_normalyzed_info{$stmt}{tempfiles}{minsize})) { 
				$normalyzed_info{$stmt}{tempfiles}{minsize} = $_normalyzed_info{$stmt}{tempfiles}{minsize};
			}
			if (!exists $normalyzed_info{$stmt}{tempfiles}{maxsize} || ($normalyzed_info{$stmt}{tempfiles}{maxsize} < $_normalyzed_info{$stmt}{tempfiles}{maxsize})) { 
				$normalyzed_info{$stmt}{tempfiles}{maxsize} = $_normalyzed_info{$stmt}{tempfiles}{maxsize};
			}
		}
	}

	### session_info ###

	foreach my $db (keys %{ $_session_info{database}}) {
		$session_info{database}{$db}{count} += $_session_info{database}{$db}{count};
		$session_info{database}{$db}{duration} += $_session_info{database}{$db}{duration};
	}

	$session_info{count} += $_session_info{count};

	foreach my $day (keys %{ $_session_info{chronos}}) {
		foreach my $hour (keys %{ $_session_info{chronos}{$day}}) {
			$session_info{chronos}{$day}{$hour}{count} += $_session_info{chronos}{$day}{$hour}{count};
			$session_info{chronos}{$day}{$hour}{duration} += $_session_info{chronos}{$day}{$hour}{duration};
		}
	}

	foreach my $user (keys %{ $_session_info{user}}) {
		$session_info{user}{$user}{count}    += $_session_info{user}{$user}{count};
		$session_info{user}{$user}{duration} += $_session_info{user}{$user}{duration};
	}

	$session_info{duration} += $_session_info{duration};

	foreach my $host (keys %{ $_session_info{host}}) {
		$session_info{host}{$host}{count}    += $_session_info{host}{$host}{count};
		$session_info{host}{$host}{duration} += $_session_info{host}{$host}{duration};
	}

	### tempfile_info ###

	$tempfile_info{count} += $_tempfile_info{count}
		if defined $_tempfile_info{count};
	$tempfile_info{size} += $_tempfile_info{size}
		if defined $_tempfile_info{size};
	$tempfile_info{maxsize} = $_tempfile_info{maxsize}
		if defined $_tempfile_info{maxsize} and ( not defined $tempfile_info{maxsize}
			or $tempfile_info{maxsize} < $_tempfile_info{maxsize} );

	foreach my $day ( %{ $_tempfile_info{chronos} } ) {
		foreach my $hour ( %{ $_tempfile_info{chronos}{$day} } ) {

			$tempfile_info{chronos}{$day}{$hour}{count} +=
				$_tempfile_info{chronos}{$day}{$hour}{count}
					if defined $_tempfile_info{chronos}{$day}{$hour}{count};

			$tempfile_info{chronos}{$day}{$hour}{size} +=
				$_tempfile_info{chronos}{$day}{$hour}{size}
					if defined $_tempfile_info{chronos}{$day}{$hour}{size};
		}
	}

	### top_slowest ###
	my @tmp_top_slowest = sort {$b->[0] <=> $a->[0]} (@top_slowest, @_top_slowest);
	@top_slowest = ();
	for (my $i = 0; $i <= $#tmp_top_slowest; $i++) {
		last if ($i == $end_top);
		push(@top_slowest, $tmp_top_slowest[$i]);
	}

	### checkpoint_info ###
	$checkpoint_info{file_removed} += $_checkpoint_info{file_removed};
	$checkpoint_info{sync} += $_checkpoint_info{sync};
	$checkpoint_info{wbuffer} += $_checkpoint_info{wbuffer};
	$checkpoint_info{file_recycled} += $_checkpoint_info{file_recycled};
	$checkpoint_info{total} += $_checkpoint_info{total};
	$checkpoint_info{file_added} += $_checkpoint_info{file_added};
	$checkpoint_info{write} += $_checkpoint_info{write};

	foreach my $day (keys %{ $_checkpoint_info{chronos} }) {
		foreach my $hour (keys %{ $_checkpoint_info{chronos}{$day} }) {
			$checkpoint_info{chronos}{$day}{$hour}{file_removed} += $_checkpoint_info{chronos}{$day}{$hour}{file_removed};
			$checkpoint_info{chronos}{$day}{$hour}{sync} += $_checkpoint_info{chronos}{$day}{$hour}{sync};
			$checkpoint_info{chronos}{$day}{$hour}{wbuffer} += $_checkpoint_info{chronos}{$day}{$hour}{wbuffer};
			$checkpoint_info{chronos}{$day}{$hour}{file_recycled} += $_checkpoint_info{chronos}{$day}{$hour}{file_recycled};
			$checkpoint_info{chronos}{$day}{$hour}{total} += $_checkpoint_info{chronos}{$day}{$hour}{total};
			$checkpoint_info{chronos}{$day}{$hour}{file_added} += $_checkpoint_info{chronos}{$day}{$hour}{file_added};
			$checkpoint_info{chronos}{$day}{$hour}{write} += $_checkpoint_info{chronos}{$day}{$hour}{write};
		}
	}

	### restartpoint_info ###
	$restartpoint_info{sync} += $_restartpoint_info{sync};
	$restartpoint_info{wbuffer} += $_restartpoint_info{wbuffer};
	$restartpoint_info{total} += $_restartpoint_info{total};
	$restartpoint_info{write} += $_restartpoint_info{write};

	foreach my $day (keys %{ $_restartpoint_info{chronos} }) {
		foreach my $hour (keys %{ $_restartpoint_info{chronos}{$day} }) {
			$restartpoint_info{chronos}{$day}{$hour}{sync} += $_restartpoint_info{chronos}{$day}{$hour}{sync};
			$restartpoint_info{chronos}{$day}{$hour}{wbuffer} += $_restartpoint_info{chronos}{$day}{$hour}{wbuffer};
			$restartpoint_info{chronos}{$day}{$hour}{total} += $_restartpoint_info{chronos}{$day}{$hour}{total};
			$restartpoint_info{chronos}{$day}{$hour}{write} += $_restartpoint_info{chronos}{$day}{$hour}{write};
		}
	}

	#### Autovacuum infos ####

	$autovacuum_info{count} += $_autovacuum_info{count};

	foreach my $day (keys %{ $_autovacuum_info{chronos} }) {
		foreach my $hour (keys %{ $_autovacuum_info{chronos}{$day} }) {
			$autovacuum_info{chronos}{$day}{$hour}{count} += $_autovacuum_info{chronos}{$day}{$hour}{count};
		}
	}
	foreach my $table (keys %{ $_autovacuum_info{tables} }) {
		$autovacuum_info{tables}{$table}{vacuums} += $_autovacuum_info{tables}{$table}{vacuums};
		$autovacuum_info{tables}{$table}{idxscans} += $_autovacuum_info{tables}{$table}{idxscans};
		$autovacuum_info{tables}{$table}{tuples}{removed} += $_autovacuum_info{tables}{$table}{tuples}{removed};
		$autovacuum_info{tables}{$table}{pages}{removed} += $_autovacuum_info{tables}{$table}{pages}{removed};
	}

	#### Autoanalyze infos ####

	$autoanalyze_info{count} += $_autoanalyze_info{count};

	foreach my $day (keys %{ $_autoanalyze_info{chronos} }) {
		foreach my $hour (keys %{ $_autoanalyze_info{chronos}{$day} }) {
			$autoanalyze_info{chronos}{$day}{$hour}{count} += $_autoanalyze_info{chronos}{$day}{$hour}{count};
		}
	}
	foreach my $table (keys %{ $_autoanalyze_info{tables} }) {
		$autoanalyze_info{tables}{$table}{analyzes} += $_autoanalyze_info{tables}{$table}{analyzes};
	}

	return;
}

sub dump_as_binary
{
	my $lfh = shift();

	store_fd({
		'overall_stat' => \%overall_stat,
		'normalyzed_info' => \%normalyzed_info,
		'error_info' => \%error_info,
		'connection_info' => \%connection_info,
		'database_info' => \%database_info,
		'application_info' => \%application_info,
		'checkpoint_info' => \%checkpoint_info,
		'restartpoint_info' => \%restartpoint_info,
		'session_info' => \%session_info,
		'tempfile_info' => \%tempfile_info,
		'error_info' => \%error_info,
		'logs_type' => \%logs_type,
		'lock_info' => \%lock_info,
		'per_hour_info' => \%per_hour_info,
		'per_minute_info' => \%per_minute_info,
		'top_slowest' => \@top_slowest,
		'nlines' => $nlines,
		'log_files' => \@log_files,
		'autovacuum_info' => \%autovacuum_info,
		'autoanalyze_info' => \%autoanalyze_info
	}, $lfh) || die ("Couldn't save binary data to «$outfile»!\n");
}

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

	# Try to escape HTML code
	$code =~ s/<([\/a-zA-Z])\b/\&lt;$1/sg;

	# Do not try to prettify queries longuer
	# than 10KB this will take too much time
	return $code if (length($code) > 10240);

	# prettify SQL query
	if (!$noprettify) {
		$sql_prettified->query($code);
		$code = $sql_prettified->beautify;
	}

	return $code if ($nohighlight);

	my $i = 0;
	my @qqcode = ();
	while ($code =~ s/("[^\"]*")/QQCODEY${i}A/s) {
		push(@qqcode, $1);
		$i++;
	}
	$i = 0;
	my @qcode = ();
	while ($code =~ s/('[^\']*')/QCODEY${i}B/s) {
		push(@qcode, $1);
		$i++;
	}

	foreach my $x (keys %SYMBOLS) {
		$code =~ s/$x/\$\$STYLESY0A\$\$$SYMBOLS{$x}\$\$STYLESY0B\$\$/gs;
	}
	for (my $x = 0 ; $x <= $#KEYWORDS1 ; $x++) {
		$code =~ s/\b$KEYWORDS1[$x]\b/<span class="kw1">$KEYWORDS1[$x]<\/span>/igs;
		$code =~ s/(?<!STYLESY0B\$\$)\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">$KEYWORDS2[$x]<\/span>/igs;
	}
	for (my $x = 0 ; $x <= $#KEYWORDS3 ; $x++) {
		$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/\$\$STYLESY0A\$\$([^\$]+)\$\$STYLESY0B\$\$/<span class="sy0">$1<\/span>/gs;

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

	for (my $x = 0; $x <= $#qcode; $x++) {
		$code =~ s/QCODEY${x}B/$qcode[$x]/s;
	}
	for (my $x = 0; $x <= $#qqcode; $x++) {
		$code =~ s/QQCODEY${x}A/$qqcode[$x]/s;
	}

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

	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 does not match the required dbclient
		$prefix_vars{'t_client'} ||= $prefix_vars{'t_hostport'};
		if (!$prefix_vars{'t_client'} || !grep(/^$prefix_vars{'t_client'}$/i, @dbclient)) {
			delete $cur_info{$t_pid};
			return 0;
		}
	}
	if ($#dbappname >= 0) {

		# Log line does 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 matches 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'} || ($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'} || ($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 tabulation 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}++;
		# Store current lock information that will be used later
		# when we will parse the query responsible of the locks
		$cur_lock_info{$t_pid}{wait} = $3;
		return;
	}

	# Stores query related to last lock information
	if (($prefix_vars{'t_loglevel'} eq 'STATEMENT') && exists $cur_lock_info{$t_pid}) {
		$cur_lock_info{$t_pid}{query}     = $prefix_vars{'t_query'};
		$cur_lock_info{$t_pid}{timestamp} = $prefix_vars{'t_timestamp'};
		$cur_lock_info{$t_pid}{dbname}    = $prefix_vars{'t_dbname'};
		$cur_lock_info{$t_pid}{dbuser}    = $prefix_vars{'t_dbuser'};
		$cur_lock_info{$t_pid}{dbclient}  = $prefix_vars{'t_client'};
		$cur_lock_info{$t_pid}{dbappname} = $prefix_vars{'t_appname'};
		$cur_lock_info{$t_pid}{timestamp} = $prefix_vars{'t_timestamp'};
		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);
		# Store current temporary file information that will be used later
		# when we will parse the query responsible of the tempfile
		$cur_temp_info{$t_pid}{size} = $1;
		return;
	}

	# Stores query related to last created temporary file
	if (($prefix_vars{'t_loglevel'} eq 'STATEMENT') && exists $cur_temp_info{$t_pid}) {
		$cur_temp_info{$t_pid}{query}     = $prefix_vars{'t_query'};
		$cur_temp_info{$t_pid}{timestamp} = $prefix_vars{'t_timestamp'};
		$cur_temp_info{$t_pid}{dbname}    = $prefix_vars{'t_dbname'};
		$cur_temp_info{$t_pid}{dbuser}    = $prefix_vars{'t_dbuser'};
		$cur_temp_info{$t_pid}{dbclient}  = $prefix_vars{'t_client'};
		$cur_temp_info{$t_pid}{dbappname} = $prefix_vars{'t_appname'};
		$cur_temp_info{$t_pid}{timestamp} = $prefix_vars{'t_timestamp'};
		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;
	}

	# Store session duration
	if (($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~
			/disconnection: session time: ([^\s]+) user=([^\s]+) database=([^\s]+) host=([^\s]+)/))
	{
		return if ($disable_session);
		if ($extension eq 'tsung') {
			$tsung_session{$prefix_vars{'t_pid'}}{disconnection}{date} = $prefix_vars{'t_timestamp'};
		}
		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 autovacuum information
	if (
		($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~
/automatic vacuum of table "([^\s]+)": index scans: (\d+)/
		   )
	   )
	{
		return if ($disable_autovacuum);
		$autovacuum_info{count}++;
		$autovacuum_info{tables}{$1}{vacuums} += 1;
		$autovacuum_info{tables}{$1}{idxscans} += $2;
		$autovacuum_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{count}++;
		$cur_info{$t_pid}{vacuum} = $1;
		return;
	}
	if (
		($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~
/automatic analyze of table "([^\s]+)"/
		   )
	   )
	{
		return if ($disable_autovacuum);
		$autoanalyze_info{count}++;
		$autoanalyze_info{tables}{$1}{analyzes} += 1;
		$autoanalyze_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{count}++;
	}

	# 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 restartpoint information
	if (
		($prefix_vars{'t_loglevel'} eq 'LOG')
		&& ($prefix_vars{'t_query'} =~
/restartpoint complete: wrote (\d+) buffers \(([^\)]+)\); write=([0-9\.]+) s, sync=([0-9\.]+) s, total=([0-9\.]+) s/
		   )
	   )
	{
		return if ($disable_checkpoint);
		$restartpoint_info{wbuffer} += $1;

		#$restartpoint_info{percent_wbuffer} += $2;
		$restartpoint_info{write}         += $3;
		$restartpoint_info{sync}          += $4;
		$restartpoint_info{total}         += $5;

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

		#$restartpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{percent_wbuffer} += $2;
		$restartpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{write}         += $3;
		$restartpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{sync}          += $4;
		$restartpoint_info{chronos}{$date_part}{$prefix_vars{'t_hour'}}{total}         += $5;
		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 connection 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: (.*)/)) {
				$cur_info{$t_pid}{parameters} = "$1";
				# go look at other params
				return;
			}
		}
		# 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'} || ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session})))
		) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
	}

	# Registrer previous query storage into global statistics before starting to store current query 
	if (exists $cur_info{$t_pid} && (!$prefix_vars{'t_session_line'} || ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session}))) {
		&store_queries($t_pid);
		delete $cur_info{$t_pid};
	}

	# Log lines with duration only, generated by log_duration = on in postgresql.conf
	if ($prefix_vars{'t_query'} =~ s/duration: ([0-9\.]+) ms$//s) {
		$prefix_vars{'t_duration'} = $1;
		$prefix_vars{'t_query'} = '';
		&set_current_infos($t_pid);
		return;
	}

	# Store info as tsung session following the output file extension
	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'};
	}

	my $t_action = '';
	# Store query duration generated by log_min_duration >= 0 in postgresql.conf
	if ($prefix_vars{'t_query'} =~ s/duration: ([0-9\.]+) ms  (query|statement): //is) {
		$prefix_vars{'t_duration'} = $1;
		$t_action   = $2;
	# Log line with duration and statement from prepared queries
	} elsif ($prefix_vars{'t_query'} =~ s/duration: ([0-9\.]+) ms  (prepare|parse|bind|execute|execute from fetch)\s+[^:]+:\s//is)
	{
		$prefix_vars{'t_duration'} = $1;
		$t_action   = $2;
		# Skipping parse and bind logs
		return if ($t_action !~ /query|statement|execute/);
	# Log line without duration at all
	} elsif ($prefix_vars{'t_query'} =~ s/(query|statement): //is) {
		$t_action = $1;
	# Log line without duration at all from prepared queries
	} elsif ($prefix_vars{'t_query'} =~ s/(prepare|parse|bind|execute|execute from fetch)\s+[^:]+:\s//is)
	{
		$t_action = $1;
		# Skipping parse and bind logs
		return if ($t_action !~ /query|statement|execute/);
	# Log line that should not be parse
	} 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'} || ($prefix_vars{'t_session_line'} != $cur_info{$t_pid}{session}))) {
			&store_queries($t_pid);
			delete $cur_info{$t_pid};
		}
		return;
	}

	if ( ($format eq 'csv') && ($prefix_vars{'t_loglevel'} ne 'LOG')) {
		$cur_info{$t_pid}{detail} = $prefix_vars{'t_detail'};
		$cur_info{$t_pid}{hint} = $prefix_vars{'t_hint'};
		$cur_info{$t_pid}{context} = $prefix_vars{'t_context'};
		$cur_info{$t_pid}{statement} = $prefix_vars{'t_statement'}
	}
	&set_current_infos($t_pid);

	return 1;
}

sub set_current_infos
{

	my $t_pid = shift;

	$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}{timestamp} = $prefix_vars{'t_timestamp'};
	$cur_info{$t_pid}{ident}     = $prefix_vars{'t_ident'};
	$cur_info{$t_pid}{query}     = $prefix_vars{'t_query'};
	$cur_info{$t_pid}{duration}  = $prefix_vars{'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}{date}      = $prefix_vars{'t_date'};

}

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;

	# Replace bind parameters values in the query if any
	if (exists $cur_info{$t_pid}{parameters}) {
		my @t_res = split(/[,\s]*\$(\d+)\s=\s/, $cur_info{$t_pid}{parameters});
		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;
		}
	}

	# We only process stored object with query here
	if ($cur_info{$t_pid}{query}) {
		# 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}{dbname}) {
					$tsung_session{$t_pid}{connection}{database} = $cur_info{$t_pid}{dbname};
					$tsung_session{$t_pid}{connection}{user}     = $cur_info{$t_pid}{dbuser};
					$tsung_session{$t_pid}{connection}{date}     = $cur_info{$t_pid}{date};
				}
			}
			return;
		}
	}

	my $cur_day_str = "$cur_info{$t_pid}{year}$cur_info{$t_pid}{month}$cur_info{$t_pid}{day}";
	my $cur_hour_str = "$cur_info{$t_pid}{hour}";

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

		# Add log level at beginning of the query and normalize 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 normalized error count
		$overall_stat{'errors_number'}++;
		$overall_stat{'unique_normalized_errors'}{"$normalized_error"}++;
		$error_info{$normalized_error}{count}++;

		# Stores normalized error count per time
		$error_info{$normalized_error}{chronos}{"$cur_day_str"}{"$cur_hour_str"}{count}++;

		# Stores normalized 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}, $cur_info{$t_pid}{dbname}
		);

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

		# Stores global statistics

		$overall_stat{'queries_number'}++;
		$overall_stat{'queries_duration'} += $cur_info{$t_pid}{duration} if ($cur_info{$t_pid}{duration});

		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}";
		if (!$overall_stat{'first_query_ts'} || ($overall_stat{'first_query_ts'} gt $cur_last_log_timestamp)) {
			$overall_stat{'first_query_ts'}   = $cur_last_log_timestamp;
		}
		if (!$overall_stat{'last_query_ts'} || ($overall_stat{'last_query_ts'} lt $cur_last_log_timestamp)) {
			$overall_stat{'last_query_ts'} = $cur_last_log_timestamp;
		}
		$overall_stat{'query_peak'}{$cur_last_log_timestamp}++;
		$per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{count}++;
		if ($cur_info{$t_pid}{duration}) { 
			$per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{duration} += $cur_info{$t_pid}{duration};
			# Store min / max duration
			if (!exists $per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{min} || ($per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{min} > $cur_info{$t_pid}{duration})) {
				$per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{min} = $cur_info{$t_pid}{duration};
			}
			if (!exists $per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{max} || ($per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{max} < $cur_info{$t_pid}{duration})) {
				$per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{max} = $cur_info{$t_pid}{duration};
			}
		}

		if ($graph) {
			$per_minute_info{query}{"$cur_day_str"}{"$cur_hour_str"}{$cur_info{$t_pid}{min}}{count}++;
			$per_minute_info{query}{"$cur_day_str"}{"$cur_hour_str"}{$cur_info{$t_pid}{min}}{second}{$cur_info{$t_pid}{sec}}++;
			$per_minute_info{query}{"$cur_day_str"}{"$cur_hour_str"}{$cur_info{$t_pid}{min}}{duration} += $cur_info{$t_pid}{duration} if ($cur_info{$t_pid}{duration});
		}

		# Counter per database and application name
		if ($cur_info{$t_pid}{dbname}) {
			$database_info{$cur_info{$t_pid}{dbname}}{count}++;
		}
		if ($cur_info{$t_pid}{dbappname}) {
			$application_info{$cur_info{$t_pid}{dbappname}}{count}++;
		} else {
			$application_info{others}{count}++;
		}

		# Store normalized query temp file size if required
		if (exists $cur_temp_info{$t_pid} && ($cur_temp_info{$t_pid} ne '') ) {

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

			# Normalize query
			my $normalized = &normalize_query($cur_temp_info{$t_pid}{query});

			$normalyzed_info{$normalized}{tempfiles}{size} += $cur_temp_info{$t_pid}{size};
			$normalyzed_info{$normalized}{tempfiles}{count}++;
			if ($normalyzed_info{$normalized}{tempfiles}{maxsize} < $cur_temp_info{$t_pid}{size}) {
				$normalyzed_info{$normalized}{tempfiles}{maxsize} = $cur_temp_info{$t_pid}{size};
			}
			if (!exists($normalyzed_info{$normalized}{tempfiles}{minsize})
			    || $normalyzed_info{$normalized}{tempfiles}{minsize} > $cur_temp_info{$t_pid}{size}) {
				$normalyzed_info{$normalized}{tempfiles}{minsize} = $cur_temp_info{$t_pid}{size};
			}
			&set_top_tempfile_info($cur_temp_info{$t_pid}{query}, $cur_temp_info{$t_pid}{size}, $cur_temp_info{$t_pid}{timestamp}, $cur_temp_info{$t_pid}{dbname}, $cur_temp_info{$t_pid}{dbuser}, $cur_temp_info{$t_pid}{dbclient}, $cur_temp_info{$t_pid}{dbappname});
			delete $cur_temp_info{$t_pid};
		}

		# Store normalized query that waited the most if required
		if (exists $cur_lock_info{$t_pid}) {

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

			# Normalize query
			my $normalized = &normalize_query($cur_lock_info{$t_pid}{query});

			$normalyzed_info{$normalized}{locks}{wait} += $cur_lock_info{$t_pid}{wait};
			$normalyzed_info{$normalized}{locks}{count}++;
			if ($normalyzed_info{$normalized}{locks}{maxwait} < $cur_lock_info{$t_pid}{wait}) {
				$normalyzed_info{$normalized}{locks}{maxwait} = $cur_lock_info{$t_pid}{wait};
			}
			if (!exists($normalyzed_info{$normalized}{locks}{minwait})
			    || $normalyzed_info{$normalized}{locks}{minwait} > $cur_lock_info{$t_pid}{wait}) {
				$normalyzed_info{$normalized}{locks}{minwait} = $cur_lock_info{$t_pid}{wait};
			}
			&set_top_locked_info($cur_lock_info{$t_pid}{query}, $cur_lock_info{$t_pid}{wait}, $cur_lock_info{$t_pid}{timestamp}, $cur_lock_info{$t_pid}{dbname}, $cur_lock_info{$t_pid}{dbuser}, $cur_lock_info{$t_pid}{dbclient}, $cur_lock_info{$t_pid}{dbappname});
			delete $cur_lock_info{$t_pid};
		}


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

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

			foreach my $act (@action_regex) {
				if ($normalized =~ $act) {
					my $action = uc($1);
					$overall_stat{$action}++;
					$per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{$action}{count}++;
					$per_hour_info{"$cur_day_str"}{"$cur_hour_str"}{$action}{duration} += $cur_info{$t_pid}{duration} if ($cur_info{$t_pid}{duration});
					if ($cur_info{$t_pid}{dbname}) {
						$database_info{$cur_info{$t_pid}{dbname}}{$action}++;
					}
					if ($cur_info{$t_pid}{dbappname}) {
						$application_info{$cur_info{$t_pid}{dbappname}}{$action}++;
					} else {
						$application_info{others}{$action}++;
					}
					last;
				}
			}

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

			# Store normalized query count and duration per time
			$normalyzed_info{$normalized}{chronos}{"$cur_day_str"}{"$cur_hour_str"}{count}++;
			if ($cur_info{$t_pid}{duration}) {

				# Updtate top slowest queries statistics
				&set_top_slowest($cur_info{$t_pid}{query}, $cur_info{$t_pid}{duration}, $cur_last_log_timestamp, $cur_info{$t_pid}{dbname}, $cur_info{$t_pid}{dbuser}, $cur_info{$t_pid}{dbclient},$cur_info{$t_pid}{dbappname});

				# Store normalized query total duration
				$normalyzed_info{$normalized}{duration} += $cur_info{$t_pid}{duration};
				# Store min / max duration
				if (!exists $normalyzed_info{$normalized}{min} || ($normalyzed_info{$normalized}{min} > $cur_info{$t_pid}{duration})) {
					$normalyzed_info{$normalized}{min} = $cur_info{$t_pid}{duration};
				}
				if (!exists $normalyzed_info{$normalized}{max} || ($normalyzed_info{$normalized}{max} < $cur_info{$t_pid}{duration})) {
					$normalyzed_info{$normalized}{max} = $cur_info{$t_pid}{duration};
				}

				# Store normalized query count and duration per time
				$normalyzed_info{$normalized}{chronos}{"$cur_day_str"}{"$cur_hour_str"}{duration} += $cur_info{$t_pid}{duration};

				# Store normalized query samples
				&set_top_sample($normalized, $cur_info{$t_pid}{query}, $cur_info{$t_pid}{duration}, $overall_stat{'last_log_ts'},$cur_info{$t_pid}{dbname}, $cur_info{$t_pid}{dbuser}, $cur_info{$t_pid}{dbclient},$cur_info{$t_pid}{dbappname});
			}
		}
	}
}

# Normalize 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    = '';

	die "FATAL: can't open file $file, $!\n" unless(open(TESTFILE, $file));
	binmode(TESTFILE);
	my $fltf = <TESTFILE>;
	close($fltf);
	# is file in binary format ?
	if ( $fltf =~ /^pst\d/ ) {
		$fmt = 'binary';
	}
	else { # try to detect syslogs or csv
		my ($tfile, $totalsize) = &get_log_file($file);
		my %ident_name = ();
		while (my $line = <$tfile>) {
			chomp($line);
			$line =~ s/\r//;
			next if (!$line);
			$nline++;

			# Are 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):/
			   )
			{
				$fmt = 'syslog';
				$nfound++;
				$ident_name{$1}++;

			} elsif ($line =~
	/^\d+-\d+-\d+T\d+:\d+:\d+(?:.[^\s]+)?\s[^\s]+\s([^\s\[]+)\[\d+\]:(?:\s\[[^\]]+\])?\s\[\d+\-\d+\].*?(LOG|WARNING|ERROR|FATAL|PANIC|DETAIL|STATEMENT|HINT|CONTEXT):/
			   )
			{
				$fmt = 'syslog2';
				$nfound++;
				$ident_name{$1}++;

				# Are 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+/
				)
			{
				$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 progress_bar
{
	my ($got, $total, $width, $char, $queries, $errors) = @_;
	$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, ($queries || $tsung_queries)
		);
	} elsif($format eq 'binary') {
		my $file = $_[-1];
		sprintf(
			"Loaded %d queries and %d events from binary file %s...\r",
			$overall_stat{'queries_number'}, $overall_stat{'errors_number'}, $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, ($queries || $overall_stat{'queries_number'}), ($errors || $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">
/* <![CDATA[ */
(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",
	    backgroundOpacity: 0.4
        },
	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">
/* <![CDATA[ */
(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",
	    backgroundOpacity: 0.4
        }
    });
    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',     '([a-zA-Z0-9\-\.]+|\[local\]|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?[\(\d\)]*')],     # remote host and port
		'%h' => [('t_client',       '([a-zA-Z0-9\-\.]+|\[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]);
	}
	# replace %% by a single %
	$log_line_prefix =~ s/\%\%/\%/;
	return @param_list;
}

# Inclusion of Perl package SQL::Beautify
# Copyright (C) 2009 by Jonas Kramer
# Published under the terms of the Artistic License 2.0.
{

	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 if ($self->_next_token
							and $self->_next_token !~ /^AS$/i
							and $self->_next_token ne ')'
							and $self->_next_token !~ /::/
							and $self->_next_token ne ';'
					);
			}

			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|BEGIN|SET)$/i) {
				$self->_back if ($last and $last ne '(' and $last ne 'FOR');
				$self->_new_line;
				$self->_add_token($token);
				$self->_new_line if ((($token ne 'SET') || $last) and $self->_next_token and $self->_next_token ne '(' 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 =~ /^(?:CASE)$/i) {
				$self->_add_token($token);
				$self->_over;
			}

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

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

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

			elsif ($token =~ /^(?:UNION|INTERSECT|EXCEPT)$/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 =~ /^(?: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;
			}

			elsif ($token =~ /^--/) {
				if (!$self->{no_comments}) {
					$self->_add_token($token);
					$self->_new_line;
				}
			}

			elsif ($token =~ /^\/\*.*\*\/$/s) {
				if (!$self->{no_comments}) {
					$token =~ s/\n[\s\t]+\*/\n\*/gs;
					$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 =~ /^[,;.]$/);
	}

}

sub get_log_file
{
	my $logf = shift;

	my $lfile = undef;

	# get file size
	my $totalsize = (stat("$logf"))[7] || 0;

	# Open a file handle
	if ($logf !~ /\.(gz|bz2|zip)/i) {
		open($lfile, $logf) || die "FATAL: cannot read log file $logf. $!\n";
		$totalsize = 0 if ($lfile eq '-');
	} else {
		my $uncompress = $zcat;
		if (($logf =~ /\.bz2/i) && ($zcat =~ /^$zcat_cmd$/)) {
			$uncompress = $bzcat;
		} elsif (($logf =~ /\.zip/i) && ($zcat =~ /^$zcat_cmd$/)) {
			$uncompress = $ucat;
		}
		&logmsg('DEBUG', "Compressed log file, will use command: $uncompress \"$logf\"");

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

		# Real size of the file is unknown, try to find it
		# bz2 does not report real size
		$totalsize = 0;
		if ($logf =~ /\.(gz|zip)/i) {
			my $cmd_file_size = $gzip_uncompress_size;
			if ($logf =~ /\.zip/i) {
				$cmd_file_size = $zip_uncompress_size;
			}
			$cmd_file_size =~ s/\%f/$logf/g;
			$totalsize = `$cmd_file_size`;
			chomp($totalsize);
		}
		if ($queue_size) {
			$job_per_file = $queue_size;
			$queue_size = 0;
		}
	}

	# In list context returns the filehandle and the size of the file
	if (wantarray()) {
		return ($lfile, $totalsize);
	}
	# In scalar context return size only
	close($lfile);
	return $totalsize;
}

sub split_logfile
{
	my $logf = shift;

	# CSV file can't be parsed using multiprocessing
	return (0, -1) if ( $format eq 'csv' );

	# get file size
	my $totalsize = (stat("$logf"))[7] || 0;

	# Real size of the file is unknown, try to find it
	# bz2 does not report real size
	if ($logf =~ /\.(gz|zip)/i) {
		$totalsize = 0;
		my $cmd_file_size = $gzip_uncompress_size;
		if ($logf =~ /\.zip/i) {
			$cmd_file_size = $zip_uncompress_size;
		}
		$cmd_file_size =~ s/\%f/$logf/g;
		$totalsize = `$cmd_file_size`;
		chomp($totalsize);
		if ($queue_size) {
			$job_per_file = $queue_size;
			$queue_size = 0;
		}
	} elsif ($logf =~ /\.bz2/i) {
		$totalsize = 0;
	}

	return (0, -1) if (!$totalsize);

	my @chunks = (0);
	my $i = 1;
	while ($i < $queue_size) {
		push(@chunks, int(($totalsize/$queue_size) * $i));
		$i++;
	}
	push(@chunks, $totalsize);

	return @chunks;
}

__DATA__

<script type="text/javascript">
/* <![CDATA[ */
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>

