!21 StrongerGS数据库工具箱-全国半决赛提交代码

Merge pull request !21 from yinyuan/master
This commit is contained in:
opengauss-bot 2022-09-23 06:11:20 +00:00 committed by Gitee
commit 7eccef5b32
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9278 changed files with 1932375 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,8 @@
LICENSE
Makefile.PL
MANIFEST
META.yml
pgbadger
README
doc/pgBadger.pod
ChangeLog

View File

@ -0,0 +1,10 @@
name: pgBadger
version: 11.1
version_from: pgbadger
installdirs: site
recommends:
Text::CSV_XS: 0
JSON::XS: 0
distribution_type: script
generated_by: ExtUtils::MakeMaker version 6.17

View File

@ -0,0 +1,840 @@
# This Makefile is for the pgBadger extension to perl.
#
# It was generated automatically by MakeMaker version
# 6.68 (Revision: 66800) from the contents of
# Makefile.PL. Don't edit this file, edit Makefile.PL instead.
#
# ANY CHANGES MADE HERE WILL BE LOST!
#
# MakeMaker ARGV: ()
#
# MakeMaker Parameters:
# ABSTRACT => q[pgBadger - PostgreSQL log analysis report]
# AUTHOR => [q[Gilles Darold (gilles@darold.net)]]
# BUILD_REQUIRES => { }
# CONFIGURE_REQUIRES => { }
# DESTDIR => q[]
# DISTNAME => q[pgbadger]
# EXE_FILES => [q[pgbadger]]
# INSTALLDIRS => q[site]
# MAN1PODS => { doc/pgBadger.pod=>q[blib/man1/pgbadger.1p] }
# META_MERGE => { resources=>{ repository=>{ web=>q[https://github.com/darold/pgbadger], git=>q[git@github.com:darold/pgbadger.git], type=>q[git] }, homepage=>q[http://pgbadger.darold.net/] } }
# NAME => q[pgBadger]
# PREREQ_PM => { }
# TEST_REQUIRES => { }
# VERSION_FROM => q[pgbadger]
# clean => { }
# dist => { ZIPFLAGS=>q[-rl], ZIP=>q[/usr/bin/zip], COMPRESS=>q[gzip -9f], SUFFIX=>q[gz] }
# --- MakeMaker post_initialize section:
# --- MakeMaker const_config section:
# These definitions are from config.sh (via /usr/lib64/perl5/Config.pm).
# They may have been overridden via Makefile.PL or on the command line.
AR = ar
CC = gcc
CCCDLFLAGS = -fPIC
CCDLFLAGS = -Wl,--enable-new-dtags -Wl,-rpath,/usr/lib64/perl5/CORE
DLEXT = so
DLSRC = dl_dlopen.xs
EXE_EXT =
FULL_AR = /usr/bin/ar
LD = gcc
LDDLFLAGS = -shared -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -Wl,-z,relro
LDFLAGS = -fstack-protector
LIBC =
LIB_EXT = .a
OBJ_EXT = .o
OSNAME = linux
OSVERS = 3.10.0-957.1.3.el7.x86_64
RANLIB = :
SITELIBEXP = /usr/local/share/perl5
SITEARCHEXP = /usr/local/lib64/perl5
SO = so
VENDORARCHEXP = /usr/lib64/perl5/vendor_perl
VENDORLIBEXP = /usr/share/perl5/vendor_perl
# --- MakeMaker constants section:
AR_STATIC_ARGS = cr
DIRFILESEP = /
DFSEP = $(DIRFILESEP)
NAME = pgBadger
NAME_SYM = pgBadger
VERSION = 11.1
VERSION_MACRO = VERSION
VERSION_SYM = 11_1
DEFINE_VERSION = -D$(VERSION_MACRO)=\"$(VERSION)\"
XS_VERSION = 11.1
XS_VERSION_MACRO = XS_VERSION
XS_DEFINE_VERSION = -D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\"
INST_ARCHLIB = blib/arch
INST_SCRIPT = blib/script
INST_BIN = blib/bin
INST_LIB = blib/lib
INST_MAN1DIR = blib/man1
INST_MAN3DIR = blib/man3
MAN1EXT = 1
MAN3EXT = 3pm
INSTALLDIRS = site
DESTDIR =
PREFIX = $(SITEPREFIX)
PERLPREFIX = /usr
SITEPREFIX = /usr/local
VENDORPREFIX = /usr
INSTALLPRIVLIB = /usr/share/perl5
DESTINSTALLPRIVLIB = $(DESTDIR)$(INSTALLPRIVLIB)
INSTALLSITELIB = /usr/local/share/perl5
DESTINSTALLSITELIB = $(DESTDIR)$(INSTALLSITELIB)
INSTALLVENDORLIB = /usr/share/perl5/vendor_perl
DESTINSTALLVENDORLIB = $(DESTDIR)$(INSTALLVENDORLIB)
INSTALLARCHLIB = /usr/lib64/perl5
DESTINSTALLARCHLIB = $(DESTDIR)$(INSTALLARCHLIB)
INSTALLSITEARCH = /usr/local/lib64/perl5
DESTINSTALLSITEARCH = $(DESTDIR)$(INSTALLSITEARCH)
INSTALLVENDORARCH = /usr/lib64/perl5/vendor_perl
DESTINSTALLVENDORARCH = $(DESTDIR)$(INSTALLVENDORARCH)
INSTALLBIN = /usr/bin
DESTINSTALLBIN = $(DESTDIR)$(INSTALLBIN)
INSTALLSITEBIN = /usr/local/bin
DESTINSTALLSITEBIN = $(DESTDIR)$(INSTALLSITEBIN)
INSTALLVENDORBIN = /usr/bin
DESTINSTALLVENDORBIN = $(DESTDIR)$(INSTALLVENDORBIN)
INSTALLSCRIPT = /usr/bin
DESTINSTALLSCRIPT = $(DESTDIR)$(INSTALLSCRIPT)
INSTALLSITESCRIPT = /usr/local/bin
DESTINSTALLSITESCRIPT = $(DESTDIR)$(INSTALLSITESCRIPT)
INSTALLVENDORSCRIPT = /usr/bin
DESTINSTALLVENDORSCRIPT = $(DESTDIR)$(INSTALLVENDORSCRIPT)
INSTALLMAN1DIR = /usr/share/man/man1
DESTINSTALLMAN1DIR = $(DESTDIR)$(INSTALLMAN1DIR)
INSTALLSITEMAN1DIR = /usr/local/share/man/man1
DESTINSTALLSITEMAN1DIR = $(DESTDIR)$(INSTALLSITEMAN1DIR)
INSTALLVENDORMAN1DIR = /usr/share/man/man1
DESTINSTALLVENDORMAN1DIR = $(DESTDIR)$(INSTALLVENDORMAN1DIR)
INSTALLMAN3DIR = /usr/share/man/man3
DESTINSTALLMAN3DIR = $(DESTDIR)$(INSTALLMAN3DIR)
INSTALLSITEMAN3DIR = /usr/local/share/man/man3
DESTINSTALLSITEMAN3DIR = $(DESTDIR)$(INSTALLSITEMAN3DIR)
INSTALLVENDORMAN3DIR = /usr/share/man/man3
DESTINSTALLVENDORMAN3DIR = $(DESTDIR)$(INSTALLVENDORMAN3DIR)
PERL_LIB = /usr/share/perl5
PERL_ARCHLIB = /usr/lib64/perl5
LIBPERL_A = libperl.a
FIRST_MAKEFILE = Makefile
MAKEFILE_OLD = Makefile.old
MAKE_APERL_FILE = Makefile.aperl
PERLMAINCC = $(CC)
PERL_INC = /usr/lib64/perl5/CORE
PERL = /usr/bin/perl
FULLPERL = /usr/bin/perl
ABSPERL = $(PERL)
PERLRUN = $(PERL)
FULLPERLRUN = $(FULLPERL)
ABSPERLRUN = $(ABSPERL)
PERLRUNINST = $(PERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
FULLPERLRUNINST = $(FULLPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
ABSPERLRUNINST = $(ABSPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)"
PERL_CORE = 0
PERM_DIR = 755
PERM_RW = 644
PERM_RWX = 755
MAKEMAKER = /usr/share/perl5/vendor_perl/ExtUtils/MakeMaker.pm
MM_VERSION = 6.68
MM_REVISION = 66800
# FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle).
# BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle)
# PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar)
# DLBASE = Basename part of dynamic library. May be just equal BASEEXT.
MAKE = make
FULLEXT = pgBadger
BASEEXT = pgBadger
PARENT_NAME =
DLBASE = $(BASEEXT)
VERSION_FROM = pgbadger
OBJECT =
LDFROM = $(OBJECT)
LINKTYPE = dynamic
BOOTDEP =
# Handy lists of source code files:
XS_FILES =
C_FILES =
O_FILES =
H_FILES =
MAN1PODS = doc/pgBadger.pod
MAN3PODS =
# Where is the Config information that we are using/depend on
CONFIGDEP = $(PERL_ARCHLIB)$(DFSEP)Config.pm $(PERL_INC)$(DFSEP)config.h
# Where to build things
INST_LIBDIR = $(INST_LIB)
INST_ARCHLIBDIR = $(INST_ARCHLIB)
INST_AUTODIR = $(INST_LIB)/auto/$(FULLEXT)
INST_ARCHAUTODIR = $(INST_ARCHLIB)/auto/$(FULLEXT)
INST_STATIC =
INST_DYNAMIC =
INST_BOOT =
# Extra linker info
EXPORT_LIST =
PERL_ARCHIVE =
PERL_ARCHIVE_AFTER =
TO_INST_PM =
PM_TO_BLIB =
# --- MakeMaker platform_constants section:
MM_Unix_VERSION = 6.68
PERL_MALLOC_DEF = -DPERL_EXTMALLOC_DEF -Dmalloc=Perl_malloc -Dfree=Perl_mfree -Drealloc=Perl_realloc -Dcalloc=Perl_calloc
# --- MakeMaker tool_autosplit section:
# Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto
AUTOSPLITFILE = $(ABSPERLRUN) -e 'use AutoSplit; autosplit($$$$ARGV[0], $$$$ARGV[1], 0, 1, 1)' --
# --- MakeMaker tool_xsubpp section:
# --- MakeMaker tools_other section:
SHELL = /bin/sh
CHMOD = chmod
CP = cp
MV = mv
NOOP = $(TRUE)
NOECHO = @
RM_F = rm -f
RM_RF = rm -rf
TEST_F = test -f
TOUCH = touch
UMASK_NULL = umask 0
DEV_NULL = > /dev/null 2>&1
MKPATH = $(ABSPERLRUN) -MExtUtils::Command -e 'mkpath' --
EQUALIZE_TIMESTAMP = $(ABSPERLRUN) -MExtUtils::Command -e 'eqtime' --
FALSE = false
TRUE = true
ECHO = echo
ECHO_N = echo -n
UNINST = 0
VERBINST = 0
MOD_INSTALL = $(ABSPERLRUN) -MExtUtils::Install -e 'install([ from_to => {@ARGV}, verbose => '\''$(VERBINST)'\'', uninstall_shadows => '\''$(UNINST)'\'', dir_mode => '\''$(PERM_DIR)'\'' ]);' --
DOC_INSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'perllocal_install' --
UNINSTALL = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'uninstall' --
WARN_IF_OLD_PACKLIST = $(ABSPERLRUN) -MExtUtils::Command::MM -e 'warn_if_old_packlist' --
MACROSTART =
MACROEND =
USEMAKEFILE = -f
FIXIN = $(ABSPERLRUN) -MExtUtils::MY -e 'MY->fixin(shift)' --
# --- MakeMaker makemakerdflt section:
makemakerdflt : all
$(NOECHO) $(NOOP)
# --- MakeMaker dist section:
TAR = tar
TARFLAGS = cvf
ZIP = /usr/bin/zip
ZIPFLAGS = -rl
COMPRESS = gzip -9f
SUFFIX = gz
SHAR = shar
PREOP = $(NOECHO) $(NOOP)
POSTOP = $(NOECHO) $(NOOP)
TO_UNIX = $(NOECHO) $(NOOP)
CI = ci -u
RCS_LABEL = rcs -Nv$(VERSION_SYM): -q
DIST_CP = best
DIST_DEFAULT = tardist
DISTNAME = pgbadger
DISTVNAME = pgbadger-11.1
# --- MakeMaker macro section:
# --- MakeMaker depend section:
# --- MakeMaker cflags section:
# --- MakeMaker const_loadlibs section:
# --- MakeMaker const_cccmd section:
# --- MakeMaker post_constants section:
# --- MakeMaker pasthru section:
PASTHRU = LIBPERL_A="$(LIBPERL_A)"\
LINKTYPE="$(LINKTYPE)"\
PREFIX="$(PREFIX)"
# --- MakeMaker special_targets section:
.SUFFIXES : .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT)
.PHONY: all config static dynamic test linkext manifest blibdirs clean realclean disttest distdir
# --- MakeMaker c_o section:
# --- MakeMaker xs_c section:
# --- MakeMaker xs_o section:
# --- MakeMaker top_targets section:
all :: pure_all manifypods
$(NOECHO) $(NOOP)
pure_all :: config pm_to_blib subdirs linkext
$(NOECHO) $(NOOP)
subdirs :: $(MYEXTLIB)
$(NOECHO) $(NOOP)
config :: $(FIRST_MAKEFILE) blibdirs
$(NOECHO) $(NOOP)
help :
perldoc ExtUtils::MakeMaker
# --- MakeMaker blibdirs section:
blibdirs : $(INST_LIBDIR)$(DFSEP).exists $(INST_ARCHLIB)$(DFSEP).exists $(INST_AUTODIR)$(DFSEP).exists $(INST_ARCHAUTODIR)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists $(INST_SCRIPT)$(DFSEP).exists $(INST_MAN1DIR)$(DFSEP).exists $(INST_MAN3DIR)$(DFSEP).exists
$(NOECHO) $(NOOP)
# Backwards compat with 6.18 through 6.25
blibdirs.ts : blibdirs
$(NOECHO) $(NOOP)
$(INST_LIBDIR)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_LIBDIR)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_LIBDIR)
$(NOECHO) $(TOUCH) $(INST_LIBDIR)$(DFSEP).exists
$(INST_ARCHLIB)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_ARCHLIB)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHLIB)
$(NOECHO) $(TOUCH) $(INST_ARCHLIB)$(DFSEP).exists
$(INST_AUTODIR)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_AUTODIR)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_AUTODIR)
$(NOECHO) $(TOUCH) $(INST_AUTODIR)$(DFSEP).exists
$(INST_ARCHAUTODIR)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_ARCHAUTODIR)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_ARCHAUTODIR)
$(NOECHO) $(TOUCH) $(INST_ARCHAUTODIR)$(DFSEP).exists
$(INST_BIN)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_BIN)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_BIN)
$(NOECHO) $(TOUCH) $(INST_BIN)$(DFSEP).exists
$(INST_SCRIPT)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_SCRIPT)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_SCRIPT)
$(NOECHO) $(TOUCH) $(INST_SCRIPT)$(DFSEP).exists
$(INST_MAN1DIR)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_MAN1DIR)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN1DIR)
$(NOECHO) $(TOUCH) $(INST_MAN1DIR)$(DFSEP).exists
$(INST_MAN3DIR)$(DFSEP).exists :: Makefile.PL
$(NOECHO) $(MKPATH) $(INST_MAN3DIR)
$(NOECHO) $(CHMOD) $(PERM_DIR) $(INST_MAN3DIR)
$(NOECHO) $(TOUCH) $(INST_MAN3DIR)$(DFSEP).exists
# --- MakeMaker linkext section:
linkext :: $(LINKTYPE)
$(NOECHO) $(NOOP)
# --- MakeMaker dlsyms section:
# --- MakeMaker dynamic section:
dynamic :: $(FIRST_MAKEFILE) $(INST_DYNAMIC) $(INST_BOOT)
$(NOECHO) $(NOOP)
# --- MakeMaker dynamic_bs section:
BOOTSTRAP =
# --- MakeMaker dynamic_lib section:
# --- MakeMaker static section:
## $(INST_PM) has been moved to the all: target.
## It remains here for awhile to allow for old usage: "make static"
static :: $(FIRST_MAKEFILE) $(INST_STATIC)
$(NOECHO) $(NOOP)
# --- MakeMaker static_lib section:
# --- MakeMaker manifypods section:
POD2MAN_EXE = $(PERLRUN) "-MExtUtils::Command::MM" -e pod2man "--"
POD2MAN = $(POD2MAN_EXE)
manifypods : pure_all \
doc/pgBadger.pod
$(NOECHO) $(POD2MAN) --section=1 --perm_rw=$(PERM_RW) \
doc/pgBadger.pod blib/man1/pgbadger.1p
# --- MakeMaker processPL section:
# --- MakeMaker installbin section:
EXE_FILES = pgbadger
pure_all :: $(INST_SCRIPT)/pgbadger
$(NOECHO) $(NOOP)
realclean ::
$(RM_F) \
$(INST_SCRIPT)/pgbadger
$(INST_SCRIPT)/pgbadger : pgbadger $(FIRST_MAKEFILE) $(INST_SCRIPT)$(DFSEP).exists $(INST_BIN)$(DFSEP).exists
$(NOECHO) $(RM_F) $(INST_SCRIPT)/pgbadger
$(CP) pgbadger $(INST_SCRIPT)/pgbadger
$(FIXIN) $(INST_SCRIPT)/pgbadger
-$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_SCRIPT)/pgbadger
# --- MakeMaker subdirs section:
# none
# --- MakeMaker clean_subdirs section:
clean_subdirs :
$(NOECHO) $(NOOP)
# --- MakeMaker clean section:
# Delete temporary files but do not touch installed files. We don't delete
# the Makefile here so a later make realclean still has a makefile to use.
clean :: clean_subdirs
- $(RM_F) \
*$(LIB_EXT) core \
core.[0-9] $(INST_ARCHAUTODIR)/extralibs.all \
core.[0-9][0-9] $(BASEEXT).bso \
pm_to_blib.ts MYMETA.json \
core.[0-9][0-9][0-9][0-9] MYMETA.yml \
$(BASEEXT).x $(BOOTSTRAP) \
perl$(EXE_EXT) tmon.out \
*$(OBJ_EXT) pm_to_blib \
$(INST_ARCHAUTODIR)/extralibs.ld blibdirs.ts \
core.[0-9][0-9][0-9][0-9][0-9] *perl.core \
core.*perl.*.? $(MAKE_APERL_FILE) \
$(BASEEXT).def perl \
core.[0-9][0-9][0-9] mon.out \
lib$(BASEEXT).def perlmain.c \
perl.exe so_locations \
$(BASEEXT).exp
- $(RM_RF) \
blib
- $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL)
# --- MakeMaker realclean_subdirs section:
realclean_subdirs :
$(NOECHO) $(NOOP)
# --- MakeMaker realclean section:
# Delete temporary files (via clean) and also delete dist files
realclean purge :: clean realclean_subdirs
- $(RM_F) \
$(MAKEFILE_OLD) $(FIRST_MAKEFILE)
- $(RM_RF) \
$(DISTVNAME)
# --- MakeMaker metafile section:
metafile :
$(NOECHO) $(NOOP)
# --- MakeMaker signature section:
signature :
cpansign -s
# --- MakeMaker dist_basics section:
distclean :: realclean distcheck
$(NOECHO) $(NOOP)
distcheck :
$(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck
skipcheck :
$(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck
manifest :
$(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest
veryclean : realclean
$(RM_F) *~ */*~ *.orig */*.orig *.bak */*.bak *.old */*.old
# --- MakeMaker dist_core section:
dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE)
$(NOECHO) $(ABSPERLRUN) -l -e 'print '\''Warning: Makefile possibly out of date with $(VERSION_FROM)'\''' \
-e ' if -e '\''$(VERSION_FROM)'\'' and -M '\''$(VERSION_FROM)'\'' < -M '\''$(FIRST_MAKEFILE)'\'';' --
tardist : $(DISTVNAME).tar$(SUFFIX)
$(NOECHO) $(NOOP)
uutardist : $(DISTVNAME).tar$(SUFFIX)
uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu
$(DISTVNAME).tar$(SUFFIX) : distdir
$(PREOP)
$(TO_UNIX)
$(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME)
$(RM_RF) $(DISTVNAME)
$(COMPRESS) $(DISTVNAME).tar
$(POSTOP)
zipdist : $(DISTVNAME).zip
$(NOECHO) $(NOOP)
$(DISTVNAME).zip : distdir
$(PREOP)
$(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME)
$(RM_RF) $(DISTVNAME)
$(POSTOP)
shdist : distdir
$(PREOP)
$(SHAR) $(DISTVNAME) > $(DISTVNAME).shar
$(RM_RF) $(DISTVNAME)
$(POSTOP)
# --- MakeMaker distdir section:
create_distdir :
$(RM_RF) $(DISTVNAME)
$(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \
-e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');"
distdir : create_distdir distmeta
$(NOECHO) $(NOOP)
# --- MakeMaker dist_test section:
disttest : distdir
cd $(DISTVNAME) && $(ABSPERLRUN) Makefile.PL
cd $(DISTVNAME) && $(MAKE) $(PASTHRU)
cd $(DISTVNAME) && $(MAKE) test $(PASTHRU)
# --- MakeMaker dist_ci section:
ci :
$(PERLRUN) "-MExtUtils::Manifest=maniread" \
-e "@all = keys %{ maniread() };" \
-e "print(qq{Executing $(CI) @all\n}); system(qq{$(CI) @all});" \
-e "print(qq{Executing $(RCS_LABEL) ...\n}); system(qq{$(RCS_LABEL) @all});"
# --- MakeMaker distmeta section:
distmeta : create_distdir metafile
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'exit unless -e q{META.yml};' \
-e 'eval { maniadd({q{META.yml} => q{Module YAML meta-data (added by MakeMaker)}}) }' \
-e ' or print "Could not add META.yml to MANIFEST: $$$${'\''@'\''}\n"' --
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'exit unless -f q{META.json};' \
-e 'eval { maniadd({q{META.json} => q{Module JSON meta-data (added by MakeMaker)}}) }' \
-e ' or print "Could not add META.json to MANIFEST: $$$${'\''@'\''}\n"' --
# --- MakeMaker distsignature section:
distsignature : create_distdir
$(NOECHO) cd $(DISTVNAME) && $(ABSPERLRUN) -MExtUtils::Manifest=maniadd -e 'eval { maniadd({q{SIGNATURE} => q{Public-key signature (added by MakeMaker)}}) } ' \
-e ' or print "Could not add SIGNATURE to MANIFEST: $$$${'\''@'\''}\n"' --
$(NOECHO) cd $(DISTVNAME) && $(TOUCH) SIGNATURE
cd $(DISTVNAME) && cpansign -s
# --- MakeMaker install section:
install :: pure_install doc_install
$(NOECHO) $(NOOP)
install_perl :: pure_perl_install doc_perl_install
$(NOECHO) $(NOOP)
install_site :: pure_site_install doc_site_install
$(NOECHO) $(NOOP)
install_vendor :: pure_vendor_install doc_vendor_install
$(NOECHO) $(NOOP)
pure_install :: pure_$(INSTALLDIRS)_install
$(NOECHO) $(NOOP)
doc_install :: doc_$(INSTALLDIRS)_install
$(NOECHO) $(NOOP)
pure__install : pure_site_install
$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
doc__install : doc_site_install
$(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site
pure_perl_install :: all
$(NOECHO) $(MOD_INSTALL) \
read $(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist \
write $(DESTINSTALLARCHLIB)/auto/$(FULLEXT)/.packlist \
$(INST_LIB) $(DESTINSTALLPRIVLIB) \
$(INST_ARCHLIB) $(DESTINSTALLARCHLIB) \
$(INST_BIN) $(DESTINSTALLBIN) \
$(INST_SCRIPT) $(DESTINSTALLSCRIPT) \
$(INST_MAN1DIR) $(DESTINSTALLMAN1DIR) \
$(INST_MAN3DIR) $(DESTINSTALLMAN3DIR)
$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
$(SITEARCHEXP)/auto/$(FULLEXT)
pure_site_install :: all
$(NOECHO) $(MOD_INSTALL) \
read $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist \
write $(DESTINSTALLSITEARCH)/auto/$(FULLEXT)/.packlist \
$(INST_LIB) $(DESTINSTALLSITELIB) \
$(INST_ARCHLIB) $(DESTINSTALLSITEARCH) \
$(INST_BIN) $(DESTINSTALLSITEBIN) \
$(INST_SCRIPT) $(DESTINSTALLSITESCRIPT) \
$(INST_MAN1DIR) $(DESTINSTALLSITEMAN1DIR) \
$(INST_MAN3DIR) $(DESTINSTALLSITEMAN3DIR)
$(NOECHO) $(WARN_IF_OLD_PACKLIST) \
$(PERL_ARCHLIB)/auto/$(FULLEXT)
pure_vendor_install :: all
$(NOECHO) $(MOD_INSTALL) \
read $(VENDORARCHEXP)/auto/$(FULLEXT)/.packlist \
write $(DESTINSTALLVENDORARCH)/auto/$(FULLEXT)/.packlist \
$(INST_LIB) $(DESTINSTALLVENDORLIB) \
$(INST_ARCHLIB) $(DESTINSTALLVENDORARCH) \
$(INST_BIN) $(DESTINSTALLVENDORBIN) \
$(INST_SCRIPT) $(DESTINSTALLVENDORSCRIPT) \
$(INST_MAN1DIR) $(DESTINSTALLVENDORMAN1DIR) \
$(INST_MAN3DIR) $(DESTINSTALLVENDORMAN3DIR)
doc_perl_install :: all
$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod
-$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB)
-$(NOECHO) $(DOC_INSTALL) \
"Module" "$(NAME)" \
"installed into" "$(INSTALLPRIVLIB)" \
LINKTYPE "$(LINKTYPE)" \
VERSION "$(VERSION)" \
EXE_FILES "$(EXE_FILES)" \
>> $(DESTINSTALLARCHLIB)/perllocal.pod
doc_site_install :: all
$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod
-$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB)
-$(NOECHO) $(DOC_INSTALL) \
"Module" "$(NAME)" \
"installed into" "$(INSTALLSITELIB)" \
LINKTYPE "$(LINKTYPE)" \
VERSION "$(VERSION)" \
EXE_FILES "$(EXE_FILES)" \
>> $(DESTINSTALLARCHLIB)/perllocal.pod
doc_vendor_install :: all
$(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod
-$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB)
-$(NOECHO) $(DOC_INSTALL) \
"Module" "$(NAME)" \
"installed into" "$(INSTALLVENDORLIB)" \
LINKTYPE "$(LINKTYPE)" \
VERSION "$(VERSION)" \
EXE_FILES "$(EXE_FILES)" \
>> $(DESTINSTALLARCHLIB)/perllocal.pod
uninstall :: uninstall_from_$(INSTALLDIRS)dirs
$(NOECHO) $(NOOP)
uninstall_from_perldirs ::
$(NOECHO) $(UNINSTALL) $(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist
uninstall_from_sitedirs ::
$(NOECHO) $(UNINSTALL) $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist
uninstall_from_vendordirs ::
$(NOECHO) $(UNINSTALL) $(VENDORARCHEXP)/auto/$(FULLEXT)/.packlist
# --- MakeMaker force section:
# Phony target to force checking subdirectories.
FORCE :
$(NOECHO) $(NOOP)
# --- MakeMaker perldepend section:
# --- MakeMaker makefile section:
# We take a very conservative approach here, but it's worth it.
# We move Makefile to Makefile.old here to avoid gnu make looping.
$(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP)
$(NOECHO) $(ECHO) "Makefile out-of-date with respect to $?"
$(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..."
-$(NOECHO) $(RM_F) $(MAKEFILE_OLD)
-$(NOECHO) $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD)
- $(MAKE) $(USEMAKEFILE) $(MAKEFILE_OLD) clean $(DEV_NULL)
$(PERLRUN) Makefile.PL
$(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <=="
$(NOECHO) $(ECHO) "==> Please rerun the $(MAKE) command. <=="
$(FALSE)
# --- MakeMaker staticmake section:
# --- MakeMaker makeaperl section ---
MAP_TARGET = perl
FULLPERL = /usr/bin/perl
$(MAP_TARGET) :: static $(MAKE_APERL_FILE)
$(MAKE) $(USEMAKEFILE) $(MAKE_APERL_FILE) $@
$(MAKE_APERL_FILE) : $(FIRST_MAKEFILE) pm_to_blib
$(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET)
$(NOECHO) $(PERLRUNINST) \
Makefile.PL DIR= \
MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \
MAKEAPERL=1 NORECURS=1 CCCDLFLAGS=
# --- MakeMaker test section:
TEST_VERBOSE=0
TEST_TYPE=test_$(LINKTYPE)
TEST_FILE = test.pl
TEST_FILES = t/*.t
TESTDB_SW = -d
testdb :: testdb_$(LINKTYPE)
test :: $(TEST_TYPE) subdirs-test
subdirs-test ::
$(NOECHO) $(NOOP)
test_dynamic :: pure_all
PERL_DL_NONLAZY=1 $(FULLPERLRUN) "-MExtUtils::Command::MM" "-e" "test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES)
testdb_dynamic :: pure_all
PERL_DL_NONLAZY=1 $(FULLPERLRUN) $(TESTDB_SW) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE)
test_ : test_dynamic
test_static :: test_dynamic
testdb_static :: testdb_dynamic
# --- MakeMaker ppd section:
# Creates a PPD (Perl Package Description) for a binary distribution.
ppd :
$(NOECHO) $(ECHO) '<SOFTPKG NAME="$(DISTNAME)" VERSION="$(VERSION)">' > $(DISTNAME).ppd
$(NOECHO) $(ECHO) ' <ABSTRACT>pgBadger - PostgreSQL log analysis report</ABSTRACT>' >> $(DISTNAME).ppd
$(NOECHO) $(ECHO) ' <AUTHOR>Gilles Darold (gilles@darold.net)</AUTHOR>' >> $(DISTNAME).ppd
$(NOECHO) $(ECHO) ' <IMPLEMENTATION>' >> $(DISTNAME).ppd
$(NOECHO) $(ECHO) ' <ARCHITECTURE NAME="x86_64-linux-thread-multi-5.16" />' >> $(DISTNAME).ppd
$(NOECHO) $(ECHO) ' <CODEBASE HREF="" />' >> $(DISTNAME).ppd
$(NOECHO) $(ECHO) ' </IMPLEMENTATION>' >> $(DISTNAME).ppd
$(NOECHO) $(ECHO) '</SOFTPKG>' >> $(DISTNAME).ppd
# --- MakeMaker pm_to_blib section:
pm_to_blib : $(FIRST_MAKEFILE) $(TO_INST_PM)
$(NOECHO) $(TOUCH) pm_to_blib
# --- MakeMaker selfdocument section:
# --- MakeMaker postamble section:
USE_MARKDOWN=$(shell which pod2markdown)
README: doc/pgBadger.pod
pod2text $^ > $@
ifneq ("$(USE_MARKDOWN)", "")
cat doc/pgBadger.pod | grep "=head1 " | sed 's/^=head1 \(.*\)/- [\1](#\1)/' | sed 's/ /-/g' | sed 's/--/- /' > $@.md
sed -i '1s/^/### TABLE OF CONTENTS\n\n/' $@.md
echo >> $@.md
pod2markdown $^ | sed 's/^## /#### /' | sed 's/^# /### /' >> $@.md
endif
.INTERMEDIATE: doc/synopsis.pod
doc/synopsis.pod: Makefile pgbadger
echo "=head1 SYNOPSIS" > $@
./pgbadger --help >> $@
echo "=head1 DESCRIPTION" >> $@
sed -i.bak 's/ +$$//g' $@
rm $@.bak
.PHONY: doc/pgBadger.pod
doc/pgBadger.pod: doc/synopsis.pod Makefile
sed -i.bak '/^=head1 SYNOPSIS/,/^=head1 DESCRIPTION/d' $@
sed -i.bak '4r $<' $@
rm $@.bak
# End.

View File

@ -0,0 +1,83 @@
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
use strict;
my @ALLOWED_ARGS = ('INSTALLDIRS','DESTDIR');
# Parse command line arguments and store them as environment variables
while ($_ = shift) {
my ($k,$v) = split(/=/, $_, 2);
if (grep(/^$k$/, @ALLOWED_ARGS)) {
$ENV{$k} = $v;
}
}
$ENV{DESTDIR} =~ s/\/$//;
# Default install path
my $DESTDIR = $ENV{DESTDIR} || '';
my $INSTALLDIRS = $ENV{INSTALLDIRS} || 'site';
my %merge_compat = ();
if ($ExtUtils::MakeMaker::VERSION >= 6.46) {
%merge_compat = (
'META_MERGE' => {
resources => {
homepage => 'http://pgbadger.darold.net/',
repository => {
type => 'git',
git => 'git@github.com:darold/pgbadger.git',
web => 'https://github.com/darold/pgbadger',
},
},
}
);
}
sub MY::postamble {
return <<'EOMAKE';
USE_MARKDOWN=$(shell which pod2markdown)
README: doc/pgBadger.pod
pod2text $^ > $@
ifneq ("$(USE_MARKDOWN)", "")
cat doc/pgBadger.pod | grep "=head1 " | sed 's/^=head1 \(.*\)/- [\1](#\1)/' | sed 's/ /-/g' | sed 's/--/- /' > $@.md
sed -i '1s/^/### TABLE OF CONTENTS\n\n/' $@.md
echo >> $@.md
pod2markdown $^ | sed 's/^## /#### /' | sed 's/^# /### /' >> $@.md
endif
.INTERMEDIATE: doc/synopsis.pod
doc/synopsis.pod: Makefile pgbadger
echo "=head1 SYNOPSIS" > $@
./pgbadger --help >> $@
echo "=head1 DESCRIPTION" >> $@
sed -i.bak 's/ +$$//g' $@
rm $@.bak
.PHONY: doc/pgBadger.pod
doc/pgBadger.pod: doc/synopsis.pod Makefile
sed -i.bak '/^=head1 SYNOPSIS/,/^=head1 DESCRIPTION/d' $@
sed -i.bak '4r $<' $@
rm $@.bak
EOMAKE
}
WriteMakefile(
'DISTNAME' => 'pgbadger',
'NAME' => 'pgBadger',
'VERSION_FROM' => 'pgbadger',
'dist' => {
'COMPRESS'=>'gzip -9f', 'SUFFIX' => 'gz',
'ZIP'=>'/usr/bin/zip','ZIPFLAGS'=>'-rl'
},
'AUTHOR' => 'Gilles Darold (gilles@darold.net)',
'ABSTRACT' => 'pgBadger - PostgreSQL log analysis report',
'EXE_FILES' => [ qw(pgbadger) ],
'MAN1PODS' => { 'doc/pgBadger.pod' => 'blib/man1/pgbadger.1p' },
'DESTDIR' => $DESTDIR,
'INSTALLDIRS' => $INSTALLDIRS,
'clean' => {},
%merge_compat
);

View File

@ -0,0 +1,737 @@
### TABLE OF CONTENTS
- [NAME](#NAME)
- [SYNOPSIS](#SYNOPSIS)
- [DESCRIPTION](#DESCRIPTION)
- [FEATURE](#FEATURE)
- [REQUIREMENT](#REQUIREMENT)
- [INSTALLATION](#INSTALLATION)
- [openGauss-CONFIGURATION](#openGauss-CONFIGURATION)
- [LOG-STATEMENTS](#LOG-STATEMENTS)
- [PARALLEL-PROCESSING](#PARALLEL-PROCESSING)
- [INCREMENTAL-REPORTS](#INCREMENTAL-REPORTS)
- [BINARY-FORMAT](#BINARY-FORMAT)
- [JSON-FORMAT](#JSON-FORMAT)
- [AUTHORS](#AUTHORS)
- [LICENSE](#LICENSE)
### NAME
gsbadger - a fast openGauss log analysis report
### SYNOPSIS
Usage: gsbadger \[options\] logfile \[...\]
openGauss 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. Default 5 minutes.
-A | --histo-average min: number of minutes to build the histogram graphs
of queries. Default 60 minutes.
-b | --begin datetime : start date/time for the data to be parsed in log
(either a timestamp or a time)
-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.
-D | --dns-resolv : client ip addresses are replaced by their DNS name.
Be warned that this can really slow down gsbadger.
-e | --end datetime : end date/time for the data to be parsed in log
(either a timestamp or a time)
-E | --explode : explode the main report by generating one report
per database. Global information not related to a
database are added to the postgres database report.
-f | --format logtype : possible values: syslog, syslog2, stderr, jsonlog,
cvs, pgbouncer, logplex and rds. Use this option
when gsbadger is not able to detect the log format.
-G | --nograph : disable graphs on HTML output. Enabled by default.
-h | --help : show this message and exit.
-H | --html-dir path : path to directory where HTML report must be written
in incremental mode, binary files stay on directory
defined with -O, --outdir option.
-i | --ident name : programname used as syslog ident. Default: postgres
-I | --incremental : use incremental mode, reports will be generated by
days in a separate directory, --outdir must be set.
-j | --jobs number : number of jobs to run at same time. Run as single
by default or when working with csvlog.
-J | --Jobs number : number of log file to parse in parallel. Process one
file at a time by default or when csvlog is used.
-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.
-L | --logfile-list file:file containing a list of log file to parse.
-m | --maxlength size : maximum length of a query, it will be restricted to
the given size. Default: no truncate
-M | --no-multiline : do not collect multiline statement to avoid garbage
especially on errors that generate a huge report.
-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, out.bin,
out.json or out.tsung. This option can be used
multiple time to output several format. To use json
output the Perl module JSON::XS must be installed,
To dump output to stdout use - as filename.
-O | --outdir path : directory where out file must be saved.
-p | --prefix string : the value of your custom log_line_prefix as
defined in your openGauss.conf. Only use it if you
aren't using one of the standard prefixes specified
in the gsbadger 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, not even a progress
bar.
-r | --remote-host ip : set the host where to execute the cat command on
remote logfile to parse locally the file.
-R | --retention N : number of weeks to keep in incremental mode. Default
to 0, disabled. Used to set the number of weeks to
keep in output directory. Older weeks and days
directory are automatically removed.
-s | --sample number : number of query samples to store. Default: 3.
-S | --select-only : only report SELECT queries.
-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. Can be used multiple time.
-v | --verbose : enable verbose or debug mode. Disabled by default.
-V | --version : show gsbadger version and exit.
-w | --watch-mode : only report errors just like logwatch could do.
-x | --extension : output format. Values: text, html, bin, json or
tsung. Default: html
-X | --extra-files : in incremental mode allow gsbadger to write CSS and
JS files in the output directory as separate files.
-z | --zcat exec_path : set the full path to the zcat program. Use it if
zcat or bzcat or unzip is not in your path.
-Z | --timezone +/-XX : Set the number of hours from GMT of the timezone.
Use this to adjust date/time in JavaScript graphs.
--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. You can use this
option multiple times. For example: "(tbl1|tbl2)".
--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 report of queries by type, database
or user.
--disable-query : do not generate query reports (slowest, most
frequent, queries by users, by database, ...).
--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.
--charset : used to set the HTML charset to be used.
Default: utf-8.
--csv-separator : used to set the CSV field separator, default: ,
--exclude-time regex : any timestamp matching the given regex will be
excluded from the report. Example: "2013-04-12 .*"
You can use this option multiple times.
--include-time regex : only timestamps matching the given regex will be
included in the report. Example: "2013-04-12 .*"
You can use this option multiple times.
--exclude-db name : exclude entries for the specified database from
report. Example: "gs_dump". Can be used multiple
time.
--exclude-appname name : exclude entries for the specified application name
from report. Example: "gs_dump". Can be used
multiple time.
--exclude-line regex : gsbadger will start to exclude any log entry that
will match the given regex. Can be used multiple
time.
--exclude-client name : exclude log entries for the specified client ip.
Can be used multiple time.
--anonymize : obscure all literals in queries, useful to hide
confidential data.
--noreport : prevent gsbadger to create reports in incremental
mode.
--log-duration : force gsbadger to associate log entries generated
by both log_duration = on and log_statement = 'all'
--enable-checksum : used to add a md5 sum under each query report.
--journalctl command : command to use to replace openGauss logfile by
a call to journalctl. Basically it might be:
journalctl -u openGauss-9.5
--pid-dir path : set the path where the pid file must be stored.
Default /tmp
--pid-file file : set the name of the pid file to manage concurrent
execution of gsbadger. Default: gsbadger.pid
--rebuild : used to rebuild all html reports in incremental
output directories where there's binary data files.
--pgbouncer-only : only show PgBouncer related menu in the header.
--start-monday : in incremental mode, calendar's weeks start on
sunday. Use this option to start on monday.
--normalized-only : only dump all normalized query to out.txt
--log-timezone +/-XX : Set the number of hours from GMT of the timezone
that must be used to adjust date/time read from
log file before beeing parsed. Using this option
make more difficult log search with a date/time.
--prettify-json : use it if you want json output to be prettified.
--month-report YYYY-MM : create a cumulative HTML report over the specified
month. Requires incremental output directories and
the presence of all necessary binary data files
gsbadger is able to parse a remote log file using a passwordless ssh connection.
Use the -r or --remote-host to set the host ip address or hostname. There's also
some additional options to fully control the ssh connection.
--ssh-program ssh path to the ssh program to use. Default: ssh.
--ssh-user username connection login name. Default to running user.
--ssh-identity file path to the identity file to use.
--ssh-timeout second timeout to ssh connection failure. Default 10 secs.
--ssh-option options list of -o options to use for the ssh connection.
Options always used:
-o ConnectTimeout=$ssh_timeout
-o PreferredAuthentications=hostbased,publickey
Log file to parse can also be specified using an URI, supported protocol are
http\[s\] and \[s\]ftp. The curl command will be used to download the file and the
file will be parsed during download. The ssh protocol is also supported and will
use the ssh command like with the remote host use. See examples bellow.
Examples:
gsbadger /var/log/openGauss.log
gsbadger /var/log/postgres.log.2.gz /var/log/postgres.log.1.gz /var/log/postgres.log
gsbadger /var/log/openGauss/openGauss-2012-05-*
gsbadger --exclude-query="^(COPY|COMMIT)" /var/log/openGauss.log
gsbadger -b "2012-06-25 10:56:11" -e "2012-06-25 10:59:11" /var/log/openGauss.log
cat /var/log/postgres.log | gsbadger -
# Log prefix with stderr log output
gsbadger --prefix '%t [%p]: user=%u,db=%d,client=%h' /pglog/openGauss-2012-08-21*
gsbadger --prefix '%m %u@%d %p %r %a : ' /pglog/openGauss.log
# Log line prefix with syslog log output
gsbadger --prefix 'user=%u,db=%d,client=%h,appname=%a' /pglog/openGauss-2012-08-21*
# Use my 8 CPUs to parse my 10GB file faster, much faster
gsbadger -j 8 /pglog/openGauss-10.1-main.log
Use URI notation for remote log file:
gsbadger http://172.12.110.1//var/log/openGauss/openGauss-10.1-main.log
gsbadger ftp://username@172.12.110.14/openGauss-10.1-main.log
gsbadger ssh://username@172.12.110.14//var/log/openGauss/openGauss-10.1-main.log*
You can use together a local openGauss log and a remote pgbouncer log file to parse:
gsbadger /var/log/openGauss/openGauss-10.1-main.log ssh://username@172.12.110.14/pgbouncer.log
Generate Tsung sessions XML file with select queries only:
gsbadger -S -o sessions.tsung --prefix '%t [%p]: user=%u,db=%d ' /pglog/openGauss-10.1.log
Reporting errors every week by cron job:
30 23 * * 1 /usr/bin/gsbadger -q -w /var/log/openGauss.log -o /var/reports/gs_errors.html
Generate report every week using incremental behavior:
0 4 * * 1 /usr/bin/gsbadger -q `find /var/log/ -mtime -7 -name "openGauss.log*"` -o /var/reports/gs_errors-`date +\%F`.html -l /var/reports/gsbadger_incremental_file.dat
This supposes that your log file and HTML report are also rotated every week.
Or better, use the auto-generated incremental reports:
0 4 * * * /usr/bin/gsbadger -I -q /var/log/openGauss/openGauss.log.1 -O /var/www/gs_reports/
will generate a report per day and per week.
In incremental mode, you can also specify the number of week to keep in the
reports:
/usr/bin/gsbadger --retention 2 -I -q /var/log/openGauss/openGauss.log.1 -O /var/www/gs_reports/
If you have a pg\_dump at 23:00 and 13:00 each day during half an hour, you can
use gsbadger as follow to exclude these period from the report:
gsbadger --exclude-time "2013-09-.* (23|13):.*" openGauss.log
This will help avoid having COPY statements, as generated by pg\_dump, on top of
the list of slowest queries. You can also use --exclude-appname "pg\_dump" to
solve this problem in a simpler way.
You can also parse journalctl output just as if it was a log file:
gsbadger --journalctl 'journalctl -u openGauss-9.5'
or worst, call it from a remote host:
gsbadger -r 192.168.1.159 --journalctl 'journalctl -u openGauss-9.5'
you don't need to specify any log file at command line, but if you have other
openGauss log file to parse, you can add them as usual.
To rebuild all incremental html reports after, proceed as follow:
rm /path/to/reports/*.js
rm /path/to/reports/*.css
gsbadger -X -I -O /path/to/reports/ --rebuild
it will also update all resource files (JS and CSS). Use -E or --explode
if the reports were built using this option.
gsbadger also support Heroku openGauss logs using logplex format:
heroku logs -p postgres | gsbadger -f logplex -o heroku.html -
this will stream Heroku openGauss log to gsbadger through stdin.
gsbadger can auto detect RDS and cloudwatch openGauss logs using
rds format:
gsbadger -f rds -o rds_out.html rds.log
To create a cumulative report over a month use command:
gsbadger --month-report 2919-05 /path/to/incremantal/reports/
this will add a link to the month name into the calendar view in
incremental reports to look at report for month 2019 May.
Use -E or --explode if the reports were built using this option.
### DESCRIPTION
gsbadger is a openGauss log analyzer built for speed with fully reports
from your openGauss log file. It's a single and small Perl script that
outperforms any other openGauss log analyzer.
It is written in pure Perl and uses a JavaScript library (flotr2) to draw
graphs so that you don't need to install any additional Perl modules or
other packages. Furthermore, this library gives us more features such
as zooming. gsbadger also uses the Bootstrap JavaScript library and
the FontAwesome webfont for better design. Everything is embedded.
gsbadger is able to autodetect your log file format (syslog, stderr, csvlog
or jsonlog) if the file is long enough. It is designed to parse huge log
files as well as compressed files. Supported compressed format are gzip,
bzip2 and xz. For the xz format you must have an xz version upper than 5.05
that supports the --robot option. For the complete list of features see below.
All charts are zoomable and can be saved as PNG images.
You can also limit gsbadger to only report errors or remove any part of the
report using command line options.
gsbadger supports any custom format set into the log\_line\_prefix directive of
your openGauss.conf file as long as it at least specify the %t and %p patterns.
gsbadger allows parallel processing of a single log file or multiple
files through the use of the -j option specifying the number of CPUs.
If you want to save system performance you can also use log\_duration instead of
log\_min\_duration\_statement to have reports on duration and number of queries only.
### FEATURE
gsbadger reports everything about your SQL queries:
Overall statistics.
The most frequent waiting queries.
Queries that waited the most.
Queries generating the most temporary files.
Queries generating the largest temporary files.
The slowest queries.
Queries that took up the most time.
The most frequent queries.
The most frequent errors.
Histogram of query times.
Histogram of sessions times.
Users involved in top queries.
Applications involved in top queries.
Queries generating the most cancellation.
Queries most cancelled.
The most time consuming prepare/bind queries
The following reports are also available with hourly charts divided into
periods of five minutes:
SQL queries statistics.
Temporary file statistics.
Checkpoints statistics.
Autovacuum and autoanalyze statistics.
Cancelled queries.
Error events (panic, fatal, error and warning).
Error class distribution.
There are also some pie charts about distribution of:
Locks statistics.
Queries by type (select/insert/update/delete).
Distribution of queries type per database/application
Sessions per database/user/client/application.
Connections per database/user/client/application.
Autovacuum and autoanalyze per table.
Queries per user and total duration per user.
All charts are zoomable and can be saved as PNG images. SQL queries reported are
highlighted and beautified automatically.
gsbadger is also able to parse PgBouncer log files and to create the following
reports:
Request Throughput
Bytes I/O Throughput
Queries Average duration
Simultaneous sessions
Histogram of sessions times
Sessions per database
Sessions per user
Sessions per host
Established connections
Connections per database
Connections per user
Connections per host
Most used reserved pools
Most Frequent Errors/Events
You can also have incremental reports with one report per day and a cumulative
report per week. Two multiprocess modes are available to speed up log parsing,
one using one core per log file, and the second using multiple cores to parse
a single file. These modes can be combined.
Histogram granularity can be adjusted using the -A command line option. By default
they will report the mean of each top queries/errors occurring per hour, but you can
specify the granularity down to the minute.
gsbadger can also be used in a central place to parse remote log files using a
passwordless SSH connection. This mode can be used with compressed files and in
the multiprocess per file mode (-J) but can not be used with the CSV log format.
### REQUIREMENT
gsbadger comes as a single Perl script - you do not need anything other than a modern
Perl distribution. Charts are rendered using a JavaScript library so you don't need
anything other than a web browser. Your browser will do all the work.
If you planned to parse openGauss CSV log files you might need some Perl Modules:
Text::CSV_XS - to parse openGauss CSV log files.
This module is optional, if you don't have openGauss log in the CSV format you don't
need to install it.
If you want to export statistics as JSON file you need an additional Perl module:
JSON::XS - JSON serialising/deserialising, done correctly and fast
This module is optional, if you don't select the json output format you don't
need to install it. You can install it on a Debian like system using:
sudo apt-get install libjson-xs-perl
and in RPM like system using:
sudo yum install perl-JSON-XS
Compressed log file format is autodetected from the file extension. If gsbadger find
a gz extension it will use the zcat utility, with a bz2 extension it will use bzcat
and if the file extension is zip or xz then the unzip or xz utilities will be used.
If those utilities are not found in the PATH environment variable then use the --zcat
command line option to change this path. For example:
--zcat="/usr/local/bin/gunzip -c" or --zcat="/usr/local/bin/bzip2 -dc"
--zcat="C:\tools\unzip -p"
By default gsbadger will use the zcat, bzcat and unzip utilities following the
file extension. If you use the default autodetection compress format you can
mixed gz, bz2, xz or zip files. Specifying a custom value to --zcat option will
remove this feature of mixed compressed format.
Note that multiprocessing can not be used with compressed files or CSV files as
well as under Windows platform.
### INSTALLATION
Download the tarball from GitHub and unpack the archive as follow:
tar xzf gsbadger-11.x.tar.gz
cd gsbadger-11.x/
perl Makefile.PL
make && sudo make install
This will copy the Perl script gsbadger to /usr/local/bin/gsbadger by default and the
man page into /usr/local/share/man/man1/gsbadger.1. Those are the default installation
directories for 'site' install.
If you want to install all under /usr/ location, use INSTALLDIRS='perl' as an argument
of Makefile.PL. The script will be installed into /usr/bin/gsbadger and the manpage
into /usr/share/man/man1/gsbadger.1.
For example, to install everything just like Debian does, proceed as follows:
perl Makefile.PL INSTALLDIRS=vendor
By default INSTALLDIRS is set to site.
### openGauss CONFIGURATION
You must enable and set some configuration directives in your openGauss.conf
before starting.
You must first enable SQL query logging to have something to parse:
log_min_duration_statement = 0
Here every statement will be logged, on a busy server you may want to increase
this value to only log queries with a longer duration. Note that if you have
log\_statement set to 'all' nothing will be logged through the log\_min\_duration\_statement
directive. See the next chapter for more information.
gsbadger supports any custom format set into the log\_line\_prefix directive of
your openGauss.conf file as long as it at least specify a time escape sequence
(%t, %m or %n) and the process related escape sequence (%p or %c).
For example, with 'stderr' log format, log\_line\_prefix must be at least:
log_line_prefix = '%t [%p]: '
Log line prefix could add user, database name, application name and client ip
address as follows:
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '
or for syslog log file format:
log_line_prefix = 'user=%u,db=%d,app=%a,client=%h '
Log line prefix for stderr output could also be:
log_line_prefix = '%t [%p]: db=%d,user=%u,app=%a,client=%h '
or for syslog output:
log_line_prefix = 'db=%d,user=%u,app=%a,client=%h '
You need to enable other parameters in openGauss.conf to get more information from your log files:
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0
log_error_verbosity = default
Do not enable log\_statement as its log format will not be parsed by gsbadger.
Of course your log messages should be in English without locale support:
lc_messages='C'
but this is not only recommended by gsbadger.
### LOG STATEMENTS
Considerations about log\_min\_duration\_statement, log\_duration and log\_statement
configuration directives.
If you want the query statistics to include the actual query strings, you
must set log\_min\_duration\_statement to 0 or more milliseconds.
If you just want to report duration and number of queries and don't want all
details about queries, set log\_min\_duration\_statement to -1 to disable it and
enable log\_duration in your openGauss.conf file. If you want to add the most
common request report you can either choose to set log\_min\_duration\_statement
to a higher value or choose to enable log\_statement.
Enabling log\_min\_duration\_statement will add reports about slowest queries and
queries that took up the most time. Take care that if you have log\_statement
set to 'all' nothing will be logged with log\_min\_duration\_statement.
Warning: Do not enable both log\_min\_duration\_statement, log\_duration and
log\_statement all together, this will result in wrong counter values. Note
that this will also increase drastically the size of your log.
log\_min\_duration\_statement should always be preferred.
### PARALLEL PROCESSING
To enable parallel processing you just have to use the -j N option where N is
the number of cores you want to use.
gsbadger will then proceed as follow:
for each log file
chunk size = int(file size / N)
look at start/end offsets of these chunks
fork N processes and seek to the start offset of each chunk
each process will terminate when the parser reach the end offset
of its chunk
each process write stats into a binary temporary file
wait for all children has terminated
All binary temporary files generated will then be read and loaded into
memory to build the html output.
With that method, at start/end of chunks gsbadger may truncate or omit a
maximum of N queries per log file which is an insignificant gap if you have
millions of queries in your log file. The chance that the query that you were
looking for is lost is near 0, this is why I think this gap is livable. Most
of the time the query is counted twice but truncated.
When you have many small log files and many CPUs it is speedier to dedicate
one core to one log file at a time. To enable this behavior you have to use
option -J N instead. With 200 log files of 10MB each the use of the -J option
starts being really interesting with 8 Cores. Using this method you will be
sure not to lose any queries in the reports.
He are a benchmark done on a server with 8 CPUs and a single file of 9.5GB.
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
--------+---------+-------+-------+------
-j | 1h41m18 | 50m25 | 25m39 | 15m58
-J | 1h41m18 | 54m28 | 41m16 | 34m45
With 200 log files of 10MB each and a total of 2GB the results are slightly
different:
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
--------+-------+-------+-------+------
-j | 20m15 | 9m56 | 5m20 | 4m20
-J | 20m15 | 9m49 | 5m00 | 2m40
So it is recommended to use -j unless you have hundreds of small log files
and can use at least 8 CPUs.
IMPORTANT: when you are using parallel parsing gsbadger will generate a
lot of temporary files in the /tmp directory and will remove them at the
end, so do not remove those files unless gsbadger is not running. They are
all named with the following template tmp\_gsbadgerXXXX.bin so they can be
easily identified.
### INCREMENTAL REPORTS
gsbadger includes an automatic incremental report mode using option -I or
\--incremental. When running in this mode, gsbadger will generate one report
per day and a cumulative report per week. Output is first done in binary
format into the mandatory output directory (see option -O or --outdir),
then in HTML format for daily and weekly reports with a main index file.
The main index file will show a dropdown menu per week with a link to each
week report and links to daily reports of each week.
For example, if you run gsbadger as follows based on a daily rotated file:
0 4 * * * /usr/bin/gsbadger -I -q /var/log/openGauss/openGauss.log.1 -O /var/www/gs_reports/
you will have all daily and weekly reports for the full running period.
In this mode gsbadger will create an automatic incremental file in the
output directory, so you don't have to use the -l option unless you want
to change the path of that file. This means that you can run gsbadger in
this mode each day on a log file rotated each week, and it will not count
the log entries twice.
To save disk space you may want to use the -X or --extra-files command line
option to force gsbadger to write JavaScript and CSS to separate files in
the output directory. The resources will then be loaded using script and
link tags.
#### Rebuilding reports
Incremental reports can be rebuilt after a gsbadger report fix or a new
feature to update all HTML reports. To rebuild all reports where a binary
file is still present proceed as follow:
rm /path/to/reports/*.js
rm /path/to/reports/*.css
gsbadger -X -I -O /path/to/reports/ --rebuild
it will also update all resource files (JS and CSS). Use -E or --explode
if the reports were built using this option.
#### Monthly reports
By default gsbadger in incremental mode only compute daily and weekly reports.
If you want monthly cumulative reports you will have to use a separate command
to specify the report to build. For example to build a report for August 2019:
gsbadger -X --month-report 2919-08 /var/www/gs_reports/
this will add a link to the month name into the calendar view of incremental
reports to look at monthly report. The report for a current month can be run
every day it is entirely rebuilt each time. The monthly report is not built by
default because it could take lot of time following the amount of data.
If reports were built with the per database option ( -E | --explode ) it must
be used too when calling gsbadger to build monthly report:
gsbadger -E -X --month-report 2919-08 /var/www/gs_reports/
This is the same when using the rebuild option ( -R | --rebuild ).
### BINARY FORMAT
Using the binary format it is possible to create custom incremental and
cumulative reports. For example, if you want to refresh a gsbadger
report each hour from a daily openGauss log file, you can proceed by
running each hour the following commands:
gsbadger --last-parsed .gsbadger_last_state_file -o sunday/hourX.bin /var/log/pgsql/openGauss-Sun.log
to generate the incremental data files in binary format. And to generate the fresh HTML
report from that binary file:
gsbadger sunday/*.bin
Or as another example, if you generate one log file per hour and you want
reports to be rebuilt each time the log file is rotated, proceed as
follows:
gsbadger -o day1/hour01.bin /var/log/pgsql/pglog/openGauss-2012-03-23_10.log
gsbadger -o day1/hour02.bin /var/log/pgsql/pglog/openGauss-2012-03-23_11.log
gsbadger -o day1/hour03.bin /var/log/pgsql/pglog/openGauss-2012-03-23_12.log
...
When you want to refresh the HTML report, for example each time after a new binary file
is generated, just do the following:
gsbadger -o day1_report.html day1/*.bin
Adjust the commands to suit your particular needs.
### JSON FORMAT
JSON format is good for sharing data with other languages, which makes it
easy to integrate gsbadger result into other monitoring tools like Cacti
or Graphite.
### AUTHORS
gsbadger is an original work from Gilles Darold.
The gsbadger logo is an original creation of Damien Cazeils.
The gsbadger v4.x design comes from the "Art is code" company.
This web site is a work of Gilles Darold.
gsbadger is maintained by Gilles Darold and every one who wants to contribute.
Many people have contributed to gsbadger, they are all quoted in the Changelog file.
### LICENSE
gsbadger is free software distributed under the openGauss Licence.
Copyright (c) 2012-2019, Gilles Darold
A modified version of the SQL::Beautify Perl Module is embedded in gsbadger
with copyright (C) 2009 by Jonas Kramer and is published under the terms of
the Artistic License 2.0.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,942 @@
.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" Set up some character translations and predefined strings. \*(-- will
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
.\" double quote, and \*(R" will give a right double quote. \*(C+ will
.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
.\" nothing in troff, for use with C<>.
.tr \(*W-
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
.ie n \{\
. ds -- \(*W-
. ds PI pi
. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
. ds L" ""
. ds R" ""
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds -- \|\(em\|
. ds PI \(*p
. ds L" ``
. ds R" ''
. ds C`
. ds C'
'br\}
.\"
.\" Escape single quotes in literal strings from groff's Unicode transform.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\"
.\" If the F register is turned on, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.\"
.\" Avoid warning from groff about undefined register 'F'.
.de IX
..
.nr rF 0
.if \n(.g .if rF .nr rF 1
.if (\n(rF:(\n(.g==0)) \{
. if \nF \{
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. if !\nF==2 \{
. nr % 0
. nr F 2
. \}
. \}
.\}
.rr rF
.\"
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear. Run. Save yourself. No user-serviceable parts.
. \" fudge factors for nroff and troff
.if n \{\
. ds #H 0
. ds #V .8m
. ds #F .3m
. ds #[ \f1
. ds #] \fP
.\}
.if t \{\
. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
. ds #V .6m
. ds #F 0
. ds #[ \&
. ds #] \&
.\}
. \" simple accents for nroff and troff
.if n \{\
. ds ' \&
. ds ` \&
. ds ^ \&
. ds , \&
. ds ~ ~
. ds /
.\}
.if t \{\
. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
.\}
. \" troff and (daisy-wheel) nroff accents
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
.ds ae a\h'-(\w'a'u*4/10)'e
.ds Ae A\h'-(\w'A'u*4/10)'E
. \" corrections for vroff
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
. \" for low resolution devices (crt and lpr)
.if \n(.H>23 .if \n(.V>19 \
\{\
. ds : e
. ds 8 ss
. ds o a
. ds d- d\h'-1'\(ga
. ds D- D\h'-1'\(hy
. ds th \o'bp'
. ds Th \o'LP'
. ds ae ae
. ds Ae AE
.\}
.rm #[ #] #H #V #F C
.\" ========================================================================
.\"
.IX Title "PGBADGER 1"
.TH PGBADGER 1 "2022-08-08" "perl v5.16.3" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
.nh
.SH "NAME"
pgBadger \- a fast PostgreSQL log analysis report
.SH "SYNOPSIS"
.IX Header "SYNOPSIS"
Usage: pgbadger [options] logfile [...]
.PP
.Vb 1
\& PostgreSQL log analyzer with fully detailed reports and graphs.
.Ve
.PP
Arguments:
.PP
.Vb 3
\& 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.
.Ve
.PP
Options:
.PP
.Vb 10
\& \-a | \-\-average minutes : number of minutes to build the average graphs of
\& queries and connections. Default 5 minutes.
\& \-A | \-\-histo\-average min: number of minutes to build the histogram graphs
\& of queries. Default 60 minutes.
\& \-b | \-\-begin datetime : start date/time for the data to be parsed in log
\& (either a timestamp or a time)
\& \-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.
\& \-D | \-\-dns\-resolv : client ip addresses are replaced by their DNS name.
\& Be warned that this can really slow down pgBadger.
\& \-e | \-\-end datetime : end date/time for the data to be parsed in log
\& (either a timestamp or a time)
\& \-E | \-\-explode : explode the main report by generating one report
\& per database. Global information not related to a
\& database are added to the postgres database report.
\& \-f | \-\-format logtype : possible values: syslog, syslog2, stderr, jsonlog,
\& cvs, pgbouncer, logplex and rds. Use this option
\& when pgBadger is not able to detect the log format.
\& \-G | \-\-nograph : disable graphs on HTML output. Enabled by default.
\& \-h | \-\-help : show this message and exit.
\& \-H | \-\-html\-dir path : path to directory where HTML report must be written
\& in incremental mode, binary files stay on directory
\& defined with \-O, \-\-outdir option.
\& \-i | \-\-ident name : programname used as syslog ident. Default: postgres
\& \-I | \-\-incremental : use incremental mode, reports will be generated by
\& days in a separate directory, \-\-outdir must be set.
\& \-j | \-\-jobs number : number of jobs to run at same time. Run as single
\& by default or when working with csvlog.
\& \-J | \-\-Jobs number : number of log file to parse in parallel. Process one
\& file at a time by default or when csvlog is used.
\& \-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.
\& \-L | \-\-logfile\-list file:file containing a list of log file to parse.
\& \-m | \-\-maxlength size : maximum length of a query, it will be restricted to
\& the given size. Default: no truncate
\& \-M | \-\-no\-multiline : do not collect multiline statement to avoid garbage
\& especially on errors that generate a huge report.
\& \-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, out.bin,
\& out.json or out.tsung. This option can be used
\& multiple time to output several format. To use json
\& output the Perl module JSON::XS must be installed,
\& To dump output to stdout use \- as filename.
\& \-O | \-\-outdir path : directory where out file must be saved.
\& \-p | \-\-prefix string : the value of your custom log_line_prefix as
\& defined in your postgresql.conf. Only use it if you
\& aren\*(Aqt 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\*(Aqt print anything to stdout, not even a progress
\& bar.
\& \-r | \-\-remote\-host ip : set the host where to execute the cat command on
\& remote logfile to parse locally the file.
\& \-R | \-\-retention N : number of weeks to keep in incremental mode. Default
\& to 0, disabled. Used to set the number of weeks to
\& keep in output directory. Older weeks and days
\& directory are automatically removed.
\& \-s | \-\-sample number : number of query samples to store. Default: 3.
\& \-S | \-\-select\-only : only report SELECT queries.
\& \-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. Can be used multiple time.
\& \-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, bin, json or
\& tsung. Default: html
\& \-X | \-\-extra\-files : in incremental mode allow pgBadger to write CSS and
\& JS files in the output directory as separate files.
\& \-z | \-\-zcat exec_path : set the full path to the zcat program. Use it if
\& zcat or bzcat or unzip is not in your path.
\& \-Z | \-\-timezone +/\-XX : Set the number of hours from GMT of the timezone.
\& Use this to adjust date/time in JavaScript graphs.
\& \-\-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. You can use this
\& option multiple times. For example: "(tbl1|tbl2)".
\& \-\-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 report of queries by type, database
\& or user.
\& \-\-disable\-query : do not generate query reports (slowest, most
\& frequent, queries by users, by database, ...).
\& \-\-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.
\& \-\-charset : used to set the HTML charset to be used.
\& Default: utf\-8.
\& \-\-csv\-separator : used to set the CSV field separator, default: ,
\& \-\-exclude\-time regex : any timestamp matching the given regex will be
\& excluded from the report. Example: "2013\-04\-12 .*"
\& You can use this option multiple times.
\& \-\-include\-time regex : only timestamps matching the given regex will be
\& included in the report. Example: "2013\-04\-12 .*"
\& You can use this option multiple times.
\& \-\-exclude\-db name : exclude entries for the specified database from
\& report. Example: "pg_dump". Can be used multiple
\& time.
\& \-\-exclude\-appname name : exclude entries for the specified application name
\& from report. Example: "pg_dump". Can be used
\& multiple time.
\& \-\-exclude\-line regex : pgBadger will start to exclude any log entry that
\& will match the given regex. Can be used multiple
\& time.
\& \-\-exclude\-client name : exclude log entries for the specified client ip.
\& Can be used multiple time.
\& \-\-anonymize : obscure all literals in queries, useful to hide
\& confidential data.
\& \-\-noreport : prevent pgBadger to create reports in incremental
\& mode.
\& \-\-log\-duration : force pgBadger to associate log entries generated
\& by both log_duration = on and log_statement = \*(Aqall\*(Aq
\& \-\-enable\-checksum : used to add a md5 sum under each query report.
\& \-\-journalctl command : command to use to replace PostgreSQL logfile by
\& a call to journalctl. Basically it might be:
\& journalctl \-u postgresql\-9.5
\& \-\-pid\-dir path : set the path where the pid file must be stored.
\& Default /tmp
\& \-\-pid\-file file : set the name of the pid file to manage concurrent
\& execution of pgBadger. Default: pgbadger.pid
\& \-\-rebuild : used to rebuild all html reports in incremental
\& output directories where there\*(Aqs binary data files.
\& \-\-pgbouncer\-only : only show PgBouncer related menu in the header.
\& \-\-start\-monday : in incremental mode, calendar\*(Aqs weeks start on
\& sunday. Use this option to start on monday.
\& \-\-normalized\-only : only dump all normalized query to out.txt
\& \-\-log\-timezone +/\-XX : Set the number of hours from GMT of the timezone
\& that must be used to adjust date/time read from
\& log file before beeing parsed. Using this option
\& make more difficult log search with a date/time.
\& \-\-prettify\-json : use it if you want json output to be prettified.
\& \-\-month\-report YYYY\-MM : create a cumulative HTML report over the specified
\& month. Requires incremental output directories and
\& the presence of all necessary binary data files
.Ve
.PP
pgBadger is able to parse a remote log file using a passwordless ssh connection.
Use the \-r or \-\-remote\-host to set the host ip address or hostname. There's also
some additional options to fully control the ssh connection.
.PP
.Vb 8
\& \-\-ssh\-program ssh path to the ssh program to use. Default: ssh.
\& \-\-ssh\-user username connection login name. Default to running user.
\& \-\-ssh\-identity file path to the identity file to use.
\& \-\-ssh\-timeout second timeout to ssh connection failure. Default 10 secs.
\& \-\-ssh\-option options list of \-o options to use for the ssh connection.
\& Options always used:
\& \-o ConnectTimeout=$ssh_timeout
\& \-o PreferredAuthentications=hostbased,publickey
.Ve
.PP
Log file to parse can also be specified using an \s-1URI,\s0 supported protocol are
http[s] and [s]ftp. The curl command will be used to download the file and the
file will be parsed during download. The ssh protocol is also supported and will
use the ssh command like with the remote host use. See examples bellow.
.PP
Examples:
.PP
.Vb 10
\& 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
\& pgbadger \-\-prefix \*(Aq%t [%p]: user=%u,db=%d,client=%h\*(Aq /pglog/postgresql\-2012\-08\-21*
\& pgbadger \-\-prefix \*(Aq%m %u@%d %p %r %a : \*(Aq /pglog/postgresql.log
\& # Log line prefix with syslog log output
\& pgbadger \-\-prefix \*(Aquser=%u,db=%d,client=%h,appname=%a\*(Aq /pglog/postgresql\-2012\-08\-21*
\& # Use my 8 CPUs to parse my 10GB file faster, much faster
\& pgbadger \-j 8 /pglog/postgresql\-10.1\-main.log
.Ve
.PP
Use \s-1URI\s0 notation for remote log file:
.PP
.Vb 3
\& pgbadger http://172.12.110.1//var/log/postgresql/postgresql\-10.1\-main.log
\& pgbadger ftp://username@172.12.110.14/postgresql\-10.1\-main.log
\& pgbadger ssh://username@172.12.110.14//var/log/postgresql/postgresql\-10.1\-main.log*
.Ve
.PP
You can use together a local PostgreSQL log and a remote pgbouncer log file to parse:
.PP
.Vb 1
\& pgbadger /var/log/postgresql/postgresql\-10.1\-main.log ssh://username@172.12.110.14/pgbouncer.log
.Ve
.PP
Generate Tsung sessions \s-1XML\s0 file with select queries only:
.PP
.Vb 1
\& pgbadger \-S \-o sessions.tsung \-\-prefix \*(Aq%t [%p]: user=%u,db=%d \*(Aq /pglog/postgresql\-10.1.log
.Ve
.PP
Reporting errors every week by cron job:
.PP
.Vb 1
\& 30 23 * * 1 /usr/bin/pgbadger \-q \-w /var/log/postgresql.log \-o /var/reports/pg_errors.html
.Ve
.PP
Generate report every week using incremental behavior:
.PP
.Vb 1
\& 0 4 * * 1 /usr/bin/pgbadger \-q \`find /var/log/ \-mtime \-7 \-name "postgresql.log*"\` \-o /var/reports/pg_errors\-\`date +\e%F\`.html \-l /var/reports/pgbadger_incremental_file.dat
.Ve
.PP
This supposes that your log file and \s-1HTML\s0 report are also rotated every week.
.PP
Or better, use the auto-generated incremental reports:
.PP
.Vb 1
\& 0 4 * * * /usr/bin/pgbadger \-I \-q /var/log/postgresql/postgresql.log.1 \-O /var/www/pg_reports/
.Ve
.PP
will generate a report per day and per week.
.PP
In incremental mode, you can also specify the number of week to keep in the
reports:
.PP
.Vb 1
\& /usr/bin/pgbadger \-\-retention 2 \-I \-q /var/log/postgresql/postgresql.log.1 \-O /var/www/pg_reports/
.Ve
.PP
If you have a pg_dump at 23:00 and 13:00 each day during half an hour, you can
use pgBadger as follow to exclude these period from the report:
.PP
.Vb 1
\& pgbadger \-\-exclude\-time "2013\-09\-.* (23|13):.*" postgresql.log
.Ve
.PP
This will help avoid having \s-1COPY\s0 statements, as generated by pg_dump, on top of
the list of slowest queries. You can also use \-\-exclude\-appname \*(L"pg_dump\*(R" to
solve this problem in a simpler way.
.PP
You can also parse journalctl output just as if it was a log file:
.PP
.Vb 1
\& pgbadger \-\-journalctl \*(Aqjournalctl \-u postgresql\-9.5\*(Aq
.Ve
.PP
or worst, call it from a remote host:
.PP
.Vb 1
\& pgbadger \-r 192.168.1.159 \-\-journalctl \*(Aqjournalctl \-u postgresql\-9.5\*(Aq
.Ve
.PP
you don't need to specify any log file at command line, but if you have other
PostgreSQL log file to parse, you can add them as usual.
.PP
To rebuild all incremental html reports after, proceed as follow:
.PP
.Vb 3
\& rm /path/to/reports/*.js
\& rm /path/to/reports/*.css
\& pgbadger \-X \-I \-O /path/to/reports/ \-\-rebuild
.Ve
.PP
it will also update all resource files (\s-1JS\s0 and \s-1CSS\s0). Use \-E or \-\-explode
if the reports were built using this option.
.PP
pgBadger also support Heroku PostgreSQL logs using logplex format:
.PP
.Vb 1
\& heroku logs \-p postgres | pgbadger \-f logplex \-o heroku.html \-
.Ve
.PP
this will stream Heroku PostgreSQL log to pgbadger through stdin.
.PP
pgBadger can auto detect \s-1RDS\s0 and cloudwatch PostgreSQL logs using
rds format:
.PP
.Vb 1
\& pgbadger \-f rds \-o rds_out.html rds.log
.Ve
.PP
To create a cumulative report over a month use command:
.PP
.Vb 1
\& pgbadger \-\-month\-report 2919\-05 /path/to/incremantal/reports/
.Ve
.PP
this will add a link to the month name into the calendar view in
incremental reports to look at report for month 2019 May.
Use \-E or \-\-explode if the reports were built using this option.
.SH "DESCRIPTION"
.IX Header "DESCRIPTION"
pgBadger is a PostgreSQL log analyzer built for speed with fully reports
from your PostgreSQL log file. It's a single and small Perl script that
outperforms any other PostgreSQL log analyzer.
.PP
It is written in pure Perl and uses a JavaScript library (flotr2) to draw
graphs so that you don't need to install any additional Perl modules or
other packages. Furthermore, this library gives us more features such
as zooming. pgBadger also uses the Bootstrap JavaScript library and
the FontAwesome webfont for better design. Everything is embedded.
.PP
pgBadger is able to autodetect your log file format (syslog, stderr, csvlog
or jsonlog) if the file is long enough. It is designed to parse huge log
files as well as compressed files. Supported compressed format are gzip,
bzip2 and xz. For the xz format you must have an xz version upper than 5.05
that supports the \-\-robot option. For the complete list of features see below.
.PP
All charts are zoomable and can be saved as \s-1PNG\s0 images.
.PP
You can also limit pgBadger to only report errors or remove any part of the
report using command line options.
.PP
pgBadger supports any custom format set into the log_line_prefix directive of
your postgresql.conf file as long as it at least specify the \f(CW%t\fR and \f(CW%p\fR patterns.
.PP
pgBadger allows parallel processing of a single log file or multiple
files through the use of the \-j option specifying the number of CPUs.
.PP
If you want to save system performance you can also use log_duration instead of
log_min_duration_statement to have reports on duration and number of queries only.
.SH "FEATURE"
.IX Header "FEATURE"
pgBadger reports everything about your \s-1SQL\s0 queries:
.PP
.Vb 10
\& Overall statistics.
\& The most frequent waiting queries.
\& Queries that waited the most.
\& Queries generating the most temporary files.
\& Queries generating the largest temporary files.
\& The slowest queries.
\& Queries that took up the most time.
\& The most frequent queries.
\& The most frequent errors.
\& Histogram of query times.
\& Histogram of sessions times.
\& Users involved in top queries.
\& Applications involved in top queries.
\& Queries generating the most cancellation.
\& Queries most cancelled.
\& The most time consuming prepare/bind queries
.Ve
.PP
The following reports are also available with hourly charts divided into
periods of five minutes:
.PP
.Vb 7
\& SQL queries statistics.
\& Temporary file statistics.
\& Checkpoints statistics.
\& Autovacuum and autoanalyze statistics.
\& Cancelled queries.
\& Error events (panic, fatal, error and warning).
\& Error class distribution.
.Ve
.PP
There are also some pie charts about distribution of:
.PP
.Vb 7
\& Locks statistics.
\& Queries by type (select/insert/update/delete).
\& Distribution of queries type per database/application
\& Sessions per database/user/client/application.
\& Connections per database/user/client/application.
\& Autovacuum and autoanalyze per table.
\& Queries per user and total duration per user.
.Ve
.PP
All charts are zoomable and can be saved as \s-1PNG\s0 images. \s-1SQL\s0 queries reported are
highlighted and beautified automatically.
.PP
pgBadger is also able to parse PgBouncer log files and to create the following
reports:
.PP
.Vb 10
\& Request Throughput
\& Bytes I/O Throughput
\& Queries Average duration
\& Simultaneous sessions
\& Histogram of sessions times
\& Sessions per database
\& Sessions per user
\& Sessions per host
\& Established connections
\& Connections per database
\& Connections per user
\& Connections per host
\& Most used reserved pools
\& Most Frequent Errors/Events
.Ve
.PP
You can also have incremental reports with one report per day and a cumulative
report per week. Two multiprocess modes are available to speed up log parsing,
one using one core per log file, and the second using multiple cores to parse
a single file. These modes can be combined.
.PP
Histogram granularity can be adjusted using the \-A command line option. By default
they will report the mean of each top queries/errors occurring per hour, but you can
specify the granularity down to the minute.
.PP
pgBadger can also be used in a central place to parse remote log files using a
passwordless \s-1SSH\s0 connection. This mode can be used with compressed files and in
the multiprocess per file mode (\-J) but can not be used with the \s-1CSV\s0 log format.
.SH "REQUIREMENT"
.IX Header "REQUIREMENT"
pgBadger comes as a single Perl script \- you do not need anything other than a modern
Perl distribution. Charts are rendered using a JavaScript library so you don't need
anything other than a web browser. Your browser will do all the work.
.PP
If you planned to parse PostgreSQL \s-1CSV\s0 log files you might need some Perl Modules:
.PP
.Vb 1
\& Text::CSV_XS \- to parse PostgreSQL CSV log files.
.Ve
.PP
This module is optional, if you don't have PostgreSQL log in the \s-1CSV\s0 format you don't
need to install it.
.PP
If you want to export statistics as \s-1JSON\s0 file you need an additional Perl module:
.PP
.Vb 1
\& JSON::XS \- JSON serialising/deserialising, done correctly and fast
.Ve
.PP
This module is optional, if you don't select the json output format you don't
need to install it. You can install it on a Debian like system using:
.PP
.Vb 1
\& sudo apt\-get install libjson\-xs\-perl
.Ve
.PP
and in \s-1RPM\s0 like system using:
.PP
.Vb 1
\& sudo yum install perl\-JSON\-XS
.Ve
.PP
Compressed log file format is autodetected from the file extension. If pgBadger find
a gz extension it will use the zcat utility, with a bz2 extension it will use bzcat
and if the file extension is zip or xz then the unzip or xz utilities will be used.
.PP
If those utilities are not found in the \s-1PATH\s0 environment variable then use the \-\-zcat
command line option to change this path. For example:
.PP
.Vb 2
\& \-\-zcat="/usr/local/bin/gunzip \-c" or \-\-zcat="/usr/local/bin/bzip2 \-dc"
\& \-\-zcat="C:\etools\eunzip \-p"
.Ve
.PP
By default pgBadger will use the zcat, bzcat and unzip utilities following the
file extension. If you use the default autodetection compress format you can
mixed gz, bz2, xz or zip files. Specifying a custom value to \-\-zcat option will
remove this feature of mixed compressed format.
.PP
Note that multiprocessing can not be used with compressed files or \s-1CSV\s0 files as
well as under Windows platform.
.SH "INSTALLATION"
.IX Header "INSTALLATION"
Download the tarball from GitHub and unpack the archive as follow:
.PP
.Vb 4
\& tar xzf pgbadger\-11.x.tar.gz
\& cd pgbadger\-11.x/
\& perl Makefile.PL
\& make && sudo make install
.Ve
.PP
This will copy the Perl script pgbadger to /usr/local/bin/pgbadger by default and the
man page into /usr/local/share/man/man1/pgbadger.1. Those are the default installation
directories for 'site' install.
.PP
If you want to install all under /usr/ location, use INSTALLDIRS='perl' as an argument
of Makefile.PL. The script will be installed into /usr/bin/pgbadger and the manpage
into /usr/share/man/man1/pgbadger.1.
.PP
For example, to install everything just like Debian does, proceed as follows:
.PP
.Vb 1
\& perl Makefile.PL INSTALLDIRS=vendor
.Ve
.PP
By default \s-1INSTALLDIRS\s0 is set to site.
.SH "POSTGRESQL CONFIGURATION"
.IX Header "POSTGRESQL CONFIGURATION"
You must enable and set some configuration directives in your postgresql.conf
before starting.
.PP
You must first enable \s-1SQL\s0 query logging to have something to parse:
.PP
.Vb 1
\& log_min_duration_statement = 0
.Ve
.PP
Here every statement will be logged, on a busy server you may want to increase
this value to only log queries with a longer duration. Note that if you have
log_statement set to 'all' nothing will be logged through the log_min_duration_statement
directive. See the next chapter for more information.
.PP
pgBadger supports any custom format set into the log_line_prefix directive of
your postgresql.conf file as long as it at least specify a time escape sequence
(%t, \f(CW%m\fR or \f(CW%n\fR) and the process related escape sequence (%p or \f(CW%c\fR).
.PP
For example, with 'stderr' log format, log_line_prefix must be at least:
.PP
.Vb 1
\& log_line_prefix = \*(Aq%t [%p]: \*(Aq
.Ve
.PP
Log line prefix could add user, database name, application name and client ip
address as follows:
.PP
.Vb 1
\& log_line_prefix = \*(Aq%t [%p]: user=%u,db=%d,app=%a,client=%h \*(Aq
.Ve
.PP
or for syslog log file format:
.PP
.Vb 1
\& log_line_prefix = \*(Aquser=%u,db=%d,app=%a,client=%h \*(Aq
.Ve
.PP
Log line prefix for stderr output could also be:
.PP
.Vb 1
\& log_line_prefix = \*(Aq%t [%p]: db=%d,user=%u,app=%a,client=%h \*(Aq
.Ve
.PP
or for syslog output:
.PP
.Vb 1
\& log_line_prefix = \*(Aqdb=%d,user=%u,app=%a,client=%h \*(Aq
.Ve
.PP
You need to enable other parameters in postgresql.conf to get more information from your log files:
.PP
.Vb 7
\& log_checkpoints = on
\& log_connections = on
\& log_disconnections = on
\& log_lock_waits = on
\& log_temp_files = 0
\& log_autovacuum_min_duration = 0
\& log_error_verbosity = default
.Ve
.PP
Do not enable log_statement as its log format will not be parsed by pgBadger.
.PP
Of course your log messages should be in English without locale support:
.PP
.Vb 1
\& lc_messages=\*(AqC\*(Aq
.Ve
.PP
but this is not only recommended by pgBadger.
.SH "LOG STATEMENTS"
.IX Header "LOG STATEMENTS"
Considerations about log_min_duration_statement, log_duration and log_statement
configuration directives.
.PP
If you want the query statistics to include the actual query strings, you
must set log_min_duration_statement to 0 or more milliseconds.
.PP
If you just want to report duration and number of queries and don't want all
details about queries, set log_min_duration_statement to \-1 to disable it and
enable log_duration in your postgresql.conf file. If you want to add the most
common request report you can either choose to set log_min_duration_statement
to a higher value or choose to enable log_statement.
.PP
Enabling log_min_duration_statement will add reports about slowest queries and
queries that took up the most time. Take care that if you have log_statement
set to 'all' nothing will be logged with log_min_duration_statement.
.PP
Warning: Do not enable both log_min_duration_statement, log_duration and
log_statement all together, this will result in wrong counter values. Note
that this will also increase drastically the size of your log.
log_min_duration_statement should always be preferred.
.SH "PARALLEL PROCESSING"
.IX Header "PARALLEL PROCESSING"
To enable parallel processing you just have to use the \-j N option where N is
the number of cores you want to use.
.PP
pgBadger will then proceed as follow:
.PP
.Vb 10
\& for each log file
\& chunk size = int(file size / N)
\& look at start/end offsets of these chunks
\& fork N processes and seek to the start offset of each chunk
\& each process will terminate when the parser reach the end offset
\& of its chunk
\& each process write stats into a binary temporary file
\& wait for all children has terminated
\& All binary temporary files generated will then be read and loaded into
\& memory to build the html output.
.Ve
.PP
With that method, at start/end of chunks pgBadger may truncate or omit a
maximum of N queries per log file which is an insignificant gap if you have
millions of queries in your log file. The chance that the query that you were
looking for is lost is near 0, this is why I think this gap is livable. Most
of the time the query is counted twice but truncated.
.PP
When you have many small log files and many CPUs it is speedier to dedicate
one core to one log file at a time. To enable this behavior you have to use
option \-J N instead. With 200 log files of 10MB each the use of the \-J option
starts being really interesting with 8 Cores. Using this method you will be
sure not to lose any queries in the reports.
.PP
He are a benchmark done on a server with 8 CPUs and a single file of 9.5GB.
.PP
.Vb 4
\& Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
\& \-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-
\& \-j | 1h41m18 | 50m25 | 25m39 | 15m58
\& \-J | 1h41m18 | 54m28 | 41m16 | 34m45
.Ve
.PP
With 200 log files of 10MB each and a total of 2GB the results are slightly
different:
.PP
.Vb 4
\& Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
\& \-\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-\-+\-\-\-\-\-\-
\& \-j | 20m15 | 9m56 | 5m20 | 4m20
\& \-J | 20m15 | 9m49 | 5m00 | 2m40
.Ve
.PP
So it is recommended to use \-j unless you have hundreds of small log files
and can use at least 8 CPUs.
.PP
\&\s-1IMPORTANT:\s0 when you are using parallel parsing pgBadger will generate a
lot of temporary files in the /tmp directory and will remove them at the
end, so do not remove those files unless pgBadger is not running. They are
all named with the following template tmp_pgbadgerXXXX.bin so they can be
easily identified.
.SH "INCREMENTAL REPORTS"
.IX Header "INCREMENTAL REPORTS"
pgBadger includes an automatic incremental report mode using option \-I or
\&\-\-incremental. When running in this mode, pgBadger will generate one report
per day and a cumulative report per week. Output is first done in binary
format into the mandatory output directory (see option \-O or \-\-outdir),
then in \s-1HTML\s0 format for daily and weekly reports with a main index file.
.PP
The main index file will show a dropdown menu per week with a link to each
week report and links to daily reports of each week.
.PP
For example, if you run pgBadger as follows based on a daily rotated file:
.PP
.Vb 1
\& 0 4 * * * /usr/bin/pgbadger \-I \-q /var/log/postgresql/postgresql.log.1 \-O /var/www/pg_reports/
.Ve
.PP
you will have all daily and weekly reports for the full running period.
.PP
In this mode pgBadger will create an automatic incremental file in the
output directory, so you don't have to use the \-l option unless you want
to change the path of that file. This means that you can run pgBadger in
this mode each day on a log file rotated each week, and it will not count
the log entries twice.
.PP
To save disk space you may want to use the \-X or \-\-extra\-files command line
option to force pgBadger to write JavaScript and \s-1CSS\s0 to separate files in
the output directory. The resources will then be loaded using script and
link tags.
.SS "Rebuilding reports"
.IX Subsection "Rebuilding reports"
Incremental reports can be rebuilt after a pgbadger report fix or a new
feature to update all \s-1HTML\s0 reports. To rebuild all reports where a binary
file is still present proceed as follow:
.PP
.Vb 3
\& rm /path/to/reports/*.js
\& rm /path/to/reports/*.css
\& pgbadger \-X \-I \-O /path/to/reports/ \-\-rebuild
.Ve
.PP
it will also update all resource files (\s-1JS\s0 and \s-1CSS\s0). Use \-E or \-\-explode
if the reports were built using this option.
.SS "Monthly reports"
.IX Subsection "Monthly reports"
By default pgBadger in incremental mode only compute daily and weekly reports.
If you want monthly cumulative reports you will have to use a separate command
to specify the report to build. For example to build a report for August 2019:
.PP
.Vb 1
\& pgbadger \-X \-\-month\-report 2919\-08 /var/www/pg_reports/
.Ve
.PP
this will add a link to the month name into the calendar view of incremental
reports to look at monthly report. The report for a current month can be run
every day it is entirely rebuilt each time. The monthly report is not built by
default because it could take lot of time following the amount of data.
.PP
If reports were built with the per database option ( \-E | \-\-explode ) it must
be used too when calling pgbadger to build monthly report:
.PP
.Vb 1
\& pgbadger \-E \-X \-\-month\-report 2919\-08 /var/www/pg_reports/
.Ve
.PP
This is the same when using the rebuild option ( \-R | \-\-rebuild ).
.SH "BINARY FORMAT"
.IX Header "BINARY FORMAT"
Using the binary format it is possible to create custom incremental and
cumulative reports. For example, if you want to refresh a pgBadger
report each hour from a daily PostgreSQL log file, you can proceed by
running each hour the following commands:
.PP
.Vb 1
\& pgbadger \-\-last\-parsed .pgbadger_last_state_file \-o sunday/hourX.bin /var/log/pgsql/postgresql\-Sun.log
.Ve
.PP
to generate the incremental data files in binary format. And to generate the fresh \s-1HTML\s0
report from that binary file:
.PP
.Vb 1
\& pgbadger sunday/*.bin
.Ve
.PP
Or as another example, if you generate one log file per hour and you want
reports to be rebuilt each time the log file is rotated, proceed as
follows:
.PP
.Vb 4
\& pgbadger \-o day1/hour01.bin /var/log/pgsql/pglog/postgresql\-2012\-03\-23_10.log
\& pgbadger \-o day1/hour02.bin /var/log/pgsql/pglog/postgresql\-2012\-03\-23_11.log
\& pgbadger \-o day1/hour03.bin /var/log/pgsql/pglog/postgresql\-2012\-03\-23_12.log
\& ...
.Ve
.PP
When you want to refresh the \s-1HTML\s0 report, for example each time after a new binary file
is generated, just do the following:
.PP
.Vb 1
\& pgbadger \-o day1_report.html day1/*.bin
.Ve
.PP
Adjust the commands to suit your particular needs.
.SH "JSON FORMAT"
.IX Header "JSON FORMAT"
\&\s-1JSON\s0 format is good for sharing data with other languages, which makes it
easy to integrate pgBadger result into other monitoring tools like Cacti
or Graphite.
.SH "AUTHORS"
.IX Header "AUTHORS"
pgBadger is an original work from Gilles Darold.
.PP
The pgBadger logo is an original creation of Damien Cazeils.
.PP
The pgBadger v4.x design comes from the \*(L"Art is code\*(R" company.
.PP
This web site is a work of Gilles Darold.
.PP
pgBadger is maintained by Gilles Darold and every one who wants to contribute.
.PP
Many people have contributed to pgBadger, they are all quoted in the Changelog file.
.SH "LICENSE"
.IX Header "LICENSE"
pgBadger is free software distributed under the PostgreSQL Licence.
.PP
Copyright (c) 2012\-2019, Gilles Darold
.PP
A modified version of the SQL::Beautify Perl Module is embedded in pgBadger
with copyright (C) 2009 by Jonas Kramer and is published under the terms of
the Artistic License 2.0.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,721 @@
=head1 NAME
pgBadger - a fast PostgreSQL log analysis report
=head1 SYNOPSIS
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. Default 5 minutes.
-A | --histo-average min: number of minutes to build the histogram graphs
of queries. Default 60 minutes.
-b | --begin datetime : start date/time for the data to be parsed in log
(either a timestamp or a time)
-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.
-D | --dns-resolv : client ip addresses are replaced by their DNS name.
Be warned that this can really slow down pgBadger.
-e | --end datetime : end date/time for the data to be parsed in log
(either a timestamp or a time)
-E | --explode : explode the main report by generating one report
per database. Global information not related to a
database are added to the postgres database report.
-f | --format logtype : possible values: syslog, syslog2, stderr, jsonlog,
cvs, pgbouncer, logplex and rds. Use this option
when pgBadger is not able to detect the log format.
-G | --nograph : disable graphs on HTML output. Enabled by default.
-h | --help : show this message and exit.
-H | --html-dir path : path to directory where HTML report must be written
in incremental mode, binary files stay on directory
defined with -O, --outdir option.
-i | --ident name : programname used as syslog ident. Default: postgres
-I | --incremental : use incremental mode, reports will be generated by
days in a separate directory, --outdir must be set.
-j | --jobs number : number of jobs to run at same time. Run as single
by default or when working with csvlog.
-J | --Jobs number : number of log file to parse in parallel. Process one
file at a time by default or when csvlog is used.
-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.
-L | --logfile-list file:file containing a list of log file to parse.
-m | --maxlength size : maximum length of a query, it will be restricted to
the given size. Default: no truncate
-M | --no-multiline : do not collect multiline statement to avoid garbage
especially on errors that generate a huge report.
-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, out.bin,
out.json or out.tsung. This option can be used
multiple time to output several format. To use json
output the Perl module JSON::XS must be installed,
To dump output to stdout use - as filename.
-O | --outdir path : directory where out file must be saved.
-p | --prefix string : the value of your custom log_line_prefix as
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, not even a progress
bar.
-r | --remote-host ip : set the host where to execute the cat command on
remote logfile to parse locally the file.
-R | --retention N : number of weeks to keep in incremental mode. Default
to 0, disabled. Used to set the number of weeks to
keep in output directory. Older weeks and days
directory are automatically removed.
-s | --sample number : number of query samples to store. Default: 3.
-S | --select-only : only report SELECT queries.
-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. Can be used multiple time.
-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, bin, json or
tsung. Default: html
-X | --extra-files : in incremental mode allow pgBadger to write CSS and
JS files in the output directory as separate files.
-z | --zcat exec_path : set the full path to the zcat program. Use it if
zcat or bzcat or unzip is not in your path.
-Z | --timezone +/-XX : Set the number of hours from GMT of the timezone.
Use this to adjust date/time in JavaScript graphs.
--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. You can use this
option multiple times. For example: "(tbl1|tbl2)".
--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 report of queries by type, database
or user.
--disable-query : do not generate query reports (slowest, most
frequent, queries by users, by database, ...).
--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.
--charset : used to set the HTML charset to be used.
Default: utf-8.
--csv-separator : used to set the CSV field separator, default: ,
--exclude-time regex : any timestamp matching the given regex will be
excluded from the report. Example: "2013-04-12 .*"
You can use this option multiple times.
--include-time regex : only timestamps matching the given regex will be
included in the report. Example: "2013-04-12 .*"
You can use this option multiple times.
--exclude-db name : exclude entries for the specified database from
report. Example: "pg_dump". Can be used multiple
time.
--exclude-appname name : exclude entries for the specified application name
from report. Example: "pg_dump". Can be used
multiple time.
--exclude-line regex : pgBadger will start to exclude any log entry that
will match the given regex. Can be used multiple
time.
--exclude-client name : exclude log entries for the specified client ip.
Can be used multiple time.
--anonymize : obscure all literals in queries, useful to hide
confidential data.
--noreport : prevent pgBadger to create reports in incremental
mode.
--log-duration : force pgBadger to associate log entries generated
by both log_duration = on and log_statement = 'all'
--enable-checksum : used to add a md5 sum under each query report.
--journalctl command : command to use to replace PostgreSQL logfile by
a call to journalctl. Basically it might be:
journalctl -u postgresql-9.5
--pid-dir path : set the path where the pid file must be stored.
Default /tmp
--pid-file file : set the name of the pid file to manage concurrent
execution of pgBadger. Default: pgbadger.pid
--rebuild : used to rebuild all html reports in incremental
output directories where there's binary data files.
--pgbouncer-only : only show PgBouncer related menu in the header.
--start-monday : in incremental mode, calendar's weeks start on
sunday. Use this option to start on monday.
--normalized-only : only dump all normalized query to out.txt
--log-timezone +/-XX : Set the number of hours from GMT of the timezone
that must be used to adjust date/time read from
log file before beeing parsed. Using this option
make more difficult log search with a date/time.
--prettify-json : use it if you want json output to be prettified.
--month-report YYYY-MM : create a cumulative HTML report over the specified
month. Requires incremental output directories and
the presence of all necessary binary data files
pgBadger is able to parse a remote log file using a passwordless ssh connection.
Use the -r or --remote-host to set the host ip address or hostname. There's also
some additional options to fully control the ssh connection.
--ssh-program ssh path to the ssh program to use. Default: ssh.
--ssh-user username connection login name. Default to running user.
--ssh-identity file path to the identity file to use.
--ssh-timeout second timeout to ssh connection failure. Default 10 secs.
--ssh-option options list of -o options to use for the ssh connection.
Options always used:
-o ConnectTimeout=$ssh_timeout
-o PreferredAuthentications=hostbased,publickey
Log file to parse can also be specified using an URI, supported protocol are
http[s] and [s]ftp. The curl command will be used to download the file and the
file will be parsed during download. The ssh protocol is also supported and will
use the ssh command like with the remote host use. See examples bellow.
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
pgbadger --prefix '%t [%p]: user=%u,db=%d,client=%h' /pglog/postgresql-2012-08-21*
pgbadger --prefix '%m %u@%d %p %r %a : ' /pglog/postgresql.log
# Log line prefix with syslog log output
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, much faster
pgbadger -j 8 /pglog/postgresql-10.1-main.log
Use URI notation for remote log file:
pgbadger http://172.12.110.1//var/log/postgresql/postgresql-10.1-main.log
pgbadger ftp://username@172.12.110.14/postgresql-10.1-main.log
pgbadger ssh://username@172.12.110.14//var/log/postgresql/postgresql-10.1-main.log*
You can use together a local PostgreSQL log and a remote pgbouncer log file to parse:
pgbadger /var/log/postgresql/postgresql-10.1-main.log ssh://username@172.12.110.14/pgbouncer.log
Generate Tsung sessions XML file with select queries only:
pgbadger -S -o sessions.tsung --prefix '%t [%p]: user=%u,db=%d ' /pglog/postgresql-10.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.
Or better, use the auto-generated incremental reports:
0 4 * * * /usr/bin/pgbadger -I -q /var/log/postgresql/postgresql.log.1 -O /var/www/pg_reports/
will generate a report per day and per week.
In incremental mode, you can also specify the number of week to keep in the
reports:
/usr/bin/pgbadger --retention 2 -I -q /var/log/postgresql/postgresql.log.1 -O /var/www/pg_reports/
If you have a pg_dump at 23:00 and 13:00 each day during half an hour, you can
use pgBadger as follow to exclude these period from the report:
pgbadger --exclude-time "2013-09-.* (23|13):.*" postgresql.log
This will help avoid having COPY statements, as generated by pg_dump, on top of
the list of slowest queries. You can also use --exclude-appname "pg_dump" to
solve this problem in a simpler way.
You can also parse journalctl output just as if it was a log file:
pgbadger --journalctl 'journalctl -u postgresql-9.5'
or worst, call it from a remote host:
pgbadger -r 192.168.1.159 --journalctl 'journalctl -u postgresql-9.5'
you don't need to specify any log file at command line, but if you have other
PostgreSQL log file to parse, you can add them as usual.
To rebuild all incremental html reports after, proceed as follow:
rm /path/to/reports/*.js
rm /path/to/reports/*.css
pgbadger -X -I -O /path/to/reports/ --rebuild
it will also update all resource files (JS and CSS). Use -E or --explode
if the reports were built using this option.
pgBadger also support Heroku PostgreSQL logs using logplex format:
heroku logs -p postgres | pgbadger -f logplex -o heroku.html -
this will stream Heroku PostgreSQL log to pgbadger through stdin.
pgBadger can auto detect RDS and cloudwatch PostgreSQL logs using
rds format:
pgbadger -f rds -o rds_out.html rds.log
To create a cumulative report over a month use command:
pgbadger --month-report 2919-05 /path/to/incremantal/reports/
this will add a link to the month name into the calendar view in
incremental reports to look at report for month 2019 May.
Use -E or --explode if the reports were built using this option.
=head1 DESCRIPTION
pgBadger is a PostgreSQL log analyzer built for speed with fully reports
from your PostgreSQL log file. It's a single and small Perl script that
outperforms any other PostgreSQL log analyzer.
It is written in pure Perl and uses a JavaScript library (flotr2) to draw
graphs so that you don't need to install any additional Perl modules or
other packages. Furthermore, this library gives us more features such
as zooming. pgBadger also uses the Bootstrap JavaScript library and
the FontAwesome webfont for better design. Everything is embedded.
pgBadger is able to autodetect your log file format (syslog, stderr, csvlog
or jsonlog) if the file is long enough. It is designed to parse huge log
files as well as compressed files. Supported compressed format are gzip,
bzip2 and xz. For the xz format you must have an xz version upper than 5.05
that supports the --robot option. For the complete list of features see below.
All charts are zoomable and can be saved as PNG images.
You can also limit pgBadger to only report errors or remove any part of the
report using command line options.
pgBadger supports any custom format set into the log_line_prefix directive of
your postgresql.conf file as long as it at least specify the %t and %p patterns.
pgBadger allows parallel processing of a single log file or multiple
files through the use of the -j option specifying the number of CPUs.
If you want to save system performance you can also use log_duration instead of
log_min_duration_statement to have reports on duration and number of queries only.
=head1 FEATURE
pgBadger reports everything about your SQL queries:
Overall statistics.
The most frequent waiting queries.
Queries that waited the most.
Queries generating the most temporary files.
Queries generating the largest temporary files.
The slowest queries.
Queries that took up the most time.
The most frequent queries.
The most frequent errors.
Histogram of query times.
Histogram of sessions times.
Users involved in top queries.
Applications involved in top queries.
Queries generating the most cancellation.
Queries most cancelled.
The most time consuming prepare/bind queries
The following reports are also available with hourly charts divided into
periods of five minutes:
SQL queries statistics.
Temporary file statistics.
Checkpoints statistics.
Autovacuum and autoanalyze statistics.
Cancelled queries.
Error events (panic, fatal, error and warning).
Error class distribution.
There are also some pie charts about distribution of:
Locks statistics.
Queries by type (select/insert/update/delete).
Distribution of queries type per database/application
Sessions per database/user/client/application.
Connections per database/user/client/application.
Autovacuum and autoanalyze per table.
Queries per user and total duration per user.
All charts are zoomable and can be saved as PNG images. SQL queries reported are
highlighted and beautified automatically.
pgBadger is also able to parse PgBouncer log files and to create the following
reports:
Request Throughput
Bytes I/O Throughput
Queries Average duration
Simultaneous sessions
Histogram of sessions times
Sessions per database
Sessions per user
Sessions per host
Established connections
Connections per database
Connections per user
Connections per host
Most used reserved pools
Most Frequent Errors/Events
You can also have incremental reports with one report per day and a cumulative
report per week. Two multiprocess modes are available to speed up log parsing,
one using one core per log file, and the second using multiple cores to parse
a single file. These modes can be combined.
Histogram granularity can be adjusted using the -A command line option. By default
they will report the mean of each top queries/errors occurring per hour, but you can
specify the granularity down to the minute.
pgBadger can also be used in a central place to parse remote log files using a
passwordless SSH connection. This mode can be used with compressed files and in
the multiprocess per file mode (-J) but can not be used with the CSV log format.
=head1 REQUIREMENT
pgBadger comes as a single Perl script - you do not need anything other than a modern
Perl distribution. Charts are rendered using a JavaScript library so you don't need
anything other than a web browser. Your browser will do all the work.
If you planned to parse PostgreSQL CSV log files you might need some Perl Modules:
Text::CSV_XS - to parse PostgreSQL CSV log files.
This module is optional, if you don't have PostgreSQL log in the CSV format you don't
need to install it.
If you want to export statistics as JSON file you need an additional Perl module:
JSON::XS - JSON serialising/deserialising, done correctly and fast
This module is optional, if you don't select the json output format you don't
need to install it. You can install it on a Debian like system using:
sudo apt-get install libjson-xs-perl
and in RPM like system using:
sudo yum install perl-JSON-XS
Compressed log file format is autodetected from the file extension. If pgBadger find
a gz extension it will use the zcat utility, with a bz2 extension it will use bzcat
and if the file extension is zip or xz then the unzip or xz utilities will be used.
If those utilities are not found in the PATH environment variable then use the --zcat
command line option to change this path. For example:
--zcat="/usr/local/bin/gunzip -c" or --zcat="/usr/local/bin/bzip2 -dc"
--zcat="C:\tools\unzip -p"
By default pgBadger will use the zcat, bzcat and unzip utilities following the
file extension. If you use the default autodetection compress format you can
mixed gz, bz2, xz or zip files. Specifying a custom value to --zcat option will
remove this feature of mixed compressed format.
Note that multiprocessing can not be used with compressed files or CSV files as
well as under Windows platform.
=head1 INSTALLATION
Download the tarball from GitHub and unpack the archive as follow:
tar xzf pgbadger-11.x.tar.gz
cd pgbadger-11.x/
perl Makefile.PL
make && sudo make install
This will copy the Perl script pgbadger to /usr/local/bin/pgbadger by default and the
man page into /usr/local/share/man/man1/pgbadger.1. Those are the default installation
directories for 'site' install.
If you want to install all under /usr/ location, use INSTALLDIRS='perl' as an argument
of Makefile.PL. The script will be installed into /usr/bin/pgbadger and the manpage
into /usr/share/man/man1/pgbadger.1.
For example, to install everything just like Debian does, proceed as follows:
perl Makefile.PL INSTALLDIRS=vendor
By default INSTALLDIRS is set to site.
=head1 POSTGRESQL CONFIGURATION
You must enable and set some configuration directives in your postgresql.conf
before starting.
You must first enable SQL query logging to have something to parse:
log_min_duration_statement = 0
Here every statement will be logged, on a busy server you may want to increase
this value to only log queries with a longer duration. Note that if you have
log_statement set to 'all' nothing will be logged through the log_min_duration_statement
directive. See the next chapter for more information.
pgBadger supports any custom format set into the log_line_prefix directive of
your postgresql.conf file as long as it at least specify a time escape sequence
(%t, %m or %n) and the process related escape sequence (%p or %c).
For example, with 'stderr' log format, log_line_prefix must be at least:
log_line_prefix = '%t [%p]: '
Log line prefix could add user, database name, application name and client ip
address as follows:
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h '
or for syslog log file format:
log_line_prefix = 'user=%u,db=%d,app=%a,client=%h '
Log line prefix for stderr output could also be:
log_line_prefix = '%t [%p]: db=%d,user=%u,app=%a,client=%h '
or for syslog output:
log_line_prefix = 'db=%d,user=%u,app=%a,client=%h '
You need to enable other parameters in postgresql.conf to get more information from your log files:
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0
log_error_verbosity = default
Do not enable log_statement as its log format will not be parsed by pgBadger.
Of course your log messages should be in English without locale support:
lc_messages='C'
but this is not only recommended by pgBadger.
=head1 LOG STATEMENTS
Considerations about log_min_duration_statement, log_duration and log_statement
configuration directives.
If you want the query statistics to include the actual query strings, you
must set log_min_duration_statement to 0 or more milliseconds.
If you just want to report duration and number of queries and don't want all
details about queries, set log_min_duration_statement to -1 to disable it and
enable log_duration in your postgresql.conf file. If you want to add the most
common request report you can either choose to set log_min_duration_statement
to a higher value or choose to enable log_statement.
Enabling log_min_duration_statement will add reports about slowest queries and
queries that took up the most time. Take care that if you have log_statement
set to 'all' nothing will be logged with log_min_duration_statement.
Warning: Do not enable both log_min_duration_statement, log_duration and
log_statement all together, this will result in wrong counter values. Note
that this will also increase drastically the size of your log.
log_min_duration_statement should always be preferred.
=head1 PARALLEL PROCESSING
To enable parallel processing you just have to use the -j N option where N is
the number of cores you want to use.
pgBadger will then proceed as follow:
for each log file
chunk size = int(file size / N)
look at start/end offsets of these chunks
fork N processes and seek to the start offset of each chunk
each process will terminate when the parser reach the end offset
of its chunk
each process write stats into a binary temporary file
wait for all children has terminated
All binary temporary files generated will then be read and loaded into
memory to build the html output.
With that method, at start/end of chunks pgBadger may truncate or omit a
maximum of N queries per log file which is an insignificant gap if you have
millions of queries in your log file. The chance that the query that you were
looking for is lost is near 0, this is why I think this gap is livable. Most
of the time the query is counted twice but truncated.
When you have many small log files and many CPUs it is speedier to dedicate
one core to one log file at a time. To enable this behavior you have to use
option -J N instead. With 200 log files of 10MB each the use of the -J option
starts being really interesting with 8 Cores. Using this method you will be
sure not to lose any queries in the reports.
He are a benchmark done on a server with 8 CPUs and a single file of 9.5GB.
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
--------+---------+-------+-------+------
-j | 1h41m18 | 50m25 | 25m39 | 15m58
-J | 1h41m18 | 54m28 | 41m16 | 34m45
With 200 log files of 10MB each and a total of 2GB the results are slightly
different:
Option | 1 CPU | 2 CPU | 4 CPU | 8 CPU
--------+-------+-------+-------+------
-j | 20m15 | 9m56 | 5m20 | 4m20
-J | 20m15 | 9m49 | 5m00 | 2m40
So it is recommended to use -j unless you have hundreds of small log files
and can use at least 8 CPUs.
IMPORTANT: when you are using parallel parsing pgBadger will generate a
lot of temporary files in the /tmp directory and will remove them at the
end, so do not remove those files unless pgBadger is not running. They are
all named with the following template tmp_pgbadgerXXXX.bin so they can be
easily identified.
=head1 INCREMENTAL REPORTS
pgBadger includes an automatic incremental report mode using option -I or
--incremental. When running in this mode, pgBadger will generate one report
per day and a cumulative report per week. Output is first done in binary
format into the mandatory output directory (see option -O or --outdir),
then in HTML format for daily and weekly reports with a main index file.
The main index file will show a dropdown menu per week with a link to each
week report and links to daily reports of each week.
For example, if you run pgBadger as follows based on a daily rotated file:
0 4 * * * /usr/bin/pgbadger -I -q /var/log/postgresql/postgresql.log.1 -O /var/www/pg_reports/
you will have all daily and weekly reports for the full running period.
In this mode pgBadger will create an automatic incremental file in the
output directory, so you don't have to use the -l option unless you want
to change the path of that file. This means that you can run pgBadger in
this mode each day on a log file rotated each week, and it will not count
the log entries twice.
To save disk space you may want to use the -X or --extra-files command line
option to force pgBadger to write JavaScript and CSS to separate files in
the output directory. The resources will then be loaded using script and
link tags.
=head2 Rebuilding reports
Incremental reports can be rebuilt after a pgbadger report fix or a new
feature to update all HTML reports. To rebuild all reports where a binary
file is still present proceed as follow:
rm /path/to/reports/*.js
rm /path/to/reports/*.css
pgbadger -X -I -O /path/to/reports/ --rebuild
it will also update all resource files (JS and CSS). Use -E or --explode
if the reports were built using this option.
=head2 Monthly reports
By default pgBadger in incremental mode only compute daily and weekly reports.
If you want monthly cumulative reports you will have to use a separate command
to specify the report to build. For example to build a report for August 2019:
pgbadger -X --month-report 2919-08 /var/www/pg_reports/
this will add a link to the month name into the calendar view of incremental
reports to look at monthly report. The report for a current month can be run
every day it is entirely rebuilt each time. The monthly report is not built by
default because it could take lot of time following the amount of data.
If reports were built with the per database option ( -E | --explode ) it must
be used too when calling pgbadger to build monthly report:
pgbadger -E -X --month-report 2919-08 /var/www/pg_reports/
This is the same when using the rebuild option ( -R | --rebuild ).
=head1 BINARY FORMAT
Using the binary format it is possible to create custom incremental and
cumulative reports. For example, if you want to refresh a pgBadger
report each hour from a daily PostgreSQL log file, you can proceed by
running each hour the following commands:
pgbadger --last-parsed .pgbadger_last_state_file -o sunday/hourX.bin /var/log/pgsql/postgresql-Sun.log
to generate the incremental data files in binary format. And to generate the fresh HTML
report from that binary file:
pgbadger sunday/*.bin
Or as another example, if you generate one log file per hour and you want
reports to be rebuilt each time the log file is rotated, proceed as
follows:
pgbadger -o day1/hour01.bin /var/log/pgsql/pglog/postgresql-2012-03-23_10.log
pgbadger -o day1/hour02.bin /var/log/pgsql/pglog/postgresql-2012-03-23_11.log
pgbadger -o day1/hour03.bin /var/log/pgsql/pglog/postgresql-2012-03-23_12.log
...
When you want to refresh the HTML report, for example each time after a new binary file
is generated, just do the following:
pgbadger -o day1_report.html day1/*.bin
Adjust the commands to suit your particular needs.
=head1 JSON FORMAT
JSON format is good for sharing data with other languages, which makes it
easy to integrate pgBadger result into other monitoring tools like Cacti
or Graphite.
=head1 AUTHORS
pgBadger is an original work from Gilles Darold.
The pgBadger logo is an original creation of Damien Cazeils.
The pgBadger v4.x design comes from the "Art is code" company.
This web site is a work of Gilles Darold.
pgBadger is maintained by Gilles Darold and every one who wants to contribute.
Many people have contributed to pgBadger, they are all quoted in the Changelog file.
=head1 LICENSE
pgBadger is free software distributed under the PostgreSQL Licence.
Copyright (c) 2012-2019, Gilles Darold
A modified version of the SQL::Beautify Perl Module is embedded in pgBadger
with copyright (C) 2009 by Jonas Kramer and is published under the terms of
the Artistic License 2.0.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
min/

View File

@ -0,0 +1,166 @@
Licenses
--------
bean.js:
copyright (c) Jacob Thornton 2011-2012
* https://github.com/fat/bean
* MIT license
bootstrap.*:
Copyright 2011-2016 Twitter, Inc.
* Licensed under the MIT license
fontawesome.*:
* - The Font Awesome font is licensed under SIL OFL 1.1 -
* http://scripts.sil.org/OFL
* - Font Awesome CSS, LESS, and SASS files are licensed under MIT License -
* http://opensource.org/licenses/mit-license.html
* - Attribution is no longer required in Font Awesome 3.0, but much appreciated:
* "Font Awesome by Dave Gandy - http://fontawesome.io"
jqplot.*:
Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
jquery.jqplot.*:
Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT and GPL version 2.0 licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* See <GPL Version 2> and <MIT License> contained within this distribution for further information.
*
* The author would appreciate an email letting him know of any substantial
* use of jqPlot. You can reach the author at: chris at jqplot dot com
* or see http://www.jqplot.com/info.php. This is, of course, not required.
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php.
jquery.*:
Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
underscore.js:
(c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
The MIT License (MIT)
---------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
----------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,58 @@
Resources files are collected from their respective download places as follow:
jqPlot:
-------
mkdir jqPlot-1.0.8/ && cd jqPlot-1.0.8/
wget http://www.jqplot.com/download/jquery.jqplot.1.0.8r1250.tar.gz
tar xzf jquery.jqplot.1.0.8r1250.tar.gz
cp dist/jquery.jqplot.js /home/git/pgbadger/resources/
cp dist/jquery.jqplot.css /home/git/pgbadger/resources/
cp dist/jquery.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.pieRenderer.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.barRenderer.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.dateAxisRenderer.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.canvasTextRenderer.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.categoryAxisRenderer.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.canvasAxisTickRenderer.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.highlighter.js /home/git/pgbadger/resources/
cp dist/plugins/jqplot.cursor.js /home/git/pgbadger/resources/
cd ..
Bootstrap:
----------
wget https://github.com/twbs/bootstrap/archive/v3.3.7.zip
unzip v3.3.7.zip bootstrap-3.3.7/dist/js/bootstrap.js bootstrap-3.3.7/dist/css/bootstrap.css
cp bootstrap-3.3.7/dist/js/bootstrap.js /home/git/pgbadger/resources/
cp bootstrap-3.3.7/dist/css/bootstrap.css /home/git/pgbadger/resources/
Fontawesome:
------------
http://fontawesome.io/3.2.1/assets/font-awesome.zip
unzip font-awesome.zip font-awesome/css/font-awesome.css font-awesome/fonts/fontawesome-webfont.ttf
cp font-awesome/css/font-awesome.css /home/git/pgbadger/resources/fontawesome.css
cp font-awesome/font/FontAwesome.otf /home/git/pgbadger/resources/font/
Note that the hyphen is removed from font-awesome.css during the copy for
backward compatibility.
bean:
-----
wget https://github.com/fat/bean/archive/v1.0.14.tar.gz
tar xzf v1.0.14.tar.gz bean-1.0.14/src/bean.js
cp bean-1.0.14/src/bean.js /home/git/pgbadger/resources/
underscore.js:
--------------
wget http://underscorejs.org/underscore.js -O /home/git/pgbadger/resources/underscore.js
Then file are minified using yui-compressor and the font embedded using the
Perl script tools/updt_embedded_rsc.pl This script also embedded all minified
files into the pgbadger Perl script.

View File

@ -0,0 +1,741 @@
/*!
* Bean - copyright (c) Jacob Thornton 2011-2012
* https://github.com/fat/bean
* MIT license
*/
(function (name, context, definition) {
if (typeof module != 'undefined' && module.exports) module.exports = definition()
else if (typeof define == 'function' && define.amd) define(definition)
else context[name] = definition()
})('bean', this, function (name, context) {
name = name || 'bean'
context = context || this
var win = window
, old = context[name]
, namespaceRegex = /[^\.]*(?=\..*)\.|.*/
, nameRegex = /\..*/
, addEvent = 'addEventListener'
, removeEvent = 'removeEventListener'
, doc = document || {}
, root = doc.documentElement || {}
, W3C_MODEL = root[addEvent]
, eventSupport = W3C_MODEL ? addEvent : 'attachEvent'
, ONE = {} // singleton for quick matching making add() do one()
, slice = Array.prototype.slice
, str2arr = function (s, d) { return s.split(d || ' ') }
, isString = function (o) { return typeof o == 'string' }
, isFunction = function (o) { return typeof o == 'function' }
// events that we consider to be 'native', anything not in this list will
// be treated as a custom event
, standardNativeEvents =
'click dblclick mouseup mousedown contextmenu ' + // mouse buttons
'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel
'mouseover mouseout mousemove selectstart selectend ' + // mouse movement
'keydown keypress keyup ' + // keyboard
'orientationchange ' + // mobile
'focus blur change reset select submit ' + // form elements
'load unload beforeunload resize move DOMContentLoaded ' + // window
'readystatechange message ' + // window
'error abort scroll ' // misc
// element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
// that doesn't actually exist, so make sure we only do these on newer browsers
, w3cNativeEvents =
'show ' + // mouse buttons
'input invalid ' + // form elements
'touchstart touchmove touchend touchcancel ' + // touch
'gesturestart gesturechange gestureend ' + // gesture
'textinput ' + // TextEvent
'readystatechange pageshow pagehide popstate ' + // window
'hashchange offline online ' + // window
'afterprint beforeprint ' + // printing
'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd
'loadstart progress suspend emptied stalled loadmetadata ' + // media
'loadeddata canplay canplaythrough playing waiting seeking ' + // media
'seeked ended durationchange timeupdate play pause ratechange ' + // media
'volumechange cuechange ' + // media
'checking noupdate downloading cached updateready obsolete ' // appcache
// convert to a hash for quick lookups
, nativeEvents = (function (hash, events, i) {
for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1)
return hash
}({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : ''))))
// custom events are events that we *fake*, they are not provided natively but
// we can use native events to generate them
, customEvents = (function () {
var isAncestor = 'compareDocumentPosition' in root
? function (element, container) {
return container.compareDocumentPosition && (container.compareDocumentPosition(element) & 16) === 16
}
: 'contains' in root
? function (element, container) {
container = container.nodeType === 9 || container === window ? root : container
return container !== element && container.contains(element)
}
: function (element, container) {
while (element = element.parentNode) if (element === container) return 1
return 0
}
, check = function (event) {
var related = event.relatedTarget
return !related
? related == null
: (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString())
&& !isAncestor(related, this))
}
return {
mouseenter: { base: 'mouseover', condition: check }
, mouseleave: { base: 'mouseout', condition: check }
, mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
}
}())
// we provide a consistent Event object across browsers by taking the actual DOM
// event object and generating a new one from its properties.
, Event = (function () {
// a whitelist of properties (for different event types) tells us what to check for and copy
var commonProps = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' +
'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey ' +
'srcElement target timeStamp type view which propertyName')
, mouseProps = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer ' +
'fromElement offsetX offsetY pageX pageY screenX screenY toElement'))
, mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' +
'axis')) // 'axis' is FF specific
, keyProps = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier ' +
'keyLocation location'))
, textProps = commonProps.concat(str2arr('data'))
, touchProps = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation'))
, messageProps = commonProps.concat(str2arr('data origin source'))
, stateProps = commonProps.concat(str2arr('state'))
, overOutRegex = /over|out/
// some event types need special handling and some need special properties, do that all here
, typeFixers = [
{ // key events
reg: /key/i
, fix: function (event, newEvent) {
newEvent.keyCode = event.keyCode || event.which
return keyProps
}
}
, { // mouse events
reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i
, fix: function (event, newEvent, type) {
newEvent.rightClick = event.which === 3 || event.button === 2
newEvent.pos = { x: 0, y: 0 }
if (event.pageX || event.pageY) {
newEvent.clientX = event.pageX
newEvent.clientY = event.pageY
} else if (event.clientX || event.clientY) {
newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
}
if (overOutRegex.test(type)) {
newEvent.relatedTarget = event.relatedTarget
|| event[(type == 'mouseover' ? 'from' : 'to') + 'Element']
}
return mouseProps
}
}
, { // mouse wheel events
reg: /mouse.*(wheel|scroll)/i
, fix: function () { return mouseWheelProps }
}
, { // TextEvent
reg: /^text/i
, fix: function () { return textProps }
}
, { // touch and gesture events
reg: /^touch|^gesture/i
, fix: function () { return touchProps }
}
, { // message events
reg: /^message$/i
, fix: function () { return messageProps }
}
, { // popstate events
reg: /^popstate$/i
, fix: function () { return stateProps }
}
, { // everything else
reg: /.*/
, fix: function () { return commonProps }
}
]
, typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism
, Event = function (event, element, isNative) {
if (!arguments.length) return
event = event || ((element.ownerDocument || element.document || element).parentWindow || win).event
this.originalEvent = event
this.isNative = isNative
this.isBean = true
if (!event) return
var type = event.type
, target = event.target || event.srcElement
, i, l, p, props, fixer
this.target = target && target.nodeType === 3 ? target.parentNode : target
if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless
fixer = typeFixerMap[type]
if (!fixer) { // haven't encountered this event type before, map a fixer function for it
for (i = 0, l = typeFixers.length; i < l; i++) {
if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .*
typeFixerMap[type] = fixer = typeFixers[i].fix
break
}
}
}
props = fixer(event, this, type)
for (i = props.length; i--;) {
if (!((p = props[i]) in this) && p in event) this[p] = event[p]
}
}
}
// preventDefault() and stopPropagation() are a consistent interface to those functions
// on the DOM, stop() is an alias for both of them together
Event.prototype.preventDefault = function () {
if (this.originalEvent.preventDefault) this.originalEvent.preventDefault()
else this.originalEvent.returnValue = false
}
Event.prototype.stopPropagation = function () {
if (this.originalEvent.stopPropagation) this.originalEvent.stopPropagation()
else this.originalEvent.cancelBubble = true
}
Event.prototype.stop = function () {
this.preventDefault()
this.stopPropagation()
this.stopped = true
}
// stopImmediatePropagation() has to be handled internally because we manage the event list for
// each element
// note that originalElement may be a Bean#Event object in some situations
Event.prototype.stopImmediatePropagation = function () {
if (this.originalEvent.stopImmediatePropagation) this.originalEvent.stopImmediatePropagation()
this.isImmediatePropagationStopped = function () { return true }
}
Event.prototype.isImmediatePropagationStopped = function () {
return this.originalEvent.isImmediatePropagationStopped && this.originalEvent.isImmediatePropagationStopped()
}
Event.prototype.clone = function (currentTarget) {
//TODO: this is ripe for optimisation, new events are *expensive*
// improving this will speed up delegated events
var ne = new Event(this, this.element, this.isNative)
ne.currentTarget = currentTarget
return ne
}
return Event
}())
// if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
, targetElement = function (element, isNative) {
return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
}
/**
* Bean maintains an internal registry for event listeners. We don't touch elements, objects
* or functions to identify them, instead we store everything in the registry.
* Each event listener has a RegEntry object, we have one 'registry' for the whole instance.
*/
, RegEntry = (function () {
// each handler is wrapped so we can handle delegation and custom events
var wrappedHandler = function (element, fn, condition, args) {
var call = function (event, eargs) {
return fn.apply(element, args ? slice.call(eargs, event ? 0 : 1).concat(args) : eargs)
}
, findTarget = function (event, eventElement) {
return fn.__beanDel ? fn.__beanDel.ft(event.target, element) : eventElement
}
, handler = condition
? function (event) {
var target = findTarget(event, this) // deleated event
if (condition.apply(target, arguments)) {
if (event) event.currentTarget = target
return call(event, arguments)
}
}
: function (event) {
if (fn.__beanDel) event = event.clone(findTarget(event)) // delegated event, fix the fix
return call(event, arguments)
}
handler.__beanDel = fn.__beanDel
return handler
}
, RegEntry = function (element, type, handler, original, namespaces, args, root) {
var customType = customEvents[type]
, isNative
if (type == 'unload') {
// self clean-up
handler = once(removeListener, element, type, handler, original)
}
if (customType) {
if (customType.condition) {
handler = wrappedHandler(element, handler, customType.condition, args)
}
type = customType.base || type
}
this.isNative = isNative = nativeEvents[type] && !!element[eventSupport]
this.customType = !W3C_MODEL && !isNative && type
this.element = element
this.type = type
this.original = original
this.namespaces = namespaces
this.eventType = W3C_MODEL || isNative ? type : 'propertychange'
this.target = targetElement(element, isNative)
this[eventSupport] = !!this.target[eventSupport]
this.root = root
this.handler = wrappedHandler(element, handler, null, args)
}
// given a list of namespaces, is our entry in any of them?
RegEntry.prototype.inNamespaces = function (checkNamespaces) {
var i, j, c = 0
if (!checkNamespaces) return true
if (!this.namespaces) return false
for (i = checkNamespaces.length; i--;) {
for (j = this.namespaces.length; j--;) {
if (checkNamespaces[i] == this.namespaces[j]) c++
}
}
return checkNamespaces.length === c
}
// match by element, original fn (opt), handler fn (opt)
RegEntry.prototype.matches = function (checkElement, checkOriginal, checkHandler) {
return this.element === checkElement &&
(!checkOriginal || this.original === checkOriginal) &&
(!checkHandler || this.handler === checkHandler)
}
return RegEntry
}())
, registry = (function () {
// our map stores arrays by event type, just because it's better than storing
// everything in a single array.
// uses '$' as a prefix for the keys for safety and 'r' as a special prefix for
// rootListeners so we can look them up fast
var map = {}
// generic functional search of our registry for matching listeners,
// `fn` returns false to break out of the loop
, forAll = function (element, type, original, handler, root, fn) {
var pfx = root ? 'r' : '$'
if (!type || type == '*') {
// search the whole registry
for (var t in map) {
if (t.charAt(0) == pfx) {
forAll(element, t.substr(1), original, handler, root, fn)
}
}
} else {
var i = 0, l, list = map[pfx + type], all = element == '*'
if (!list) return
for (l = list.length; i < l; i++) {
if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return
}
}
}
, has = function (element, type, original, root) {
// we're not using forAll here simply because it's a bit slower and this
// needs to be fast
var i, list = map[(root ? 'r' : '$') + type]
if (list) {
for (i = list.length; i--;) {
if (!list[i].root && list[i].matches(element, original, null)) return true
}
}
return false
}
, get = function (element, type, original, root) {
var entries = []
forAll(element, type, original, null, root, function (entry) {
return entries.push(entry)
})
return entries
}
, put = function (entry) {
var has = !entry.root && !this.has(entry.element, entry.type, null, false)
, key = (entry.root ? 'r' : '$') + entry.type
;(map[key] || (map[key] = [])).push(entry)
return has
}
, del = function (entry) {
forAll(entry.element, entry.type, null, entry.handler, entry.root, function (entry, list, i) {
list.splice(i, 1)
entry.removed = true
if (list.length === 0) delete map[(entry.root ? 'r' : '$') + entry.type]
return false
})
}
// dump all entries, used for onunload
, entries = function () {
var t, entries = []
for (t in map) {
if (t.charAt(0) == '$') entries = entries.concat(map[t])
}
return entries
}
return { has: has, get: get, put: put, del: del, entries: entries }
}())
// we need a selector engine for delegated events, use querySelectorAll if it exists
// but for older browsers we need Qwery, Sizzle or similar
, selectorEngine
, setSelectorEngine = function (e) {
if (!arguments.length) {
selectorEngine = doc.querySelectorAll
? function (s, r) {
return r.querySelectorAll(s)
}
: function () {
throw new Error('Bean: No selector engine installed') // eeek
}
} else {
selectorEngine = e
}
}
// we attach this listener to each DOM event that we need to listen to, only once
// per event type per DOM element
, rootListener = function (event, type) {
if (!W3C_MODEL && type && event && event.propertyName != '_on' + type) return
var listeners = registry.get(this, type || event.type, null, false)
, l = listeners.length
, i = 0
event = new Event(event, this, true)
if (type) event.type = type
// iterate through all handlers registered for this type, calling them unless they have
// been removed by a previous handler or stopImmediatePropagation() has been called
for (; i < l && !event.isImmediatePropagationStopped(); i++) {
if (!listeners[i].removed) listeners[i].handler.call(this, event)
}
}
// add and remove listeners to DOM elements
, listener = W3C_MODEL
? function (element, type, add) {
// new browsers
element[add ? addEvent : removeEvent](type, rootListener, false)
}
: function (element, type, add, custom) {
// IE8 and below, use attachEvent/detachEvent and we have to piggy-back propertychange events
// to simulate event bubbling etc.
var entry
if (add) {
registry.put(entry = new RegEntry(
element
, custom || type
, function (event) { // handler
rootListener.call(element, event, custom)
}
, rootListener
, null
, null
, true // is root
))
if (custom && element['_on' + custom] == null) element['_on' + custom] = 0
entry.target.attachEvent('on' + entry.eventType, entry.handler)
} else {
entry = registry.get(element, custom || type, rootListener, true)[0]
if (entry) {
entry.target.detachEvent('on' + entry.eventType, entry.handler)
registry.del(entry)
}
}
}
, once = function (rm, element, type, fn, originalFn) {
// wrap the handler in a handler that does a remove as well
return function () {
fn.apply(this, arguments)
rm(element, type, originalFn)
}
}
, removeListener = function (element, orgType, handler, namespaces) {
var type = orgType && orgType.replace(nameRegex, '')
, handlers = registry.get(element, type, null, false)
, removed = {}
, i, l
for (i = 0, l = handlers.length; i < l; i++) {
if ((!handler || handlers[i].original === handler) && handlers[i].inNamespaces(namespaces)) {
// TODO: this is problematic, we have a registry.get() and registry.del() that
// both do registry searches so we waste cycles doing this. Needs to be rolled into
// a single registry.forAll(fn) that removes while finding, but the catch is that
// we'll be splicing the arrays that we're iterating over. Needs extra tests to
// make sure we don't screw it up. @rvagg
registry.del(handlers[i])
if (!removed[handlers[i].eventType] && handlers[i][eventSupport])
removed[handlers[i].eventType] = { t: handlers[i].eventType, c: handlers[i].type }
}
}
// check each type/element for removed listeners and remove the rootListener where it's no longer needed
for (i in removed) {
if (!registry.has(element, removed[i].t, null, false)) {
// last listener of this type, remove the rootListener
listener(element, removed[i].t, false, removed[i].c)
}
}
}
// set up a delegate helper using the given selector, wrap the handler function
, delegate = function (selector, fn) {
//TODO: findTarget (therefore $) is called twice, once for match and once for
// setting e.currentTarget, fix this so it's only needed once
var findTarget = function (target, root) {
var i, array = isString(selector) ? selectorEngine(selector, root) : selector
for (; target && target !== root; target = target.parentNode) {
for (i = array.length; i--;) {
if (array[i] === target) return target
}
}
}
, handler = function (e) {
var match = findTarget(e.target, this)
if (match) fn.apply(match, arguments)
}
// __beanDel isn't pleasant but it's a private function, not exposed outside of Bean
handler.__beanDel = {
ft : findTarget // attach it here for customEvents to use too
, selector : selector
}
return handler
}
, fireListener = W3C_MODEL ? function (isNative, type, element) {
// modern browsers, do a proper dispatchEvent()
var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
element.dispatchEvent(evt)
} : function (isNative, type, element) {
// old browser use onpropertychange, just increment a custom property to trigger the event
element = targetElement(element, isNative)
isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
}
/**
* Public API: off(), on(), add(), (remove()), one(), fire(), clone()
*/
/**
* off(element[, eventType(s)[, handler ]])
*/
, off = function (element, typeSpec, fn) {
var isTypeStr = isString(typeSpec)
, k, type, namespaces, i
if (isTypeStr && typeSpec.indexOf(' ') > 0) {
// off(el, 't1 t2 t3', fn) or off(el, 't1 t2 t3')
typeSpec = str2arr(typeSpec)
for (i = typeSpec.length; i--;)
off(element, typeSpec[i], fn)
return element
}
type = isTypeStr && typeSpec.replace(nameRegex, '')
if (type && customEvents[type]) type = customEvents[type].base
if (!typeSpec || isTypeStr) {
// off(el) or off(el, t1.ns) or off(el, .ns) or off(el, .ns1.ns2.ns3)
if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.')
removeListener(element, type, fn, namespaces)
} else if (isFunction(typeSpec)) {
// off(el, fn)
removeListener(element, null, typeSpec)
} else {
// off(el, { t1: fn1, t2, fn2 })
for (k in typeSpec) {
if (typeSpec.hasOwnProperty(k)) off(element, k, typeSpec[k])
}
}
return element
}
/**
* on(element, eventType(s)[, selector], handler[, args ])
*/
, on = function(element, events, selector, fn) {
var originalFn, type, types, i, args, entry, first
//TODO: the undefined check means you can't pass an 'args' argument, fix this perhaps?
if (selector === undefined && typeof events == 'object') {
//TODO: this can't handle delegated events
for (type in events) {
if (events.hasOwnProperty(type)) {
on.call(this, element, type, events[type])
}
}
return
}
if (!isFunction(selector)) {
// delegated event
originalFn = fn
args = slice.call(arguments, 4)
fn = delegate(selector, originalFn, selectorEngine)
} else {
args = slice.call(arguments, 3)
fn = originalFn = selector
}
types = str2arr(events)
// special case for one(), wrap in a self-removing handler
if (this === ONE) {
fn = once(off, element, events, fn, originalFn)
}
for (i = types.length; i--;) {
// add new handler to the registry and check if it's the first for this element/type
first = registry.put(entry = new RegEntry(
element
, types[i].replace(nameRegex, '') // event type
, fn
, originalFn
, str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces
, args
, false // not root
))
if (entry[eventSupport] && first) {
// first event of this type on this element, add root listener
listener(element, entry.eventType, true, entry.customType)
}
}
return element
}
/**
* add(element[, selector], eventType(s), handler[, args ])
*
* Deprecated: kept (for now) for backward-compatibility
*/
, add = function (element, events, fn, delfn) {
return on.apply(
null
, !isString(fn)
? slice.call(arguments)
: [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : [])
)
}
/**
* one(element, eventType(s)[, selector], handler[, args ])
*/
, one = function () {
return on.apply(ONE, arguments)
}
/**
* fire(element, eventType(s)[, args ])
*
* The optional 'args' argument must be an array, if no 'args' argument is provided
* then we can use the browser's DOM event system, otherwise we trigger handlers manually
*/
, fire = function (element, type, args) {
var types = str2arr(type)
, i, j, l, names, handlers
for (i = types.length; i--;) {
type = types[i].replace(nameRegex, '')
if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.')
if (!names && !args && element[eventSupport]) {
fireListener(nativeEvents[type], type, element)
} else {
// non-native event, either because of a namespace, arguments or a non DOM element
// iterate over all listeners and manually 'fire'
handlers = registry.get(element, type, null, false)
args = [false].concat(args)
for (j = 0, l = handlers.length; j < l; j++) {
if (handlers[j].inNamespaces(names)) {
handlers[j].handler.apply(element, args)
}
}
}
}
return element
}
/**
* clone(dstElement, srcElement[, eventType ])
*
* TODO: perhaps for consistency we should allow the same flexibility in type specifiers?
*/
, clone = function (element, from, type) {
var handlers = registry.get(from, type, null, false)
, l = handlers.length
, i = 0
, args, beanDel
for (; i < l; i++) {
if (handlers[i].original) {
args = [ element, handlers[i].type ]
if (beanDel = handlers[i].handler.__beanDel) args.push(beanDel.selector)
args.push(handlers[i].original)
on.apply(null, args)
}
}
return element
}
, bean = {
'on' : on
, 'add' : add
, 'one' : one
, 'off' : off
, 'remove' : off
, 'clone' : clone
, 'fire' : fire
, 'Event' : Event
, 'setSelectorEngine' : setSelectorEngine
, 'noConflict' : function () {
context[name] = old
return this
}
}
// for IE, clean up on unload to avoid leaks
if (win.attachEvent) {
var cleanup = function () {
var i, entries = registry.entries()
for (i in entries) {
if (entries[i].type && entries[i].type !== 'unload') off(entries[i].element, entries[i].type)
}
win.detachEvent('onunload', cleanup)
win.CollectGarbage && win.CollectGarbage()
}
win.attachEvent('onunload', cleanup)
}
// initialize selector engine to internal default (qSA or throw Error)
setSelectorEngine()
return bean
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,801 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
// Class: $.jqplot.BarRenderer
// A plugin renderer for jqPlot to draw a bar plot.
// Draws series as a line.
$.jqplot.BarRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.BarRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.BarRenderer.prototype.constructor = $.jqplot.BarRenderer;
// called with scope of series.
$.jqplot.BarRenderer.prototype.init = function(options, plot) {
// Group: Properties
//
// prop: barPadding
// Number of pixels between adjacent bars at the same axis value.
this.barPadding = 8;
// prop: barMargin
// Number of pixels between groups of bars at adjacent axis values.
this.barMargin = 10;
// prop: barDirection
// 'vertical' = up and down bars, 'horizontal' = side to side bars
this.barDirection = 'vertical';
// prop: barWidth
// Width of the bar in pixels (auto by devaul). null = calculated automatically.
this.barWidth = null;
// prop: shadowOffset
// offset of the shadow from the slice and offset of
// each succesive stroke of the shadow from the last.
this.shadowOffset = 2;
// prop: shadowDepth
// number of strokes to apply to the shadow,
// each stroke offset shadowOffset from the last.
this.shadowDepth = 5;
// prop: shadowAlpha
// transparency of the shadow (0 = transparent, 1 = opaque)
this.shadowAlpha = 0.08;
// prop: waterfall
// true to enable waterfall plot.
this.waterfall = false;
// prop: groups
// group bars into this many groups
this.groups = 1;
// prop: varyBarColor
// true to color each bar of a series separately rather than
// have every bar of a given series the same color.
// If used for non-stacked multiple series bar plots, user should
// specify a separate 'seriesColors' array for each series.
// Otherwise, each series will set their bars to the same color array.
// This option has no Effect for stacked bar charts and is disabled.
this.varyBarColor = false;
// prop: highlightMouseOver
// True to highlight slice when moused over.
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
this.highlightMouseOver = true;
// prop: highlightMouseDown
// True to highlight when a mouse button is pressed over a slice.
// This will be disabled if highlightMouseOver is true.
this.highlightMouseDown = false;
// prop: highlightColors
// an array of colors to use when highlighting a bar.
this.highlightColors = [];
// prop: transposedData
// NOT IMPLEMENTED YET. True if this is a horizontal bar plot and
// x and y values are "transposed". Tranposed, or "swapped", data is
// required prior to rev. 894 builds of jqPlot with horizontal bars.
// Allows backward compatability of bar renderer horizontal bars with
// old style data sets.
this.transposedData = true;
this.renderer.animation = {
show: false,
direction: 'down',
speed: 3000,
_supported: true
};
this._type = 'bar';
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
if (options.highlightMouseDown && options.highlightMouseOver == null) {
options.highlightMouseOver = false;
}
//////
// This is probably wrong here.
// After going back and forth on whether renderer should be the thing
// or extend the thing, it seems that it it best if it is a property
// on the thing. This should be something that is commonized
// among series renderers in the future.
//////
$.extend(true, this, options);
// really should probably do this
$.extend(true, this.renderer, options);
// fill is still needed to properly draw the legend.
// bars have to be filled.
this.fill = true;
// if horizontal bar and animating, reset the default direction
if (this.barDirection === 'horizontal' && this.rendererOptions.animation && this.rendererOptions.animation.direction == null) {
this.renderer.animation.direction = 'left';
}
if (this.waterfall) {
this.fillToZero = false;
this.disableStack = true;
}
if (this.barDirection == 'vertical' ) {
this._primaryAxis = '_xaxis';
this._stackAxis = 'y';
this.fillAxis = 'y';
}
else {
this._primaryAxis = '_yaxis';
this._stackAxis = 'x';
this.fillAxis = 'x';
}
// index of the currenty highlighted point, if any
this._highlightedPoint = null;
// total number of values for all bar series, total number of bar series, and position of this series
this._plotSeriesInfo = null;
// Array of actual data colors used for each data point.
this._dataColors = [];
this._barPoints = [];
// set the shape renderer options
var opts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, strokeStyle:this.color, fillStyle:this.color, closePath:this.fill};
this.renderer.shapeRenderer.init(opts);
// set the shadow renderer options
var sopts = {lineJoin:'miter', lineCap:'round', fill:true, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, closePath:this.fill};
this.renderer.shadowRenderer.init(sopts);
plot.postInitHooks.addOnce(postInit);
plot.postDrawHooks.addOnce(postPlotDraw);
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
};
// called with scope of series
function barPreInit(target, data, seriesDefaults, options) {
if (this.rendererOptions.barDirection == 'horizontal') {
this._stackAxis = 'x';
this._primaryAxis = '_yaxis';
}
if (this.rendererOptions.waterfall == true) {
this._data = $.extend(true, [], this.data);
var sum = 0;
var pos = (!this.rendererOptions.barDirection || this.rendererOptions.barDirection === 'vertical' || this.transposedData === false) ? 1 : 0;
for(var i=0; i<this.data.length; i++) {
sum += this.data[i][pos];
if (i>0) {
this.data[i][pos] += this.data[i-1][pos];
}
}
this.data[this.data.length] = (pos == 1) ? [this.data.length+1, sum] : [sum, this.data.length+1];
this._data[this._data.length] = (pos == 1) ? [this._data.length+1, sum] : [sum, this._data.length+1];
}
if (this.rendererOptions.groups > 1) {
this.breakOnNull = true;
var l = this.data.length;
var skip = parseInt(l/this.rendererOptions.groups, 10);
var count = 0;
for (var i=skip; i<l; i+=skip) {
this.data.splice(i+count, 0, [null, null]);
this._plotData.splice(i+count, 0, [null, null]);
this._stackData.splice(i+count, 0, [null, null]);
count++;
}
for (i=0; i<this.data.length; i++) {
if (this._primaryAxis == '_xaxis') {
this.data[i][0] = i+1;
this._plotData[i][0] = i+1;
this._stackData[i][0] = i+1;
}
else {
this.data[i][1] = i+1;
this._plotData[i][1] = i+1;
this._stackData[i][1] = i+1;
}
}
}
}
$.jqplot.preSeriesInitHooks.push(barPreInit);
// needs to be called with scope of series, not renderer.
$.jqplot.BarRenderer.prototype.calcSeriesNumbers = function() {
var nvals = 0;
var nseries = 0;
var paxis = this[this._primaryAxis];
var s, series, pos;
// loop through all series on this axis
for (var i=0; i < paxis._series.length; i++) {
series = paxis._series[i];
if (series === this) {
pos = i;
}
// is the series rendered as a bar?
if (series.renderer.constructor == $.jqplot.BarRenderer) {
// gridData may not be computed yet, use data length insted
nvals += series.data.length;
nseries += 1;
}
}
// return total number of values for all bar series, total number of bar series, and position of this series
return [nvals, nseries, pos];
};
$.jqplot.BarRenderer.prototype.setBarWidth = function() {
// need to know how many data values we have on the approprate axis and figure it out.
var i;
var nvals = 0;
var nseries = 0;
var paxis = this[this._primaryAxis];
var s, series, pos;
var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
nvals = temp[0];
nseries = temp[1];
var nticks = paxis.numberTicks;
var nbins = (nticks-1)/2;
// so, now we have total number of axis values.
if (paxis.name == 'xaxis' || paxis.name == 'x2axis') {
if (this._stack) {
this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals * nseries - this.barMargin;
}
else {
this.barWidth = ((paxis._offsets.max - paxis._offsets.min)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
// this.barWidth = (paxis._offsets.max - paxis._offsets.min) / nvals - this.barPadding - this.barMargin/nseries;
}
}
else {
if (this._stack) {
this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals * nseries - this.barMargin;
}
else {
this.barWidth = ((paxis._offsets.min - paxis._offsets.max)/nbins - this.barPadding * (nseries-1) - this.barMargin*2)/nseries;
// this.barWidth = (paxis._offsets.min - paxis._offsets.max) / nvals - this.barPadding - this.barMargin/nseries;
}
}
return [nvals, nseries];
};
function computeHighlightColors (colors) {
var ret = [];
for (var i=0; i<colors.length; i++){
var rgba = $.jqplot.getColorComponents(colors[i]);
var newrgb = [rgba[0], rgba[1], rgba[2]];
var sum = newrgb[0] + newrgb[1] + newrgb[2];
for (var j=0; j<3; j++) {
// when darkening, lowest color component can be is 60.
newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
newrgb[j] = parseInt(newrgb[j], 10);
}
ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
}
return ret;
}
function getStart(sidx, didx, comp, plot, axis) {
// check if sign change
var seriesIndex = sidx,
prevSeriesIndex = sidx - 1,
start,
prevVal,
aidx = (axis === 'x') ? 0 : 1;
// is this not the first series?
if (seriesIndex > 0) {
prevVal = plot.series[prevSeriesIndex]._plotData[didx][aidx];
// is there a sign change
if ((comp * prevVal) < 0) {
start = getStart(prevSeriesIndex, didx, comp, plot, axis);
}
// no sign change.
else {
start = plot.series[prevSeriesIndex].gridData[didx][aidx];
}
}
// if first series, return value at 0
else {
start = (aidx === 0) ? plot.series[seriesIndex]._xaxis.series_u2p(0) : plot.series[seriesIndex]._yaxis.series_u2p(0);
}
return start;
}
$.jqplot.BarRenderer.prototype.draw = function(ctx, gridData, options, plot) {
var i;
// Ughhh, have to make a copy of options b/c it may be modified later.
var opts = $.extend({}, options);
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
var xaxis = this.xaxis;
var yaxis = this.yaxis;
var xp = this._xaxis.series_u2p;
var yp = this._yaxis.series_u2p;
var pointx, pointy;
// clear out data colors.
this._dataColors = [];
this._barPoints = [];
if (this.barWidth == null) {
this.renderer.setBarWidth.call(this);
}
var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
var nvals = temp[0];
var nseries = temp[1];
var pos = temp[2];
var points = [];
if (this._stack) {
this._barNudge = 0;
}
else {
this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
}
if (showLine) {
var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors);
var positiveColors = new $.jqplot.ColorGenerator(this.seriesColors);
var negativeColor = negativeColors.get(this.index);
if (! this.useNegativeColors) {
negativeColor = opts.fillStyle;
}
var positiveColor = opts.fillStyle;
var base;
var xstart;
var ystart;
if (this.barDirection == 'vertical') {
for (var i=0; i<gridData.length; i++) {
if (!this._stack && this.data[i][1] == null) {
continue;
}
points = [];
base = gridData[i][0] + this._barNudge;
// stacked
if (this._stack && this._prevGridData.length) {
ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
}
// not stacked
else {
if (this.fillToZero) {
ystart = this._yaxis.series_u2p(0);
}
else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
ystart = this.gridData[i-1][1];
}
else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
ystart = this._yaxis.series_u2p(0);
}
else if (this._yaxis.min > 0) {
ystart = ctx.canvas.height;
}
else {
ystart = 0;
}
}
else if (this.waterfall && i == this.gridData.length - 1) {
if (this._yaxis.min <= 0 && this._yaxis.max >= 0) {
ystart = this._yaxis.series_u2p(0);
}
else if (this._yaxis.min > 0) {
ystart = ctx.canvas.height;
}
else {
ystart = 0;
}
}
else {
ystart = ctx.canvas.height;
}
}
if ((this.fillToZero && this._plotData[i][1] < 0) || (this.waterfall && this._data[i][1] < 0)) {
if (this.varyBarColor && !this._stack) {
if (this.useNegativeColors) {
opts.fillStyle = negativeColors.next();
}
else {
opts.fillStyle = positiveColors.next();
}
}
else {
opts.fillStyle = negativeColor;
}
}
else {
if (this.varyBarColor && !this._stack) {
opts.fillStyle = positiveColors.next();
}
else {
opts.fillStyle = positiveColor;
}
}
if (!this.fillToZero || this._plotData[i][1] >= 0) {
points.push([base-this.barWidth/2, ystart]);
points.push([base-this.barWidth/2, gridData[i][1]]);
points.push([base+this.barWidth/2, gridData[i][1]]);
points.push([base+this.barWidth/2, ystart]);
}
// for negative bars make sure points are always ordered clockwise
else {
points.push([base-this.barWidth/2, gridData[i][1]]);
points.push([base-this.barWidth/2, ystart]);
points.push([base+this.barWidth/2, ystart]);
points.push([base+this.barWidth/2, gridData[i][1]]);
}
this._barPoints.push(points);
// now draw the shadows if not stacked.
// for stacked plots, they are predrawn by drawShadow
if (shadow && !this._stack) {
var sopts = $.extend(true, {}, opts);
// need to get rid of fillStyle on shadow.
delete sopts.fillStyle;
this.renderer.shadowRenderer.draw(ctx, points, sopts);
}
var clr = opts.fillStyle || this.color;
this._dataColors.push(clr);
this.renderer.shapeRenderer.draw(ctx, points, opts);
}
}
else if (this.barDirection == 'horizontal'){
for (var i=0; i<gridData.length; i++) {
if (!this._stack && this.data[i][0] == null) {
continue;
}
points = [];
base = gridData[i][1] - this._barNudge;
xstart;
if (this._stack && this._prevGridData.length) {
xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
}
// not stacked
else {
if (this.fillToZero) {
xstart = this._xaxis.series_u2p(0);
}
else if (this.waterfall && i > 0 && i < this.gridData.length-1) {
xstart = this.gridData[i-1][0];
}
else if (this.waterfall && i == 0 && i < this.gridData.length-1) {
if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
xstart = this._xaxis.series_u2p(0);
}
else if (this._xaxis.min > 0) {
xstart = 0;
}
else {
xstart = 0;
}
}
else if (this.waterfall && i == this.gridData.length - 1) {
if (this._xaxis.min <= 0 && this._xaxis.max >= 0) {
xstart = this._xaxis.series_u2p(0);
}
else if (this._xaxis.min > 0) {
xstart = 0;
}
else {
xstart = ctx.canvas.width;
}
}
else {
xstart = 0;
}
}
if ((this.fillToZero && this._plotData[i][0] < 0) || (this.waterfall && this._data[i][0] < 0)) {
if (this.varyBarColor && !this._stack) {
if (this.useNegativeColors) {
opts.fillStyle = negativeColors.next();
}
else {
opts.fillStyle = positiveColors.next();
}
}
else {
opts.fillStyle = negativeColor;
}
}
else {
if (this.varyBarColor && !this._stack) {
opts.fillStyle = positiveColors.next();
}
else {
opts.fillStyle = positiveColor;
}
}
if (!this.fillToZero || this._plotData[i][0] >= 0) {
points.push([xstart, base + this.barWidth / 2]);
points.push([xstart, base - this.barWidth / 2]);
points.push([gridData[i][0], base - this.barWidth / 2]);
points.push([gridData[i][0], base + this.barWidth / 2]);
}
else {
points.push([gridData[i][0], base + this.barWidth / 2]);
points.push([gridData[i][0], base - this.barWidth / 2]);
points.push([xstart, base - this.barWidth / 2]);
points.push([xstart, base + this.barWidth / 2]);
}
this._barPoints.push(points);
// now draw the shadows if not stacked.
// for stacked plots, they are predrawn by drawShadow
if (shadow && !this._stack) {
var sopts = $.extend(true, {}, opts);
delete sopts.fillStyle;
this.renderer.shadowRenderer.draw(ctx, points, sopts);
}
var clr = opts.fillStyle || this.color;
this._dataColors.push(clr);
this.renderer.shapeRenderer.draw(ctx, points, opts);
}
}
}
if (this.highlightColors.length == 0) {
this.highlightColors = $.jqplot.computeHighlightColors(this._dataColors);
}
else if (typeof(this.highlightColors) == 'string') {
var temp = this.highlightColors;
this.highlightColors = [];
for (var i=0; i<this._dataColors.length; i++) {
this.highlightColors.push(temp);
}
}
};
// for stacked plots, shadows will be pre drawn by drawShadow.
$.jqplot.BarRenderer.prototype.drawShadow = function(ctx, gridData, options, plot) {
var i;
var opts = (options != undefined) ? options : {};
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
var xaxis = this.xaxis;
var yaxis = this.yaxis;
var xp = this._xaxis.series_u2p;
var yp = this._yaxis.series_u2p;
var pointx, points, pointy, nvals, nseries, pos;
if (this._stack && this.shadow) {
if (this.barWidth == null) {
this.renderer.setBarWidth.call(this);
}
var temp = this._plotSeriesInfo = this.renderer.calcSeriesNumbers.call(this);
nvals = temp[0];
nseries = temp[1];
pos = temp[2];
if (this._stack) {
this._barNudge = 0;
}
else {
this._barNudge = (-Math.abs(nseries/2 - 0.5) + pos) * (this.barWidth + this.barPadding);
}
if (showLine) {
if (this.barDirection == 'vertical') {
for (var i=0; i<gridData.length; i++) {
if (this.data[i][1] == null) {
continue;
}
points = [];
var base = gridData[i][0] + this._barNudge;
var ystart;
if (this._stack && this._prevGridData.length) {
ystart = getStart(this.index, i, this._plotData[i][1], plot, 'y');
}
else {
if (this.fillToZero) {
ystart = this._yaxis.series_u2p(0);
}
else {
ystart = ctx.canvas.height;
}
}
points.push([base-this.barWidth/2, ystart]);
points.push([base-this.barWidth/2, gridData[i][1]]);
points.push([base+this.barWidth/2, gridData[i][1]]);
points.push([base+this.barWidth/2, ystart]);
this.renderer.shadowRenderer.draw(ctx, points, opts);
}
}
else if (this.barDirection == 'horizontal'){
for (var i=0; i<gridData.length; i++) {
if (this.data[i][0] == null) {
continue;
}
points = [];
var base = gridData[i][1] - this._barNudge;
var xstart;
if (this._stack && this._prevGridData.length) {
xstart = getStart(this.index, i, this._plotData[i][0], plot, 'x');
}
else {
if (this.fillToZero) {
xstart = this._xaxis.series_u2p(0);
}
else {
xstart = 0;
}
}
points.push([xstart, base+this.barWidth/2]);
points.push([gridData[i][0], base+this.barWidth/2]);
points.push([gridData[i][0], base-this.barWidth/2]);
points.push([xstart, base-this.barWidth/2]);
this.renderer.shadowRenderer.draw(ctx, points, opts);
}
}
}
}
};
function postInit(target, data, options) {
for (var i=0; i<this.series.length; i++) {
if (this.series[i].renderer.constructor == $.jqplot.BarRenderer) {
// don't allow mouseover and mousedown at same time.
if (this.series[i].highlightMouseOver) {
this.series[i].highlightMouseDown = false;
}
}
}
}
// called within context of plot
// create a canvas which we can draw on.
// insert it before the eventCanvas, so eventCanvas will still capture events.
function postPlotDraw() {
// Memory Leaks patch
if (this.plugins.barRenderer && this.plugins.barRenderer.highlightCanvas) {
this.plugins.barRenderer.highlightCanvas.resetCanvas();
this.plugins.barRenderer.highlightCanvas = null;
}
this.plugins.barRenderer = {highlightedSeriesIndex:null};
this.plugins.barRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
this.eventCanvas._elem.before(this.plugins.barRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-barRenderer-highlight-canvas', this._plotDimensions, this));
this.plugins.barRenderer.highlightCanvas.setContext();
this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
}
function highlight (plot, sidx, pidx, points) {
var s = plot.series[sidx];
var canvas = plot.plugins.barRenderer.highlightCanvas;
canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
s._highlightedPoint = pidx;
plot.plugins.barRenderer.highlightedSeriesIndex = sidx;
var opts = {fillStyle: s.highlightColors[pidx]};
s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
canvas = null;
}
function unhighlight (plot) {
var canvas = plot.plugins.barRenderer.highlightCanvas;
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
for (var i=0; i<plot.series.length; i++) {
plot.series[i]._highlightedPoint = null;
}
plot.plugins.barRenderer.highlightedSeriesIndex = null;
plot.target.trigger('jqplotDataUnhighlight');
canvas = null;
}
function handleMove(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
var evt1 = jQuery.Event('jqplotDataMouseOver');
evt1.pageX = ev.pageX;
evt1.pageY = ev.pageY;
plot.target.trigger(evt1, ins);
if (plot.series[ins[0]].show && plot.series[ins[0]].highlightMouseOver &&
!(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
var evt = jQuery.Event('jqplotDataHighlight');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
}
}
else if (neighbor == null) {
unhighlight (plot);
}
}
function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.barRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
var evt = jQuery.Event('jqplotDataHighlight');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
}
}
else if (neighbor == null) {
unhighlight (plot);
}
}
function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
if (idx != null && plot.series[idx].highlightMouseDown) {
unhighlight(plot);
}
}
function handleClick(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
var evt = jQuery.Event('jqplotDataClick');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
}
}
function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
var idx = plot.plugins.barRenderer.highlightedSeriesIndex;
if (idx != null && plot.series[idx].highlightMouseDown) {
unhighlight(plot);
}
var evt = jQuery.Event('jqplotDataRightClick');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
}
}
})(jQuery);

View File

@ -0,0 +1,253 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.CanvasAxisTickRenderer
* Renderer to draw axis ticks with a canvas element to support advanced
* featrues such as rotated text. This renderer uses a separate rendering engine
* to draw the text on the canvas. Two modes of rendering the text are available.
* If the browser has native font support for canvas fonts (currently Mozila 3.5
* and Safari 4), you can enable text rendering with the canvas fillText method.
* You do so by setting the "enableFontSupport" option to true.
*
* Browsers lacking native font support will have the text drawn on the canvas
* using the Hershey font metrics. Even if the "enableFontSupport" option is true
* non-supporting browsers will still render with the Hershey font.
*/
$.jqplot.CanvasAxisTickRenderer = function(options) {
// Group: Properties
// prop: mark
// tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null.
this.mark = 'outside';
// prop: showMark
// whether or not to show the mark on the axis.
this.showMark = true;
// prop: showGridline
// whether or not to draw the gridline on the grid at this tick.
this.showGridline = true;
// prop: isMinorTick
// if this is a minor tick.
this.isMinorTick = false;
// prop: angle
// angle of text, measured clockwise from x axis.
this.angle = 0;
// prop: markSize
// Length of the tick marks in pixels. For 'cross' style, length
// will be stoked above and below axis, so total length will be twice this.
this.markSize = 4;
// prop: show
// whether or not to show the tick (mark and label).
this.show = true;
// prop: showLabel
// whether or not to show the label.
this.showLabel = true;
// prop: labelPosition
// 'auto', 'start', 'middle' or 'end'.
// Whether tick label should be positioned so the start, middle, or end
// of the tick mark.
this.labelPosition = 'auto';
this.label = '';
this.value = null;
this._styles = {};
// prop: formatter
// A class of a formatter for the tick text.
// The default $.jqplot.DefaultTickFormatter uses sprintf.
this.formatter = $.jqplot.DefaultTickFormatter;
// prop: formatString
// string passed to the formatter.
this.formatString = '';
// prop: prefix
// String to prepend to the tick label.
// Prefix is prepended to the formatted tick label.
this.prefix = '';
// prop: fontFamily
// css spec for the font-family css attribute.
this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif';
// prop: fontSize
// CSS spec for font size.
this.fontSize = '10pt';
// prop: fontWeight
// CSS spec for fontWeight
this.fontWeight = 'normal';
// prop: fontStretch
// Multiplier to condense or expand font width.
// Applies only to browsers which don't support canvas native font rendering.
this.fontStretch = 1.0;
// prop: textColor
// css spec for the color attribute.
this.textColor = '#666666';
// prop: enableFontSupport
// true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+.
// If true, tick label will be drawn with canvas tag native support for fonts.
// If false, tick label will be drawn with Hershey font metrics.
this.enableFontSupport = true;
// prop: pt2px
// Point to pixel scaling factor, used for computing height of bounding box
// around a label. The labels text renderer has a default setting of 1.4, which
// should be suitable for most fonts. Leave as null to use default. If tops of
// letters appear clipped, increase this. If bounding box seems too big, decrease.
// This is an issue only with the native font renderering capabilities of Mozilla
// 3.5 and Safari 4 since they do not provide a method to determine the font height.
this.pt2px = null;
this._elem;
this._ctx;
this._plotWidth;
this._plotHeight;
this._plotDimensions = {height:null, width:null};
$.extend(true, this, options);
var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily};
if (this.pt2px) {
ropts.pt2px = this.pt2px;
}
if (this.enableFontSupport) {
if ($.jqplot.support_canvas_text()) {
this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts);
}
else {
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
}
}
else {
this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts);
}
};
$.jqplot.CanvasAxisTickRenderer.prototype.init = function(options) {
$.extend(true, this, options);
this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily});
};
// return width along the x axis
// will check first to see if an element exists.
// if not, will return the computed text box width.
$.jqplot.CanvasAxisTickRenderer.prototype.getWidth = function(ctx) {
if (this._elem) {
return this._elem.outerWidth(true);
}
else {
var tr = this._textRenderer;
var l = tr.getWidth(ctx);
var h = tr.getHeight(ctx);
var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l);
return w;
}
};
// return height along the y axis.
$.jqplot.CanvasAxisTickRenderer.prototype.getHeight = function(ctx) {
if (this._elem) {
return this._elem.outerHeight(true);
}
else {
var tr = this._textRenderer;
var l = tr.getWidth(ctx);
var h = tr.getHeight(ctx);
var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l);
return w;
}
};
// return top.
$.jqplot.CanvasAxisTickRenderer.prototype.getTop = function(ctx) {
if (this._elem) {
return this._elem.position().top;
}
else {
return null;
}
};
$.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad = function() {
var a = this.angle * Math.PI/180;
return a;
};
$.jqplot.CanvasAxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
this.value = value;
if (isMinor) {
this.isMinorTick = true;
}
return this;
};
$.jqplot.CanvasAxisTickRenderer.prototype.draw = function(ctx, plot) {
if (!this.label) {
this.label = this.prefix + this.formatter(this.formatString, this.value);
}
// Memory Leaks patch
if (this._elem) {
if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
}
this._elem.emptyForce();
this._elem = null;
}
// create a canvas here, but can't draw on it untill it is appended
// to dom for IE compatability.
var elem = plot.canvasManager.getCanvas();
this._textRenderer.setText(this.label, ctx);
var w = this.getWidth(ctx);
var h = this.getHeight(ctx);
// canvases seem to need to have width and heigh attributes directly set.
elem.width = w;
elem.height = h;
elem.style.width = w;
elem.style.height = h;
elem.style.textAlign = 'left';
elem.style.position = 'absolute';
elem = plot.canvasManager.initCanvas(elem);
this._elem = $(elem);
this._elem.css(this._styles);
this._elem.addClass('jqplot-'+this.axis+'-tick');
elem = null;
return this._elem;
};
$.jqplot.CanvasAxisTickRenderer.prototype.pack = function() {
this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label);
};
})(jQuery);

View File

@ -0,0 +1,449 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
* included jsDate library by Chris Leonello:
*
* Copyright (c) 2010-2013 Chris Leonello
*
* jsDate is currently available for use in all personal or commercial projects
* under both the MIT and GPL version 2.0 licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* jsDate borrows many concepts and ideas from the Date Instance
* Methods by Ken Snyder along with some parts of Ken's actual code.
*
* Ken's original Date Instance Methods and copyright notice:
*
* Ken Snyder (ken d snyder at gmail dot com)
* 2008-09-10
* version 2.0.2 (http://kendsnyder.com/sandbox/date/)
* Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
*
* jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
* Larry has generously given permission to adapt his code for inclusion
* into jqPlot.
*
* Larry's original code can be found here:
*
* https://github.com/lsiden/export-jqplot-to-png
*
*
*/
(function($) {
// This code is a modified version of the canvastext.js code, copyright below:
//
// This code is released to the public domain by Jim Studt, 2007.
// He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/
//
$.jqplot.CanvasTextRenderer = function(options){
this.fontStyle = 'normal'; // normal, italic, oblique [not implemented]
this.fontVariant = 'normal'; // normal, small caps [not implemented]
this.fontWeight = 'normal'; // normal, bold, bolder, lighter, 100 - 900
this.fontSize = '10px';
this.fontFamily = 'sans-serif';
this.fontStretch = 1.0;
this.fillStyle = '#666666';
this.angle = 0;
this.textAlign = 'start';
this.textBaseline = 'alphabetic';
this.text;
this.width;
this.height;
this.pt2px = 1.28;
$.extend(true, this, options);
this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
this.setHeight();
};
$.jqplot.CanvasTextRenderer.prototype.init = function(options) {
$.extend(true, this, options);
this.normalizedFontSize = this.normalizeFontSize(this.fontSize);
this.setHeight();
};
// convert css spec into point size
// returns float
$.jqplot.CanvasTextRenderer.prototype.normalizeFontSize = function(sz) {
sz = String(sz);
var n = parseFloat(sz);
if (sz.indexOf('px') > -1) {
return n/this.pt2px;
}
else if (sz.indexOf('pt') > -1) {
return n;
}
else if (sz.indexOf('em') > -1) {
return n*12;
}
else if (sz.indexOf('%') > -1) {
return n*12/100;
}
// default to pixels;
else {
return n/this.pt2px;
}
};
$.jqplot.CanvasTextRenderer.prototype.fontWeight2Float = function(w) {
// w = normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
// return values adjusted for Hershey font.
if (Number(w)) {
return w/400;
}
else {
switch (w) {
case 'normal':
return 1;
break;
case 'bold':
return 1.75;
break;
case 'bolder':
return 2.25;
break;
case 'lighter':
return 0.75;
break;
default:
return 1;
break;
}
}
};
$.jqplot.CanvasTextRenderer.prototype.getText = function() {
return this.text;
};
$.jqplot.CanvasTextRenderer.prototype.setText = function(t, ctx) {
this.text = t;
this.setWidth(ctx);
return this;
};
$.jqplot.CanvasTextRenderer.prototype.getWidth = function(ctx) {
return this.width;
};
$.jqplot.CanvasTextRenderer.prototype.setWidth = function(ctx, w) {
if (!w) {
this.width = this.measure(ctx, this.text);
}
else {
this.width = w;
}
return this;
};
// return height in pixels.
$.jqplot.CanvasTextRenderer.prototype.getHeight = function(ctx) {
return this.height;
};
// w - height in pt
// set heigh in px
$.jqplot.CanvasTextRenderer.prototype.setHeight = function(w) {
if (!w) {
//height = this.fontSize /0.75;
this.height = this.normalizedFontSize * this.pt2px;
}
else {
this.height = w;
}
return this;
};
$.jqplot.CanvasTextRenderer.prototype.letter = function (ch)
{
return this.letters[ch];
};
$.jqplot.CanvasTextRenderer.prototype.ascent = function()
{
return this.normalizedFontSize;
};
$.jqplot.CanvasTextRenderer.prototype.descent = function()
{
return 7.0*this.normalizedFontSize/25.0;
};
$.jqplot.CanvasTextRenderer.prototype.measure = function(ctx, str)
{
var total = 0;
var len = str.length;
for (var i = 0; i < len; i++) {
var c = this.letter(str.charAt(i));
if (c) {
total += c.width * this.normalizedFontSize / 25.0 * this.fontStretch;
}
}
return total;
};
$.jqplot.CanvasTextRenderer.prototype.draw = function(ctx,str)
{
var x = 0;
// leave room at bottom for descenders.
var y = this.height*0.72;
var total = 0;
var len = str.length;
var mag = this.normalizedFontSize / 25.0;
ctx.save();
var tx, ty;
// 1st quadrant
if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
tx = 0;
ty = -Math.sin(this.angle) * this.width;
}
// 4th quadrant
else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
tx = Math.sin(this.angle) * this.height;
ty = 0;
}
// 2nd quadrant
else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
tx = -Math.cos(this.angle) * this.width;
ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
}
// 3rd quadrant
else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
ty = -Math.cos(this.angle) * this.height;
}
ctx.strokeStyle = this.fillStyle;
ctx.fillStyle = this.fillStyle;
ctx.translate(tx, ty);
ctx.rotate(this.angle);
ctx.lineCap = "round";
// multiplier was 2.0
var fact = (this.normalizedFontSize > 30) ? 2.0 : 2 + (30 - this.normalizedFontSize)/20;
ctx.lineWidth = fact * mag * this.fontWeight2Float(this.fontWeight);
for ( var i = 0; i < len; i++) {
var c = this.letter( str.charAt(i));
if ( !c) {
continue;
}
ctx.beginPath();
var penUp = 1;
var needStroke = 0;
for ( var j = 0; j < c.points.length; j++) {
var a = c.points[j];
if ( a[0] == -1 && a[1] == -1) {
penUp = 1;
continue;
}
if ( penUp) {
ctx.moveTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
penUp = false;
} else {
ctx.lineTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag);
}
}
ctx.stroke();
x += c.width*mag*this.fontStretch;
}
ctx.restore();
return total;
};
$.jqplot.CanvasTextRenderer.prototype.letters = {
' ': { width: 16, points: [] },
'!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
'"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] },
'#': { width: 21, points: [[11,25],[4,-7],[-1,-1],[17,25],[10,-7],[-1,-1],[4,12],[18,12],[-1,-1],[3,6],[17,6]] },
'$': { width: 20, points: [[8,25],[8,-4],[-1,-1],[12,25],[12,-4],[-1,-1],[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
'%': { width: 24, points: [[21,21],[3,0],[-1,-1],[8,21],[10,19],[10,17],[9,15],[7,14],[5,14],[3,16],[3,18],[4,20],[6,21],[8,21],[10,20],[13,19],[16,19],[19,20],[21,21],[-1,-1],[17,7],[15,6],[14,4],[14,2],[16,0],[18,0],[20,1],[21,3],[21,5],[19,7],[17,7]] },
'&': { width: 26, points: [[23,12],[23,13],[22,14],[21,14],[20,13],[19,11],[17,6],[15,3],[13,1],[11,0],[7,0],[5,1],[4,2],[3,4],[3,6],[4,8],[5,9],[12,13],[13,14],[14,16],[14,18],[13,20],[11,21],[9,20],[8,18],[8,16],[9,13],[11,10],[16,3],[18,1],[20,0],[22,0],[23,1],[23,2]] },
'\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] },
'(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] },
')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] },
'*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] },
'+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] },
',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
'-': { width: 18, points: [[6,9],[12,9]] },
'.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] },
'/': { width: 22, points: [[20,25],[2,-7]] },
'0': { width: 20, points: [[9,21],[6,20],[4,17],[3,12],[3,9],[4,4],[6,1],[9,0],[11,0],[14,1],[16,4],[17,9],[17,12],[16,17],[14,20],[11,21],[9,21]] },
'1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] },
'2': { width: 20, points: [[4,16],[4,17],[5,19],[6,20],[8,21],[12,21],[14,20],[15,19],[16,17],[16,15],[15,13],[13,10],[3,0],[17,0]] },
'3': { width: 20, points: [[5,21],[16,21],[10,13],[13,13],[15,12],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
'4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] },
'5': { width: 20, points: [[15,21],[5,21],[4,12],[5,13],[8,14],[11,14],[14,13],[16,11],[17,8],[17,6],[16,3],[14,1],[11,0],[8,0],[5,1],[4,2],[3,4]] },
'6': { width: 20, points: [[16,18],[15,20],[12,21],[10,21],[7,20],[5,17],[4,12],[4,7],[5,3],[7,1],[10,0],[11,0],[14,1],[16,3],[17,6],[17,7],[16,10],[14,12],[11,13],[10,13],[7,12],[5,10],[4,7]] },
'7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] },
'8': { width: 20, points: [[8,21],[5,20],[4,18],[4,16],[5,14],[7,13],[11,12],[14,11],[16,9],[17,7],[17,4],[16,2],[15,1],[12,0],[8,0],[5,1],[4,2],[3,4],[3,7],[4,9],[6,11],[9,12],[13,13],[15,14],[16,16],[16,18],[15,20],[12,21],[8,21]] },
'9': { width: 20, points: [[16,14],[15,11],[13,9],[10,8],[9,8],[6,9],[4,11],[3,14],[3,15],[4,18],[6,20],[9,21],[10,21],[13,20],[15,18],[16,14],[16,9],[15,4],[13,1],[10,0],[8,0],[5,1],[4,3]] },
':': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] },
';': { width: 10, points: [[5,14],[4,13],[5,12],[6,13],[5,14],[-1,-1],[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] },
'<': { width: 24, points: [[20,18],[4,9],[20,0]] },
'=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] },
'>': { width: 24, points: [[4,18],[20,9],[4,0]] },
'?': { width: 18, points: [[3,16],[3,17],[4,19],[5,20],[7,21],[11,21],[13,20],[14,19],[15,17],[15,15],[14,13],[13,12],[9,10],[9,7],[-1,-1],[9,2],[8,1],[9,0],[10,1],[9,2]] },
'@': { width: 27, points: [[18,13],[17,15],[15,16],[12,16],[10,15],[9,14],[8,11],[8,8],[9,6],[11,5],[14,5],[16,6],[17,8],[-1,-1],[12,16],[10,14],[9,11],[9,8],[10,6],[11,5],[-1,-1],[18,16],[17,8],[17,6],[19,5],[21,5],[23,7],[24,10],[24,12],[23,15],[22,17],[20,19],[18,20],[15,21],[12,21],[9,20],[7,19],[5,17],[4,15],[3,12],[3,9],[4,6],[5,4],[7,2],[9,1],[12,0],[15,0],[18,1],[20,2],[21,3],[-1,-1],[19,16],[18,8],[18,6],[19,5]] },
'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] },
'B': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[-1,-1],[4,11],[13,11],[16,10],[17,9],[18,7],[18,4],[17,2],[16,1],[13,0],[4,0]] },
'C': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5]] },
'D': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[11,21],[14,20],[16,18],[17,16],[18,13],[18,8],[17,5],[16,3],[14,1],[11,0],[4,0]] },
'E': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11],[-1,-1],[4,0],[17,0]] },
'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] },
'G': { width: 21, points: [[18,16],[17,18],[15,20],[13,21],[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[18,8],[-1,-1],[13,8],[18,8]] },
'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] },
'I': { width: 8, points: [[4,21],[4,0]] },
'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] },
'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] },
'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] },
'M': { width: 24, points: [[4,21],[4,0],[-1,-1],[4,21],[12,0],[-1,-1],[20,21],[12,0],[-1,-1],[20,21],[20,0]] },
'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] },
'O': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21]] },
'P': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,14],[17,12],[16,11],[13,10],[4,10]] },
'Q': { width: 22, points: [[9,21],[7,20],[5,18],[4,16],[3,13],[3,8],[4,5],[5,3],[7,1],[9,0],[13,0],[15,1],[17,3],[18,5],[19,8],[19,13],[18,16],[17,18],[15,20],[13,21],[9,21],[-1,-1],[12,4],[18,-2]] },
'R': { width: 21, points: [[4,21],[4,0],[-1,-1],[4,21],[13,21],[16,20],[17,19],[18,17],[18,15],[17,13],[16,12],[13,11],[4,11],[-1,-1],[11,11],[18,0]] },
'S': { width: 20, points: [[17,18],[15,20],[12,21],[8,21],[5,20],[3,18],[3,16],[4,14],[5,13],[7,12],[13,10],[15,9],[16,8],[17,6],[17,3],[15,1],[12,0],[8,0],[5,1],[3,3]] },
'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] },
'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] },
'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] },
'W': { width: 24, points: [[2,21],[7,0],[-1,-1],[12,21],[7,0],[-1,-1],[12,21],[17,0],[-1,-1],[22,21],[17,0]] },
'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] },
'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] },
'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] },
'[': { width: 14, points: [[4,25],[4,-7],[-1,-1],[5,25],[5,-7],[-1,-1],[4,25],[11,25],[-1,-1],[4,-7],[11,-7]] },
'\\': { width: 14, points: [[0,21],[14,-3]] },
']': { width: 14, points: [[9,25],[9,-7],[-1,-1],[10,25],[10,-7],[-1,-1],[3,25],[10,25],[-1,-1],[3,-7],[10,-7]] },
'^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] },
'_': { width: 16, points: [[0,-2],[16,-2]] },
'`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] },
'a': { width: 19, points: [[15,14],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'b': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
'c': { width: 18, points: [[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'd': { width: 19, points: [[15,21],[15,0],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'e': { width: 18, points: [[3,8],[15,8],[15,10],[14,12],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] },
'g': { width: 19, points: [[15,14],[15,-2],[14,-5],[13,-6],[11,-7],[8,-7],[6,-6],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] },
'j': { width: 10, points: [[5,21],[6,20],[7,21],[6,22],[5,21],[-1,-1],[6,14],[6,-3],[5,-6],[3,-7],[1,-7]] },
'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] },
'l': { width: 8, points: [[4,21],[4,0]] },
'm': { width: 30, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0],[-1,-1],[15,10],[18,13],[20,14],[23,14],[25,13],[26,10],[26,0]] },
'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] },
'o': { width: 19, points: [[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3],[16,6],[16,8],[15,11],[13,13],[11,14],[8,14]] },
'p': { width: 19, points: [[4,14],[4,-7],[-1,-1],[4,11],[6,13],[8,14],[11,14],[13,13],[15,11],[16,8],[16,6],[15,3],[13,1],[11,0],[8,0],[6,1],[4,3]] },
'q': { width: 19, points: [[15,14],[15,-7],[-1,-1],[15,11],[13,13],[11,14],[8,14],[6,13],[4,11],[3,8],[3,6],[4,3],[6,1],[8,0],[11,0],[13,1],[15,3]] },
'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] },
's': { width: 17, points: [[14,11],[13,13],[10,14],[7,14],[4,13],[3,11],[4,9],[6,8],[11,7],[13,6],[14,4],[14,3],[13,1],[10,0],[7,0],[4,1],[3,3]] },
't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] },
'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] },
'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] },
'w': { width: 22, points: [[3,14],[7,0],[-1,-1],[11,14],[7,0],[-1,-1],[11,14],[15,0],[-1,-1],[19,14],[15,0]] },
'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] },
'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] },
'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] },
'{': { width: 14, points: [[9,25],[7,24],[6,23],[5,21],[5,19],[6,17],[7,16],[8,14],[8,12],[6,10],[-1,-1],[7,24],[6,22],[6,20],[7,18],[8,17],[9,15],[9,13],[8,11],[4,9],[8,7],[9,5],[9,3],[8,1],[7,0],[6,-2],[6,-4],[7,-6],[-1,-1],[6,8],[8,6],[8,4],[7,2],[6,1],[5,-1],[5,-3],[6,-5],[7,-6],[9,-7]] },
'|': { width: 8, points: [[4,25],[4,-7]] },
'}': { width: 14, points: [[5,25],[7,24],[8,23],[9,21],[9,19],[8,17],[7,16],[6,14],[6,12],[8,10],[-1,-1],[7,24],[8,22],[8,20],[7,18],[6,17],[5,15],[5,13],[6,11],[10,9],[6,7],[5,5],[5,3],[6,1],[7,0],[8,-2],[8,-4],[7,-6],[-1,-1],[8,8],[6,6],[6,4],[7,2],[8,1],[9,-1],[9,-3],[8,-5],[7,-6],[5,-7]] },
'~': { width: 24, points: [[3,6],[3,8],[4,11],[6,12],[8,12],[10,11],[14,8],[16,7],[18,7],[20,8],[21,10],[-1,-1],[3,8],[4,10],[6,11],[8,11],[10,10],[14,7],[16,6],[18,6],[20,7],[21,10],[21,12]] }
};
$.jqplot.CanvasFontRenderer = function(options) {
options = options || {};
if (!options.pt2px) {
options.pt2px = 1.5;
}
$.jqplot.CanvasTextRenderer.call(this, options);
};
$.jqplot.CanvasFontRenderer.prototype = new $.jqplot.CanvasTextRenderer({});
$.jqplot.CanvasFontRenderer.prototype.constructor = $.jqplot.CanvasFontRenderer;
$.jqplot.CanvasFontRenderer.prototype.measure = function(ctx, str)
{
// var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
var fstyle = this.fontSize+' '+this.fontFamily;
ctx.save();
ctx.font = fstyle;
var w = ctx.measureText(str).width;
ctx.restore();
return w;
};
$.jqplot.CanvasFontRenderer.prototype.draw = function(ctx, str)
{
var x = 0;
// leave room at bottom for descenders.
var y = this.height*0.72;
//var y = 12;
ctx.save();
var tx, ty;
// 1st quadrant
if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) {
tx = 0;
ty = -Math.sin(this.angle) * this.width;
}
// 4th quadrant
else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) {
tx = Math.sin(this.angle) * this.height;
ty = 0;
}
// 2nd quadrant
else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) {
tx = -Math.cos(this.angle) * this.width;
ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height;
}
// 3rd quadrant
else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) {
tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width;
ty = -Math.cos(this.angle) * this.height;
}
ctx.strokeStyle = this.fillStyle;
ctx.fillStyle = this.fillStyle;
// var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily;
var fstyle = this.fontSize+' '+this.fontFamily;
ctx.font = fstyle;
ctx.translate(tx, ty);
ctx.rotate(this.angle);
ctx.fillText(str, x, y);
// ctx.strokeText(str, x, y);
ctx.restore();
};
})(jQuery);

View File

@ -0,0 +1,679 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* class: $.jqplot.CategoryAxisRenderer
* A plugin for jqPlot to render a category style axis, with equal pixel spacing between y data values of a series.
*
* To use this renderer, include the plugin in your source
* > <script type="text/javascript" language="javascript" src="plugins/jqplot.categoryAxisRenderer.js"></script>
*
* and supply the appropriate options to your plot
*
* > {axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer}}}
**/
$.jqplot.CategoryAxisRenderer = function(options) {
$.jqplot.LinearAxisRenderer.call(this);
// prop: sortMergedLabels
// True to sort tick labels when labels are created by merging
// x axis values from multiple series. That is, say you have
// two series like:
// > line1 = [[2006, 4], [2008, 9], [2009, 16]];
// > line2 = [[2006, 3], [2007, 7], [2008, 6]];
// If no label array is specified, tick labels will be collected
// from the x values of the series. With sortMergedLabels
// set to true, tick labels will be:
// > [2006, 2007, 2008, 2009]
// With sortMergedLabels set to false, tick labels will be:
// > [2006, 2008, 2009, 2007]
//
// Note, this property is specified on the renderOptions for the
// axes when creating a plot:
// > axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer, rendererOptions:{sortMergedLabels:true}}}
this.sortMergedLabels = false;
};
$.jqplot.CategoryAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.CategoryAxisRenderer.prototype.constructor = $.jqplot.CategoryAxisRenderer;
$.jqplot.CategoryAxisRenderer.prototype.init = function(options){
this.groups = 1;
this.groupLabels = [];
this._groupLabels = [];
this._grouped = false;
this._barsPerGroup = null;
this.reverse = false;
// prop: tickRenderer
// A class of a rendering engine for creating the ticks labels displayed on the plot,
// See <$.jqplot.AxisTickRenderer>.
// this.tickRenderer = $.jqplot.AxisTickRenderer;
// this.labelRenderer = $.jqplot.AxisLabelRenderer;
$.extend(true, this, {tickOptions:{formatString:'%d'}}, options);
var db = this._dataBounds;
// Go through all the series attached to this axis and find
// the min/max bounds for this axis.
for (var i=0; i<this._series.length; i++) {
var s = this._series[i];
if (s.groups) {
this.groups = s.groups;
}
var d = s.data;
for (var j=0; j<d.length; j++) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
if (d[j][0] < db.min || db.min == null) {
db.min = d[j][0];
}
if (d[j][0] > db.max || db.max == null) {
db.max = d[j][0];
}
}
else {
if (d[j][1] < db.min || db.min == null) {
db.min = d[j][1];
}
if (d[j][1] > db.max || db.max == null) {
db.max = d[j][1];
}
}
}
}
if (this.groupLabels.length) {
this.groups = this.groupLabels.length;
}
};
$.jqplot.CategoryAxisRenderer.prototype.createTicks = function() {
// we're are operating on an axis here
var ticks = this._ticks;
var userTicks = this.ticks;
var name = this.name;
// databounds were set on axis initialization.
var db = this._dataBounds;
var dim, interval;
var min, max;
var pos1, pos2;
var tt, i;
// if we already have ticks, use them.
if (userTicks.length) {
// adjust with blanks if we have groups
if (this.groups > 1 && !this._grouped) {
var l = userTicks.length;
var skip = parseInt(l/this.groups, 10);
var count = 0;
for (var i=skip; i<l; i+=skip) {
userTicks.splice(i+count, 0, ' ');
count++;
}
this._grouped = true;
}
this.min = 0.5;
this.max = userTicks.length + 0.5;
var range = this.max - this.min;
this.numberTicks = 2*userTicks.length + 1;
for (i=0; i<userTicks.length; i++){
tt = this.min + 2 * i * range / (this.numberTicks-1);
// need a marker before and after the tick
var t = new this.tickRenderer(this.tickOptions);
t.showLabel = false;
// t.showMark = true;
t.setTick(tt, this.name);
this._ticks.push(t);
var t = new this.tickRenderer(this.tickOptions);
t.label = userTicks[i];
// t.showLabel = true;
t.showMark = false;
t.showGridline = false;
t.setTick(tt+0.5, this.name);
this._ticks.push(t);
}
// now add the last tick at the end
var t = new this.tickRenderer(this.tickOptions);
t.showLabel = false;
// t.showMark = true;
t.setTick(tt+1, this.name);
this._ticks.push(t);
}
// we don't have any ticks yet, let's make some!
else {
if (name == 'xaxis' || name == 'x2axis') {
dim = this._plotDimensions.width;
}
else {
dim = this._plotDimensions.height;
}
// if min, max and number of ticks specified, user can't specify interval.
if (this.min != null && this.max != null && this.numberTicks != null) {
this.tickInterval = null;
}
// if max, min, and interval specified and interval won't fit, ignore interval.
if (this.min != null && this.max != null && this.tickInterval != null) {
if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) {
this.tickInterval = null;
}
}
// find out how many categories are in the lines and collect labels
var labels = [];
var numcats = 0;
var min = 0.5;
var max, val;
var isMerged = false;
for (var i=0; i<this._series.length; i++) {
var s = this._series[i];
for (var j=0; j<s.data.length; j++) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
val = s.data[j][0];
}
else {
val = s.data[j][1];
}
if ($.inArray(val, labels) == -1) {
isMerged = true;
numcats += 1;
labels.push(val);
}
}
}
if (isMerged && this.sortMergedLabels) {
if (typeof labels[0] == "string") {
labels.sort();
} else {
labels.sort(function(a,b) { return a - b; });
}
}
// keep a reference to these tick labels to use for redrawing plot (see bug #57)
this.ticks = labels;
// now bin the data values to the right lables.
for (var i=0; i<this._series.length; i++) {
var s = this._series[i];
for (var j=0; j<s.data.length; j++) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
val = s.data[j][0];
}
else {
val = s.data[j][1];
}
// for category axis, force the values into category bins.
// we should have the value in the label array now.
var idx = $.inArray(val, labels)+1;
if (this.name == 'xaxis' || this.name == 'x2axis') {
s.data[j][0] = idx;
}
else {
s.data[j][1] = idx;
}
}
}
// adjust with blanks if we have groups
if (this.groups > 1 && !this._grouped) {
var l = labels.length;
var skip = parseInt(l/this.groups, 10);
var count = 0;
for (var i=skip; i<l; i+=skip+1) {
labels[i] = ' ';
}
this._grouped = true;
}
max = numcats + 0.5;
if (this.numberTicks == null) {
this.numberTicks = 2*numcats + 1;
}
var range = max - min;
this.min = min;
this.max = max;
var track = 0;
// todo: adjust this so more ticks displayed.
var maxVisibleTicks = parseInt(3+dim/10, 10);
var skip = parseInt(numcats/maxVisibleTicks, 10);
if (this.tickInterval == null) {
this.tickInterval = range / (this.numberTicks-1);
}
// if tickInterval is specified, we will ignore any computed maximum.
for (var i=0; i<this.numberTicks; i++){
tt = this.min + i * this.tickInterval;
var t = new this.tickRenderer(this.tickOptions);
// if even tick, it isn't a category, it's a divider
if (i/2 == parseInt(i/2, 10)) {
t.showLabel = false;
t.showMark = true;
}
else {
if (skip>0 && track<skip) {
t.showLabel = false;
track += 1;
}
else {
t.showLabel = true;
track = 0;
}
t.label = t.formatter(t.formatString, labels[(i-1)/2]);
t.showMark = false;
t.showGridline = false;
}
t.setTick(tt, this.name);
this._ticks.push(t);
}
}
};
// called with scope of axis
$.jqplot.CategoryAxisRenderer.prototype.draw = function(ctx, plot) {
if (this.show) {
// populate the axis label and value properties.
// createTicks is a method on the renderer, but
// call it within the scope of the axis.
this.renderer.createTicks.call(this);
// fill a div with axes labels in the right direction.
// Need to pregenerate each axis to get its bounds and
// position it and the labels correctly on the plot.
var dim=0;
var temp;
// Added for theming.
if (this._elem) {
// this._elem.empty();
// Memory Leaks patch
this._elem.emptyForce();
}
this._elem = this._elem || $('<div class="jqplot-axis jqplot-'+this.name+'" style="position:absolute;"></div>');
if (this.name == 'xaxis' || this.name == 'x2axis') {
this._elem.width(this._plotDimensions.width);
}
else {
this._elem.height(this._plotDimensions.height);
}
// create a _label object.
this.labelOptions.axis = this.name;
this._label = new this.labelRenderer(this.labelOptions);
if (this._label.show) {
var elem = this._label.draw(ctx, plot);
elem.appendTo(this._elem);
}
var t = this._ticks;
for (var i=0; i<t.length; i++) {
var tick = t[i];
if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
var elem = tick.draw(ctx, plot);
elem.appendTo(this._elem);
}
}
this._groupLabels = [];
// now make group labels
for (var i=0; i<this.groupLabels.length; i++)
{
var elem = $('<div style="position:absolute;" class="jqplot-'+this.name+'-groupLabel"></div>');
elem.html(this.groupLabels[i]);
this._groupLabels.push(elem);
elem.appendTo(this._elem);
}
}
return this._elem;
};
// called with scope of axis
$.jqplot.CategoryAxisRenderer.prototype.set = function() {
var dim = 0;
var temp;
var w = 0;
var h = 0;
var lshow = (this._label == null) ? false : this._label.show;
if (this.show) {
var t = this._ticks;
for (var i=0; i<t.length; i++) {
var tick = t[i];
if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
temp = tick._elem.outerHeight(true);
}
else {
temp = tick._elem.outerWidth(true);
}
if (temp > dim) {
dim = temp;
}
}
}
var dim2 = 0;
for (var i=0; i<this._groupLabels.length; i++) {
var l = this._groupLabels[i];
if (this.name == 'xaxis' || this.name == 'x2axis') {
temp = l.outerHeight(true);
}
else {
temp = l.outerWidth(true);
}
if (temp > dim2) {
dim2 = temp;
}
}
if (lshow) {
w = this._label._elem.outerWidth(true);
h = this._label._elem.outerHeight(true);
}
if (this.name == 'xaxis') {
dim += dim2 + h;
this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
}
else if (this.name == 'x2axis') {
dim += dim2 + h;
this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
}
else if (this.name == 'yaxis') {
dim += dim2 + w;
this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
else {
dim += dim2 + w;
this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
this._label._elem.css('width', w+'px');
}
}
}
};
// called with scope of axis
$.jqplot.CategoryAxisRenderer.prototype.pack = function(pos, offsets) {
var ticks = this._ticks;
var max = this.max;
var min = this.min;
var offmax = offsets.max;
var offmin = offsets.min;
var lshow = (this._label == null) ? false : this._label.show;
var i;
for (var p in pos) {
this._elem.css(p, pos[p]);
}
this._offsets = offsets;
// pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
var pixellength = offmax - offmin;
var unitlength = max - min;
if (!this.reverse) {
// point to unit and unit to point conversions references to Plot DOM element top left corner.
this.u2p = function(u){
return (u - min) * pixellength / unitlength + offmin;
};
this.p2u = function(p){
return (p - offmin) * unitlength / pixellength + min;
};
if (this.name == 'xaxis' || this.name == 'x2axis'){
this.series_u2p = function(u){
return (u - min) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + min;
};
}
else {
this.series_u2p = function(u){
return (u - max) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + max;
};
}
}
else {
// point to unit and unit to point conversions references to Plot DOM element top left corner.
this.u2p = function(u){
return offmin + (max - u) * pixellength / unitlength;
};
this.p2u = function(p){
return min + (p - offmin) * unitlength / pixellength;
};
if (this.name == 'xaxis' || this.name == 'x2axis'){
this.series_u2p = function(u){
return (max - u) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + max;
};
}
else {
this.series_u2p = function(u){
return (min - u) * pixellength / unitlength;
};
this.series_p2u = function(p){
return p * unitlength / pixellength + min;
};
}
}
if (this.show) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
for (i=0; i<ticks.length; i++) {
var t = ticks[i];
if (t.show && t.showLabel) {
var shim;
if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
// will need to adjust auto positioning based on which axis this is.
var temp = (this.name == 'xaxis') ? 1 : -1;
switch (t.labelPosition) {
case 'auto':
// position at end
if (temp * t.angle < 0) {
shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
}
// position at start
else {
shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
}
break;
case 'end':
shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
break;
case 'start':
shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
break;
case 'middle':
shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
break;
default:
shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
break;
}
}
else {
shim = -t.getWidth()/2;
}
var val = this.u2p(t.value) + shim + 'px';
t._elem.css('left', val);
t.pack();
}
}
var labeledge=['bottom', 0];
if (lshow) {
var w = this._label._elem.outerWidth(true);
this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
if (this.name == 'xaxis') {
this._label._elem.css('bottom', '0px');
labeledge = ['bottom', this._label._elem.outerHeight(true)];
}
else {
this._label._elem.css('top', '0px');
labeledge = ['top', this._label._elem.outerHeight(true)];
}
this._label.pack();
}
// draw the group labels
var step = parseInt(this._ticks.length/this.groups, 10) + 1;
for (i=0; i<this._groupLabels.length; i++) {
var mid = 0;
var count = 0;
for (var j=i*step; j<(i+1)*step; j++) {
if (j >= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
if (this._ticks[j]._elem && this._ticks[j].label != " ") {
var t = this._ticks[j]._elem;
var p = t.position();
mid += p.left + t.outerWidth(true)/2;
count++;
}
}
mid = mid/count;
this._groupLabels[i].css({'left':(mid - this._groupLabels[i].outerWidth(true)/2)});
this._groupLabels[i].css(labeledge[0], labeledge[1]);
}
}
else {
for (i=0; i<ticks.length; i++) {
var t = ticks[i];
if (t.show && t.showLabel) {
var shim;
if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
// will need to adjust auto positioning based on which axis this is.
var temp = (this.name == 'yaxis') ? 1 : -1;
switch (t.labelPosition) {
case 'auto':
// position at end
case 'end':
if (temp * t.angle < 0) {
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
}
else {
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
}
break;
case 'start':
if (t.angle > 0) {
shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
}
else {
shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
}
break;
case 'middle':
// if (t.angle > 0) {
// shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
// }
// else {
// shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
// }
shim = -t.getHeight()/2;
break;
default:
shim = -t.getHeight()/2;
break;
}
}
else {
shim = -t.getHeight()/2;
}
var val = this.u2p(t.value) + shim + 'px';
t._elem.css('top', val);
t.pack();
}
}
var labeledge=['left', 0];
if (lshow) {
var h = this._label._elem.outerHeight(true);
this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
if (this.name == 'yaxis') {
this._label._elem.css('left', '0px');
labeledge = ['left', this._label._elem.outerWidth(true)];
}
else {
this._label._elem.css('right', '0px');
labeledge = ['right', this._label._elem.outerWidth(true)];
}
this._label.pack();
}
// draw the group labels, position top here, do left after label position.
var step = parseInt(this._ticks.length/this.groups, 10) + 1; // step is one more than before as we don't want to have overlaps in loops
for (i=0; i<this._groupLabels.length; i++) {
var mid = 0;
var count = 0;
for (var j=i*step; j<(i+1)*step; j++) { // j must never reach (i+1)*step as we don't want to have overlap between loops
if (j >= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one.
if (this._ticks[j]._elem && this._ticks[j].label != " ") {
var t = this._ticks[j]._elem;
var p = t.position();
mid += p.top + t.outerHeight()/2;
count++;
}
}
mid = mid/count;
this._groupLabels[i].css({'top':mid - this._groupLabels[i].outerHeight()/2});
this._groupLabels[i].css(labeledge[0], labeledge[1]);
}
}
}
};
})(jQuery);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,741 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.DateAxisRenderer
* A plugin for a jqPlot to render an axis as a series of date values.
* This renderer has no options beyond those supplied by the <Axis> class.
* It supplies its own tick formatter, so the tickOptions.formatter option
* should not be overridden.
*
* Thanks to Ken Synder for his enhanced Date instance methods which are
* included with this code <http://kendsnyder.com/sandbox/date/>.
*
* To use this renderer, include the plugin in your source
* > <script type="text/javascript" language="javascript" src="plugins/jqplot.dateAxisRenderer.js"></script>
*
* and supply the appropriate options to your plot
*
* > {axes:{xaxis:{renderer:$.jqplot.DateAxisRenderer}}}
*
* Dates can be passed into the axis in almost any recognizable value and
* will be parsed. They will be rendered on the axis in the format
* specified by tickOptions.formatString. e.g. tickOptions.formatString = '%Y-%m-%d'.
*
* Accecptable format codes
* are:
*
* > Code Result Description
* > == Years ==
* > %Y 2008 Four-digit year
* > %y 08 Two-digit year
* > == Months ==
* > %m 09 Two-digit month
* > %#m 9 One or two-digit month
* > %B September Full month name
* > %b Sep Abbreviated month name
* > == Days ==
* > %d 05 Two-digit day of month
* > %#d 5 One or two-digit day of month
* > %e 5 One or two-digit day of month
* > %A Sunday Full name of the day of the week
* > %a Sun Abbreviated name of the day of the week
* > %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
* > %o th The ordinal suffix string following the day of the month
* > == Hours ==
* > %H 23 Hours in 24-hour format (two digits)
* > %#H 3 Hours in 24-hour integer format (one or two digits)
* > %I 11 Hours in 12-hour format (two digits)
* > %#I 3 Hours in 12-hour integer format (one or two digits)
* > %p PM AM or PM
* > == Minutes ==
* > %M 09 Minutes (two digits)
* > %#M 9 Minutes (one or two digits)
* > == Seconds ==
* > %S 02 Seconds (two digits)
* > %#S 2 Seconds (one or two digits)
* > %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
* > == Milliseconds ==
* > %N 008 Milliseconds (three digits)
* > %#N 8 Milliseconds (one to three digits)
* > == Timezone ==
* > %O 360 difference in minutes between local time and GMT
* > %Z Mountain Standard Time Name of timezone as reported by browser
* > %G -06:00 Hours and minutes between GMT
* > == Shortcuts ==
* > %F 2008-03-26 %Y-%m-%d
* > %T 05:06:30 %H:%M:%S
* > %X 05:06:30 %H:%M:%S
* > %x 03/26/08 %m/%d/%y
* > %D 03/26/08 %m/%d/%y
* > %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
* > %v 3-Sep-2008 %e-%b-%Y
* > %R 15:31 %H:%M
* > %r 3:31:00 PM %I:%M:%S %p
* > == Characters ==
* > %n \n Newline
* > %t \t Tab
* > %% % Percent Symbol
*/
$.jqplot.DateAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
this.date = new $.jsDate();
};
var second = 1000;
var minute = 60 * second;
var hour = 60 * minute;
var day = 24 * hour;
var week = 7 * day;
// these are less definitive
var month = 30.4368499 * day;
var year = 365.242199 * day;
var daysInMonths = [31,28,31,30,31,30,31,30,31,30,31,30];
// array of consistent nice intervals. Longer intervals
// will depend on days in month, days in year, etc.
var niceFormatStrings = ['%M:%S.%#N', '%M:%S.%#N', '%M:%S.%#N', '%M:%S', '%M:%S', '%M:%S', '%M:%S', '%H:%M:%S', '%H:%M:%S', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%H:%M', '%a %H:%M', '%a %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%b %e %H:%M', '%v', '%v', '%v', '%v', '%v', '%v', '%v'];
var niceIntervals = [0.1*second, 0.2*second, 0.5*second, second, 2*second, 5*second, 10*second, 15*second, 30*second, minute, 2*minute, 5*minute, 10*minute, 15*minute, 30*minute, hour, 2*hour, 4*hour, 6*hour, 8*hour, 12*hour, day, 2*day, 3*day, 4*day, 5*day, week, 2*week];
var niceMonthlyIntervals = [];
function bestDateInterval(min, max, titarget) {
// iterate through niceIntervals to find one closest to titarget
var badness = Number.MAX_VALUE;
var temp, bestTi, bestfmt;
for (var i=0, l=niceIntervals.length; i < l; i++) {
temp = Math.abs(titarget - niceIntervals[i]);
if (temp < badness) {
badness = temp;
bestTi = niceIntervals[i];
bestfmt = niceFormatStrings[i];
}
}
return [bestTi, bestfmt];
}
$.jqplot.DateAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.DateAxisRenderer.prototype.constructor = $.jqplot.DateAxisRenderer;
$.jqplot.DateTickFormatter = function(format, val) {
if (!format) {
format = '%Y/%m/%d';
}
return $.jsDate.strftime(val, format);
};
$.jqplot.DateAxisRenderer.prototype.init = function(options){
// prop: tickRenderer
// A class of a rendering engine for creating the ticks labels displayed on the plot,
// See <$.jqplot.AxisTickRenderer>.
// this.tickRenderer = $.jqplot.AxisTickRenderer;
// this.labelRenderer = $.jqplot.AxisLabelRenderer;
this.tickOptions.formatter = $.jqplot.DateTickFormatter;
// prop: tickInset
// Controls the amount to inset the first and last ticks from
// the edges of the grid, in multiples of the tick interval.
// 0 is no inset, 0.5 is one half a tick interval, 1 is a full
// tick interval, etc.
this.tickInset = 0;
// prop: drawBaseline
// True to draw the axis baseline.
this.drawBaseline = true;
// prop: baselineWidth
// width of the baseline in pixels.
this.baselineWidth = null;
// prop: baselineColor
// CSS color spec for the baseline.
this.baselineColor = null;
this.daTickInterval = null;
this._daTickInterval = null;
$.extend(true, this, options);
var db = this._dataBounds,
stats,
sum,
s,
d,
pd,
sd,
intv;
// Go through all the series attached to this axis and find
// the min/max bounds for this axis.
for (var i=0; i<this._series.length; i++) {
stats = {intervals:[], frequencies:{}, sortedIntervals:[], min:null, max:null, mean:null};
sum = 0;
s = this._series[i];
d = s.data;
pd = s._plotData;
sd = s._stackData;
intv = 0;
for (var j=0; j<d.length; j++) {
if (this.name == 'xaxis' || this.name == 'x2axis') {
d[j][0] = new $.jsDate(d[j][0]).getTime();
pd[j][0] = new $.jsDate(d[j][0]).getTime();
sd[j][0] = new $.jsDate(d[j][0]).getTime();
if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
db.min = d[j][0];
}
if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
db.max = d[j][0];
}
if (j>0) {
intv = Math.abs(d[j][0] - d[j-1][0]);
stats.intervals.push(intv);
if (stats.frequencies.hasOwnProperty(intv)) {
stats.frequencies[intv] += 1;
}
else {
stats.frequencies[intv] = 1;
}
}
sum += intv;
}
else {
d[j][1] = new $.jsDate(d[j][1]).getTime();
pd[j][1] = new $.jsDate(d[j][1]).getTime();
sd[j][1] = new $.jsDate(d[j][1]).getTime();
if ((d[j][1] != null && d[j][1] < db.min) || db.min == null) {
db.min = d[j][1];
}
if ((d[j][1] != null && d[j][1] > db.max) || db.max == null) {
db.max = d[j][1];
}
if (j>0) {
intv = Math.abs(d[j][1] - d[j-1][1]);
stats.intervals.push(intv);
if (stats.frequencies.hasOwnProperty(intv)) {
stats.frequencies[intv] += 1;
}
else {
stats.frequencies[intv] = 1;
}
}
}
sum += intv;
}
if (s.renderer.bands) {
if (s.renderer.bands.hiData.length) {
var bd = s.renderer.bands.hiData;
for (var j=0, l=bd.length; j < l; j++) {
if (this.name === 'xaxis' || this.name === 'x2axis') {
bd[j][0] = new $.jsDate(bd[j][0]).getTime();
if ((bd[j][0] != null && bd[j][0] > db.max) || db.max == null) {
db.max = bd[j][0];
}
}
else {
bd[j][1] = new $.jsDate(bd[j][1]).getTime();
if ((bd[j][1] != null && bd[j][1] > db.max) || db.max == null) {
db.max = bd[j][1];
}
}
}
}
if (s.renderer.bands.lowData.length) {
var bd = s.renderer.bands.lowData;
for (var j=0, l=bd.length; j < l; j++) {
if (this.name === 'xaxis' || this.name === 'x2axis') {
bd[j][0] = new $.jsDate(bd[j][0]).getTime();
if ((bd[j][0] != null && bd[j][0] < db.min) || db.min == null) {
db.min = bd[j][0];
}
}
else {
bd[j][1] = new $.jsDate(bd[j][1]).getTime();
if ((bd[j][1] != null && bd[j][1] < db.min) || db.min == null) {
db.min = bd[j][1];
}
}
}
}
}
var tempf = 0,
tempn=0;
for (var n in stats.frequencies) {
stats.sortedIntervals.push({interval:n, frequency:stats.frequencies[n]});
}
stats.sortedIntervals.sort(function(a, b){
return b.frequency - a.frequency;
});
stats.min = $.jqplot.arrayMin(stats.intervals);
stats.max = $.jqplot.arrayMax(stats.intervals);
stats.mean = sum/d.length;
this._intervalStats.push(stats);
stats = sum = s = d = pd = sd = null;
}
db = null;
};
// called with scope of an axis
$.jqplot.DateAxisRenderer.prototype.reset = function() {
this.min = this._options.min;
this.max = this._options.max;
this.tickInterval = this._options.tickInterval;
this.numberTicks = this._options.numberTicks;
this._autoFormatString = '';
if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
this.tickOptions.formatString = '';
}
this.daTickInterval = this._daTickInterval;
// this._ticks = this.__ticks;
};
$.jqplot.DateAxisRenderer.prototype.createTicks = function(plot) {
// we're are operating on an axis here
var ticks = this._ticks;
var userTicks = this.ticks;
var name = this.name;
// databounds were set on axis initialization.
var db = this._dataBounds;
var iv = this._intervalStats;
var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
var interval;
var min, max;
var pos1, pos2;
var tt, i;
var threshold = 30;
var insetMult = 1;
var daTickInterval = null;
// if user specified a tick interval, convert to usable.
if (this.tickInterval != null)
{
// if interval is a number or can be converted to one, use it.
// Assume it is in SECONDS!!!
if (Number(this.tickInterval)) {
daTickInterval = [Number(this.tickInterval), 'seconds'];
}
// else, parse out something we can build from.
else if (typeof this.tickInterval == "string") {
var parts = this.tickInterval.split(' ');
if (parts.length == 1) {
daTickInterval = [1, parts[0]];
}
else if (parts.length == 2) {
daTickInterval = [parts[0], parts[1]];
}
}
}
var tickInterval = this.tickInterval;
// if we already have ticks, use them.
// ticks must be in order of increasing value.
min = new $.jsDate((this.min != null) ? this.min : db.min).getTime();
max = new $.jsDate((this.max != null) ? this.max : db.max).getTime();
// see if we're zooming. if we are, don't use the min and max we're given,
// but compute some nice ones. They will be reset later.
var cursor = plot.plugins.cursor;
if (cursor && cursor._zoom && cursor._zoom.zooming) {
this.min = null;
this.max = null;
}
var range = max - min;
if (this.tickOptions == null || !this.tickOptions.formatString) {
this._overrideFormatString = true;
}
if (userTicks.length) {
// ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
for (i=0; i<userTicks.length; i++){
var ut = userTicks[i];
var t = new this.tickRenderer(this.tickOptions);
if (ut.constructor == Array) {
t.value = new $.jsDate(ut[0]).getTime();
t.label = ut[1];
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
t.setTick(t.value, this.name);
this._ticks.push(t);
}
else {
t.value = new $.jsDate(ut).getTime();
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
t.setTick(t.value, this.name);
this._ticks.push(t);
}
}
this.numberTicks = userTicks.length;
this.min = this._ticks[0].value;
this.max = this._ticks[this.numberTicks-1].value;
this.daTickInterval = [(this.max - this.min) / (this.numberTicks - 1)/1000, 'seconds'];
}
////////
// We don't have any ticks yet, let's make some!
////////
// special case when there is only one point, make three tick marks to center the point
else if (this.min == null && this.max == null && db.min == db.max)
{
var onePointOpts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
var delta = 300000;
this.min = db.min - delta;
this.max = db.max + delta;
this.numberTicks = 3;
for(var i=this.min;i<=this.max;i+= delta)
{
onePointOpts.value = i;
var t = new this.tickRenderer(onePointOpts);
if (this._overrideFormatString && this._autoFormatString != '') {
t.formatString = this._autoFormatString;
}
t.showLabel = false;
t.showMark = false;
this._ticks.push(t);
}
if(this.showTicks) {
this._ticks[1].showLabel = true;
}
if(this.showTickMarks) {
this._ticks[1].showTickMarks = true;
}
}
// if user specified min and max are null, we set those to make best ticks.
else if (this.min == null && this.max == null) {
var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
// want to find a nice interval
var nttarget,
titarget;
// if no tickInterval or numberTicks options specified, make a good guess.
if (!this.tickInterval && !this.numberTicks) {
var tdim = Math.max(dim, threshold+1);
// how many ticks to put on the axis?
// date labels tend to be long. If ticks not rotated,
// don't use too many and have a high spacing factor.
// If we are rotating ticks, use a lower factor.
var spacingFactor = 115;
if (this.tickRenderer === $.jqplot.CanvasAxisTickRenderer && this.tickOptions.angle) {
spacingFactor = 115 - 40 * Math.abs(Math.sin(this.tickOptions.angle/180*Math.PI));
}
nttarget = Math.ceil((tdim-threshold)/spacingFactor + 1);
titarget = (max - min) / (nttarget - 1);
}
// If tickInterval is specified, we'll try to honor it.
// Not guaranteed to get this interval, but we'll get as close as
// we can.
// tickInterval will be used before numberTicks, that is if
// both are specified, numberTicks will be ignored.
else if (this.tickInterval) {
titarget = new $.jsDate(0).add(daTickInterval[0], daTickInterval[1]).getTime();
}
// if numberTicks specified, try to honor it.
// Not guaranteed, but will try to get close.
else if (this.numberTicks) {
nttarget = this.numberTicks;
titarget = (max - min) / (nttarget - 1);
}
// If we can use an interval of 2 weeks or less, pick best one
if (titarget <= 19*day) {
var ret = bestDateInterval(min, max, titarget);
var tempti = ret[0];
this._autoFormatString = ret[1];
min = new $.jsDate(min);
min = Math.floor((min.getTime() - min.getUtcOffset())/tempti) * tempti + min.getUtcOffset();
nttarget = Math.ceil((max - min) / tempti) + 1;
this.min = min;
this.max = min + (nttarget - 1) * tempti;
// if max is less than max, add an interval
if (this.max < max) {
this.max += tempti;
nttarget += 1;
}
this.tickInterval = tempti;
this.numberTicks = nttarget;
for (var i=0; i<nttarget; i++) {
opts.value = this.min + i * tempti;
t = new this.tickRenderer(opts);
if (this._overrideFormatString && this._autoFormatString != '') {
t.formatString = this._autoFormatString;
}
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
this._ticks.push(t);
}
insetMult = this.tickInterval;
}
// should we use a monthly interval?
else if (titarget <= 9 * month) {
this._autoFormatString = '%v';
// how many months in an interval?
var intv = Math.round(titarget/month);
if (intv < 1) {
intv = 1;
}
else if (intv > 6) {
intv = 6;
}
// figure out the starting month and ending month.
var mstart = new $.jsDate(min).setDate(1).setHours(0,0,0,0);
// See if max ends exactly on a month
var tempmend = new $.jsDate(max);
var mend = new $.jsDate(max).setDate(1).setHours(0,0,0,0);
if (tempmend.getTime() !== mend.getTime()) {
mend = mend.add(1, 'month');
}
var nmonths = mend.diff(mstart, 'month');
nttarget = Math.ceil(nmonths/intv) + 1;
this.min = mstart.getTime();
this.max = mstart.clone().add((nttarget - 1) * intv, 'month').getTime();
this.numberTicks = nttarget;
for (var i=0; i<nttarget; i++) {
if (i === 0) {
opts.value = mstart.getTime();
}
else {
opts.value = mstart.add(intv, 'month').getTime();
}
t = new this.tickRenderer(opts);
if (this._overrideFormatString && this._autoFormatString != '') {
t.formatString = this._autoFormatString;
}
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
this._ticks.push(t);
}
insetMult = intv * month;
}
// use yearly intervals
else {
this._autoFormatString = '%v';
// how many years in an interval?
var intv = Math.round(titarget/year);
if (intv < 1) {
intv = 1;
}
// figure out the starting and ending years.
var mstart = new $.jsDate(min).setMonth(0, 1).setHours(0,0,0,0);
var mend = new $.jsDate(max).add(1, 'year').setMonth(0, 1).setHours(0,0,0,0);
var nyears = mend.diff(mstart, 'year');
nttarget = Math.ceil(nyears/intv) + 1;
this.min = mstart.getTime();
this.max = mstart.clone().add((nttarget - 1) * intv, 'year').getTime();
this.numberTicks = nttarget;
for (var i=0; i<nttarget; i++) {
if (i === 0) {
opts.value = mstart.getTime();
}
else {
opts.value = mstart.add(intv, 'year').getTime();
}
t = new this.tickRenderer(opts);
if (this._overrideFormatString && this._autoFormatString != '') {
t.formatString = this._autoFormatString;
}
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
this._ticks.push(t);
}
insetMult = intv * year;
}
}
////////
// Some option(s) specified, work around that.
////////
else {
if (name == 'xaxis' || name == 'x2axis') {
dim = this._plotDimensions.width;
}
else {
dim = this._plotDimensions.height;
}
// if min, max and number of ticks specified, user can't specify interval.
if (this.min != null && this.max != null && this.numberTicks != null) {
this.tickInterval = null;
}
if (this.tickInterval != null && daTickInterval != null) {
this.daTickInterval = daTickInterval;
}
// if min and max are same, space them out a bit
if (min == max) {
var adj = 24*60*60*500; // 1/2 day
min -= adj;
max += adj;
}
range = max - min;
var optNumTicks = 2 + parseInt(Math.max(0, dim-100)/100, 10);
var rmin, rmax;
rmin = (this.min != null) ? new $.jsDate(this.min).getTime() : min - range/2*(this.padMin - 1);
rmax = (this.max != null) ? new $.jsDate(this.max).getTime() : max + range/2*(this.padMax - 1);
this.min = rmin;
this.max = rmax;
range = this.max - this.min;
if (this.numberTicks == null){
// if tickInterval is specified by user, we will ignore computed maximum.
// max will be equal or greater to fit even # of ticks.
if (this.daTickInterval != null) {
var nc = new $.jsDate(this.max).diff(this.min, this.daTickInterval[1], true);
this.numberTicks = Math.ceil(nc/this.daTickInterval[0]) +1;
// this.max = new $.jsDate(this.min).add(this.numberTicks-1, this.daTickInterval[1]).getTime();
this.max = new $.jsDate(this.min).add((this.numberTicks-1) * this.daTickInterval[0], this.daTickInterval[1]).getTime();
}
else if (dim > 200) {
this.numberTicks = parseInt(3+(dim-200)/100, 10);
}
else {
this.numberTicks = 2;
}
}
insetMult = range / (this.numberTicks-1)/1000;
if (this.daTickInterval == null) {
this.daTickInterval = [insetMult, 'seconds'];
}
for (var i=0; i<this.numberTicks; i++){
var min = new $.jsDate(this.min);
tt = min.add(i*this.daTickInterval[0], this.daTickInterval[1]).getTime();
var t = new this.tickRenderer(this.tickOptions);
// var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
if (!this.showTicks) {
t.showLabel = false;
t.showMark = false;
}
else if (!this.showTickMarks) {
t.showMark = false;
}
t.setTick(tt, this.name);
this._ticks.push(t);
}
}
if (this.tickInset) {
this.min = this.min - this.tickInset * insetMult;
this.max = this.max + this.tickInset * insetMult;
}
if (this._daTickInterval == null) {
this._daTickInterval = this.daTickInterval;
}
ticks = null;
};
})(jQuery);

View File

@ -0,0 +1,465 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
/**
* Class: $.jqplot.Highlighter
* Plugin which will highlight data points when they are moused over.
*
* To use this plugin, include the js
* file in your source:
*
* > <script type="text/javascript" src="plugins/jqplot.highlighter.js"></script>
*
* A tooltip providing information about the data point is enabled by default.
* To disable the tooltip, set "showTooltip" to false.
*
* You can control what data is displayed in the tooltip with various
* options. The "tooltipAxes" option controls whether the x, y or both
* data values are displayed.
*
* Some chart types (e.g. hi-low-close) have more than one y value per
* data point. To display the additional values in the tooltip, set the
* "yvalues" option to the desired number of y values present (3 for a hlc chart).
*
* By default, data values will be formatted with the same formatting
* specifiers as used to format the axis ticks. A custom format code
* can be supplied with the tooltipFormatString option. This will apply
* to all values in the tooltip.
*
* For more complete control, the "formatString" option can be set. This
* Allows conplete control over tooltip formatting. Values are passed to
* the format string in an order determined by the "tooltipAxes" and "yvalues"
* options. So, if you have a hi-low-close chart and you just want to display
* the hi-low-close values in the tooltip, you could set a formatString like:
*
* > highlighter: {
* > tooltipAxes: 'y',
* > yvalues: 3,
* > formatString:'<table class="jqplot-highlighter">
* > <tr><td>hi:</td><td>%s</td></tr>
* > <tr><td>low:</td><td>%s</td></tr>
* > <tr><td>close:</td><td>%s</td></tr></table>'
* > }
*
*/
$.jqplot.Highlighter = function(options) {
// Group: Properties
//
//prop: show
// true to show the highlight.
this.show = $.jqplot.config.enablePlugins;
// prop: markerRenderer
// Renderer used to draw the marker of the highlighted point.
// Renderer will assimilate attributes from the data point being highlighted,
// so no attributes need set on the renderer directly.
// Default is to turn off shadow drawing on the highlighted point.
this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
// prop: showMarker
// true to show the marker
this.showMarker = true;
// prop: lineWidthAdjust
// Pixels to add to the lineWidth of the highlight.
this.lineWidthAdjust = 2.5;
// prop: sizeAdjust
// Pixels to add to the overall size of the highlight.
this.sizeAdjust = 5;
// prop: showTooltip
// Show a tooltip with data point values.
this.showTooltip = true;
// prop: tooltipLocation
// Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
this.tooltipLocation = 'nw';
// prop: fadeTooltip
// true = fade in/out tooltip, flase = show/hide tooltip
this.fadeTooltip = true;
// prop: tooltipFadeSpeed
// 'slow', 'def', 'fast', or number of milliseconds.
this.tooltipFadeSpeed = "fast";
// prop: tooltipOffset
// Pixel offset of tooltip from the highlight.
this.tooltipOffset = 2;
// prop: tooltipAxes
// Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
// 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
this.tooltipAxes = 'both';
// prop; tooltipSeparator
// String to use to separate x and y axes in tooltip.
this.tooltipSeparator = ', ';
// prop; tooltipContentEditor
// Function used to edit/augment/replace the formatted tooltip contents.
// Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
// where str is the generated tooltip html and seriesIndex and pointIndex identify
// the data point being highlighted. Should return the html for the tooltip contents.
this.tooltipContentEditor = null;
// prop: useAxesFormatters
// Use the x and y axes formatters to format the text in the tooltip.
this.useAxesFormatters = true;
// prop: tooltipFormatString
// sprintf format string for the tooltip.
// Uses Ash Searle's javascript sprintf implementation
// found here: http://hexmen.com/blog/2007/03/printf-sprintf/
// See http://perldoc.perl.org/functions/sprintf.html for reference.
// Additional "p" and "P" format specifiers added by Chris Leonello.
this.tooltipFormatString = '%.5P';
// prop: formatString
// alternative to tooltipFormatString
// will format the whole tooltip text, populating with x, y values as
// indicated by tooltipAxes option. So, you could have a tooltip like:
// 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
// If useAxesFormatters is true, values will be formatted according to
// Axes formatters and you can populate your tooltip string with
// %s placeholders.
this.formatString = null;
// prop: yvalues
// Number of y values to expect in the data point array.
// Typically this is 1. Certain plots, like OHLC, will
// have more y values in each data point array.
this.yvalues = 1;
// prop: bringSeriesToFront
// This option requires jQuery 1.4+
// True to bring the series of the highlighted point to the front
// of other series.
this.bringSeriesToFront = false;
this._tooltipElem;
this.isHighlighting = false;
this.currentNeighbor = null;
$.extend(true, this, options);
};
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
// axis.renderer.tickrenderer.formatter
// called with scope of plot
$.jqplot.Highlighter.init = function (target, data, opts){
var options = opts || {};
// add a highlighter attribute to the plot
this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
};
// called within scope of series
$.jqplot.Highlighter.parseOptions = function (defaults, options) {
// Add a showHighlight option to the series
// and set it to true by default.
this.showHighlight = true;
};
// called within context of plot
// create a canvas which we can draw on.
// insert it before the eventCanvas, so eventCanvas will still capture events.
$.jqplot.Highlighter.postPlotDraw = function() {
// Memory Leaks patch
if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
this.plugins.highlighter.highlightCanvas.resetCanvas();
this.plugins.highlighter.highlightCanvas = null;
}
if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
this.plugins.highlighter._tooltipElem.emptyForce();
this.plugins.highlighter._tooltipElem = null;
}
this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
this.plugins.highlighter.highlightCanvas.setContext();
var elem = document.createElement('div');
this.plugins.highlighter._tooltipElem = $(elem);
elem = null;
this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
};
$.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
$.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
$.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
function draw(plot, neighbor) {
var hl = plot.plugins.highlighter;
var s = plot.series[neighbor.seriesIndex];
var smr = s.markerRenderer;
var mr = hl.markerRenderer;
mr.style = smr.style;
mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
mr.size = smr.size + hl.sizeAdjust;
var rgba = $.jqplot.getColorComponents(smr.color);
var newrgb = [rgba[0], rgba[1], rgba[2]];
var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
mr.init();
mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx);
}
function showTooltip(plot, series, neighbor) {
// neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
// gridData should be x,y pixel coords on the grid.
// add the plot._gridPadding to that to get x,y in the target.
var hl = plot.plugins.highlighter;
var elem = hl._tooltipElem;
var serieshl = series.highlighter || {};
var opts = $.extend(true, {}, hl, serieshl);
if (opts.useAxesFormatters) {
var xf = series._xaxis._ticks[0].formatter;
var yf = series._yaxis._ticks[0].formatter;
var xfstr = series._xaxis._ticks[0].formatString;
var yfstr = series._yaxis._ticks[0].formatString;
var str;
var xstr = xf(xfstr, neighbor.data[0]);
var ystrs = [];
for (var i=1; i<opts.yvalues+1; i++) {
ystrs.push(yf(yfstr, neighbor.data[i]));
}
if (typeof opts.formatString === 'string') {
switch (opts.tooltipAxes) {
case 'both':
case 'xy':
ystrs.unshift(xstr);
ystrs.unshift(opts.formatString);
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
break;
case 'yx':
ystrs.push(xstr);
ystrs.unshift(opts.formatString);
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
break;
case 'x':
str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString, xstr]);
break;
case 'y':
ystrs.unshift(opts.formatString);
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
break;
default: // same as xy
ystrs.unshift(xstr);
ystrs.unshift(opts.formatString);
str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
break;
}
}
else {
switch (opts.tooltipAxes) {
case 'both':
case 'xy':
str = xstr;
for (var i=0; i<ystrs.length; i++) {
str += opts.tooltipSeparator + ystrs[i];
}
break;
case 'yx':
str = '';
for (var i=0; i<ystrs.length; i++) {
str += ystrs[i] + opts.tooltipSeparator;
}
str += xstr;
break;
case 'x':
str = xstr;
break;
case 'y':
str = ystrs.join(opts.tooltipSeparator);
break;
default: // same as 'xy'
str = xstr;
for (var i=0; i<ystrs.length; i++) {
str += opts.tooltipSeparator + ystrs[i];
}
break;
}
}
}
else {
var str;
if (typeof opts.formatString === 'string') {
str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString].concat(neighbor.data));
}
else {
if (opts.tooltipAxes == 'both' || opts.tooltipAxes == 'xy') {
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
}
else if (opts.tooltipAxes == 'yx') {
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
}
else if (opts.tooltipAxes == 'x') {
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
}
else if (opts.tooltipAxes == 'y') {
str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
}
}
}
if ($.isFunction(opts.tooltipContentEditor)) {
// args str, seriesIndex, pointIndex are essential so the hook can look up
// extra data for the point.
str = opts.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot);
}
elem.html(str);
var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]};
var ms = 0;
var fact = 0.707;
if (series.markerRenderer.show == true) {
ms = (series.markerRenderer.size + opts.sizeAdjust)/2;
}
var loc = locations;
if (series.fillToZero && series.fill && neighbor.data[1] < 0) {
loc = oppositeLocations;
}
switch (loc[locationIndicies[opts.tooltipLocation]]) {
case 'nw':
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
break;
case 'n':
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - ms;
break;
case 'ne':
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
break;
case 'e':
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + ms;
var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
break;
case 'se':
var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
break;
case 's':
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + ms;
break;
case 'sw':
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
break;
case 'w':
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - ms;
var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
break;
default: // same as 'nw'
var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
break;
}
elem.css('left', x);
elem.css('top', y);
if (opts.fadeTooltip) {
// Fix for stacked up animations. Thnanks Trevor!
elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
}
else {
elem.show();
}
elem = null;
}
function handleMove(ev, gridpos, datapos, neighbor, plot) {
var hl = plot.plugins.highlighter;
var c = plot.plugins.cursor;
if (hl.show) {
if (neighbor == null && hl.isHighlighting) {
var evt = jQuery.Event('jqplotHighlighterUnhighlight');
plot.target.trigger(evt);
var ctx = hl.highlightCanvas._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (hl.fadeTooltip) {
hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed);
}
else {
hl._tooltipElem.hide();
}
if (hl.bringSeriesToFront) {
plot.restorePreviousSeriesOrder();
}
hl.isHighlighting = false;
hl.currentNeighbor = null;
ctx = null;
}
else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) {
var evt = jQuery.Event('jqplotHighlighterHighlight');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data, plot];
plot.target.trigger(evt, ins);
hl.isHighlighting = true;
hl.currentNeighbor = neighbor;
if (hl.showMarker) {
draw(plot, neighbor);
}
if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
}
if (hl.bringSeriesToFront) {
plot.moveSeriesToFront(neighbor.seriesIndex);
}
}
// check to see if we're highlighting the wrong point.
else if (neighbor != null && hl.isHighlighting && hl.currentNeighbor != neighbor) {
// highlighting the wrong point.
// if new series allows highlighting, highlight new point.
if (plot.series[neighbor.seriesIndex].showHighlight) {
var ctx = hl.highlightCanvas._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
hl.isHighlighting = true;
hl.currentNeighbor = neighbor;
if (hl.showMarker) {
draw(plot, neighbor);
}
if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
}
if (hl.bringSeriesToFront) {
plot.moveSeriesToFront(neighbor.seriesIndex);
}
}
}
}
}
})(jQuery);

View File

@ -0,0 +1,904 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.PieRenderer
* Plugin renderer to draw a pie chart.
* x values, if present, will be used as slice labels.
* y values give slice size.
*
* To use this renderer, you need to include the
* pie renderer plugin, for example:
*
* > <script type="text/javascript" src="plugins/jqplot.pieRenderer.js"></script>
*
* Properties described here are passed into the $.jqplot function
* as options on the series renderer. For example:
*
* > plot2 = $.jqplot('chart2', [s1, s2], {
* > seriesDefaults: {
* > renderer:$.jqplot.PieRenderer,
* > rendererOptions:{
* > sliceMargin: 2,
* > startAngle: -90
* > }
* > }
* > });
*
* A pie plot will trigger events on the plot target
* according to user interaction. All events return the event object,
* the series index, the point (slice) index, and the point data for
* the appropriate slice.
*
* 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
* 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
* if highlighting is enabled.
* 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
* a highlighted slice.
* 'jqplotDataClick' - triggered when the user clicks on a slice.
* 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
* the "captureRightClick" option is set to true on the plot.
*/
$.jqplot.PieRenderer = function(){
$.jqplot.LineRenderer.call(this);
};
$.jqplot.PieRenderer.prototype = new $.jqplot.LineRenderer();
$.jqplot.PieRenderer.prototype.constructor = $.jqplot.PieRenderer;
// called with scope of a series
$.jqplot.PieRenderer.prototype.init = function(options, plot) {
// Group: Properties
//
// prop: diameter
// Outer diameter of the pie, auto computed by default
this.diameter = null;
// prop: padding
// padding between the pie and plot edges, legend, etc.
this.padding = 20;
// prop: sliceMargin
// angular spacing between pie slices in degrees.
this.sliceMargin = 0;
// prop: fill
// true or false, whether to fil the slices.
this.fill = true;
// prop: shadowOffset
// offset of the shadow from the slice and offset of
// each succesive stroke of the shadow from the last.
this.shadowOffset = 2;
// prop: shadowAlpha
// transparency of the shadow (0 = transparent, 1 = opaque)
this.shadowAlpha = 0.07;
// prop: shadowDepth
// number of strokes to apply to the shadow,
// each stroke offset shadowOffset from the last.
this.shadowDepth = 5;
// prop: highlightMouseOver
// True to highlight slice when moused over.
// This must be false to enable highlightMouseDown to highlight when clicking on a slice.
this.highlightMouseOver = true;
// prop: highlightMouseDown
// True to highlight when a mouse button is pressed over a slice.
// This will be disabled if highlightMouseOver is true.
this.highlightMouseDown = false;
// prop: highlightColors
// an array of colors to use when highlighting a slice.
this.highlightColors = [];
// prop: dataLabels
// Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
// Defaults to percentage of each pie slice.
this.dataLabels = 'percent';
// prop: showDataLabels
// true to show data labels on slices.
this.showDataLabels = false;
// prop: dataLabelFormatString
// Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
this.dataLabelFormatString = null;
// prop: dataLabelThreshold
// Threshhold in percentage (0-100) of pie area, below which no label will be displayed.
// This applies to all label types, not just to percentage labels.
this.dataLabelThreshold = 3;
// prop: dataLabelPositionFactor
// A Multiplier (0-1) of the pie radius which controls position of label on slice.
// Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
this.dataLabelPositionFactor = 0.52;
// prop: dataLabelNudge
// Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
this.dataLabelNudge = 2;
// prop: dataLabelCenterOn
// True to center the data label at its position.
// False to set the inside facing edge of the label at its position.
this.dataLabelCenterOn = true;
// prop: startAngle
// Angle to start drawing pie in degrees.
// According to orientation of canvas coordinate system:
// 0 = on the positive x axis
// -90 = on the positive y axis.
// 90 = on the negaive y axis.
// 180 or - 180 = on the negative x axis.
this.startAngle = 0;
this.tickRenderer = $.jqplot.PieTickRenderer;
// Used as check for conditions where pie shouldn't be drawn.
this._drawData = true;
this._type = 'pie';
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
if (options.highlightMouseDown && options.highlightMouseOver == null) {
options.highlightMouseOver = false;
}
$.extend(true, this, options);
if (this.sliceMargin < 0) {
this.sliceMargin = 0;
}
this._diameter = null;
this._radius = null;
// array of [start,end] angles arrays, one for each slice. In radians.
this._sliceAngles = [];
// index of the currenty highlighted point, if any
this._highlightedPoint = null;
// set highlight colors if none provided
if (this.highlightColors.length == 0) {
for (var i=0; i<this.seriesColors.length; i++){
var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
var newrgb = [rgba[0], rgba[1], rgba[2]];
var sum = newrgb[0] + newrgb[1] + newrgb[2];
for (var j=0; j<3; j++) {
// when darkening, lowest color component can be is 60.
newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
newrgb[j] = parseInt(newrgb[j], 10);
}
this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
}
}
this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors);
plot.postParseOptionsHooks.addOnce(postParseOptions);
plot.postInitHooks.addOnce(postInit);
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
plot.postDrawHooks.addOnce(postPlotDraw);
};
$.jqplot.PieRenderer.prototype.setGridData = function(plot) {
// set gridData property. This will hold angle in radians of each data point.
var stack = [];
var td = [];
var sa = this.startAngle/180*Math.PI;
var tot = 0;
// don't know if we have any valid data yet, so set plot to not draw.
this._drawData = false;
for (var i=0; i<this.data.length; i++){
if (this.data[i][1] != 0) {
// we have data, O.K. to draw.
this._drawData = true;
}
stack.push(this.data[i][1]);
td.push([this.data[i][0]]);
if (i>0) {
stack[i] += stack[i-1];
}
tot += this.data[i][1];
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i<stack.length; i++) {
td[i][1] = stack[i] * fact;
td[i][2] = this.data[i][1]/tot;
}
this.gridData = td;
};
$.jqplot.PieRenderer.prototype.makeGridData = function(data, plot) {
var stack = [];
var td = [];
var tot = 0;
var sa = this.startAngle/180*Math.PI;
// don't know if we have any valid data yet, so set plot to not draw.
this._drawData = false;
for (var i=0; i<data.length; i++){
if (this.data[i][1] != 0) {
// we have data, O.K. to draw.
this._drawData = true;
}
stack.push(data[i][1]);
td.push([data[i][0]]);
if (i>0) {
stack[i] += stack[i-1];
}
tot += data[i][1];
}
var fact = Math.PI*2/stack[stack.length - 1];
for (var i=0; i<stack.length; i++) {
td[i][1] = stack[i] * fact;
td[i][2] = data[i][1]/tot;
}
return td;
};
function calcRadiusAdjustment(ang) {
return Math.sin((ang - (ang-Math.PI) / 8 / Math.PI )/2.0);
}
function calcRPrime(ang1, ang2, sliceMargin, fill, lineWidth) {
var rprime = 0;
var ang = ang2 - ang1;
var absang = Math.abs(ang);
var sm = sliceMargin;
if (fill == false) {
sm += lineWidth;
}
if (sm > 0 && absang > 0.01 && absang < 6.282) {
rprime = parseFloat(sm) / 2.0 / calcRadiusAdjustment(ang);
}
return rprime;
}
$.jqplot.PieRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
if (this._drawData) {
var r = this._radius;
var fill = this.fill;
var lineWidth = this.lineWidth;
var sm = this.sliceMargin;
if (this.fill == false) {
sm += this.lineWidth;
}
ctx.save();
ctx.translate(this._center[0], this._center[1]);
var rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
var transx = rprime * Math.cos((ang1 + ang2) / 2.0);
var transy = rprime * Math.sin((ang1 + ang2) / 2.0);
if ((ang2 - ang1) <= Math.PI) {
r -= rprime;
}
else {
r += rprime;
}
ctx.translate(transx, transy);
if (isShadow) {
for (var i=0, l=this.shadowDepth; i<l; i++) {
ctx.save();
ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
doDraw(r);
}
for (var i=0, l=this.shadowDepth; i<l; i++) {
ctx.restore();
}
}
else {
doDraw(r);
}
ctx.restore();
}
function doDraw (rad) {
// Fix for IE and Chrome that can't seem to draw circles correctly.
// ang2 should always be <= 2 pi since that is the way the data is converted.
// 2Pi = 6.2831853, Pi = 3.1415927
if (ang2 > 6.282 + this.startAngle) {
ang2 = 6.282 + this.startAngle;
if (ang1 > ang2) {
ang1 = 6.281 + this.startAngle;
}
}
// Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
// ugly line on unfilled pies.
if (ang1 >= ang2) {
return;
}
ctx.beginPath();
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.arc(0, 0, rad, ang1, ang2, false);
ctx.lineTo(0,0);
ctx.closePath();
if (fill) {
ctx.fill();
}
else {
ctx.stroke();
}
}
};
// called with scope of series
$.jqplot.PieRenderer.prototype.draw = function (ctx, gd, options, plot) {
var i;
var opts = (options != undefined) ? options : {};
// offset and direction of offset due to legend placement
var offx = 0;
var offy = 0;
var trans = 1;
var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
var li = options.legendInfo;
switch (li.location) {
case 'nw':
offx = li.width + li.xoffset;
break;
case 'w':
offx = li.width + li.xoffset;
break;
case 'sw':
offx = li.width + li.xoffset;
break;
case 'ne':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'e':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'se':
offx = li.width + li.xoffset;
trans = -1;
break;
case 'n':
offy = li.height + li.yoffset;
break;
case 's':
offy = li.height + li.yoffset;
trans = -1;
break;
default:
break;
}
}
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
var fill = (opts.fill != undefined) ? opts.fill : this.fill;
var cw = ctx.canvas.width;
var ch = ctx.canvas.height;
var w = cw - offx - 2 * this.padding;
var h = ch - offy - 2 * this.padding;
var mindim = Math.min(w,h);
var d = mindim;
// Fixes issue #272. Thanks hugwijst!
// reset slice angles array.
this._sliceAngles = [];
var sm = this.sliceMargin;
if (this.fill == false) {
sm += this.lineWidth;
}
var rprime;
var maxrprime = 0;
var ang, ang1, ang2, shadowColor;
var sa = this.startAngle / 180 * Math.PI;
// have to pre-draw shadows, so loop throgh here and calculate some values also.
for (var i=0, l=gd.length; i<l; i++) {
ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
ang2 = gd[i][1] + sa;
this._sliceAngles.push([ang1, ang2]);
rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth);
if (Math.abs(ang2-ang1) > Math.PI) {
maxrprime = Math.max(rprime, maxrprime);
}
}
if (this.diameter != null && this.diameter > 0) {
this._diameter = this.diameter - 2*maxrprime;
}
else {
this._diameter = d - 2*maxrprime;
}
// Need to check for undersized pie. This can happen if
// plot area too small and legend is too big.
if (this._diameter < 6) {
$.jqplot.log('Diameter of pie too small, not rendering.');
return;
}
var r = this._radius = this._diameter/2;
this._center = [(cw - trans * offx)/2 + trans * offx + maxrprime * Math.cos(sa), (ch - trans*offy)/2 + trans * offy + maxrprime * Math.sin(sa)];
if (this.shadow) {
for (var i=0, l=gd.length; i<l; i++) {
shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], shadowColor, true);
}
}
for (var i=0; i<gd.length; i++) {
this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], colorGenerator.next(), false);
if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
var fstr, avgang = (this._sliceAngles[i][0] + this._sliceAngles[i][1])/2, label;
if (this.dataLabels == 'label') {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, gd[i][0]);
}
else if (this.dataLabels == 'value') {
fstr = this.dataLabelFormatString || '%d';
label = $.jqplot.sprintf(fstr, this.data[i][1]);
}
else if (this.dataLabels == 'percent') {
fstr = this.dataLabelFormatString || '%d%%';
label = $.jqplot.sprintf(fstr, gd[i][2]*100);
}
else if (this.dataLabels.constructor == Array) {
fstr = this.dataLabelFormatString || '%s';
label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
}
var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
var labelelem = $('<div class="jqplot-pie-series jqplot-data-label" style="position:absolute;">' + label + '</div>').insertBefore(plot.eventCanvas._elem);
if (this.dataLabelCenterOn) {
x -= labelelem.width()/2;
y -= labelelem.height()/2;
}
else {
x -= labelelem.width() * Math.sin(avgang/2);
y -= labelelem.height()/2;
}
x = Math.round(x);
y = Math.round(y);
labelelem.css({left: x, top: y});
}
}
};
$.jqplot.PieAxisRenderer = function() {
$.jqplot.LinearAxisRenderer.call(this);
};
$.jqplot.PieAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
$.jqplot.PieAxisRenderer.prototype.constructor = $.jqplot.PieAxisRenderer;
// There are no traditional axes on a pie chart. We just need to provide
// dummy objects with properties so the plot will render.
// called with scope of axis object.
$.jqplot.PieAxisRenderer.prototype.init = function(options){
//
this.tickRenderer = $.jqplot.PieTickRenderer;
$.extend(true, this, options);
// I don't think I'm going to need _dataBounds here.
// have to go Axis scaling in a way to fit chart onto plot area
// and provide u2p and p2u functionality for mouse cursor, etc.
// for convienence set _dataBounds to 0 and 100 and
// set min/max to 0 and 100.
this._dataBounds = {min:0, max:100};
this.min = 0;
this.max = 100;
this.showTicks = false;
this.ticks = [];
this.showMark = false;
this.show = false;
};
$.jqplot.PieLegendRenderer = function(){
$.jqplot.TableLegendRenderer.call(this);
};
$.jqplot.PieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
$.jqplot.PieLegendRenderer.prototype.constructor = $.jqplot.PieLegendRenderer;
/**
* Class: $.jqplot.PieLegendRenderer
* Legend Renderer specific to pie plots. Set by default
* when user creates a pie plot.
*/
$.jqplot.PieLegendRenderer.prototype.init = function(options) {
// Group: Properties
//
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
$.extend(true, this, options);
};
// called with context of legend
$.jqplot.PieLegendRenderer.prototype.draw = function() {
var legend = this;
if (this.show) {
var series = this._series;
this._elem = $(document.createElement('table'));
this._elem.addClass('jqplot-table-legend');
var ss = {position:'absolute'};
if (this.background) {
ss['background'] = this.background;
}
if (this.border) {
ss['border'] = this.border;
}
if (this.fontSize) {
ss['fontSize'] = this.fontSize;
}
if (this.fontFamily) {
ss['fontFamily'] = this.fontFamily;
}
if (this.textColor) {
ss['textColor'] = this.textColor;
}
if (this.marginTop != null) {
ss['marginTop'] = this.marginTop;
}
if (this.marginBottom != null) {
ss['marginBottom'] = this.marginBottom;
}
if (this.marginLeft != null) {
ss['marginLeft'] = this.marginLeft;
}
if (this.marginRight != null) {
ss['marginRight'] = this.marginRight;
}
this._elem.css(ss);
// Pie charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = false,
nr,
nc;
var s = series[0];
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j;
var tr, td1, td2;
var lt, rs, color;
var idx = 0;
var div0, div1;
for (i=0; i<nr; i++) {
tr = $(document.createElement('tr'));
tr.addClass('jqplot-table-legend');
if (reverse){
tr.prependTo(this._elem);
}
else{
tr.appendTo(this._elem);
}
for (j=0; j<nc; j++) {
if (idx < pd.length){
lt = this.labels[idx] || pd[idx][0].toString();
color = colorGenerator.next();
if (!reverse){
if (i>0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $(document.createElement('td'));
td1.addClass('jqplot-table-legend jqplot-table-legend-swatch');
td1.css({textAlign: 'center', paddingTop: rs});
div0 = $(document.createElement('div'));
div0.addClass('jqplot-table-legend-swatch-outline');
div1 = $(document.createElement('div'));
div1.addClass('jqplot-table-legend-swatch');
div1.css({backgroundColor: color, borderColor: color});
td1.append(div0.append(div1));
td2 = $(document.createElement('td'));
td2.addClass('jqplot-table-legend jqplot-table-legend-label');
td2.css('paddingTop', rs);
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
}
}
return this._elem;
};
$.jqplot.PieRenderer.prototype.handleMove = function(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
plot.target.trigger('jqplotDataMouseOver', ins);
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
plot.target.trigger('jqplotDataHighlight', ins);
highlight (plot, ins[0], ins[1]);
}
}
else if (neighbor == null) {
unhighlight (plot);
}
};
// this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
// only set these if there is a pie series
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.PieRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.PieRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.PieAxisRenderer;
options.legend.renderer = $.jqplot.PieLegendRenderer;
options.legend.preDraw = true;
options.seriesDefaults.pointLabels = {show: false};
}
}
function postInit(target, data, options) {
for (var i=0; i<this.series.length; i++) {
if (this.series[i].renderer.constructor == $.jqplot.PieRenderer) {
// don't allow mouseover and mousedown at same time.
if (this.series[i].highlightMouseOver) {
this.series[i].highlightMouseDown = false;
}
}
}
}
// called with scope of plot
function postParseOptions(options) {
for (var i=0; i<this.series.length; i++) {
this.series[i].seriesColors = this.seriesColors;
this.series[i].colorGenerator = $.jqplot.colorGenerator;
}
}
function highlight (plot, sidx, pidx) {
var s = plot.series[sidx];
var canvas = plot.plugins.pieRenderer.highlightCanvas;
canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
s._highlightedPoint = pidx;
plot.plugins.pieRenderer.highlightedSeriesIndex = sidx;
s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColorGenerator.get(pidx), false);
}
function unhighlight (plot) {
var canvas = plot.plugins.pieRenderer.highlightCanvas;
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
for (var i=0; i<plot.series.length; i++) {
plot.series[i]._highlightedPoint = null;
}
plot.plugins.pieRenderer.highlightedSeriesIndex = null;
plot.target.trigger('jqplotDataUnhighlight');
}
function handleMove(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
var evt1 = jQuery.Event('jqplotDataMouseOver');
evt1.pageX = ev.pageX;
evt1.pageY = ev.pageY;
plot.target.trigger(evt1, ins);
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
var evt = jQuery.Event('jqplotDataHighlight');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
highlight (plot, ins[0], ins[1]);
}
}
else if (neighbor == null) {
unhighlight (plot);
}
}
function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
var evt = jQuery.Event('jqplotDataHighlight');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
highlight (plot, ins[0], ins[1]);
}
}
else if (neighbor == null) {
unhighlight (plot);
}
}
function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
if (idx != null && plot.series[idx].highlightMouseDown) {
unhighlight(plot);
}
}
function handleClick(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
var evt = jQuery.Event('jqplotDataClick');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
}
}
function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
if (neighbor) {
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
var idx = plot.plugins.pieRenderer.highlightedSeriesIndex;
if (idx != null && plot.series[idx].highlightMouseDown) {
unhighlight(plot);
}
var evt = jQuery.Event('jqplotDataRightClick');
evt.which = ev.which;
evt.pageX = ev.pageX;
evt.pageY = ev.pageY;
plot.target.trigger(evt, ins);
}
}
// called within context of plot
// create a canvas which we can draw on.
// insert it before the eventCanvas, so eventCanvas will still capture events.
function postPlotDraw() {
// Memory Leaks patch
if (this.plugins.pieRenderer && this.plugins.pieRenderer.highlightCanvas) {
this.plugins.pieRenderer.highlightCanvas.resetCanvas();
this.plugins.pieRenderer.highlightCanvas = null;
}
this.plugins.pieRenderer = {highlightedSeriesIndex:null};
this.plugins.pieRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
// do we have any data labels? if so, put highlight canvas before those
var labels = $(this.targetId+' .jqplot-data-label');
if (labels.length) {
$(labels[0]).before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
}
// else put highlight canvas before event canvas.
else {
this.eventCanvas._elem.before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this));
}
var hctx = this.plugins.pieRenderer.highlightCanvas.setContext();
this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
}
$.jqplot.preInitHooks.push(preInit);
$.jqplot.PieTickRenderer = function() {
$.jqplot.AxisTickRenderer.call(this);
};
$.jqplot.PieTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
$.jqplot.PieTickRenderer.prototype.constructor = $.jqplot.PieTickRenderer;
})(jQuery);

View File

@ -0,0 +1,377 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.8
* Revision: 1250
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.PointLabels
* Plugin for putting labels at the data points.
*
* To use this plugin, include the js
* file in your source:
*
* > <script type="text/javascript" src="plugins/jqplot.pointLabels.js"></script>
*
* By default, the last value in the data ponit array in the data series is used
* for the label. For most series renderers, extra data can be added to the
* data point arrays and the last value will be used as the label.
*
* For instance,
* this series:
*
* > [[1,4], [3,5], [7,2]]
*
* Would, by default, use the y values in the labels.
* Extra data can be added to the series like so:
*
* > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']]
*
* And now the point labels would be 'mid', 'low', and 'hi'.
*
* Options to the point labels and a custom labels array can be passed into the
* "pointLabels" option on the series option like so:
*
* > series:[{pointLabels:{
* > labels:['mid', 'hi', 'low'],
* > location:'se',
* > ypadding: 12
* > }
* > }]
*
* A custom labels array in the options takes precendence over any labels
* in the series data. If you have a custom labels array in the options,
* but still want to use values from the series array as labels, set the
* "labelsFromSeries" option to true.
*
* By default, html entities (<, >, etc.) are escaped in point labels.
* If you want to include actual html markup in the labels,
* set the "escapeHTML" option to false.
*
*/
$.jqplot.PointLabels = function(options) {
// Group: Properties
//
// prop: show
// show the labels or not.
this.show = $.jqplot.config.enablePlugins;
// prop: location
// compass location where to position the label around the point.
// 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
this.location = 'n';
// prop: labelsFromSeries
// true to use labels within data point arrays.
this.labelsFromSeries = false;
// prop: seriesLabelIndex
// array index for location of labels within data point arrays.
// if null, will use the last element of the data point array.
this.seriesLabelIndex = null;
// prop: labels
// array of arrays of labels, one array for each series.
this.labels = [];
// actual labels that will get displayed.
// needed to preserve user specified labels in labels array.
this._labels = [];
// prop: stackedValue
// true to display value as stacked in a stacked plot.
// no effect if labels is specified.
this.stackedValue = false;
// prop: ypadding
// vertical padding in pixels between point and label
this.ypadding = 6;
// prop: xpadding
// horizontal padding in pixels between point and label
this.xpadding = 6;
// prop: escapeHTML
// true to escape html entities in the labels.
// If you want to include markup in the labels, set to false.
this.escapeHTML = true;
// prop: edgeTolerance
// Number of pixels that the label must be away from an axis
// boundary in order to be drawn. Negative values will allow overlap
// with the grid boundaries.
this.edgeTolerance = -5;
// prop: formatter
// A class of a formatter for the tick text. sprintf by default.
this.formatter = $.jqplot.DefaultTickFormatter;
// prop: formatString
// string passed to the formatter.
this.formatString = '';
// prop: hideZeros
// true to not show a label for a value which is 0.
this.hideZeros = false;
this._elems = [];
$.extend(true, this, options);
};
var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
// called with scope of a series
$.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){
var options = $.extend(true, {}, seriesDefaults, opts);
options.pointLabels = options.pointLabels || {};
if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) {
options.pointLabels.location = 'e';
}
// add a pointLabels attribute to the series plugins
this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels);
this.plugins.pointLabels.setLabels.call(this);
};
// called with scope of series
$.jqplot.PointLabels.prototype.setLabels = function() {
var p = this.plugins.pointLabels;
var labelIdx;
if (p.seriesLabelIndex != null) {
labelIdx = p.seriesLabelIndex;
}
else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') {
labelIdx = (this._plotData[0].length < 3) ? 0 : this._plotData[0].length -1;
}
else {
labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1;
}
p._labels = [];
if (p.labels.length === 0 || p.labelsFromSeries) {
if (p.stackedValue) {
if (this._plotData.length && this._plotData[0].length){
// var idx = p.seriesLabelIndex || this._plotData[0].length -1;
for (var i=0; i<this._plotData.length; i++) {
p._labels.push(this._plotData[i][labelIdx]);
}
}
}
else {
// var d = this._plotData;
var d = this.data;
if (this.renderer.constructor === $.jqplot.BarRenderer && this.waterfall) {
d = this._data;
}
if (d.length && d[0].length) {
// var idx = p.seriesLabelIndex || d[0].length -1;
for (var i=0; i<d.length; i++) {
p._labels.push(d[i][labelIdx]);
}
}
d = null;
}
}
else if (p.labels.length){
p._labels = p.labels;
}
};
$.jqplot.PointLabels.prototype.xOffset = function(elem, location, padding) {
location = location || this.location;
padding = padding || this.xpadding;
var offset;
switch (location) {
case 'nw':
offset = -elem.outerWidth(true) - this.xpadding;
break;
case 'n':
offset = -elem.outerWidth(true)/2;
break;
case 'ne':
offset = this.xpadding;
break;
case 'e':
offset = this.xpadding;
break;
case 'se':
offset = this.xpadding;
break;
case 's':
offset = -elem.outerWidth(true)/2;
break;
case 'sw':
offset = -elem.outerWidth(true) - this.xpadding;
break;
case 'w':
offset = -elem.outerWidth(true) - this.xpadding;
break;
default: // same as 'nw'
offset = -elem.outerWidth(true) - this.xpadding;
break;
}
return offset;
};
$.jqplot.PointLabels.prototype.yOffset = function(elem, location, padding) {
location = location || this.location;
padding = padding || this.xpadding;
var offset;
switch (location) {
case 'nw':
offset = -elem.outerHeight(true) - this.ypadding;
break;
case 'n':
offset = -elem.outerHeight(true) - this.ypadding;
break;
case 'ne':
offset = -elem.outerHeight(true) - this.ypadding;
break;
case 'e':
offset = -elem.outerHeight(true)/2;
break;
case 'se':
offset = this.ypadding;
break;
case 's':
offset = this.ypadding;
break;
case 'sw':
offset = this.ypadding;
break;
case 'w':
offset = -elem.outerHeight(true)/2;
break;
default: // same as 'nw'
offset = -elem.outerHeight(true) - this.ypadding;
break;
}
return offset;
};
// called with scope of series
$.jqplot.PointLabels.draw = function (sctx, options, plot) {
var p = this.plugins.pointLabels;
// set labels again in case they have changed.
p.setLabels.call(this);
// remove any previous labels
for (var i=0; i<p._elems.length; i++) {
// Memory Leaks patch
// p._elems[i].remove();
p._elems[i].emptyForce();
}
p._elems.splice(0, p._elems.length);
if (p.show) {
var ax = '_'+this._stackAxis+'axis';
if (!p.formatString) {
p.formatString = this[ax]._ticks[0].formatString;
p.formatter = this[ax]._ticks[0].formatter;
}
var pd = this._plotData;
var ppd = this._prevPlotData;
var xax = this._xaxis;
var yax = this._yaxis;
var elem, helem;
for (var i=0, l=p._labels.length; i < l; i++) {
var label = p._labels[i];
if (label == null || (p.hideZeros && parseInt(label, 10) == 0)) {
continue;
}
label = p.formatter(p.formatString, label);
helem = document.createElement('div');
p._elems[i] = $(helem);
elem = p._elems[i];
elem.addClass('jqplot-point-label jqplot-series-'+this.index+' jqplot-point-'+i);
elem.css('position', 'absolute');
elem.insertAfter(sctx.canvas);
if (p.escapeHTML) {
elem.text(label);
}
else {
elem.html(label);
}
var location = p.location;
if ((this.fillToZero && pd[i][1] < 0) || (this.fillToZero && this._type === 'bar' && this.barDirection === 'horizontal' && pd[i][0] < 0) || (this.waterfall && parseInt(label, 10)) < 0) {
location = oppositeLocations[locationIndicies[location]];
}
var ell = xax.u2p(pd[i][0]) + p.xOffset(elem, location);
var elt = yax.u2p(pd[i][1]) + p.yOffset(elem, location);
// we have stacked chart but are not showing stacked values,
// place labels in center.
if (this._stack && !p.stackedValue) {
if (this.barDirection === "vertical") {
elt = (this._barPoints[i][0][1] + this._barPoints[i][1][1]) / 2 + plot._gridPadding.top - 0.5 * elem.outerHeight(true);
}
else {
ell = (this._barPoints[i][2][0] + this._barPoints[i][0][0]) / 2 + plot._gridPadding.left - 0.5 * elem.outerWidth(true);
}
}
if (this.renderer.constructor == $.jqplot.BarRenderer) {
if (this.barDirection == "vertical") {
ell += this._barNudge;
}
else {
elt -= this._barNudge;
}
}
elem.css('left', ell);
elem.css('top', elt);
var elr = ell + elem.width();
var elb = elt + elem.height();
var et = p.edgeTolerance;
var scl = $(sctx.canvas).position().left;
var sct = $(sctx.canvas).position().top;
var scr = sctx.canvas.width + scl;
var scb = sctx.canvas.height + sct;
// if label is outside of allowed area, remove it
if (ell - et < scl || elt - et < sct || elr + et > scr || elb + et > scb) {
elem.remove();
}
elem = null;
helem = null;
}
// finally, animate them if the series is animated
// if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) {
// var sel = '.jqplot-point-label.jqplot-series-'+this.index;
// $(sel).hide();
// $(sel).fadeIn(1000);
// }
}
};
$.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init);
$.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw);
})(jQuery);

View File

@ -0,0 +1,259 @@
/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/
.jqplot-target {
position: relative;
color: #666666;
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
font-size: 1em;
/* height: 300px;
width: 400px;*/
}
/*rules applied to all axes*/
.jqplot-axis {
font-size: 0.75em;
}
.jqplot-xaxis {
margin-top: 10px;
}
.jqplot-x2axis {
margin-bottom: 10px;
}
.jqplot-yaxis {
margin-right: 10px;
}
.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
margin-left: 10px;
margin-right: 10px;
}
/*rules applied to all axis tick divs*/
.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
position: absolute;
white-space: pre;
}
.jqplot-xaxis-tick {
top: 0px;
/* initial position untill tick is drawn in proper place */
left: 15px;
/* padding-top: 10px;*/
vertical-align: top;
}
.jqplot-x2axis-tick {
bottom: 0px;
/* initial position untill tick is drawn in proper place */
left: 15px;
/* padding-bottom: 10px;*/
vertical-align: bottom;
}
.jqplot-yaxis-tick {
right: 0px;
/* initial position untill tick is drawn in proper place */
top: 15px;
/* padding-right: 10px;*/
text-align: right;
}
.jqplot-yaxis-tick.jqplot-breakTick {
right: -20px;
margin-right: 0px;
padding:1px 5px 1px 5px;
/*background-color: white;*/
z-index: 2;
font-size: 1.5em;
}
.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
left: 0px;
/* initial position untill tick is drawn in proper place */
top: 15px;
/* padding-left: 10px;*/
/* padding-right: 15px;*/
text-align: left;
}
.jqplot-yMidAxis-tick {
text-align: center;
white-space: nowrap;
}
.jqplot-xaxis-label {
margin-top: 10px;
font-size: 11pt;
position: absolute;
}
.jqplot-x2axis-label {
margin-bottom: 10px;
font-size: 11pt;
position: absolute;
}
.jqplot-yaxis-label {
margin-right: 10px;
/* text-align: center;*/
font-size: 11pt;
position: absolute;
}
.jqplot-yMidAxis-label {
font-size: 11pt;
position: absolute;
}
.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
/* text-align: center;*/
font-size: 11pt;
margin-left: 10px;
position: absolute;
}
.jqplot-meterGauge-tick {
font-size: 0.75em;
color: #999999;
}
.jqplot-meterGauge-label {
font-size: 1em;
color: #999999;
}
table.jqplot-table-legend {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 12px;
margin-right: 12px;
}
table.jqplot-table-legend, table.jqplot-cursor-legend {
background-color: rgba(255,255,255,0.6);
border: 1px solid #cccccc;
position: absolute;
font-size: 0.75em;
}
td.jqplot-table-legend {
vertical-align:middle;
}
/*
These rules could be used instead of assigning
element styles and relying on js object properties.
*/
/*
td.jqplot-table-legend-swatch {
padding-top: 0.5em;
text-align: center;
}
tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
padding-top: 0px;
}
*/
td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
cursor: pointer;
}
.jqplot-table-legend .jqplot-series-hidden {
text-decoration: line-through;
}
div.jqplot-table-legend-swatch-outline {
border: 1px solid #cccccc;
padding:1px;
}
div.jqplot-table-legend-swatch {
width:0px;
height:0px;
border-top-width: 5px;
border-bottom-width: 5px;
border-left-width: 6px;
border-right-width: 6px;
border-top-style: solid;
border-bottom-style: solid;
border-left-style: solid;
border-right-style: solid;
}
.jqplot-title {
top: 0px;
left: 0px;
padding-bottom: 0.5em;
font-size: 1.2em;
}
table.jqplot-cursor-tooltip {
border: 1px solid #cccccc;
font-size: 0.75em;
}
.jqplot-cursor-tooltip {
border: 1px solid #cccccc;
font-size: 0.75em;
white-space: nowrap;
background: rgba(208,208,208,0.5);
padding: 1px;
}
.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
border: 1px solid #cccccc;
font-size: 0.75em;
white-space: nowrap;
background: rgba(208,208,208,0.5);
padding: 1px;
}
.jqplot-point-label {
font-size: 0.75em;
z-index: 2;
}
td.jqplot-cursor-legend-swatch {
vertical-align: middle;
text-align: center;
}
div.jqplot-cursor-legend-swatch {
width: 1.2em;
height: 0.7em;
}
.jqplot-error {
/* Styles added to the plot target container when there is an error go here.*/
text-align: center;
}
.jqplot-error-message {
/* Styling of the custom error message div goes here.*/
position: relative;
top: 46%;
display: inline-block;
}
div.jqplot-bubble-label {
font-size: 0.8em;
/* background: rgba(90%, 90%, 90%, 0.15);*/
padding-left: 2px;
padding-right: 2px;
color: rgb(20%, 20%, 20%);
}
div.jqplot-bubble-label.jqplot-bubble-label-highlight {
background: rgba(90%, 90%, 90%, 0.7);
}
div.jqplot-noData-container {
text-align: center;
background-color: rgba(96%, 96%, 96%, 0.3);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
--- resources/jquery.jqplot.js.orig 2016-08-30 18:50:53.056335603 +0200
+++ resources/jquery.jqplot.js 2016-08-30 18:51:33.211372314 +0200
@@ -9139,7 +9139,7 @@
for (var i=0; i<wl; i++) {
w += words[i];
- if (context.measureText(w).width > tagwidth) {
+ if (context.measureText(w).width > tagwidth && w.length > words[i].length) {
breaks.push(i);
w = '';
i--;

View File

@ -0,0 +1,340 @@
/*Art is code*/
body {
background-color:#cdd5da;
background: -webkit-linear-gradient( bottom, #cdd5da, #e8e7e7);
background: -moz-linear-gradient( bottom, #cdd5da, #e8e7e7);
background: -ms-linear-gradient( bottom, #cdd5da, #e8e7e7);
background: -o-linear-gradient( bottom, #cdd5da, #e8e7e7);
background: linear-gradient( to bottom, #cdd5da, #e8e7e7);
margin-top: 50px;
}
ul.nav li.dropdown:hover ul.dropdown-menu{
display: block;
}
ul.nav li.dropdown ul.dropdown-menu{
margin: 0;
}
#pgbadger-brand {
font-size: 1.1em;
font-weight: bold;
}
ul#slides li {
list-style-type: none;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.6em;
}
h3, h3 small {
font-size: 1.1em;
text-transform: uppercase;
letter-spacing: .1em;
}
h3 small {
font-size: 1em;
}
.analysis-item {
background: #fff;
margin-bottom:2em;
padding-bottom:15px;
}
h3 {
margin: 0;
padding: 0;
color: #5f5555;
}
h1.page-header {
margin: 1em 0 1em 0;
padding: 0;
color: #5f5555;
border-bottom: none;
}
h2 {
color:#5f5555;
}
.nav-pills, .nav-tabs {
margin: 0 1em;
}
footer {
margin-top: 60px;
}
.col-md-8 .tabbable {
margin-top: 1em;
}
#global-stats .tab-content {
margin: 2em 0 3em 0;
}
#global-stats .tab-content li {
display: block;
width:14%; /*160/960*/
float:left;
margin-left: 3%;
}
#global-stats .tab-content li.first {
margin-left: 0;
}
.well {
background: #f9f9f9;
border-radius: 0;
}
.key-figures ul {
margin: 0;
padding: 0;
}
.key-figures li {
list-style-type: none;
margin-bottom: 2em;
}
.figure {
font-weight: bold;
font-size: 1.4em;
color:#2e8aa5;
}
.figure-label {
display: block;
color: #666;
}
.mfigure {
font-weight: bold;
font-size: 1.4em;
color:#d26115;
margin-left: 5px;
}
.smfigure {
font-weight: bold;
font-size: 1.1em;
color:#d26115;
}
.mfigure small {
font-weight: bold;
font-size: 0.6em;
color:#ffffff;
}
.smfigure small {
font-weight: bold;
font-size: 0.6em;
color:#ffffff;
}
.hfigure {
font-weight: bold;
font-size: 1.0em;
color:#8dbd0f;
}
.hfigure small {
font-weight: bold;
font-size: 0.6em;
color:#ffffff;
}
.navbar-inverse {
background: #5f5555;
border: none;
}
.navbar-inverse .brand, .navbar-inverse .nav > li > a {
color:#eee;
}
.linegraph {
width : 100%;
height: 400px;
}
.piegraph {
width : 100%;
height: 400px;
}
.histo-graph {
width : 100%;
height: 140px;
}
.duration-histo-graph {
width : 100%;
height: 400px;
}
@media (min-width:969px) {
#show-hide-menu {
position: absolute;
left: -9999px;
}
.navbar ul.collapse {
overflow: visible;
}
}
@media (max-width:968px) {
#show-hide-menu {
position: inherit;
}
#global-stats .tab-content li {
display: block;
width:auto; /*160/960*/
float:none;
margin-left: 0;
margin-bottom: 1em;
}
ul#slides {
margin: 0;
padding: 0;
}
ul#slides li div div {
padding:0 1.5em;
}
.linegraph {
width : 94.5%;
}
.piegraph {
width : 94.5%;
}
.histo-graph {
width : 84.5%;
}
.duration-histo-graph {
width : 94.5%;
}
.key-figures ul {
margin-top: 1.5em;
}
.navbar .nav {
margin: 0 3em 2em 3em;
}
.navbar .nav > li {
float: none;
}
.navbar ul.collapse:hover {
overflow: visible;
}
}
div#littleToc { display:none; }
html>body div#littleToc { display:block; background-color:white; color:black; position:fixed; bottom:10px; right:10px; width:50px; font-size:11px; text-align:left; border:0px; }
div#littleToc div#littleTocTitle { font-weight:bold; text-align:center;padding:2px; }
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; }
.jqplot-graph { color: #ac1316; z-index: 99;}
.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; }
.sql-smallsize {width: 450px; }
.sql-mediumsize {width: 600px;max-height:200px;overflow:auto;}
.sql-largesize {width: 950px;max-height:200px;overflow:auto;}
.pre-smallfont {font-size:10px;}
.jqplot-target {
position: relative;
color: #333;
font-size: 1.2em;
}
.jqplot-highlighter-tooltip {
border: 0px;
font-size: 1.0em;
white-space: nowrap;
font-weight: bold;
margin: 5px;
padding: 5px;
color:#ffffff;
background: #5f5555;
border:1px solid #5f5555;
border:1px solid rgba(0, 0, 0, 0.2);
-webkit-border-radius:6px;
-moz-border-radius:6px;
border-radius:6px;
-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);
}
.jqplot-title {
font-size: 1.1em;
text-transform: uppercase;
letter-spacing: .1em;
margin: 0;
padding: 0;
color: #5f5555;
}
#pgbadgerModal .modal-dialog
{
width: 850px;
overflow-y: hidden;
}
#pgbadgerModal .modal-body
{
height: 100%;
width: 830px;
background-color: white;
}
.error-pre {
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
}

View File

@ -0,0 +1,269 @@
/* <![CDATA[ */
function create_download_button (buttonid, cssclass) {
if (cssclass == undefined) {
cssclass ='btn btn-info';
}
document.writeln('<button type="button" id="download'+buttonid+'" class="'+cssclass+'">Download</button>');
}
function add_download_button_event (buttonid, divid) {
jQuery('#download'+buttonid).click( function() {
$('#pgbadgerModal img').attr('src', $('#'+divid).jqplotToImageStr({}));
$('#pgbadgerModal').modal('toggle');
});
}
function create_linegraph (divid, charttitle, ylabel, arr_series, lineseries) {
return $.jqplot(divid, lineseries, {
seriesColors: ['#6e9dc9','#f4ab3a','#ac7fa8','#8dbd0f',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc"],
seriesDefaults: { markerOptions: {show: false}, lineWidth:1 },
grid: { borderWidth: 1, background: '#ffffff'},
title: charttitle,
series: arr_series,
axes: {
xaxis: {
renderer:$.jqplot.DateAxisRenderer,
tickOptions:{ angle: -30, textColor: '#333' },
},
yaxis: {
renderer: $.jqplot.LogAxisRenderer,
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
tickOptions: {
textColor: '#333',
formatter: function(format, value) { return pretty_print_number(value, 0, ylabel); }
},
}
},
legend: {
show: true,
location: 'nw',
},
cursor:{
show: true,
zoom: true,
showTooltip:false,
looseZoom: true,
followMouse: true,
},
highlighter: {
show: true,
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
var dateToDisplay = new Date(plot.data[seriesIndex][pointIndex][0]);
var textToShow = '<div>On '+dateToDisplay.toString();
for (var i=0; i<plot.data.length;i++) {
textToShow += '<br><span class="mfigure">'+pretty_print_number(plot.data[i][pointIndex][1], 2, plot.series[i].label)+' <small>'+plot.series[i].label+'</small></span>';
}
textToShow += '</div>';
return textToShow;
}
}
});
}
function create_piechart(divid, title, data) {
return $.jqplot(divid, [data],
{
seriesColors: ['#6e9dc9','#f4ab3a','#ac7fa8','#8dbd0f',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc","#4bb2c5", "#c5b47f", "#EAA228", "#579575", "#839557","#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621","#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381","#959E5C", "#C7AF7B", "#478396", "#907294"],
grid: { borderWidth: 1, background: '#ffffff'},
title: title,
seriesDefaults: {
renderer: $.jqplot.PieRenderer,
rendererOptions: {
showDataLabels: true
}
},
legend: { show:true, location: 'e' },
highlighter: {
show: true,
tooltipLocation:'sw',
useAxesFormatters:false,
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
var textToShow = '<div>';
//textToShow += '<span class="mfigure">'+pretty_print_number(plot.data[0][pointIndex][1], 2)+' <small>'+plot.data[0][pointIndex][0]+'</small></span>';
textToShow += '<span class="mfigure">'+format_number(plot.data[0][pointIndex][1])+' <small>'+plot.data[0][pointIndex][0]+'</small></span>';
textToShow += '</div>';
return textToShow;
}
}
}
);
}
function create_bargraph (divid, title, ytitle, data, y2title) {
return $.jqplot(divid, data, {
grid: { borderWidth: 1, background: '#ffffff'},
title: title,
seriesDefaults: {
rendererOptions: {
barPadding: 4,
barMargin: 5,
}
},
seriesColors: ['#6e9dc9','#8dbd0f','#f4ab3a','#ac7fa8',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc"],
series:[
{ renderer:$.jqplot.BarRenderer, label: ytitle},
{ yaxis:'y2axis', label: y2title, markerOptions: {show: false}, lineWidth:1 }
],
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
drawMajorGridlines: false,
drawMajorTickMarks: false,
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
tickOptions: {
angle: -30,
textColor: '#333',
formatString:'%H:%M',
fontFamily:'Helvetica',
fontSize: '8pt'
}
},
yaxis: {
autoscale:true,
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
tickOptions:{ textColor: '#333' },
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
tickOptions: {
textColor: '#333',
formatter: function(format, value) { return pretty_print_number(value, 1, ytitle); },
fontFamily:'Helvetica',
fontSize: '8pt'
},
label: ytitle
},
y2axis: {
autoscale:true,
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
tickOptions: {
textColor: '#8dbd0f',
formatter: function(format, value) { return pretty_print_number(value, 1, y2title); },
fontFamily:'Helvetica',
fontSize: '8pt'
},
rendererOptions: {
alignTicks: true,
},
label: y2title,
}
},
highlighter: {
show: true,
tooltipLocation:'ne',
useAxesFormatters:false,
tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) {
var textToShow = '<div style="z-index: 99;">At '+plot.data[0][pointIndex][0];
for (var i=0; i<plot.data.length;i++) {
textToShow += '<br><span class="mfigure">'+pretty_print_number(plot.data[i][pointIndex][1], 2, plot.series[i].label)+' <small>'+plot.series[i].label+'</small></span>';
}
textToShow += '</div>';
return textToShow;
}
}
});
}
function pretty_print_number(val, scale, type)
{
if ( (scale == undefined) || ((scale == 0) && (val != 0)) ) {
scale = 1;
};
if (type != undefined) {
type = type.toLowerCase();
if (type.search('size') >= 0) {
if (Math.abs(val) >= 1125899906842624) {
val = (val / 1125899906842624);
return val.toFixed(scale) + " PiB";
} else if (Math.abs(val) >= 1099511627776) {
val = (val / 1099511627776);
return val.toFixed(scale) + " TiB";
} else if (Math.abs(val) >= 1073741824) {
val = (val / 1073741824);
return val.toFixed(scale) + " GiB";
} else if (Math.abs(val) >= 1048576) {
val = (val / 1048576);
return val.toFixed(scale) + " MiB";
} else if (Math.abs(val) >= 1024) {
val = (val / 1024);
return val.toFixed(scale) + " KiB";
} else {
return val + " B";
}
} else if (type.search('duration') >= 0) {
if (Math.abs(val) >= 1000) {
val = (val / 1000);
return val.toFixed(scale) + " sec";
} else {
return val.toFixed(scale) + " ms";
}
} else {
if (Math.abs(val) >= 1000000000000000) {
val = (val / 1000000000000000);
return val.toFixed(scale) + " P";
} else if (Math.abs(val) >= 1000000000000) {
val = (val / 1000000000000);
return val.toFixed(scale) + " T";
} else if (Math.abs(val) >= 1000000000) {
val = (val / 1000000000);
return val.toFixed(scale) + " G";
} else if (Math.abs(val) >= 1000000) {
val = (val / 1000000);
return val.toFixed(scale) + " M";
} else if (Math.abs(val) >= 1000) {
val = (val / 1000);
return val.toFixed(scale) + " K";
}
}
}
return val.toFixed(scale);
}
function format_number(val) {
var decimal = 2;
var msep = ',';
var deci = Math.round( Math.pow(10,decimal)*(Math.abs(val)-Math.floor(Math.abs(val)))) ;
val = Math.floor(Math.abs(val));
if ((decimal==0)||(deci==Math.pow(10,decimal))) {deci=0;}
var val_format=val+"";
var nb=val_format.length;
for (var i=1;i<4;i++) {
if (val>=Math.pow(10,(3*i))) {
val_format=val_format.substring(0,nb-(3*i))+msep+val_format.substring(nb-(3*i));
}
}
if (decimal>0) {
var decim="";
for (var j=0;j<(decimal-deci.toString().length);j++) {decim+="0";}
deci=decim+deci.toString();
if (deci > 0) {
val_format=val_format+"."+deci;
}
}
if (parseFloat(val)<0) {val_format="-"+val_format;}
return val_format;
}
function draw_charts() {
window.charts = $.grep(window.charts, function(chart) {
var chart_copy = chart.slice();
var type = chart_copy.shift();
var divid = chart[1];
if ($('#' + divid).is(':visible')) {
window['create_' + type].apply(null, chart_copy);
// chart loaded, don't keep it in the list
return false;
}
return true;
});
};

View File

@ -0,0 +1,82 @@
jQuery(function (){
jQuery('#pgbadger-brand').tooltip();
jQuery("#pop-infos").popover('hide');
jQuery('.slide').hide();
jQuery('.active-slide').show();
/* Go to specific slide and report from external link call */
var activeMenu = location.hash;
if (activeMenu) {
activeMenu = activeMenu.substring(1);
var lkobj = document.getElementById(activeMenu);
var liId = jQuery(lkobj).parent().attr("id");
if (liId != undefined) {
var slideId = '#'+liId;
jQuery('#main-container li.slide').removeClass('active-slide').hide();
jQuery(slideId).addClass("active-slide").fadeIn();
window.location.hash = '#'+activeMenu;
}
}
jQuery('.navbar li.dropdown').click(function() {
var id = jQuery(this).attr("id");
id = id.substring(5);
var slideId = '#'+id+'-slide';
var currentSlide = jQuery('#main-container .active-slide').attr("id");
currentSlide = '#'+currentSlide;
if(slideId != currentSlide) {
jQuery('#main-container li.slide').removeClass('active-slide').hide();
jQuery(slideId).addClass("active-slide").fadeIn();
}
scrollTo(0,0);
draw_charts();
});
jQuery('.navbar li ul li').click(function() {
var liId = jQuery(this).parent().parent().attr("id");
var id = liId.substring(5);
var slideId = '#'+id+'-slide';
jQuery('#main-container li.slide').removeClass('active-slide').hide();
jQuery(slideId).addClass("active-slide").fadeIn();
draw_charts();
});
draw_charts();
});
jQuery(document).ready(function () {
jQuery('.sql').dblclick(function () {
if (this.style == undefined || this.style.whiteSpace == 'pre') {
this.style.whiteSpace ='normal';
} else {
this.style.whiteSpace = 'pre';
}
});
jQuery('.icon-copy').click(function () {
var obj = $(this).parent()[0];
if (window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
var range = document.createRange();
range.selectNodeContents(obj);
sel.addRange(range);
} else if (document.selection) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(obj);
textRange.select();
}
});
function shiftWindow() {
scrollBy(0, -50);
}
if (location.hash) {
shiftWindow();
}
window.addEventListener("hashchange",shiftWindow);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
select user from list where id = dong or 1=1 0.998
select * from data where uname='a' and 1=1 and 'a'='a' 0.953
select * from user_name where id = 'admin' and password='1234';drop table user;-- 0.908
select * from user where id='1111' and password='1234' and convert(char, no) -- 0.863
select * from user where id='1111' union select * from member where id='admin'--' and password='1234' 0.818

View File

@ -0,0 +1,8 @@
use Test::Simple tests => 2;
my $ret = `perl -wc pgbadger 2>&1`;
ok( $? == 0, "PERL syntax check");
$ret = `podchecker doc/*.pod 2>&1`;
ok( $? == 0, "pod syntax check");

View File

@ -0,0 +1,55 @@
use Test::Simple tests => 10;
my $LOG = 't/fixtures/light.postgres.log.bz2';
my $SYSLOG = 't/fixtures/pg-syslog.1.bz2';
my $BIN = 't/fixtures/light.postgres.bin';
my $JSON = 't/out.json';
my $TEXT = 't/out.txt';
my $ret = `perl pgbadger --help`;
ok( $? == 0, "Inline help");
$ret = `perl pgbadger -q -o - $LOG`;
ok( $? == 0 && length($ret) > 0, "Light log report to stdout");
`rm -f out.html`;
$ret = `perl pgbadger -q --outdir '.' $LOG`;
ok( $? == 0 && -e "out.html", "Light log report to HTML");
$ret = `perl pgbadger -q -o $BIN $LOG`;
ok( $? == 0 && -e "$BIN", "Light log to binary");
`rm -f $JSON`;
$ret = `perl pgbadger -q -o $JSON --format binary $BIN`;
$ret = `cat $JSON | perl -pe 's/.*"SELECT":(\\d+),.*/\$1/'`;
ok( $? == 0 && $ret > 0, "From binary to JSON");
`mkdir t/test_incr/`;
$ret = `perl pgbadger -q -O t/test_incr -I --extra-files $LOG`;
ok( $? == 0 && -e "t/test_incr/2017/09/06/index.html"
&& -e "t/test_incr/2017/week-37/index.html", "Incremental mode report");
$ret = `grep 'src="../../../.*/bootstrap.min.js"' t/test_incr/2017/09/06/index.html`;
ok( $? == 0 && substr($ret, 32, 14) eq 'src="../../../', "Ressources files in incremental mode");
`rm -f $JSON`;
$ret = `bunzip2 -c $LOG | perl pgbadger -q -o $JSON -`;
$ret = `cat $JSON | perl -pe 's/.*"SELECT":(\\d+),.*/\$1/'`;
ok( $? == 0 && $ret > 0, "Light log from STDIN");
$ret = `perl pgbadger -q --outdir '.' -o $TEXT -o $JSON -o - -x json $LOG > t/ret.json`;
my $ret2 = `stat --printf='%s' t/ret.json $TEXT $JSON`;
chomp($ret);
ok( $? == 0 && $ret2 eq '13276116001132761', "Multiple output format '$ret2' = '13276116001132761'");
$ret = `perl pgbadger -q -o - $SYSLOG`;
ok( $? == 0 && (length($ret) >= 24060), "syslog report to stdout");
`rm -f out.html`;
# Remove files generated during the tests
`rm -f out.html`;
`rm -r $JSON`;
`rm -r $TEXT`;
`rm -f $BIN`;
`rm -rf t/test_incr/`;
`rm t/ret.json`

View File

@ -0,0 +1,40 @@
use Test::Simple tests => 8;
use JSON::XS;
my $json = new JSON::XS;
my $LOG = 't/fixtures/light.postgres.log.bz2 t/fixtures/pgbouncer.log.gz';
my $HLOG = 't/fixtures/logplex.gz';
my $BIN = 'out.bin';
my $OUT = 'out.json';
my $ret = `perl pgbadger -q -o $BIN $LOG`;
ok( $? == 0, "Generate intermediate binary file from log");
$ret = `perl pgbadger -q -o $OUT --format binary $BIN`;
ok( $? == 0, "Generate json report from binary file");
`rm -f $BIN`;
my $json_ref = $json->decode(`cat $OUT`);
#
# Assert that analyzing json file provide the right results
#
ok( $json_ref->{database_info}{postgres}{postgres}{count} == 629, "Consistent count");
ok( $json_ref->{overall_stat}{postgres}{histogram}{query_total} == 629, "Consistent query_total");
ok( $json_ref->{overall_stat}{postgres}{peak}{"2017-09-06 08:48:45"}{write} == 1, "Consistent peak write");
ok( $json_ref->{pgb_session_info}{chronos}{20180912}{16}{count} == 63943, "pgBouncer connections");
`rm -f $OUT`;
$ret = `perl pgbadger -q -o $OUT $HLOG`;
ok( $? == 0, "Generate json report for heroku log file");
$json_ref = $json->decode(`cat $OUT`);
ok( $json_ref->{database_info}{postgres}{GREEN}{"CTE|duration"} eq "21761.546", "Consistent CTE duration");
`rm -f $OUT`;

Binary file not shown.

View File

@ -0,0 +1,91 @@
------------------------------------------------------------------------------
pgbadger_tools - Tools based on pgBadger binary files
This program is open source, licensed under the PostgreSQL Licence.
For license terms, see the LICENSE file.
------------------------------------------------------------------------------
This program is first used to demonstrate how to deal with pgBadger binary
files to build your own tool. If you don't want to rewrite all, you can
post pull request of this tool with your own option so that pgbadger_tools
will do what your need.
All statistics and data gathered by pgBadger binary file is load into memory
using method load_stat(). To generate a binary file you simply have to set
the output file extension to .bin. Note that if you use incremental mode with
pgbadger it already generate binary file in daily directories.
example: pgbadger -o out.bin /var/log/postgresql/postgresql.log
PGBADGER_TOOLS OPTIONS AND TOOLS
--------------------------------
**Auto explain**
The first option added is --explain-slowest that dump top slowest queries in
an explain analyze statement, ready to be executed. An extended version could
be created to automatically execute those explain statement on the database.
./pgbadger_tools --explain-slowest out.bin
You can use several binary file as input, for example from an incremental
output directory:
./pgbadger_tools --explain-slowest /var/www/pgbadger/2014/09/03/*.bin
The explain tool is base on an original work of Rodolphe Quiedeville, that was
first sent as a pull request on pgbadger. But we need to keep pgBadger simple
so the place of this kind of addons are more in peripheral tools. It is possible
that in future, the tsung and json output will be removed from pgbadger and put
in pgbadger_tools
If you want to chain tools and HTML report, you can proceed as follow:
pgbadger -o out.bin /var/log/postgresql/postgresql.log
./pgbadger_tools --explain-slowest out.bin > explain_top_slowest.sql
pgbadger -o report.html out.bin
**CSV output**
_
When using the following option, pgbadger_tools will export top queries section
results in csv format, for example for later analysis. This is an original work
of bricklen.
Here are the supported options, only one of the following is mandatory:
--csv-time-consuming : generate a CSV file with top time consuming queries
--csv-slowest : generate a CSV file with top slowest queries
--csv-normalized : generate a CSV file with top normalized queries
./pgbadger_tools --csv-time-consuming out.bin
Those options can not be used together. By default the output file is named
out.csv, you can use the --csv-filename option to renamed this file. Ex:
./pgbadger_tools --csv-slowest --csv-filename slowest.csv out.bin
Option to limit top queries to minimum duration:
--max-duration MS : set the number of milliseconds above which queries
will not be reported. Use it if you want to auto
execute explain statements.
If you want to chain tools and HTML report, you can proceed as follow:
pgbadger -o out.bin /var/log/postgresql/postgresql.log
./pgbadger_tools --csv-slowest out.bin --csv-filename top_slowest.csv
pgbadger -o report.html out.bin
CONTRIBUTION:
-------------
Feel free to extend pgbadger_tools of new features. To see how to integrate
you Perl code in this program, search for "Add your own" string in the code
and look at the example searching on explain_slowest.
Regards,
--
Gilles Darold

View File

@ -0,0 +1,7 @@
------------------------------------------------------------------------------
updt_embedded_rsc.pl - script to update all embedded javascript and css
snippets into the pgbadger Perl script.
This program is open source, licensed under the PostgreSQL Licence.
For license terms, see the LICENSE file.
------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,91 @@
#!/usr/bin/env perl
#-----------------------------------------------------------------------------
#
# Script used to update javascript and css files embedded into pgbadger.
# It will replace all content after the __DATA__ in the right order with
# the files content stored into the resources/min/ directory.
#
# This script must be executed from the main source repository as follow:
# 
# This script must be executed from the main source repository as follow:
#  ./tools/updt_embedded_rsc.pl
#
# The fontawesome.css must also embedded the TrueType font, this is done
#-----------------------------------------------------------------------------
use strict;
my $RSC_DIR = 'resources';
my $PGBADGER_PROG = 'pgbadger';
my $DEST_TMP_FILE = 'pgbadger.new';
# Ordered resources files list
my @rsc_list = qw(jquery.jqplot.css jquery.js jquery.jqplot.js jqplot.pieRenderer.js jqplot.barRenderer.js jqplot.dateAxisRenderer.js jqplot.canvasTextRenderer.js jqplot.categoryAxisRenderer.js jqplot.canvasAxisTickRenderer.js jqplot.highlighter.js jqplot.highlighter.js jqplot.cursor.js jqplot.pointLabels.js bean.js underscore.js bootstrap.css fontawesome.css bootstrap.js pgbadger_slide.js pgbadger.css pgbadger.js);
my @min_rsc_list = ();
if (!-d $RSC_DIR) {
die "FATAL: can't find directory: $RSC_DIR.\n";
}
# Apply patch on jquery.jqplot.js to fix infinite loop
# May be removed with next jqplot release update
`patch -r - -s -N resources/jquery.jqplot.js -i resources/patch-jquery.jqplot.js`;
# Generate all minified resources files
mkdir "$RSC_DIR/min";
foreach my $f (@rsc_list) {
my $dest = $f;
$dest =~ s/\.(js|css)$/.min.$1/;
push(@min_rsc_list, $dest);
# minify resources files
`yui-compressor $RSC_DIR/$f -o $RSC_DIR/min/$dest`;
}
# Embedded fontawesome webfont into the CSS file as base64 data
print `base64 -w 0 $RSC_DIR/font/FontAwesome.otf > $RSC_DIR/font/FontAwesome.otf.b64`;
open(IN, "$RSC_DIR/font/FontAwesome.otf.b64") or die "FATAL: can't open file $RSC_DIR/font/FontAwesome.otf.b64, $!\n";
my $b64_font = <IN>;
close(IN);
# Update minimized fontawesome.css file
open(IN, "$RSC_DIR/min/fontawesome.min.css") or die "FATAL: can't open file $RSC_DIR/min/fontawesome.min.css\n";
my @content = <IN>;
close(IN);
open(OUT, ">$RSC_DIR/min/fontawesome.min.css") or die "FATAL: can't write to file $RSC_DIR/min/fontawesome.min.css\n";
foreach my $l (@content) {
$l =~ s|;src:url.* format.* format\('svg'\);|;src: url('data:font\/opentype;charset=utf-8;base64,$b64_font') format('truetype');|;
print OUT $l;
}
close(OUT);
if (!-e $PGBADGER_PROG) {
die "FATAL: can't find pgbadger script: $PGBADGER_PROG\n";
}
# Extract content of pgbadger script until __DATA__ is found
my $content = '';
open(IN, $PGBADGER_PROG) or die "FATAL: can't open file $PGBADGER_PROG, $!\n";
while (<IN>) {
last if (/^__DATA__$/);
$content .= $_;
}
close(IN);
# Write script base to destination file
open(OUT, ">$DEST_TMP_FILE") or die "FATAL: can't write to file $DEST_TMP_FILE, $!\n";
print OUT $content;
print OUT "__DATA__\n";
# Append each minified resources file
foreach my $f (@min_rsc_list) {
print OUT "\nWRFILE: $f\n\n";
open(IN, "$RSC_DIR/min/$f") or die "FATAL: can't open file $RSC_DIR/min/$f, $!\n";
my @tmp = <IN>;
close(IN);
print OUT @tmp;
}
close(OUT);
# Clobber original pgbadger file.
rename($DEST_TMP_FILE, $PGBADGER_PROG) or die "FATAL: can't rename $DEST_TMP_FILE into $PGBADGER_PROG\n";
exit 0;

Binary file not shown.

View File

@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
"""
Created on Thu Aug 18 21:10:36 2022
@author: LKJ
"""
import numpy as np
from torchtext.vocab import vocab
from collections import Counter, OrderedDict
from torch.utils.data import Dataset, DataLoader
from torchtext.transforms import VocabTransform # 注意torchtext版本0.12+
import torch
from torch import nn
from torch.nn import functional as F
def get_text():
# sentence_list = [ # 假设这是全部的训练语料
# "nlp drives computer programs that translate text from one language to another",
# "nlp combines computational linguistics rule based modeling of human language with statistical",
# "nlp model respond to text or voice data and respond with text",
# ]
p_sample = []
f = open("data\\p_sample.txt",encoding='utf-8') #读取预训练的正样本
while True:
line = f.readline()
if line:
line = line.split('\t')[1]
p_sample.append(line)
else:
break
f.close()
n_sample = []
f = open("data\\n_sample.txt",encoding='utf-8') #读取预训练的负样本
while True:
line = f.readline()
if line:
line = line.split('\t')[1]
n_sample.append(line)
else:
break
f.close()
sentence_list = p_sample + n_sample
return sentence_list
class CbowDataSet(Dataset):
def __init__(self, text_list, side_window=2):
"""
构造Word2vec的CBOW采样Dataset
:param text_list: 语料
:param side_window: 单侧正例构造背景词采样数总正例是2 * side_window
"""
super(CbowDataSet, self).__init__()
self.side_window = side_window
text_vocab, vocab_transform = self.reform_vocab(text_list)
self.text_list = text_list # 原始文本
self.text_vocab = text_vocab # torchtext的vocab
self.vocab_transform = vocab_transform # torchtext的vocab_transform
self.cbow_data = self.generate_cbow()
def __len__(self):
return len(self.cbow_data)
def __getitem__(self, idx):
data_row = self.cbow_data[idx]
return data_row[0], data_row[1]
def reform_vocab(self, text_list):
"""根据语料构造torchtext的vocab"""
total_word_list = []
for _ in text_list: # 将嵌套的列表([[xx,xx],[xx,xx]...])拉平 ([xx,xx,xx...])
total_word_list += _.split("#")
# total_word_list += _.split(" ")
counter = Counter(total_word_list) # 统计计数
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 构造成可接受的格式:[(单词,num), ...]
ordered_dict = OrderedDict(sorted_by_freq_tuples)
# 开始构造 vocab
special_token = ["<UNK>", "<SEP>"] # 特殊字符
text_vocab = vocab(ordered_dict, specials=special_token) # 单词转tokenspecials里是特殊字符可以为空
text_vocab.set_default_index(0)
vocab_transform = VocabTransform(text_vocab)
return text_vocab, vocab_transform
def generate_cbow(self):
"""生成CBOW的训练数据"""
cbow_data = []
for sentence in self.text_list:
sentence_id_list = np.array(self.vocab_transform(sentence.split('#')))
for center_index in range(
self.side_window, len(sentence_id_list) - self.side_window): # 防止前面或后面取不到足够的值这是取index的上下界
pos_index = list(range(center_index - self.side_window, center_index + self.side_window + 1))
del pos_index[self.side_window]
cbow_data.append([sentence_id_list[center_index], sentence_id_list[pos_index]])
return cbow_data
def get_vocab_transform(self):
return self.vocab_transform
def get_vocab_size(self):
return len(self.text_vocab)
class Word2VecModel(nn.Module):
def __init__(self, vocab_size, batch_size, word_embedding_size=32, hidden=64):
"""
Word2vec模型CBOW实现
:param vocab_size: 单词个数
:param word_embedding_size: 每个词的词向量维度
:param hidden: 隐层维度
"""
super(Word2VecModel, self).__init__()
self.vocab_size = vocab_size
self.word_embedding_size = word_embedding_size
self.hidden = hidden
self.batch_size = batch_size
self.word_embedding = nn.Embedding(self.vocab_size, self.word_embedding_size) # token对应的embedding
# 建模
self.linear_in = nn.Linear(self.word_embedding_size, self.hidden)
self.linear_out = nn.Linear(self.hidden, self.vocab_size)
def forward(self, input_labels):
around_embedding = self.word_embedding(input_labels)
avg_around_embedding = torch.mean(around_embedding, dim=1) # 1. 输入的词向量对应位置求平均
in_emb = F.relu(self.linear_in(avg_around_embedding)) # 2. 过第一个linear使用relu激活函数
out_emb = F.log_softmax(self.linear_out(in_emb)) # 3. 过第二个linear得到维度是[batch_size, 单词总数]
return out_emb
def get_embedding(self, token_list: list):
return self.word_embedding(torch.Tensor(token_list).long())
batch_size = 1
sentence_list = get_text()
cbow_data_set = CbowDataSet(sentence_list) # 构造 DataSet
data_loader = DataLoader(cbow_data_set, batch_size=batch_size, drop_last=True) # 将DataSet封装成DataLoader
# 开始训练
model = Word2VecModel(cbow_data_set.get_vocab_size(), batch_size)
optimizer = torch.optim.Adam(model.parameters())
criterion = nn.CrossEntropyLoss()
for _epoch_i in range(10):
loss_list = []
for center_token, back_token in data_loader:
# 开始训练
optimizer.zero_grad()
model_out = model(back_token)
center_token= torch.Tensor(center_token).long()
loss = criterion(model_out, center_token)
loss.backward()
optimizer.step()
loss_list.append(loss.item())
print("训练中:", _epoch_i, "Loss:", np.sum(loss_list))
#保存预训练好的word2vec模型
torch.save(model.state_dict(), 'word2vec_model_final.pth')
'''
# 最后测试一下
# 得到: nlp can translate text from one language to another 的词向量
sentence = "nlp can translate text from one language to another"
vocab_transform = cbow_data_set.get_vocab_transform()
sentence_ids = vocab_transform(sentence.split(' '))
sentence_embedding = model.get_embedding(sentence_ids)
print("这个是句向量的维度:", sentence_embedding.shape)
'''
# W = model.word_embedding.weight.cpu().detach().numpy()
# v = cbow_data_set.get_vocab_transform()

View File

@ -0,0 +1,3 @@
-379 union select concat_ws(0x7c,user(),database(),version()),2,3/* 379#union#select#concat_ws#(#0x7c#(#)#,#database#(#)#,#version#(#)#,#2#/*#3
37 and 1=0 37#and#1#0
37' aNd 1=0 UNION SELECT 1,2,3,4,version() -- 37#aNd#1#UNION#SELECT#1#,#version#(#)#--

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
select user from list where id = dong or 1=1 0.5135439
select * from data where uname='a' and 1=1 and 'a'='a' 0.5954708
select * from user_name where id = 'admin' and password='1234';drop table user;-- 0.67799324
select * from user where id='1111' and password='1234' and convert(char, no) -- 0.81503713
select * from user where id='1111' union select * from member where id='admin'--' and password='1234' 0.5274527

View File

@ -0,0 +1,5 @@
select user from list where id = dong or 1=1
select * from data where uname='a' and 1=1 and 'a'='a'
select * from user_name where id = 'admin' and password='1234';drop table user;--
select * from user where id='1111' and password='1234' and convert(char, no) --
select * from user where id='1111' union select * from member where id='admin'--' and password='1234'

Binary file not shown.

View File

@ -0,0 +1,28 @@
# SQLIDetection - Dataprepocess
##### gsbadger工具中SQL注入检测功能的数据预处理部分的实现基于LibInjection-java开源项目二次开发。
##### SQL语句预处理流程大致如下
首先如果sqli经过unicode编码过则进行unicode解码再将原始sql语句进行tokenize分词。tokenize分词过程大概分为大小写统一、泛化处理、分词按空格、操作符和括号等特殊符号拆分这几个步骤。
以下为是使用范例:
```Java
public class Main {
public static void main(String[] args) {
/* test a sqli string */
Libinjection a = new Libinjection();
a.libinjection_sqli("admin' OR 1=1--");
System.out.println(a.getOutput());
/* test a file and output its tokenizing results to another file, with options to urldecode and time (in milliseconds) */
Test t = new Test();
t.testfile("data/sqli.txt", "data/sqli-tokenize.txt", true, false);
}
}
```

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
(select 99999999 from pg_sleep(15))
9999'||(select 99999999 from pg_sleep(15))||'9999
9999"||(select 99999999 from pg_sleep(15))||"9999
ascii(substring(version() from 1 for 1))
length((select length(version())))
ascii(substring(length(version()),1,1))
(select id from (select id,@v:=@v+1 as pos from articles y join (select @v:=0) k limit 255) x where pos=1)
(select id from (select id,@v:=@v+1 as pos from articles y join (select @v:=0) k limit 255) x where pos=ascii(substring(version() from 1 for 1)))
vulnerable_site' and 1=5 or title=() #'

View File

@ -0,0 +1,9 @@
(select 99999999 from pg_sleep(15)) select#99999999#from#pg_sleep#(#15#)
9999'||(select 99999999 from pg_sleep(15))||'9999 9999#||#(#select#99999999#from#pg_sleep#(#15#)#||#9999
9999"||(select 99999999 from pg_sleep(15))||"9999 9999#||#(#select#99999999#from#pg_sleep#(#15#)#||#9999
ascii(substring(version() from 1 for 1)) ascii#(#substring#(#version#(#)#from#1#for#1#)
length((select length(version()))) length#(#select#length#(#version#(#)
ascii(substring(length(version()),1,1)) ascii#(#substring#(#length#(#version#(#)#,#1#)
(select id from (select id,@v:=@v 1 as pos from articles y join (select @v:=0) k limit 255) x where pos=1) select#id#from#(#select#id#:=#v#1#as#pos#from#articles#y#join#(#select#v#)#k#limit#255#)#x#where#pos#)
(select id from (select id,@v:=@v 1 as pos from articles y join (select @v:=0) k limit 255) x where pos=ascii(substring(version() from 1 for 1))) select#id#from#(#select#id#:=#v#1#as#pos#from#articles#y#join#(#select#v#)#k#limit#255#)#x#where#pos#=#ascii#(#substring#(#version#(
vulnerable_site' and 1=5 or title=() #' vulnerable_site#and#title#=#(#)##'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.csg</groupId>
<artifactId>datapreprocess</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<!-- 最终打包的名字 -->
<finalName>datapreprocess</finalName>
<!-- 手动指定文件夹为resources加载java下所有的xml文件-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<!-- 生成的jar中包含pom.xml和pom.properties这两个文件 -->
<addMavenDescriptor>true</addMavenDescriptor>
<!-- 生成MANIFEST.MF的设置 -->
<manifest>
<!--这个属性特别关键如果没有这个属性有时候我们引用的包maven库
下面可能会有多个包,并且只有一个是正确的,其余的可能是带时间戳的,
此时会在classpath下面把那个带时间戳的给添加上去然后我们
在依赖打包的时候,打的是正确的,所以两头会对不上,报错。-->
<useUniqueVersions>false</useUniqueVersions>
<!-- 为依赖包添加路径, 这些路径会写在MANIFEST文件的Class-Path下 -->
<addClasspath>true</addClasspath>
<!-- 这个jar所依赖的jar包添加classPath的时候的前缀如果这个
jar本身和依赖包在同一级目录则不需要添加-->
<classpathPrefix>lib/</classpathPrefix>
<!-- jar启动入口类-->
<mainClass>Main</mainClass>
</manifest>
<manifestEntries>
<!-- 在Class-Path下添加配置文件的路径 -->
<Class-Path>../config/</Class-Path>
<!-- 假如这个项目可能要引入一些外部资源,但是你打包的时候并不想把
这些资源文件打进包里面,这个时候你必须在这边额外指定一些这些资源
文件的路径,这个位置指定的话,要根据你预期的这些位置去设置,我这边
所有jar都在lib下资源文件都在config下lib和config是同级的 -->
<!-- 同时还需要注意另外一个问题假如你的pom文件里面配置了
<scope>system</scope>,就是你依赖是你本地的资源,这个时候使用
这个插件classPath里面是不会添加所以你得手动把这个依赖添加进
这个地方,用空格隔开就行-->
</manifestEntries>
</archive>
<!-- jar包的位置-->
<outputDirectory>${project.build.directory}</outputDirectory>
<includes>
<!-- 打jar包时只打包class文件 -->
<!-- 有时候可能需要一些其他文件,这边可以配置,包括剔除的文件等等-->
<include>**/*.*</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,46 @@
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class Keyword {
HashMap<String, Character> keywordMap = new HashMap<String, Character>();
Keyword() {
String word;
char type;
Pattern wordpattern, typepattern;
Matcher matchedword, matchedtype;
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("Keywords.txt");
Scanner in = new Scanner(new BufferedInputStream(inputStream));
String line;
while (in.hasNextLine()) {
line = in.nextLine();
wordpattern = Pattern.compile("\\{\"(.*)\"");
typepattern = Pattern.compile("\'(.*)\'");
matchedword = wordpattern.matcher(line);
matchedtype = typepattern.matcher(line);
while (matchedword.find() && matchedtype.find()) {
word = matchedword.group(1);
type = matchedtype.group(1).charAt(0);
keywordMap.put(word, type);
}
}
in.close();
}
void printKeywordMap() {
for (String keyword : keywordMap.keySet()) {
String keytype = keywordMap.get(keyword).toString();
System.out.println("word: " + keyword + " type: " + keytype);
}
System.out.println("table size: " + keywordMap.size());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class Main {
public static void main(String[] args) throws UnsupportedEncodingException {
/* test a string */
Libinjection a = new Libinjection();
//personal change
String sql = "9999'||(select 99999999 from pg_sleep(15))||'9999";
/*
* urldecode
*/
try {
sql = URLDecoder.decode(sql, "UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
boolean issqli = a.libinjection_sqli(sql);
// System.out.println(a.getOutput());
// System.out.println(issqli);
/* test a file and output its results, with options to urldecode and time */
Test t = new Test();
// t.testfile("data/sqli.txt", "data/sqli_tokenize.txt", true, false);
if(args.length<2){
throw new RuntimeException("lack of parameters");
}
String queries_file = args[0];
String tokenize_file = args[1];
t.testfile(queries_file, tokenize_file, true, false);
/* performance test */
// Libinjection b = new Libinjection();
// String[] test = {
// "123 LIKE -1234.5678E+2;",
// "APPLE 19.123 'FOO' \"BAR\"",
// "/* BAR */ UNION ALL SELECT (2,3,4)",
// "1 || COS(+0X04) --FOOBAR",
// "dog apple @cat banana bar",
// "dog apple cat \"banana \'bar",
// "102 TABLE CLOTH",
// "(1001-'1') union select 1,2,3,4 from credit_cards"
// };
//
// /* print output for above inputs */
// for (int i = 0 ; i < test.length; i++) {
// b.libinjection_sqli(test[i]);
// System.out.println(b.getOutput());
// }
//
// /* let jvm optimize for 100000 iterations */
// for (int c = 0; c < 100000; c++) {
// b.libinjection_sqli(test[c % 8]);
// }
//
// /* time */
// double start = System.currentTimeMillis();
// for (int c = 0; c < 1000000; c++) {
// b.libinjection_sqli(test[c % 8]);
// }
// double end = System.currentTimeMillis();
//
// double total = (end - start) / 1000.0;
// double tps = 1000000.0 / total;
// System.out.println("iterations: " + 10000000 + "\ntotal time: " + total +" sec");
// System.out.println((int) tps + " / sec");
}
}

View File

@ -0,0 +1,50 @@
class State {
String s; /* input string */
int slen; /* length of input */
int fplen; /* length of fingerprint */
int flags; /* flag to indicate which mode we're running in: example.) flag_quote_none AND flag_sql_ansi */
int pos; /* index in string during tokenization */
int current; /* current position in tokenvec*/
int stats_comment_ddw;
int stats_comment_ddx;
int stats_comment_c; /* c-style comments found /x .. x/ */
int stats_comment_hash; /* '#' operators or MySQL EOL comments found */
int stats_folds;
int stats_tokens;
//personal change
Token[] tokenvec = new Token[35];//original 8
String fingerprint;
State(String s, int len, int flags) {
if (flags == 0) {
flags = Libinjection.FLAG_QUOTE_NONE | Libinjection.FLAG_SQL_ANSI;
}
this.s = s;
this.slen = len;
this.flags = flags;
this.current = 0;
}
void printState() {
System.out.printf("Input: %s\n", s);
System.out.printf("Length: %d\n", slen);
System.out.printf("Flags: %d\n", flags);
System.out.printf("Position in input string: %d\n", pos);
System.out.printf("Current token index: %d\n", current);
System.out.println("Tokenvec:");
if (tokenvec != null) {
int i = 0;
while (tokenvec[i] != null) {
System.out.printf("[%s ", tokenvec[i].type);
System.out.printf("%s] \n", tokenvec[i].val);
i++;
}
System.out.println();
}
}
}

View File

@ -0,0 +1,78 @@
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Scanner;
public class Test {
public long testfile(String inputfile, String outputfile, boolean decode, boolean time) {
long startTime;
long endTime;
long sum = 0;
int count = 0;
Scanner in = null;
PrintWriter out = null;
try {
in = new Scanner(new FileReader(inputfile));
out = new PrintWriter(outputfile, "UTF-8");
Libinjection libinjection = new Libinjection();
while (in.hasNextLine()) {
String line = in.nextLine();
/*
* urldecode
*/
if (decode) {
try {
line = URLDecoder.decode(line, "UTF-8");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
}
/*
* test and add result to outputfile
*/
if (time) {
startTime = System.currentTimeMillis();
libinjection.libinjection_sqli(line);
endTime = System.currentTimeMillis();
sum += (endTime - startTime);
count++;
} else {
libinjection.libinjection_sqli(line);
}
out.println(libinjection.getOutput());
}
if (time) {
System.out.println("Total: " + sum + " Average: " + sum/count);
}
return sum;
} catch (FileNotFoundException | UnsupportedEncodingException ex) {
System.out.println("file not found or unsupported encoding");
} finally {
if (in != null) {
try {
in.close();
in = null;
} catch(Exception ex3) { ex3.printStackTrace(); }
}
if (out != null) {
try {
out.close();
out = null;
} catch(Exception ex3) { ex3.printStackTrace(); }
}
}
return Integer.MIN_VALUE;
}
}

View File

@ -0,0 +1,28 @@
class Token {
char type;
char str_open;
char str_close;
String val;
int count;
// position and length of input in original string
int pos;
int len;
Token(int stype, int pos, int len, String val) {
this.type = (char) stype;
this.pos = pos;
this.len = len;
this.val = val;
}
void printToken() {
System.out.printf("Token represents %s\n", val);
System.out.printf("Type: %c\n", type);
System.out.printf("Position: %d\n", pos);
System.out.printf("Length: %d\n", len);
System.out.printf("Number of '@': %d\n", count);
System.out.printf("String open: %c\n", str_open);
System.out.printf("String close: %c\n", str_close);
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,446 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 19 08:39:20 2022
@author: LKJ
"""
import torch
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
from torch.nn import functional as F
from torchtext.vocab import vocab
from collections import Counter, OrderedDict
from torch.utils.data import Dataset, DataLoader
from torchtext.transforms import VocabTransform # 注意torchtext版本0.12+
import torch.optim as optim
import itertools
import random
import numpy as np
import warnings
warnings.filterwarnings("ignore")
############################第1部分CBOW模型加载sql语句得到对应的句向量###############################
def get_text():
# sentence_list = [ # 假设这是全部的训练语料
# "nlp drives computer programs that translate text from one language to another",
# "nlp combines computational linguistics rule based modeling of human language with statistical",
# "nlp model respond to text or voice data and respond with text",
# ]
p_sample = []
f = open("data\\p_sample.txt",encoding='utf-8')
while True:
line = f.readline()
if line:
line = line.split('\t')[1]
p_sample.append(line)
else:
break
f.close()
n_sample = []
f = open("data\\n_sample.txt",encoding='utf-8')
while True:
line = f.readline()
if line:
line = line.split('\t')[1]
n_sample.append(line)
else:
break
f.close()
sentence_list = p_sample + n_sample
return sentence_list
class CbowDataSet(Dataset):
def __init__(self, text_list, side_window=3):
"""
构造Word2vec的CBOW采样Dataset
:param text_list: 语料
:param side_window: 单侧正例构造背景词采样数总正例是2 * side_window
"""
super(CbowDataSet, self).__init__()
self.side_window = side_window
text_vocab, vocab_transform = self.reform_vocab(text_list)
self.text_list = text_list # 原始文本
self.text_vocab = text_vocab # torchtext的vocab
self.vocab_transform = vocab_transform # torchtext的vocab_transform
self.cbow_data = self.generate_cbow()
def __len__(self):
return len(self.cbow_data)
def __getitem__(self, idx):
data_row = self.cbow_data[idx]
return data_row[0], data_row[1]
def reform_vocab(self, text_list):
"""根据语料构造torchtext的vocab"""
total_word_list = []
for _ in text_list: # 将嵌套的列表([[xx,xx],[xx,xx]...])拉平 ([xx,xx,xx...])
total_word_list += _.split("#")
counter = Counter(total_word_list) # 统计计数
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 构造成可接受的格式:[(单词,num), ...]
ordered_dict = OrderedDict(sorted_by_freq_tuples)
# 开始构造 vocab
special_token = ["<UNK>", "<SEP>"] # 特殊字符
text_vocab = vocab(ordered_dict, specials=special_token) # 单词转tokenspecials里是特殊字符可以为空
text_vocab.set_default_index(0)
vocab_transform = VocabTransform(text_vocab)
return text_vocab, vocab_transform
def generate_cbow(self):
"""生成CBOW的训练数据"""
cbow_data = []
for sentence in self.text_list:
sentence_id_list = np.array(self.vocab_transform(sentence.split('#')))
for center_index in range(
self.side_window, len(sentence_id_list) - self.side_window): # 防止前面或后面取不到足够的值这是取index的上下界
pos_index = list(range(center_index - self.side_window, center_index + self.side_window + 1))
del pos_index[self.side_window]
cbow_data.append([sentence_id_list[center_index], sentence_id_list[pos_index]])
return cbow_data
def get_vocab_transform(self):
return self.vocab_transform
def get_vocab_size(self):
return len(self.text_vocab)
class Word2VecModel(nn.Module):
def __init__(self, vocab_size, batch_size, word_embedding_size=32, hidden=64):
"""
Word2vec模型CBOW实现
:param vocab_size: 单词个数
:param word_embedding_size: 每个词的词向量维度
:param hidden: 隐层维度
"""
super(Word2VecModel, self).__init__()
self.vocab_size = vocab_size
self.word_embedding_size = word_embedding_size
self.hidden = hidden
self.batch_size = batch_size
self.word_embedding = nn.Embedding(self.vocab_size, self.word_embedding_size) # token对应的embedding
# 建模
self.linear_in = nn.Linear(self.word_embedding_size, self.hidden)
self.linear_out = nn.Linear(self.hidden, self.vocab_size)
def forward(self, input_labels):
around_embedding = self.word_embedding(input_labels)
avg_around_embedding = torch.mean(around_embedding, dim=1) # 1. 输入的词向量对应位置求平均
in_emb = F.relu(self.linear_in(avg_around_embedding)) # 2. 过第一个linear使用relu激活函数
out_emb = F.log_softmax(self.linear_out(in_emb)) # 3. 过第二个linear得到维度是[batch_size, 单词总数]
return out_emb
def get_embedding(self, token_list: list):
return self.word_embedding(torch.Tensor(token_list).long())
#######################################第2部分resnet模型进行句向量的分类####################################
def conv3x3(in_planes, out_planes, stride=1):
"3x3 convolution with padding"
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
class ChannelAttention(nn.Module):
def __init__(self, in_planes, ratio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc1 = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
self.relu1 = nn.ReLU()
self.fc2 = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
out = avg_out + max_out
return self.sigmoid(out)
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
padding = 3 if kernel_size == 7 else 1
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avg_out, max_out], dim=1)
x = self.conv1(x)
return self.sigmoid(x)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.ca = ChannelAttention(planes)
self.sa = SpatialAttention()
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.ca(out) * out
out = self.sa(out) * out
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.ca = ChannelAttention(planes * 4)
self.sa = SpatialAttention()
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
out = self.ca(out) * out
out = self.sa(out) * out
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=2):
self.inplanes = 32
super(ResNet, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 16, layers[0])
self.layer2 = self._make_layer(block, 16, layers[1], stride=2)
self.layer3 = self._make_layer(block, 16, layers[2], stride=2)
self.layer4 = self._make_layer(block, 16, layers[3], stride=2)
self.avgpool = nn.AvgPool2d(7, stride=1)
self.fc = nn.Linear(400 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
def _make_layer(self, block, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
# x = self.bn1(x)
x = self.relu(x)
# x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
# x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
def resnet18_cbam(pretrained=False, **kwargs):
"""Constructs a ResNet-18 model.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
"""
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
return model
########################################第三部分,执行分类####################################################
batch_size = 1
sentence_list = get_text()
cbow_data_set = CbowDataSet(sentence_list) # 构造 DataSet
word2vec_model = Word2VecModel(cbow_data_set.get_vocab_size(), batch_size)
word2vec_model.load_state_dict(torch.load('word2vec_model_final.pth')) #加载预训练好的词向量映射模型
#读取正负样本
p_sample_label = []
f = open("data\\p_sample.txt",encoding='utf-8') #加载正样本
while True:
line = f.readline()
if line:
line = line.split('\t')[1]
label_c = [1]
sample_label = []
sample_label.append(line)
sample_label.append(label_c)
p_sample_label.append(sample_label)
else:
break
f.close()
n_sample_label = []
f = open("data\\n_sample.txt",encoding='utf-8') #加载负样本
while True:
line = f.readline()
if line:
line = line.split('\t')[1]
label_c = [0]
sample_label = []
sample_label.append(line)
sample_label.append(label_c)
n_sample_label.append(sample_label)
else:
break
f.close()
t_sample_label = p_sample_label + n_sample_label
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
mymodel = resnet18_cbam().to(device)
total = 0
correct = 0
EPOCH = 10
criterion = nn.CrossEntropyLoss() #损失函数为交叉熵,多用于多分类问题
#开始训练
for epoch in range(EPOCH):
fc_loss_all = 0.0
optimizer = optim.Adam(itertools.chain(mymodel.parameters(),), lr=0.001, weight_decay=2e-4)
sum_loss = 0.0
correct = 0.0
total = 0.0
acc = 0
random.shuffle(t_sample_label)
for i in range(len(t_sample_label)):
sentence = t_sample_label[i][0]
label = torch.tensor(t_sample_label[i][1]).reshape(1)
vocab_transform = cbow_data_set.get_vocab_transform()
sentence_ids = vocab_transform(sentence.split('#'))
sentence_embedding = word2vec_model.get_embedding(sentence_ids)
w,h = sentence_embedding.shape
if w < 32:
pad = nn.ZeroPad2d(padding=(0, 0, 0, 32-w))
sentence_embedding = pad(sentence_embedding)
sentence_embedding = sentence_embedding.reshape(1,1,32,32)
if w > 32:
sentence_embedding = sentence_embedding.reshape(1,1,w,h)
sentence_embedding = F.interpolate(sentence_embedding, size=[32, 32])
out = mymodel(sentence_embedding)
optimizer.zero_grad()
loss = criterion(out, label)
loss.backward() #反向传播
optimizer.step()
print('\nEpoch: %d finish' % (epoch + 1))
torch.save(mymodel.state_dict(), 'resnet18.pth') #保持预训练好的resnet模型
# sentence = "se"
# vocab_transform = cbow_data_set.get_vocab_transform()
# sentence_ids = vocab_transform(sentence.split('#'))
# sentence_embedding = word2vec_model.get_embedding(sentence_ids)
# a = sentence_embedding
# pad = nn.ZeroPad2d(padding=(0, 0, 0, 23))
# a = pad(a)
# a = a.reshape(1,1,32,32)
# out = mymodel(a)
# _, predicted = torch.max(out, 1)
# labels = 0
# total += 1
# correct += (predicted.numpy() == labels).sum()
# # a = torch.rand(1,1,64,32)
# # a = F.interpolate(a, size=[32, 32])
# print("准确率为:")
# print(correct / total)

View File

@ -0,0 +1,446 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 19 08:39:20 2022
@author: LKJ
"""
import torch
import torch.nn as nn
import math
import torch.utils.model_zoo as model_zoo
from torch.nn import functional as F
from torchtext.vocab import vocab
from collections import Counter, OrderedDict
from torch.utils.data import Dataset, DataLoader
from torchtext.transforms import VocabTransform # 注意torchtext版本0.12+
import torch.optim as optim
import itertools
import random
import numpy as np
import warnings
import sys
warnings.filterwarnings("ignore")
############################第1部分CBOW模型加载sql语句得到对应的句向量###############################
def get_text():
# sentence_list = [ # 假设这是全部的训练语料
# "nlp drives computer programs that translate text from one language to another",
# "nlp combines computational linguistics rule based modeling of human language with statistical",
# "nlp model respond to text or voice data and respond with text",
# ]
p_sample = []
f = open("data/p_sample.txt",encoding='utf-8')
while True:
line = f.readline()
if line:
split = line.split('\t')
if len(split)==2 :
line = split[1]
p_sample.append(line)
else:
break
f.close()
n_sample = []
f = open("data/n_sample.txt",encoding='utf-8')
while True:
line = f.readline()
if line:
split = line.split('\t')
if len(split)==2 :
line = split[1]
n_sample.append(line)
else:
break
f.close()
sentence_list = p_sample + n_sample
return sentence_list
class CbowDataSet(Dataset):
def __init__(self, text_list, side_window=3):
"""
构造Word2vec的CBOW采样Dataset
:param text_list: 语料
:param side_window: 单侧正例构造背景词采样数总正例是2 * side_window
"""
super(CbowDataSet, self).__init__()
self.side_window = side_window
text_vocab, vocab_transform = self.reform_vocab(text_list)
self.text_list = text_list # 原始文本
self.text_vocab = text_vocab # torchtext的vocab
self.vocab_transform = vocab_transform # torchtext的vocab_transform
self.cbow_data = self.generate_cbow()
def __len__(self):
return len(self.cbow_data)
def __getitem__(self, idx):
data_row = self.cbow_data[idx]
return data_row[0], data_row[1]
def reform_vocab(self, text_list):
"""根据语料构造torchtext的vocab"""
total_word_list = []
for _ in text_list: # 将嵌套的列表([[xx,xx],[xx,xx]...])拉平 ([xx,xx,xx...])
total_word_list += _.split("#")
counter = Counter(total_word_list) # 统计计数
sorted_by_freq_tuples = sorted(counter.items(), key=lambda x: x[1], reverse=True) # 构造成可接受的格式:[(单词,num), ...]
ordered_dict = OrderedDict(sorted_by_freq_tuples)
# 开始构造 vocab
special_token = ["<UNK>", "<SEP>"] # 特殊字符
text_vocab = vocab(ordered_dict, specials=special_token) # 单词转tokenspecials里是特殊字符可以为空
text_vocab.set_default_index(0)
vocab_transform = VocabTransform(text_vocab)
return text_vocab, vocab_transform
def generate_cbow(self):
"""生成CBOW的训练数据"""
cbow_data = []
for sentence in self.text_list:
sentence_id_list = np.array(self.vocab_transform(sentence.split('#')))
for center_index in range(
self.side_window, len(sentence_id_list) - self.side_window): # 防止前面或后面取不到足够的值这是取index的上下界
pos_index = list(range(center_index - self.side_window, center_index + self.side_window + 1))
del pos_index[self.side_window]
cbow_data.append([sentence_id_list[center_index], sentence_id_list[pos_index]])
return cbow_data
def get_vocab_transform(self):
return self.vocab_transform
def get_vocab_size(self):
return len(self.text_vocab)
class Word2VecModel(nn.Module):
def __init__(self, vocab_size, batch_size, word_embedding_size=32, hidden=64):
"""
Word2vec模型CBOW实现
:param vocab_size: 单词个数
:param word_embedding_size: 每个词的词向量维度
:param hidden: 隐层维度
"""
super(Word2VecModel, self).__init__()
self.vocab_size = vocab_size
self.word_embedding_size = word_embedding_size
self.hidden = hidden
self.batch_size = batch_size
self.word_embedding = nn.Embedding(self.vocab_size, self.word_embedding_size) # token对应的embedding
# 建模
self.linear_in = nn.Linear(self.word_embedding_size, self.hidden)
self.linear_out = nn.Linear(self.hidden, self.vocab_size)
def forward(self, input_labels):
around_embedding = self.word_embedding(input_labels)
avg_around_embedding = torch.mean(around_embedding, dim=1) # 1. 输入的词向量对应位置求平均
in_emb = F.relu(self.linear_in(avg_around_embedding)) # 2. 过第一个linear使用relu激活函数
out_emb = F.log_softmax(self.linear_out(in_emb)) # 3. 过第二个linear得到维度是[batch_size, 单词总数]
return out_emb
def get_embedding(self, token_list: list):
return self.word_embedding(torch.Tensor(token_list).long())
#######################################第2部分resnet模型进行句向量的分类####################################
def conv3x3(in_planes, out_planes, stride=1):
"3x3 convolution with padding"
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
class ChannelAttention(nn.Module):
def __init__(self, in_planes, ratio=16):
super(ChannelAttention, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.fc1 = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
self.relu1 = nn.ReLU()
self.fc2 = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
out = avg_out + max_out
return self.sigmoid(out)
class SpatialAttention(nn.Module):
def __init__(self, kernel_size=7):
super(SpatialAttention, self).__init__()
assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
padding = 3 if kernel_size == 7 else 1
self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
avg_out = torch.mean(x, dim=1, keepdim=True)
max_out, _ = torch.max(x, dim=1, keepdim=True)
x = torch.cat([avg_out, max_out], dim=1)
x = self.conv1(x)
return self.sigmoid(x)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(inplanes, planes, stride)
self.bn1 = nn.BatchNorm2d(planes)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(planes, planes)
self.bn2 = nn.BatchNorm2d(planes)
self.ca = ChannelAttention(planes)
self.sa = SpatialAttention()
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.ca(out) * out
out = self.sa(out) * out
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, inplanes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(planes * 4)
self.relu = nn.ReLU(inplace=True)
self.ca = ChannelAttention(planes * 4)
self.sa = SpatialAttention()
self.downsample = downsample
self.stride = stride
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
out = self.ca(out) * out
out = self.sa(out) * out
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=2):
self.inplanes = 32
super(ResNet, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 16, layers[0])
self.layer2 = self._make_layer(block, 16, layers[1], stride=2)
self.layer3 = self._make_layer(block, 16, layers[2], stride=2)
self.layer4 = self._make_layer(block, 16, layers[3], stride=2)
self.avgpool = nn.AvgPool2d(7, stride=1)
self.fc = nn.Linear(400 * block.expansion, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
def _make_layer(self, block, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample))
self.inplanes = planes * block.expansion
for i in range(1, blocks):
layers.append(block(self.inplanes, planes))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
# x = self.bn1(x)
x = self.relu(x)
# x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
# x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
def resnet18_cbam(pretrained=False, **kwargs):
"""Constructs a ResNet-18 model.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
"""
model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs)
return model
########################################第三部分,执行分类####################################################
batch_size = 1
sentence_list = get_text()
cbow_data_set = CbowDataSet(sentence_list) # 构造 DataSet
word2vec_model = Word2VecModel(cbow_data_set.get_vocab_size(), batch_size)
word2vec_model.load_state_dict(torch.load('word2vec_model_final.pth')) #加载预训练好的词向量映射模型
load_filename = sys.argv[1]
save_filename = sys.argv[2]
#读取样本
test_sample = []
# f = open("data\\test_sample.txt",encoding='utf-8') #加载正样本
f = open(load_filename,encoding='utf-8') #加载正样本
while True:
line = f.readline()
if line:
# line = line.split('\t')[1]
test_sample.append(line)
else:
break
f.close()
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
mymodel = resnet18_cbam().to(device)
total = 0
correct = 0
#开始训练
for epoch in range(1):
fc_loss_all = 0.0
sum_loss = 0.0
correct = 0.0
total = 0.0
acc = 0
l=[]
for i in range(len(test_sample)):
sentence = test_sample[i].split('\t')[1]
sentence = sentence.split('\n')[0]
vocab_transform = cbow_data_set.get_vocab_transform()
sentence_ids = vocab_transform(sentence.split('#'))
sentence_embedding = word2vec_model.get_embedding(sentence_ids)
w,h = sentence_embedding.shape
if w < 32:
pad = nn.ZeroPad2d(padding=(0, 0, 0, 32-w))
sentence_embedding = pad(sentence_embedding)
sentence_embedding = sentence_embedding.reshape(1,1,32,32)
if w > 32:
sentence_embedding = sentence_embedding.reshape(1,1,w,h)
sentence_embedding = F.interpolate(sentence_embedding, size=[32, 32])
out = mymodel(sentence_embedding)
_, predicted = torch.max(out.data, 1)
out = F.softmax(out, dim=1)
p_value = out[0][predicted.detach().numpy()[0]].detach().numpy()
l.append(test_sample[i].split('\t')[0] + '\t' + str(p_value))
# f=open("data\\sqli_result.txt","w") #将预测结果存放到txt文件中
f=open(save_filename,"w") #将预测结果存放到txt文件中
for line in l:
f.write(line+'\n')
f.close()
print('finish testing')
# sentence = "se"
# vocab_transform = cbow_data_set.get_vocab_transform()
# sentence_ids = vocab_transform(sentence.split('#'))
# sentence_embedding = word2vec_model.get_embedding(sentence_ids)
# a = sentence_embedding
# pad = nn.ZeroPad2d(padding=(0, 0, 0, 23))
# a = pad(a)
# a = a.reshape(1,1,32,32)
# out = mymodel(a)
# _, predicted = torch.max(out, 1)
# labels = 0
# total += 1
# correct += (predicted.numpy() == labels).sum()
# # a = torch.rand(1,1,64,32)
# # a = F.interpolate(a, size=[32, 32])
# print("准确率为:")
# print(correct / total)

10
StrongerGS/gsdiff/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
/target/
nbactions.xml
lib
build
.settings
.classpath
.project
dist
.idea/
*.iml

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: cz.startnet.utils.pgdiff.Main

View File

@ -0,0 +1,10 @@
# openGauss Diff Tool (aka gsdiff)
openGauss Diff Tool is free openGauss diff tool that is useful for
comparison/diffing of database schemas.
To use
1 $cd /out/artifacts/gsdiff
2 $java -jar gsdiff.jar database_1.dump database_2.dump
or
$java -jar gsdiff.jar database_1.dump database_2.dump > filename

View File

@ -0,0 +1,10 @@
/target/
nbactions.xml
lib
build
.settings
.classpath
.project
dist
.idea/
*.iml

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: cz.startnet.utils.pgdiff.Main

View File

@ -0,0 +1,212 @@
# Another PostgreSQL Diff Tool (aka apgdiff)
Another PostgreSQL Diff Tool is free PostgreSQL diff tool that is useful for
comparison/diffing of database schemas. You can find more information at its
website at http://www.apgdiff.com/. If you found an issue in apgdiff, please
file it at https://github.com/fordfrog/apgdiff/issues. If you for some reason
need to contact author of this application, you can email him at
fordfrog@fordfrog.com.
This repo is mainly unmaintained. But if you found a bug and create a pull request chances are good that it will be merged.
## Changelog
### Version 2.7.0
#### New Features
* Initial Support to PARTITION BY on Create Table jalissonmello
* Initial suport to identity column. #277 jalissonmello
* Support Postgresql 12 -Add support for FUNCTION in CREATE TRIGGER #273 alexander-smyslov
* Add supports for CREATE SEQUENCE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS. UltramanJack
#### Fixes
* Fix incorrect disable trigger parsing of quoted identifiers. Bugfixes #271 marcus-kempe
* fix for null pointer exception #270 marco44
* Fix issue where rule parsing was throwing # 269-marcus-kempe
* fixed some crashes #257 d1maxa
* Error on disable/enable trigger on trigger that dont exists on old schema.
* Drop and Add constraint if table exists, issue #252
### Version 2.6
#### New Features
* PostgresSQL 10 Support
* Update dependencies to development with Java 11
* Add security barrier and another options to Views
* Add support to Create/Drop Rule
* Triggers: Rerencing,Enable,Disable
* use using column::type to cast the type of column
#### Fixes
* Fix GRANT SEQUENCE, ALTER VIEW OWNER, GRANT(cols) errors
#### Tasks Done
* Remove SourceForge repository
### Version 2.5
#### New Features
* MATERIALIZED VIEW support in PostgreSQL 9.3 (Marti Raudsepp)
* Better support for inherited tables (Daniel Watson)
* Added support for CREATE UNLOGGED TABLE (Anatoliy Basov)
* Added support for /**/ comments (yulei)
* Support of triggers for views + clause 'INSTEAD OF' (Sergej Bonich)
* Add support for GRANT and REVOKE on objects: table, view, sequence, column (serge-pouliquen-itf)
* Add support for ALTER TABLE ... OWNER TO (serge-pouliquen-itf)
* Add support for CREATE TYPE (Karol Rybak)
* Add support for CREATE EXTENSION (Átila Camurça Alves)
* Add basic support for CREATE FOREIGN TABLE (Bruno Almeida)
#### Fixes
* Added hint to use "CREATE TABLE ... CONSTRAINT name PRIMARY KEY/UNIQUE ..."
instead of "CREATE TABLE ... PRIMARY KEY/UNIQUE ..." because apgdiff cannot
easily support unnamed constraints.
* Fixed issue with incorrect end of expression detection because of ignored [
and ] brackets. This caused issues for example in statements like
"... DEFAULT ARRAY[1, 2, 3], ..." where end of expression was detected at
first comma (and not the third one) which then resulted in parser exception.
* Fixed issue when outputting unsupported command information and the
unsupported command string is shorter than 20 characters. (Linas Valiukas)
* Added Spanish translation. (Sebastian Ortiz)
* Fitted English help to 80 characters in width. (Dave Jarvis)
* View query changes are now correctly detected even if it has declared
columns that didn't change. (Marti Raudsepp)
* Fixed issue with the $ sign in the object name (Anatoliy Basov)
* Added French translation. (Jeremy Passeron)
* Native for OS line endings in resulting diff (Sergej Bonich)
* Add support for new Postgres schema dump format
* Support for Postgres 10 CREATE SEQUENCE data type
### 2012-09-21: Version 2.4
#### New Features
* Added support for ALTER SEQUENCE OWNED BY (patch by Mikhail Petrov).
* Added support for CREATE TRIGGER ... UPDATE OF column.
* Added switch --ignore-slony-triggers which causes that Slony triggers
_slony_logtrigger and _slony_denyaccess are completely ignored during parsing
and diffing.
* Added switch --ignore-schema-creation which removes the need of CREATE SCHEMA
declararions in the input files.
#### Fixes
* Fixed issue with comments not being added on newly created columns.
* Improved logging errors when parsing strings.
* Added support for IF NOT EXISTS (patch by Felipe Sateler).
* Fixed NPE when search_path contains quoted schema (patch by Steven Elliott).
* Fixed dropping of default values when --add-defaults is specified (patch by
Jim Mlodgenski).
* Fixed all bugs related to incorrect parsing of end of statement, most often
resulting in StringIndexOutOfBoundException.
* Fixed CREATE TABLE statement output when table contains no column.
### 2010-10-22: Version 2.3
#### New Features
* Added support for diffing of COMMENT ON statements.
* Added switch --list-charsets to output list of supported charsets.
#### Fixes
* Added user error messages instead of NullPointerException for cases when
referenced database object was not found in the dump.
* Fixed bug with parsing quoted object name when both first and second part of
the name was quoted.
* Fixed bug in parser that caused in some cases invalid match against expected
"word".
* Fixed bug in parser that caused array data types not detected correctly (was
caused by fix of invalid match of "word" above).
* Functions are now created after tables are create and updated, because of
functions depending on tables and columns. Later will be implemented solution
for cases where functions have to be created before table columns.
### 2010-10-09: Version 2.2.2
#### Fixes
* Added missing new line after ALTER VIEW ... ALTER COLUMN ... SET/DROP DEFAULT.
* Fixed parsing of quoted string values.
* Fixed detection of function body separator (did not work when there was
another 'AS' specified on the same line after the 'AS' starting function
body).
* If two dumps are completely same and there is more than one schema in the
dumps, the output now does not contain 'SET search_path = ...'. In other
words, if two dumps are completely same, no output is produced.
* Replaced 'ALTER VIEW name ALTER COLUMN ...' with 'ALTER TABLE view_name ALTER
COLUMN ...' to make it compatible with PostgreSQL releases prior to 8.4.
* Fixed parsing of '' escapes.
#### Other
* Added support for localization of apgdiff.
* Added Czech localization.
### 2010-10-03: Version 2.2.1
#### New Features
* Commands like OWNER TO and ENABLE/DISABLE TRIGGER/RULE are now added to the
diff output even for commands that are otherwise being parsed, like ALTER
TABLE.
#### Fixes
* Fixed bug where default values were dropped from VIEW columns even if they
were not modified.
### 2010-10-02: Version 2.2
#### New Features
* Statements that are not supported by apgdiff yet are now all ignored(till this
release apgdiff had to be instructed to know what commands to ignore which was
not good solution). This is the same behavior as in 1.* releases.
* Added command line switch <code>--output-ignored-statements</code> which can
be used to output statements that apgdiff ignores in the dump files. This
feature makes more clear what statements were ignored and developer has to
handle them manually eventually.
### 2010-09-30: Version 2.1
#### New Features
* Added support for ALTER VIEW.
* Added support for ALTER TABLE view_name/sequence_name.
#### Fixes
* Fixed issue with comparison of VIEWs when columns are not specified but query
has changed.
* Fixed parsing of quoted names at many places.
* CREATE RULE is now silently skipped.
### 2010-09-16: Version 2.0.2
#### Fixes
* CREATE DOMAIN is now silently skipped.
### 2010-09-16: Version 2.0.1
#### Fixes
* CREATE OPERATOR and ALTER LANGUAGE are now silently skipped.
### 2010-09-13: Version 2.0 Including Beta Releases
#### New Features
* SQL parser has been completely rewritten to allow safer and more flexible
parsing of SQL statements.
* Statements not supported by apgdiff are now not silenty ignored if apgdiff is
not told (by me in code) to ignore them.
* Added support for ALTER TABLE ... ALTER COLUMN ... SET STORAGE
PLAIN|EXTERNAL|EXTENDED|MAIN.
* Added support for CREATE TABLE ... TABLESPACE.
* Updated parsing of CREATE TABLE ... WITH/WITHOUT OIDS.
* Added support for CREATE TRIGGER ... WHEN and for even TRUNCATE.
* Added support for CREATE SEQUENCE ... OWNED BY.
* CREATE SCHEMA is now supported for both syntaxes.
* Added support for default values on function arguments.
* Added support for parsing ALTER TABLE ... ENABLE/DISABLE TRIGGER/PARSER, but
they are not diffed for now.
#### Fixes
* ALTER SEQUENCE and CREATE AGGREGATE are now silently skipped.
* Fixed parsing of end of function.
* Improved handling of dotted quoted names.
* Fixed quoting of SQL reserved keywords.
* Fixed parsing of function arguments.
* Triggers are now dropped before functions are dropped (bug #2991245).
* Improved diffing of CREATE TABLE ... INHERITS.
### Versions Prior to 2.0
These versions are not covered in changelog.

View File

@ -0,0 +1,112 @@
<project name="apgdiff" default="jar" basedir=".">
<property name="name" value="Another PostgreSQL Diff Tool"/>
<property name="version" value="2.7"/>
<property name="build" value="build"/>
<property name="dist" value="dist"/>
<property name="build.output" value="${build}/classes"/>
<property name="dist.javadoc" value="${dist}/javadoc"/>
<property name="build.final.name" value="${ant.project.name}-${version}"/>
<property name="lib" value="lib"/>
<property name="test.reports" value="${build}/test-reports"/>
<property name="test.output" value="${build}/test-classes"/>
<property name="src" value="src/main/java"/>
<property name="src.resources" value="src/main/resources"/>
<property name="test.src" value="src/test/java"/>
<property name="test.resources.src" value="src/test/resources"/>
<property name="source" value="1.8"/>
<property name="target" value="1.8"/>
<property name="main.class" value="cz.startnet.utils.pgdiff.Main"/>
<path id="build.classpath">
<fileset dir="${lib}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="init" description="Initializes build environment">
<tstamp/>
</target>
<target name="clean" description="Clean the output directory">
<delete dir="${build}"/>
<delete dir="${dist}"/>
<delete dir="${lib}"/>
</target>
<target name="compile" depends="init,get-deps"
description="Compile the code">
<mkdir dir="${build.output}"/>
<javac destdir="${build.output}" srcdir="${src}" source="${source}"
target="${target}" debug="true" deprecation="true"
optimize="false"/>
<copy todir="${build.output}">
<fileset dir="${src.resources}"/>
</copy>
</target>
<target name="jar" depends="compile" description="Clean the JAR">
<mkdir dir="${dist}"/>
<jar jarfile="${dist}/${build.final.name}.jar"
basedir="${build.output}">
<manifest>
<attribute name="Main-Class" value="${main.class}"/>
</manifest>
</jar>
</target>
<target name="compile-tests" depends="compile"
description="Compile the test code">
<mkdir dir="${test.output}"/>
<javac destdir="${test.output}" srcdir="${test.src}" source="${source}"
target="${target}" debug="true" deprecation="true"
optimize="false">
<classpath>
<path refid="build.classpath"/>
<pathelement location="${build.output}"/>
</classpath>
</javac>
<copy todir="${test.output}">
<fileset dir="${test.resources.src}"/>
</copy>
</target>
<target name="test" depends="compile-tests"
description="Run the test cases">
<mkdir dir="${test.reports}"/>
<junit printSummary="yes" haltonerror="true" haltonfailure="true"
fork="false" dir=".">
<sysproperty key="basedir" value="."/>
<formatter type="plain" usefile="false"/>
<classpath>
<path refid="build.classpath"/>
<pathelement location="${build.output}"/>
<pathelement location="${test.output}"/>
</classpath>
<batchtest todir="${test.reports}">
<fileset dir="${test.src}">
<include name="**/*Test.java"/>
</fileset>
</batchtest>
</junit>
</target>
<target name="get-deps" description="Download all dependencies"
unless="noget">
<mkdir dir="${lib}"/>
<get src="http://central.maven.org/maven2/junit/junit/4.8.1/junit-4.8.1.jar"
dest="${lib}/junit.jar"/>
<get src="http://central.maven.org/maven2/org/hamcrest/hamcrest-all/1.1/hamcrest-all-1.1.jar"
dest="${lib}/hamcrest-all.jar"/>
</target>
<target name="javadoc" description="Generates javadoc">
<delete dir="doc"/>
<mkdir dir="doc"/>
<javadoc packagenames="cz.*" sourcepath="${src}"
destdir="${dist.javadoc}" classpathref="build.classpath"
author="true" version="true" private="false" use="true"
splitindex="true" windowtitle="${name} ${version}"
doctitle="${name} ${version}"/>
</target>
</project>

View File

@ -0,0 +1,18 @@
Copyright (c) 2006 StartNet s.r.o.
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,195 @@
--
-- openGauss database dump
--
SET statement_timeout = 0;
SET xmloption = content;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET search_path = public;
--
-- Name: tri_insert_func(); Type: FUNCTION; Schema: public; Owner: omm
--
CREATE FUNCTION tri_insert_func() RETURNS trigger
LANGUAGE plpgsql NOT SHIPPABLE
AS $$
DECLARE
BEGIN
INSERT INTO test_trigger VALUES(NEW.account_id, NEW.first_name, NEW.last_name);
RETURN NEW;
END
$$;
ALTER FUNCTION public.tri_insert_func() OWNER TO omm;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: customer; Type: TABLE; Schema: public; Owner: omm; Tablespace:
--
CREATE TABLE customer (
customer_id integer,
account_id integer,
first_name character(10),
last_name character varying(20) DEFAULT ' '::character varying
)
WITH (orientation=row, compression=no);
ALTER TABLE customer ALTER COLUMN first_name SET STORAGE MAIN;
ALTER TABLE public.customer OWNER TO omm;
--
-- Name: myview; Type: VIEW; Schema: public; Owner: omm
--
CREATE VIEW myview(account_id,first_name) AS
SELECT customer.account_id, customer.first_name FROM customer;
ALTER VIEW public.myview OWNER TO omm;
--
-- Name: test; Type: TABLE; Schema: public; Owner: yy; Tablespace:
--
CREATE TABLE test (
t integer
)
WITH (orientation=row, compression=no);
ALTER TABLE public.test OWNER TO yy;
--
-- Name: test_trigger; Type: TABLE; Schema: public; Owner: omm; Tablespace:
--
CREATE TABLE test_trigger (
customer_id integer,
account_id integer,
first_name character(10),
last_name character varying(20)
)
WITH (orientation=row, compression=no);
ALTER TABLE public.test_trigger OWNER TO omm;
--
-- Name: serial1; Type: SEQUENCE; Schema: public; Owner: omm
--
CREATE SEQUENCE serial1
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 20;
--
-- Name: serial1; Type: LARGE SEQUENCE OWNED BY; Schema: public; Owner: omm
--
ALTER SEQUENCE serial1 OWNED BY customer.customer_id;
--
-- Name: customer_id; Type: DEFAULT; Schema: public; Owner: omm
--
ALTER TABLE customer ALTER COLUMN customer_id SET DEFAULT nextval('serial1'::regclass);
--
-- Data for Name: customer; Type: TABLE DATA; Schema: public; Owner: omm
--
COPY customer (customer_id, account_id, first_name, last_name) FROM stdin;
1 1 Job Lee
\.
;
--
-- Name: serial1; Type: SEQUENCE SET; Schema: public; Owner: omm
--
SELECT pg_catalog.setval('serial1', 20, true);
--
-- Data for Name: test; Type: TABLE DATA; Schema: public; Owner: yy
--
COPY test (t) FROM stdin;
\.
;
--
-- Data for Name: test_trigger; Type: TABLE DATA; Schema: public; Owner: omm
--
COPY test_trigger (customer_id, account_id, first_name, last_name) FROM stdin;
\.
;
--
-- Name: first_name_index; Type: INDEX; Schema: public; Owner: omm; Tablespace:
--
CREATE INDEX first_name_index ON customer USING btree (first_name) TABLESPACE pg_default;
ALTER TABLE customer CLUSTER ON first_name_index;
--
-- Name: _RETURN; Type: RULE; Schema: public; Owner: omm
--
SET enable_cluster_resize = on;
CREATE RULE "_RETURN" AS ON SELECT TO test_trigger DO INSTEAD SELECT customer.customer_id, customer.account_id, customer.first_name, customer.last_name FROM customer;
--
-- Name: _RETURN; Type: RULE; Schema: public; Owner: omm
--
SET enable_cluster_resize = on;
CREATE RULE "_RETURN" AS ON SELECT TO customer DO INSTEAD SELECT test_trigger.customer_id, test_trigger.account_id, test_trigger.first_name, test_trigger.last_name FROM test_trigger;
ALTER TABLE customer DISABLE RULE "_RETURN";
--
-- Name: insert_trigger; Type: TRIGGER; Schema: public; Owner: omm
--
CREATE TRIGGER insert_trigger BEFORE INSERT ON customer FOR EACH ROW EXECUTE PROCEDURE tri_insert_func();
ALTER TABLE customer DISABLE TRIGGER insert_trigger;
--
-- Name: public; Type: ACL; Schema: -; Owner: omm
--
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM omm;
GRANT CREATE,USAGE ON SCHEMA public TO omm;
GRANT USAGE ON SCHEMA public TO PUBLIC;
--
-- openGauss database dump complete
--

View File

@ -0,0 +1,54 @@
--
-- openGauss database dump
--
SET statement_timeout = 0;
SET xmloption = content;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET search_path = public;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: customer; Type: TABLE; Schema: public; Owner: omm; Tablespace:
--
CREATE TABLE customer (
customer_id integer,
account_id integer,
first_name character(10),
last_name character(10)
)
WITH (orientation=row, compression=no);
ALTER TABLE public.customer OWNER TO omm;
--
-- Data for Name: customer; Type: TABLE DATA; Schema: public; Owner: omm
--
COPY customer (customer_id, account_id, first_name, last_name) FROM stdin;
\.
;
--
-- Name: public; Type: ACL; Schema: -; Owner: omm
--
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM omm;
GRANT CREATE,USAGE ON SCHEMA public TO omm;
GRANT USAGE ON SCHEMA public TO PUBLIC;
--
-- openGauss database dump complete
--

View File

@ -0,0 +1,68 @@
CREATE SEQUENCE serial1
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 20;
CREATE TABLE test (
t integer
)
WITH (orientation=row, compression=no);
ALTER TABLE test OWNER TO yy;
CREATE TABLE test_trigger (
customer_id integer,
account_id integer,
first_name character(10),
last_name character varying(20)
)
WITH (orientation=row, compression=no);
ALTER TABLE test_trigger OWNER TO omm;
ALTER TABLE customer
ALTER COLUMN customer_id SET DEFAULT nextval('serial1'::regclass),
ALTER COLUMN last_name TYPE character varying(20) USING last_name::character varying(20) /* TYPE change - table: customer original: character(10) new: character varying(20) */,
ALTER COLUMN last_name SET DEFAULT ' '::character varying;
ALTER TABLE ONLY customer ALTER COLUMN first_name SET STORAGE MAIN;
ALTER SEQUENCE serial1
OWNED BY customer.customer_id;
CREATE OR REPLACE FUNCTION tri_insert_func() RETURNS trigger
LANGUAGE plpgsql NOT SHIPPABLE
AS $$
DECLARE
BEGIN
INSERT INTO test_trigger VALUES(NEW.account_id, NEW.first_name, NEW.last_name);
RETURN NEW;
END
$$;
CREATE INDEX first_name_index ON customer USING btree (first_name) TABLESPACE pg_default;
ALTER TABLE customer CLUSTER ON first_name_index;
CREATE TRIGGER insert_trigger
BEFORE INSERT ON customer
FOR EACH ROW
EXECUTE PROCEDURE tri_insert_func();
ALTER TABLE customer DISABLE TRIGGER insert_trigger;
CREATE VIEW myview (account_id, first_name) AS
SELECT customer.account_id, customer.first_name FROM customer;
ALTER VIEW myview OWNER TO omm;
CREATE RULE "_RETURN" AS
ON SELECT TO customer
DO INSTEAD SELECT test_trigger.customer_id, test_trigger.account_id, test_trigger.first_name, test_trigger.last_name FROM test_trigger;
CREATE RULE "_RETURN" AS
ON SELECT TO test_trigger
DO INSTEAD SELECT customer.customer_id, customer.account_id, customer.first_name, customer.last_name FROM customer;

View File

@ -0,0 +1,315 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<repositories>
<repository>
<id>MavenCentral</id>
<name>Maven repository</name>
<url>https://repo1.maven.org/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<groupId>cz.startnet</groupId>
<artifactId>apgdiff</artifactId>
<name>Another PostgreSQL Diff Tool</name>
<version>2.7.0</version>
<description>Simple PostgreSQL diff tool that is useful for schema upgrades. The tool compares two schema dump files and creates output file that is (after some hand-made modifications) suitable for upgrade of old schema.</description>
<url>http://www.apgdiff.com/</url>
<issueManagement>
<system>GitHub Issue Tracker</system>
<url>https://github.com/fordfrog/apgdiff/issues?sort=created&amp;state=open</url>
</issueManagement>
<inceptionYear>2006</inceptionYear>
<developers>
<developer>
<id>fordfrog</id>
<name>Miroslav Šulc</name>
<email>miroslav.sulc@startnet.cz</email>
<organization>StartNet s.r.o.</organization>
<organizationUrl>http://www.startnet.biz/</organizationUrl>
<roles>
<role>main developer</role>
</roles>
<timezone>+1</timezone>
</developer>
</developers>
<contributors>
<contributor>
<name>Matthieu Patou</name>
<roles>
<role>CLUSTER and STATISTICS support + ordering of columns according to source DDL</role>
</roles>
</contributor>
<contributor>
<name>Jure Mercun</name>
<roles>
<role>Case insensitivity of parsers + correct handling of quoted names</role>
</roles>
</contributor>
<contributor>
<name>Hans Idink</name>
<roles>
<role>Initial help with support of FUNCTIONs</role>
</roles>
</contributor>
<contributor>
<name>Anders Semb Hermansen</name>
<roles>
<role>Ignoring multiple whitespace while comparing content of functions </role>
</roles>
</contributor>
<contributor>
<name>Jalisson Mello</name>
<roles>
<role>Postgres 10 Support </role>
</roles>
</contributor>
<contributor>
<name>Velten Heyn</name>
<roles>
<role>Part time manager</role>
</roles>
</contributor>
</contributors>
<licenses>
<license>
<name>MIT</name>
<url>license.txt</url>
<distribution>repo</distribution>
<comments>A business-friendly OSS license</comments>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/fordfrog/apgdiff.git</connection>
<developerConnection>scm:git:ssh://git@github.com:fordfrog/apgdiff.git</developerConnection>
<url>https://github.com/fordfrog/apgdiff.git</url>
</scm>
<organization>
<name>StartNet s.r.o.</name>
<url>http://www.startnet.biz</url>
</organization>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>${project.build.sourceEncoding}</encoding>
<showDeprecation>true</showDeprecation>
<debug>false</debug>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib</classpathPrefix>
<mainClass>cz.startnet.utils.pgdiff.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-javadoc</id>
<phase>verify</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<source>1.8</source>
<target>1.8</target>
<show>private</show>
<tags>
<tag>
<name>todo</name>
<!-- todo tag for all places -->
<placement>a</placement>
<head>To do something:</head>
</tag>
</tags>
<source>8</source>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${basedir}/src/main/assembly/bin.xml</descriptor>
<descriptor>${basedir}/src/main/assembly/src.xml</descriptor>
</descriptors>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<type>maven-plugin</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<reporting>
<plugins>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<minmemory>128m</minmemory>
<maxmemory>512</maxmemory>
<source>1.8</source>
<target>1.8</target>
<tags>
<tag>
<name>todo</name>
<placement>a</placement>
<head>To do something:</head>
</tag>
</tags>
</configuration>
</plugin>
<plugin>
<groupId>net.sf</groupId>
<artifactId>stat-scm</artifactId>
<version>1.2.0</version>
</plugin>
<plugin>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<sourceEncoding>utf-8</sourceEncoding>
<targetJdk>1.8</targetJdk>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>taglist-maven-plugin</artifactId>
<version>2.4</version>
</plugin>
<plugin>
<artifactId>maven-jxr-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<inputEncoding>UTF-8</inputEncoding>
<outputEncoding>UTF-8</outputEncoding>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>3.0.5</version>
<configuration>
<threshold>Ignore</threshold>
<effort>Max</effort>
</configuration>
</plugin>
</plugins>
</reporting>
</project>

View File

@ -0,0 +1,21 @@
<assembly>
<id>bin</id>
<formats>
<format>zip</format>
</formats>
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
<files>
<file>
<source>${project.build.directory}/${project.build.finalName}.${project.packaging}</source>
<outputDirectory></outputDirectory>
</file>
<file>
<source>${basedir}/license.txt</source>
<outputDirectory></outputDirectory>
</file>
<file>
<source>${basedir}/README.md</source>
<outputDirectory></outputDirectory>
</file>
</files>
</assembly>

Some files were not shown because too many files have changed in this diff Show More