├── .editorconfig ├── .gitignore ├── CONTRIBUTING.md ├── ChangeLog ├── HACKING.md ├── LICENSE ├── MANIFEST ├── META.yml ├── Makefile.PL ├── README ├── README.md ├── doc └── pgBadger.pod ├── pgbadger ├── resources ├── .gitignore ├── LICENSE ├── README ├── bean.js ├── bootstrap.css ├── bootstrap.js ├── font │ ├── FontAwesome.otf │ └── fontawesome-webfont.eot ├── fontawesome.css ├── jqplot.barRenderer.js ├── jqplot.canvasAxisTickRenderer.js ├── jqplot.canvasTextRenderer.js ├── jqplot.categoryAxisRenderer.js ├── jqplot.cursor.js ├── jqplot.dateAxisRenderer.js ├── jqplot.highlighter.js ├── jqplot.pieRenderer.js ├── jqplot.pointLabels.js ├── jquery.jqplot.css ├── jquery.jqplot.js ├── jquery.js ├── patch-jquery.jqplot.js ├── pgbadger.css ├── pgbadger.js ├── pgbadger_slide.js └── underscore.js ├── t ├── 01_lint.t ├── 02_basics.t ├── 03_consistency.t ├── 04_advanced.t ├── exp │ └── stmt_type.out └── fixtures │ ├── anonymize.log │ ├── begin_end.log │ ├── cloudsql.log.gz │ ├── cnpg.log.gz │ ├── light.postgres.log.bz2 │ ├── logplex.gz │ ├── multiline_param.log │ ├── pg-syslog.1.bz2 │ ├── pg-syslog.2.gz │ ├── pg-timezones.log │ ├── pg_rawcsv.log │ ├── pg_vacuums.json.gz │ ├── pg_vacuums.log.gz │ ├── pgbouncer.1.21.log.gz │ ├── pgbouncer.log.gz │ ├── postgresql_param_range.log │ ├── queryid.log.gz │ ├── rds.log.bz2 │ ├── stmt_type.log │ ├── tempfile_only.log.gz │ └── weeknumber.log └── tools ├── README.pgbadger_tools ├── README.updt_embedded_rsc ├── pgbadger_tools └── updt_embedded_rsc.pl /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 8 7 | # Unix-style newlines 8 | end_of_line = lf 9 | # Remove any whitespace characters preceding newline characters 10 | trim_trailing_whitespace = true 11 | # Newline ending every file 12 | insert_final_newline = true 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | blib/ 2 | Makefile 3 | MYMETA.json 4 | MYMETA.yml 5 | pm_to_blib 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Before submitting an issue 4 | 5 | 1. Upgrade to the latest version of pgBadger and see if the problem remains 6 | 7 | 2. Look at the [closed issues](https://github.com/darold/pgbadger/issues?state=closed), we may have already answered a similar problem 8 | 9 | 3. [Read the doc](http://pgbadger.darold.net/documentation.html), it is short and useful 10 | 11 | ## Coding style 12 | 13 | The pgBadger project provides a [.editorconfig](http://editorconfig.org/) file to 14 | setup consistent spacing in files. Please follow it! 15 | 16 | ## Keep documentation updated 17 | 18 | The first pgBadger documentation is `pgbadger --help`. `--help` fills the 19 | SYNOPSIS section in `doc/pgBadger.pod`. The DESCRIPTION section *must* be 20 | written directly in `doc/pgBadger.pod`. `README` is the text formatting of 21 | `doc/pgBadger.pod`. 22 | 23 | After updating `doc/pdBadger.pod`, rebuild `README` and `README.md` with the following commands: 24 | ```shell 25 | $ perl Makefile.PL && make README 26 | ``` 27 | When you're done contributing to the docs, commit your changes. Note that you must have `pod2markdown` installed to generate `README.md`. 28 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Contributing on pgBadger 2 | 3 | Thanks for your attention on pgBadger ! You need Perl Module JSON::XS 4 | to run the full test suite. You can install it on a Debian like system 5 | using: 6 | 7 | sudo apt-get install libjson-xs-perl 8 | 9 | or in RPM like system using: 10 | 11 | sudo yum install perl-JSON-XS 12 | 13 | pgBadger has a TAP compatible test suite executed by `prove`: 14 | 15 | $ prove 16 | t/01_lint.t ......... ok 17 | t/02_basics.t ....... ok 18 | t/03_consistency.t .. ok 19 | All tests successful. 20 | Files=3, Tests=13, 6 wallclock secs ( 0.01 usr 0.01 sys + 5.31 cusr 0.16 csys = 5.49 CPU) 21 | Result: PASS 22 | $ 23 | 24 | or if you prefer to run test manually: 25 | 26 | $ perl Makefile.PL && make test 27 | Checking if your kit is complete... 28 | Looks good 29 | Generating a Unix-style Makefile 30 | Writing Makefile for pgBadger 31 | Writing MYMETA.yml and MYMETA.json 32 | cp pgbadger blib/script/pgbadger 33 | "/usr/bin/perl" -MExtUtils::MY -e 'MY->fixin(shift)' -- blib/script/pgbadger 34 | PERL_DL_NONLAZY=1 "/usr/bin/perl" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/lib', 'blib/arch')" t/*.t 35 | t/01_lint.t ......... ok 36 | t/02_basics.t ....... ok 37 | t/03_consistency.t .. ok 38 | All tests successful. 39 | Files=3, Tests=13, 6 wallclock secs ( 0.03 usr 0.00 sys + 5.39 cusr 0.14 csys = 5.56 CPU) 40 | Result: PASS 41 | $ make clean && rm Makefile.old 42 | 43 | Please contribute a regression test when you fix a bug or add a feature. Thanks! 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2025, Gilles Darold 2 | 3 | Permission to use, copy, modify, and distribute this software and its 4 | documentation for any purpose, without fee, and without a written agreement 5 | is hereby granted, provided that the above copyright notice and this 6 | paragraph and the following two paragraphs appear in all copies. 7 | 8 | IN NO EVENT SHALL Darold BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, 9 | SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, 10 | ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 11 | Darold HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | Darold SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED 14 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 15 | PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND Darold 16 | HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, 17 | OR MODIFICATIONS. 18 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | LICENSE 2 | Makefile.PL 3 | MANIFEST 4 | META.yml 5 | pgbadger 6 | README 7 | doc/pgBadger.pod 8 | ChangeLog 9 | -------------------------------------------------------------------------------- /META.yml: -------------------------------------------------------------------------------- 1 | name: pgBadger 2 | version: 13.1 3 | version_from: pgbadger 4 | installdirs: site 5 | recommends: 6 | Text::CSV_XS: 0 7 | JSON::XS: 0 8 | 9 | distribution_type: script 10 | generated_by: ExtUtils::MakeMaker version 6.17 11 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use ExtUtils::MakeMaker; 2 | # See lib/ExtUtils/MakeMaker.pm for details of how to influence 3 | # the contents of the Makefile that is written. 4 | 5 | use strict; 6 | 7 | my @ALLOWED_ARGS = ('INSTALLDIRS','DESTDIR'); 8 | 9 | # Parse command line arguments and store them as environment variables 10 | while ($_ = shift) { 11 | my ($k,$v) = split(/=/, $_, 2); 12 | if (grep(/^$k$/, @ALLOWED_ARGS)) { 13 | $ENV{$k} = $v; 14 | } 15 | } 16 | $ENV{DESTDIR} =~ s/\/$//; 17 | 18 | # Default install path 19 | my $DESTDIR = $ENV{DESTDIR} || ''; 20 | my $INSTALLDIRS = $ENV{INSTALLDIRS} || 'site'; 21 | my %merge_compat = (); 22 | 23 | if ($ExtUtils::MakeMaker::VERSION >= 6.46) { 24 | %merge_compat = ( 25 | 'META_MERGE' => { 26 | resources => { 27 | homepage => 'http://pgbadger.darold.net/', 28 | repository => { 29 | type => 'git', 30 | git => 'git@github.com:darold/pgbadger.git', 31 | web => 'https://github.com/darold/pgbadger', 32 | }, 33 | }, 34 | } 35 | ); 36 | } 37 | 38 | sub MY::postamble { 39 | return <<'EOMAKE'; 40 | USE_MARKDOWN=$(shell which pod2markdown) 41 | 42 | README: doc/pgBadger.pod 43 | pod2text $^ > $@ 44 | ifneq ("$(USE_MARKDOWN)", "") 45 | cat doc/pgBadger.pod | grep "=head1 " | sed 's/^=head1 \(.*\)/- [\1](#\1)/' | sed 's/ /-/g' | sed 's/--/- /' > $@.md 46 | sed -i '1s/^/### TABLE OF CONTENTS\n\n/' $@.md 47 | echo >> $@.md 48 | pod2markdown $^ | sed 's/^## /#### /' | sed 's/^# /### /' >> $@.md 49 | else 50 | $(warning You must install pod2markdown to generate README.md from doc/pgBadger.pod) 51 | endif 52 | 53 | .INTERMEDIATE: doc/synopsis.pod 54 | doc/synopsis.pod: Makefile pgbadger 55 | echo "=head1 SYNOPSIS" > $@ 56 | ./pgbadger --help >> $@ 57 | echo "=head1 DESCRIPTION" >> $@ 58 | sed -i.bak 's/ +$$//g' $@ 59 | rm $@.bak 60 | 61 | .PHONY: doc/pgBadger.pod 62 | doc/pgBadger.pod: doc/synopsis.pod Makefile 63 | sed -i.bak '/^=head1 SYNOPSIS/,/^=head1 DESCRIPTION/d' $@ 64 | sed -i.bak '4r $<' $@ 65 | rm $@.bak 66 | EOMAKE 67 | } 68 | 69 | WriteMakefile( 70 | 'DISTNAME' => 'pgbadger', 71 | 'NAME' => 'pgBadger', 72 | 'VERSION_FROM' => 'pgbadger', 73 | 'dist' => { 74 | 'COMPRESS'=>'gzip -9f', 'SUFFIX' => 'gz', 75 | 'ZIP'=>'/usr/bin/zip','ZIPFLAGS'=>'-rl' 76 | }, 77 | 'AUTHOR' => 'Gilles Darold (gilles@darold.net)', 78 | 'ABSTRACT' => 'pgBadger - PostgreSQL log analysis report', 79 | 'EXE_FILES' => [ qw(pgbadger) ], 80 | 'MAN1PODS' => { 'doc/pgBadger.pod' => 'blib/man1/pgbadger.1p' }, 81 | 'DESTDIR' => $DESTDIR, 82 | 'INSTALLDIRS' => $INSTALLDIRS, 83 | 'clean' => {}, 84 | %merge_compat 85 | ); 86 | -------------------------------------------------------------------------------- /resources/.gitignore: -------------------------------------------------------------------------------- 1 | min/ 2 | -------------------------------------------------------------------------------- /resources/LICENSE: -------------------------------------------------------------------------------- 1 | Licenses 2 | -------- 3 | 4 | bean.js: 5 | copyright (c) Jacob Thornton 2011-2012 6 | * https://github.com/fat/bean 7 | * MIT license 8 | 9 | bootstrap.*: 10 | Copyright 2011-2016 Twitter, Inc. 11 | * Licensed under the MIT license 12 | 13 | fontawesome.*: 14 | * - The Font Awesome font is licensed under SIL OFL 1.1 - 15 | * http://scripts.sil.org/OFL 16 | * - Font Awesome CSS, LESS, and SASS files are licensed under MIT License - 17 | * http://opensource.org/licenses/mit-license.html 18 | * - Attribution is no longer required in Font Awesome 3.0, but much appreciated: 19 | * "Font Awesome by Dave Gandy - http://fontawesome.io" 20 | 21 | jqplot.*: 22 | Copyright (c) 2009-2013 Chris Leonello 23 | * jqPlot is currently available for use in all personal or commercial projects 24 | * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 25 | * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 26 | * choose the license that best suits your project and use it accordingly. 27 | * 28 | * Although not required, the author would appreciate an email letting him 29 | * know of any substantial use of jqPlot. You can reach the author at: 30 | * chris at jqplot dot com or see http://www.jqplot.com/info.php . 31 | * 32 | * If you are feeling kind and generous, consider supporting the project by 33 | * making a donation at: http://www.jqplot.com/donate.php . 34 | 35 | jquery.jqplot.*: 36 | Copyright (c) 2009-2013 Chris Leonello 37 | * jqPlot is currently available for use in all personal or commercial projects 38 | * under both the MIT and GPL version 2.0 licenses. This means that you can 39 | * choose the license that best suits your project and use it accordingly. 40 | * 41 | * See and contained within this distribution for further information. 42 | * 43 | * The author would appreciate an email letting him know of any substantial 44 | * use of jqPlot. You can reach the author at: chris at jqplot dot com 45 | * or see http://www.jqplot.com/info.php. This is, of course, not required. 46 | * 47 | * If you are feeling kind and generous, consider supporting the project by 48 | * making a donation at: http://www.jqplot.com/donate.php. 49 | 50 | jquery.*: 51 | Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors 52 | * Released under the MIT license 53 | * http://jquery.org/license 54 | 55 | underscore.js: 56 | (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 57 | // Underscore may be freely distributed under the MIT license. 58 | 59 | 60 | The MIT License (MIT) 61 | --------------------- 62 | 63 | Permission is hereby granted, free of charge, to any person obtaining a copy of 64 | this software and associated documentation files (the "Software"), to deal in 65 | the Software without restriction, including without limitation the rights to 66 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 67 | of the Software, and to permit persons to whom the Software is furnished to do 68 | so, subject to the following conditions: 69 | 70 | The above copyright notice and this permission notice shall be included in all 71 | copies or substantial portions of the Software. 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 74 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 75 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 76 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 77 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 78 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 79 | SOFTWARE. 80 | 81 | 82 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 83 | ---------------------------------------------------- 84 | 85 | PREAMBLE 86 | The goals of the Open Font License (OFL) are to stimulate worldwide 87 | development of collaborative font projects, to support the font creation 88 | efforts of academic and linguistic communities, and to provide a free and 89 | open framework in which fonts may be shared and improved in partnership 90 | with others. 91 | 92 | The OFL allows the licensed fonts to be used, studied, modified and 93 | redistributed freely as long as they are not sold by themselves. The 94 | fonts, including any derivative works, can be bundled, embedded, 95 | redistributed and/or sold with any software provided that any reserved 96 | names are not used by derivative works. The fonts and derivatives, 97 | however, cannot be released under any other type of license. The 98 | requirement for fonts to remain under this license does not apply 99 | to any document created using the fonts or their derivatives. 100 | 101 | DEFINITIONS 102 | "Font Software" refers to the set of files released by the Copyright 103 | Holder(s) under this license and clearly marked as such. This may 104 | include source files, build scripts and documentation. 105 | 106 | "Reserved Font Name" refers to any names specified as such after the 107 | copyright statement(s). 108 | 109 | "Original Version" refers to the collection of Font Software components as 110 | distributed by the Copyright Holder(s). 111 | 112 | "Modified Version" refers to any derivative made by adding to, deleting, 113 | or substituting -- in part or in whole -- any of the components of the 114 | Original Version, by changing formats or by porting the Font Software to a 115 | new environment. 116 | 117 | "Author" refers to any designer, engineer, programmer, technical 118 | writer or other person who contributed to the Font Software. 119 | 120 | PERMISSION & CONDITIONS 121 | Permission is hereby granted, free of charge, to any person obtaining 122 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 123 | redistribute, and sell modified and unmodified copies of the Font 124 | Software, subject to the following conditions: 125 | 126 | 1) Neither the Font Software nor any of its individual components, 127 | in Original or Modified Versions, may be sold by itself. 128 | 129 | 2) Original or Modified Versions of the Font Software may be bundled, 130 | redistributed and/or sold with any software, provided that each copy 131 | contains the above copyright notice and this license. These can be 132 | included either as stand-alone text files, human-readable headers or 133 | in the appropriate machine-readable metadata fields within text or 134 | binary files as long as those fields can be easily viewed by the user. 135 | 136 | 3) No Modified Version of the Font Software may use the Reserved Font 137 | Name(s) unless explicit written permission is granted by the corresponding 138 | Copyright Holder. This restriction only applies to the primary font name as 139 | presented to the users. 140 | 141 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 142 | Software shall not be used to promote, endorse or advertise any 143 | Modified Version, except to acknowledge the contribution(s) of the 144 | Copyright Holder(s) and the Author(s) or with their explicit written 145 | permission. 146 | 147 | 5) The Font Software, modified or unmodified, in part or in whole, 148 | must be distributed entirely under this license, and must not be 149 | distributed under any other license. The requirement for fonts to 150 | remain under this license does not apply to any document created 151 | using the Font Software. 152 | 153 | TERMINATION 154 | This license becomes null and void if any of the above conditions are 155 | not met. 156 | 157 | DISCLAIMER 158 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 159 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 160 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 161 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 162 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 163 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 164 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 165 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 166 | OTHER DEALINGS IN THE FONT SOFTWARE. 167 | -------------------------------------------------------------------------------- /resources/README: -------------------------------------------------------------------------------- 1 | Resources files are collected from their respective download places as follow: 2 | 3 | jqPlot: 4 | ------- 5 | 6 | mkdir jqPlot-1.0.8/ && cd jqPlot-1.0.8/ 7 | wget http://www.jqplot.com/download/jquery.jqplot.1.0.8r1250.tar.gz 8 | tar xzf jquery.jqplot.1.0.8r1250.tar.gz 9 | cp dist/jquery.jqplot.js /home/git/pgbadger/resources/ 10 | cp dist/jquery.jqplot.css /home/git/pgbadger/resources/ 11 | cp dist/jquery.js /home/git/pgbadger/resources/ 12 | cp dist/plugins/jqplot.pieRenderer.js /home/git/pgbadger/resources/ 13 | cp dist/plugins/jqplot.barRenderer.js /home/git/pgbadger/resources/ 14 | cp dist/plugins/jqplot.dateAxisRenderer.js /home/git/pgbadger/resources/ 15 | cp dist/plugins/jqplot.canvasTextRenderer.js /home/git/pgbadger/resources/ 16 | cp dist/plugins/jqplot.categoryAxisRenderer.js /home/git/pgbadger/resources/ 17 | cp dist/plugins/jqplot.canvasAxisTickRenderer.js /home/git/pgbadger/resources/ 18 | cp dist/plugins/jqplot.highlighter.js /home/git/pgbadger/resources/ 19 | cp dist/plugins/jqplot.cursor.js /home/git/pgbadger/resources/ 20 | cd .. 21 | 22 | Bootstrap: 23 | ---------- 24 | 25 | wget https://github.com/twbs/bootstrap/archive/v3.3.7.zip 26 | unzip v3.3.7.zip bootstrap-3.3.7/dist/js/bootstrap.js bootstrap-3.3.7/dist/css/bootstrap.css 27 | 28 | cp bootstrap-3.3.7/dist/js/bootstrap.js /home/git/pgbadger/resources/ 29 | cp bootstrap-3.3.7/dist/css/bootstrap.css /home/git/pgbadger/resources/ 30 | 31 | Fontawesome: 32 | ------------ 33 | http://fontawesome.io/3.2.1/assets/font-awesome.zip 34 | unzip font-awesome.zip font-awesome/css/font-awesome.css font-awesome/fonts/fontawesome-webfont.ttf 35 | 36 | cp font-awesome/css/font-awesome.css /home/git/pgbadger/resources/fontawesome.css 37 | cp font-awesome/font/FontAwesome.otf /home/git/pgbadger/resources/font/ 38 | 39 | Note that the hyphen is removed from font-awesome.css during the copy for 40 | backward compatibility. 41 | 42 | bean: 43 | ----- 44 | 45 | wget https://github.com/fat/bean/archive/v1.0.14.tar.gz 46 | tar xzf v1.0.14.tar.gz bean-1.0.14/src/bean.js 47 | 48 | cp bean-1.0.14/src/bean.js /home/git/pgbadger/resources/ 49 | 50 | underscore.js: 51 | -------------- 52 | 53 | wget http://underscorejs.org/underscore.js -O /home/git/pgbadger/resources/underscore.js 54 | 55 | Then file are minified using yui-compressor and the font embedded using the 56 | Perl script tools/updt_embedded_rsc.pl This script also embedded all minified 57 | files into the pgbadger Perl script. 58 | 59 | -------------------------------------------------------------------------------- /resources/font/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/resources/font/FontAwesome.otf -------------------------------------------------------------------------------- /resources/font/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/resources/font/fontawesome-webfont.eot -------------------------------------------------------------------------------- /resources/jqplot.canvasAxisTickRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jqPlot 3 | * Pure JavaScript plotting plugin using jQuery 4 | * 5 | * Version: 1.0.8 6 | * Revision: 1250 7 | * 8 | * Copyright (c) 2009-2013 Chris Leonello 9 | * jqPlot is currently available for use in all personal or commercial projects 10 | * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 11 | * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 12 | * choose the license that best suits your project and use it accordingly. 13 | * 14 | * Although not required, the author would appreciate an email letting him 15 | * know of any substantial use of jqPlot. You can reach the author at: 16 | * chris at jqplot dot com or see http://www.jqplot.com/info.php . 17 | * 18 | * If you are feeling kind and generous, consider supporting the project by 19 | * making a donation at: http://www.jqplot.com/donate.php . 20 | * 21 | * sprintf functions contained in jqplot.sprintf.js by Ash Searle: 22 | * 23 | * version 2007.04.27 24 | * author Ash Searle 25 | * http://hexmen.com/blog/2007/03/printf-sprintf/ 26 | * http://hexmen.com/js/sprintf.js 27 | * The author (Ash Searle) has placed this code in the public domain: 28 | * "This code is unrestricted: you are free to use it however you like." 29 | * 30 | */ 31 | (function($) { 32 | /** 33 | * Class: $.jqplot.CanvasAxisTickRenderer 34 | * Renderer to draw axis ticks with a canvas element to support advanced 35 | * featrues such as rotated text. This renderer uses a separate rendering engine 36 | * to draw the text on the canvas. Two modes of rendering the text are available. 37 | * If the browser has native font support for canvas fonts (currently Mozila 3.5 38 | * and Safari 4), you can enable text rendering with the canvas fillText method. 39 | * You do so by setting the "enableFontSupport" option to true. 40 | * 41 | * Browsers lacking native font support will have the text drawn on the canvas 42 | * using the Hershey font metrics. Even if the "enableFontSupport" option is true 43 | * non-supporting browsers will still render with the Hershey font. 44 | */ 45 | $.jqplot.CanvasAxisTickRenderer = function(options) { 46 | // Group: Properties 47 | 48 | // prop: mark 49 | // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null. 50 | this.mark = 'outside'; 51 | // prop: showMark 52 | // whether or not to show the mark on the axis. 53 | this.showMark = true; 54 | // prop: showGridline 55 | // whether or not to draw the gridline on the grid at this tick. 56 | this.showGridline = true; 57 | // prop: isMinorTick 58 | // if this is a minor tick. 59 | this.isMinorTick = false; 60 | // prop: angle 61 | // angle of text, measured clockwise from x axis. 62 | this.angle = 0; 63 | // prop: markSize 64 | // Length of the tick marks in pixels. For 'cross' style, length 65 | // will be stoked above and below axis, so total length will be twice this. 66 | this.markSize = 4; 67 | // prop: show 68 | // whether or not to show the tick (mark and label). 69 | this.show = true; 70 | // prop: showLabel 71 | // whether or not to show the label. 72 | this.showLabel = true; 73 | // prop: labelPosition 74 | // 'auto', 'start', 'middle' or 'end'. 75 | // Whether tick label should be positioned so the start, middle, or end 76 | // of the tick mark. 77 | this.labelPosition = 'auto'; 78 | this.label = ''; 79 | this.value = null; 80 | this._styles = {}; 81 | // prop: formatter 82 | // A class of a formatter for the tick text. 83 | // The default $.jqplot.DefaultTickFormatter uses sprintf. 84 | this.formatter = $.jqplot.DefaultTickFormatter; 85 | // prop: formatString 86 | // string passed to the formatter. 87 | this.formatString = ''; 88 | // prop: prefix 89 | // String to prepend to the tick label. 90 | // Prefix is prepended to the formatted tick label. 91 | this.prefix = ''; 92 | // prop: fontFamily 93 | // css spec for the font-family css attribute. 94 | this.fontFamily = '"Trebuchet MS", Arial, Helvetica, sans-serif'; 95 | // prop: fontSize 96 | // CSS spec for font size. 97 | this.fontSize = '10pt'; 98 | // prop: fontWeight 99 | // CSS spec for fontWeight 100 | this.fontWeight = 'normal'; 101 | // prop: fontStretch 102 | // Multiplier to condense or expand font width. 103 | // Applies only to browsers which don't support canvas native font rendering. 104 | this.fontStretch = 1.0; 105 | // prop: textColor 106 | // css spec for the color attribute. 107 | this.textColor = '#666666'; 108 | // prop: enableFontSupport 109 | // true to turn on native canvas font support in Mozilla 3.5+ and Safari 4+. 110 | // If true, tick label will be drawn with canvas tag native support for fonts. 111 | // If false, tick label will be drawn with Hershey font metrics. 112 | this.enableFontSupport = true; 113 | // prop: pt2px 114 | // Point to pixel scaling factor, used for computing height of bounding box 115 | // around a label. The labels text renderer has a default setting of 1.4, which 116 | // should be suitable for most fonts. Leave as null to use default. If tops of 117 | // letters appear clipped, increase this. If bounding box seems too big, decrease. 118 | // This is an issue only with the native font renderering capabilities of Mozilla 119 | // 3.5 and Safari 4 since they do not provide a method to determine the font height. 120 | this.pt2px = null; 121 | 122 | this._elem; 123 | this._ctx; 124 | this._plotWidth; 125 | this._plotHeight; 126 | this._plotDimensions = {height:null, width:null}; 127 | 128 | $.extend(true, this, options); 129 | 130 | var ropts = {fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily}; 131 | if (this.pt2px) { 132 | ropts.pt2px = this.pt2px; 133 | } 134 | 135 | if (this.enableFontSupport) { 136 | if ($.jqplot.support_canvas_text()) { 137 | this._textRenderer = new $.jqplot.CanvasFontRenderer(ropts); 138 | } 139 | 140 | else { 141 | this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts); 142 | } 143 | } 144 | else { 145 | this._textRenderer = new $.jqplot.CanvasTextRenderer(ropts); 146 | } 147 | }; 148 | 149 | $.jqplot.CanvasAxisTickRenderer.prototype.init = function(options) { 150 | $.extend(true, this, options); 151 | this._textRenderer.init({fontSize:this.fontSize, fontWeight:this.fontWeight, fontStretch:this.fontStretch, fillStyle:this.textColor, angle:this.getAngleRad(), fontFamily:this.fontFamily}); 152 | }; 153 | 154 | // return width along the x axis 155 | // will check first to see if an element exists. 156 | // if not, will return the computed text box width. 157 | $.jqplot.CanvasAxisTickRenderer.prototype.getWidth = function(ctx) { 158 | if (this._elem) { 159 | return this._elem.outerWidth(true); 160 | } 161 | else { 162 | var tr = this._textRenderer; 163 | var l = tr.getWidth(ctx); 164 | var h = tr.getHeight(ctx); 165 | var w = Math.abs(Math.sin(tr.angle)*h) + Math.abs(Math.cos(tr.angle)*l); 166 | return w; 167 | } 168 | }; 169 | 170 | // return height along the y axis. 171 | $.jqplot.CanvasAxisTickRenderer.prototype.getHeight = function(ctx) { 172 | if (this._elem) { 173 | return this._elem.outerHeight(true); 174 | } 175 | else { 176 | var tr = this._textRenderer; 177 | var l = tr.getWidth(ctx); 178 | var h = tr.getHeight(ctx); 179 | var w = Math.abs(Math.cos(tr.angle)*h) + Math.abs(Math.sin(tr.angle)*l); 180 | return w; 181 | } 182 | }; 183 | 184 | // return top. 185 | $.jqplot.CanvasAxisTickRenderer.prototype.getTop = function(ctx) { 186 | if (this._elem) { 187 | return this._elem.position().top; 188 | } 189 | else { 190 | return null; 191 | } 192 | }; 193 | 194 | $.jqplot.CanvasAxisTickRenderer.prototype.getAngleRad = function() { 195 | var a = this.angle * Math.PI/180; 196 | return a; 197 | }; 198 | 199 | 200 | $.jqplot.CanvasAxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) { 201 | this.value = value; 202 | if (isMinor) { 203 | this.isMinorTick = true; 204 | } 205 | return this; 206 | }; 207 | 208 | $.jqplot.CanvasAxisTickRenderer.prototype.draw = function(ctx, plot) { 209 | if (!this.label) { 210 | this.label = this.prefix + this.formatter(this.formatString, this.value); 211 | } 212 | 213 | // Memory Leaks patch 214 | if (this._elem) { 215 | if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { 216 | window.G_vmlCanvasManager.uninitElement(this._elem.get(0)); 217 | } 218 | 219 | this._elem.emptyForce(); 220 | this._elem = null; 221 | } 222 | 223 | // create a canvas here, but can't draw on it untill it is appended 224 | // to dom for IE compatability. 225 | 226 | var elem = plot.canvasManager.getCanvas(); 227 | 228 | this._textRenderer.setText(this.label, ctx); 229 | var w = this.getWidth(ctx); 230 | var h = this.getHeight(ctx); 231 | // canvases seem to need to have width and heigh attributes directly set. 232 | elem.width = w; 233 | elem.height = h; 234 | elem.style.width = w; 235 | elem.style.height = h; 236 | elem.style.textAlign = 'left'; 237 | elem.style.position = 'absolute'; 238 | 239 | elem = plot.canvasManager.initCanvas(elem); 240 | 241 | this._elem = $(elem); 242 | this._elem.css(this._styles); 243 | this._elem.addClass('jqplot-'+this.axis+'-tick'); 244 | 245 | elem = null; 246 | return this._elem; 247 | }; 248 | 249 | $.jqplot.CanvasAxisTickRenderer.prototype.pack = function() { 250 | this._textRenderer.draw(this._elem.get(0).getContext("2d"), this.label); 251 | }; 252 | 253 | })(jQuery); -------------------------------------------------------------------------------- /resources/jqplot.canvasTextRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jqPlot 3 | * Pure JavaScript plotting plugin using jQuery 4 | * 5 | * Version: 1.0.8 6 | * Revision: 1250 7 | * 8 | * Copyright (c) 2009-2013 Chris Leonello 9 | * jqPlot is currently available for use in all personal or commercial projects 10 | * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 11 | * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 12 | * choose the license that best suits your project and use it accordingly. 13 | * 14 | * Although not required, the author would appreciate an email letting him 15 | * know of any substantial use of jqPlot. You can reach the author at: 16 | * chris at jqplot dot com or see http://www.jqplot.com/info.php . 17 | * 18 | * If you are feeling kind and generous, consider supporting the project by 19 | * making a donation at: http://www.jqplot.com/donate.php . 20 | * 21 | * sprintf functions contained in jqplot.sprintf.js by Ash Searle: 22 | * 23 | * version 2007.04.27 24 | * author Ash Searle 25 | * http://hexmen.com/blog/2007/03/printf-sprintf/ 26 | * http://hexmen.com/js/sprintf.js 27 | * The author (Ash Searle) has placed this code in the public domain: 28 | * "This code is unrestricted: you are free to use it however you like." 29 | * 30 | * included jsDate library by Chris Leonello: 31 | * 32 | * Copyright (c) 2010-2013 Chris Leonello 33 | * 34 | * jsDate is currently available for use in all personal or commercial projects 35 | * under both the MIT and GPL version 2.0 licenses. This means that you can 36 | * choose the license that best suits your project and use it accordingly. 37 | * 38 | * jsDate borrows many concepts and ideas from the Date Instance 39 | * Methods by Ken Snyder along with some parts of Ken's actual code. 40 | * 41 | * Ken's original Date Instance Methods and copyright notice: 42 | * 43 | * Ken Snyder (ken d snyder at gmail dot com) 44 | * 2008-09-10 45 | * version 2.0.2 (http://kendsnyder.com/sandbox/date/) 46 | * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/) 47 | * 48 | * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js. 49 | * Larry has generously given permission to adapt his code for inclusion 50 | * into jqPlot. 51 | * 52 | * Larry's original code can be found here: 53 | * 54 | * https://github.com/lsiden/export-jqplot-to-png 55 | * 56 | * 57 | */ 58 | 59 | (function($) { 60 | // This code is a modified version of the canvastext.js code, copyright below: 61 | // 62 | // This code is released to the public domain by Jim Studt, 2007. 63 | // He may keep some sort of up to date copy at http://www.federated.com/~jim/canvastext/ 64 | // 65 | $.jqplot.CanvasTextRenderer = function(options){ 66 | this.fontStyle = 'normal'; // normal, italic, oblique [not implemented] 67 | this.fontVariant = 'normal'; // normal, small caps [not implemented] 68 | this.fontWeight = 'normal'; // normal, bold, bolder, lighter, 100 - 900 69 | this.fontSize = '10px'; 70 | this.fontFamily = 'sans-serif'; 71 | this.fontStretch = 1.0; 72 | this.fillStyle = '#666666'; 73 | this.angle = 0; 74 | this.textAlign = 'start'; 75 | this.textBaseline = 'alphabetic'; 76 | this.text; 77 | this.width; 78 | this.height; 79 | this.pt2px = 1.28; 80 | 81 | $.extend(true, this, options); 82 | this.normalizedFontSize = this.normalizeFontSize(this.fontSize); 83 | this.setHeight(); 84 | }; 85 | 86 | $.jqplot.CanvasTextRenderer.prototype.init = function(options) { 87 | $.extend(true, this, options); 88 | this.normalizedFontSize = this.normalizeFontSize(this.fontSize); 89 | this.setHeight(); 90 | }; 91 | 92 | // convert css spec into point size 93 | // returns float 94 | $.jqplot.CanvasTextRenderer.prototype.normalizeFontSize = function(sz) { 95 | sz = String(sz); 96 | var n = parseFloat(sz); 97 | if (sz.indexOf('px') > -1) { 98 | return n/this.pt2px; 99 | } 100 | else if (sz.indexOf('pt') > -1) { 101 | return n; 102 | } 103 | else if (sz.indexOf('em') > -1) { 104 | return n*12; 105 | } 106 | else if (sz.indexOf('%') > -1) { 107 | return n*12/100; 108 | } 109 | // default to pixels; 110 | else { 111 | return n/this.pt2px; 112 | } 113 | }; 114 | 115 | 116 | $.jqplot.CanvasTextRenderer.prototype.fontWeight2Float = function(w) { 117 | // w = normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 118 | // return values adjusted for Hershey font. 119 | if (Number(w)) { 120 | return w/400; 121 | } 122 | else { 123 | switch (w) { 124 | case 'normal': 125 | return 1; 126 | break; 127 | case 'bold': 128 | return 1.75; 129 | break; 130 | case 'bolder': 131 | return 2.25; 132 | break; 133 | case 'lighter': 134 | return 0.75; 135 | break; 136 | default: 137 | return 1; 138 | break; 139 | } 140 | } 141 | }; 142 | 143 | $.jqplot.CanvasTextRenderer.prototype.getText = function() { 144 | return this.text; 145 | }; 146 | 147 | $.jqplot.CanvasTextRenderer.prototype.setText = function(t, ctx) { 148 | this.text = t; 149 | this.setWidth(ctx); 150 | return this; 151 | }; 152 | 153 | $.jqplot.CanvasTextRenderer.prototype.getWidth = function(ctx) { 154 | return this.width; 155 | }; 156 | 157 | $.jqplot.CanvasTextRenderer.prototype.setWidth = function(ctx, w) { 158 | if (!w) { 159 | this.width = this.measure(ctx, this.text); 160 | } 161 | else { 162 | this.width = w; 163 | } 164 | return this; 165 | }; 166 | 167 | // return height in pixels. 168 | $.jqplot.CanvasTextRenderer.prototype.getHeight = function(ctx) { 169 | return this.height; 170 | }; 171 | 172 | // w - height in pt 173 | // set heigh in px 174 | $.jqplot.CanvasTextRenderer.prototype.setHeight = function(w) { 175 | if (!w) { 176 | //height = this.fontSize /0.75; 177 | this.height = this.normalizedFontSize * this.pt2px; 178 | } 179 | else { 180 | this.height = w; 181 | } 182 | return this; 183 | }; 184 | 185 | $.jqplot.CanvasTextRenderer.prototype.letter = function (ch) 186 | { 187 | return this.letters[ch]; 188 | }; 189 | 190 | $.jqplot.CanvasTextRenderer.prototype.ascent = function() 191 | { 192 | return this.normalizedFontSize; 193 | }; 194 | 195 | $.jqplot.CanvasTextRenderer.prototype.descent = function() 196 | { 197 | return 7.0*this.normalizedFontSize/25.0; 198 | }; 199 | 200 | $.jqplot.CanvasTextRenderer.prototype.measure = function(ctx, str) 201 | { 202 | var total = 0; 203 | var len = str.length; 204 | 205 | for (var i = 0; i < len; i++) { 206 | var c = this.letter(str.charAt(i)); 207 | if (c) { 208 | total += c.width * this.normalizedFontSize / 25.0 * this.fontStretch; 209 | } 210 | } 211 | return total; 212 | }; 213 | 214 | $.jqplot.CanvasTextRenderer.prototype.draw = function(ctx,str) 215 | { 216 | var x = 0; 217 | // leave room at bottom for descenders. 218 | var y = this.height*0.72; 219 | var total = 0; 220 | var len = str.length; 221 | var mag = this.normalizedFontSize / 25.0; 222 | 223 | ctx.save(); 224 | var tx, ty; 225 | 226 | // 1st quadrant 227 | if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) { 228 | tx = 0; 229 | ty = -Math.sin(this.angle) * this.width; 230 | } 231 | // 4th quadrant 232 | else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) { 233 | tx = Math.sin(this.angle) * this.height; 234 | ty = 0; 235 | } 236 | // 2nd quadrant 237 | else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) { 238 | tx = -Math.cos(this.angle) * this.width; 239 | ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height; 240 | } 241 | // 3rd quadrant 242 | else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) { 243 | tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width; 244 | ty = -Math.cos(this.angle) * this.height; 245 | } 246 | 247 | ctx.strokeStyle = this.fillStyle; 248 | ctx.fillStyle = this.fillStyle; 249 | ctx.translate(tx, ty); 250 | ctx.rotate(this.angle); 251 | ctx.lineCap = "round"; 252 | // multiplier was 2.0 253 | var fact = (this.normalizedFontSize > 30) ? 2.0 : 2 + (30 - this.normalizedFontSize)/20; 254 | ctx.lineWidth = fact * mag * this.fontWeight2Float(this.fontWeight); 255 | 256 | for ( var i = 0; i < len; i++) { 257 | var c = this.letter( str.charAt(i)); 258 | if ( !c) { 259 | continue; 260 | } 261 | 262 | ctx.beginPath(); 263 | 264 | var penUp = 1; 265 | var needStroke = 0; 266 | for ( var j = 0; j < c.points.length; j++) { 267 | var a = c.points[j]; 268 | if ( a[0] == -1 && a[1] == -1) { 269 | penUp = 1; 270 | continue; 271 | } 272 | if ( penUp) { 273 | ctx.moveTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag); 274 | penUp = false; 275 | } else { 276 | ctx.lineTo( x + a[0]*mag*this.fontStretch, y - a[1]*mag); 277 | } 278 | } 279 | ctx.stroke(); 280 | x += c.width*mag*this.fontStretch; 281 | } 282 | ctx.restore(); 283 | return total; 284 | }; 285 | 286 | $.jqplot.CanvasTextRenderer.prototype.letters = { 287 | ' ': { width: 16, points: [] }, 288 | '!': { width: 10, points: [[5,21],[5,7],[-1,-1],[5,2],[4,1],[5,0],[6,1],[5,2]] }, 289 | '"': { width: 16, points: [[4,21],[4,14],[-1,-1],[12,21],[12,14]] }, 290 | '#': { 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]] }, 291 | '$': { 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]] }, 292 | '%': { 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]] }, 293 | '&': { 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]] }, 294 | '\'': { width: 10, points: [[5,19],[4,20],[5,21],[6,20],[6,18],[5,16],[4,15]] }, 295 | '(': { width: 14, points: [[11,25],[9,23],[7,20],[5,16],[4,11],[4,7],[5,2],[7,-2],[9,-5],[11,-7]] }, 296 | ')': { width: 14, points: [[3,25],[5,23],[7,20],[9,16],[10,11],[10,7],[9,2],[7,-2],[5,-5],[3,-7]] }, 297 | '*': { width: 16, points: [[8,21],[8,9],[-1,-1],[3,18],[13,12],[-1,-1],[13,18],[3,12]] }, 298 | '+': { width: 26, points: [[13,18],[13,0],[-1,-1],[4,9],[22,9]] }, 299 | ',': { width: 10, points: [[6,1],[5,0],[4,1],[5,2],[6,1],[6,-1],[5,-3],[4,-4]] }, 300 | '-': { width: 18, points: [[6,9],[12,9]] }, 301 | '.': { width: 10, points: [[5,2],[4,1],[5,0],[6,1],[5,2]] }, 302 | '/': { width: 22, points: [[20,25],[2,-7]] }, 303 | '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]] }, 304 | '1': { width: 20, points: [[6,17],[8,18],[11,21],[11,0]] }, 305 | '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]] }, 306 | '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]] }, 307 | '4': { width: 20, points: [[13,21],[3,7],[18,7],[-1,-1],[13,21],[13,0]] }, 308 | '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]] }, 309 | '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]] }, 310 | '7': { width: 20, points: [[17,21],[7,0],[-1,-1],[3,21],[17,21]] }, 311 | '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]] }, 312 | '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]] }, 313 | ':': { 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]] }, 314 | ';': { 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]] }, 315 | '<': { width: 24, points: [[20,18],[4,9],[20,0]] }, 316 | '=': { width: 26, points: [[4,12],[22,12],[-1,-1],[4,6],[22,6]] }, 317 | '>': { width: 24, points: [[4,18],[20,9],[4,0]] }, 318 | '?': { 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]] }, 319 | '@': { 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]] }, 320 | 'A': { width: 18, points: [[9,21],[1,0],[-1,-1],[9,21],[17,0],[-1,-1],[4,7],[14,7]] }, 321 | '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]] }, 322 | '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]] }, 323 | '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]] }, 324 | '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]] }, 325 | 'F': { width: 18, points: [[4,21],[4,0],[-1,-1],[4,21],[17,21],[-1,-1],[4,11],[12,11]] }, 326 | '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]] }, 327 | 'H': { width: 22, points: [[4,21],[4,0],[-1,-1],[18,21],[18,0],[-1,-1],[4,11],[18,11]] }, 328 | 'I': { width: 8, points: [[4,21],[4,0]] }, 329 | 'J': { width: 16, points: [[12,21],[12,5],[11,2],[10,1],[8,0],[6,0],[4,1],[3,2],[2,5],[2,7]] }, 330 | 'K': { width: 21, points: [[4,21],[4,0],[-1,-1],[18,21],[4,7],[-1,-1],[9,12],[18,0]] }, 331 | 'L': { width: 17, points: [[4,21],[4,0],[-1,-1],[4,0],[16,0]] }, 332 | '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]] }, 333 | 'N': { width: 22, points: [[4,21],[4,0],[-1,-1],[4,21],[18,0],[-1,-1],[18,21],[18,0]] }, 334 | '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]] }, 335 | '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]] }, 336 | '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]] }, 337 | '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]] }, 338 | '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]] }, 339 | 'T': { width: 16, points: [[8,21],[8,0],[-1,-1],[1,21],[15,21]] }, 340 | 'U': { width: 22, points: [[4,21],[4,6],[5,3],[7,1],[10,0],[12,0],[15,1],[17,3],[18,6],[18,21]] }, 341 | 'V': { width: 18, points: [[1,21],[9,0],[-1,-1],[17,21],[9,0]] }, 342 | '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]] }, 343 | 'X': { width: 20, points: [[3,21],[17,0],[-1,-1],[17,21],[3,0]] }, 344 | 'Y': { width: 18, points: [[1,21],[9,11],[9,0],[-1,-1],[17,21],[9,11]] }, 345 | 'Z': { width: 20, points: [[17,21],[3,0],[-1,-1],[3,21],[17,21],[-1,-1],[3,0],[17,0]] }, 346 | '[': { 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]] }, 347 | '\\': { width: 14, points: [[0,21],[14,-3]] }, 348 | ']': { 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]] }, 349 | '^': { width: 16, points: [[6,15],[8,18],[10,15],[-1,-1],[3,12],[8,17],[13,12],[-1,-1],[8,17],[8,0]] }, 350 | '_': { width: 16, points: [[0,-2],[16,-2]] }, 351 | '`': { width: 10, points: [[6,21],[5,20],[4,18],[4,16],[5,15],[6,16],[5,17]] }, 352 | '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]] }, 353 | '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]] }, 354 | '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]] }, 355 | '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]] }, 356 | '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]] }, 357 | 'f': { width: 12, points: [[10,21],[8,21],[6,20],[5,17],[5,0],[-1,-1],[2,14],[9,14]] }, 358 | '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]] }, 359 | 'h': { width: 19, points: [[4,21],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, 360 | 'i': { width: 8, points: [[3,21],[4,20],[5,21],[4,22],[3,21],[-1,-1],[4,14],[4,0]] }, 361 | '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]] }, 362 | 'k': { width: 17, points: [[4,21],[4,0],[-1,-1],[14,14],[4,4],[-1,-1],[8,8],[15,0]] }, 363 | 'l': { width: 8, points: [[4,21],[4,0]] }, 364 | '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]] }, 365 | 'n': { width: 19, points: [[4,14],[4,0],[-1,-1],[4,10],[7,13],[9,14],[12,14],[14,13],[15,10],[15,0]] }, 366 | '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]] }, 367 | '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]] }, 368 | '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]] }, 369 | 'r': { width: 13, points: [[4,14],[4,0],[-1,-1],[4,8],[5,11],[7,13],[9,14],[12,14]] }, 370 | '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]] }, 371 | 't': { width: 12, points: [[5,21],[5,4],[6,1],[8,0],[10,0],[-1,-1],[2,14],[9,14]] }, 372 | 'u': { width: 19, points: [[4,14],[4,4],[5,1],[7,0],[10,0],[12,1],[15,4],[-1,-1],[15,14],[15,0]] }, 373 | 'v': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0]] }, 374 | '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]] }, 375 | 'x': { width: 17, points: [[3,14],[14,0],[-1,-1],[14,14],[3,0]] }, 376 | 'y': { width: 16, points: [[2,14],[8,0],[-1,-1],[14,14],[8,0],[6,-4],[4,-6],[2,-7],[1,-7]] }, 377 | 'z': { width: 17, points: [[14,14],[3,0],[-1,-1],[3,14],[14,14],[-1,-1],[3,0],[14,0]] }, 378 | '{': { 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]] }, 379 | '|': { width: 8, points: [[4,25],[4,-7]] }, 380 | '}': { 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]] }, 381 | '~': { 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]] } 382 | }; 383 | 384 | $.jqplot.CanvasFontRenderer = function(options) { 385 | options = options || {}; 386 | if (!options.pt2px) { 387 | options.pt2px = 1.5; 388 | } 389 | $.jqplot.CanvasTextRenderer.call(this, options); 390 | }; 391 | 392 | $.jqplot.CanvasFontRenderer.prototype = new $.jqplot.CanvasTextRenderer({}); 393 | $.jqplot.CanvasFontRenderer.prototype.constructor = $.jqplot.CanvasFontRenderer; 394 | 395 | $.jqplot.CanvasFontRenderer.prototype.measure = function(ctx, str) 396 | { 397 | // var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily; 398 | var fstyle = this.fontSize+' '+this.fontFamily; 399 | ctx.save(); 400 | ctx.font = fstyle; 401 | var w = ctx.measureText(str).width; 402 | ctx.restore(); 403 | return w; 404 | }; 405 | 406 | $.jqplot.CanvasFontRenderer.prototype.draw = function(ctx, str) 407 | { 408 | var x = 0; 409 | // leave room at bottom for descenders. 410 | var y = this.height*0.72; 411 | //var y = 12; 412 | 413 | ctx.save(); 414 | var tx, ty; 415 | 416 | // 1st quadrant 417 | if ((-Math.PI/2 <= this.angle && this.angle <= 0) || (Math.PI*3/2 <= this.angle && this.angle <= Math.PI*2)) { 418 | tx = 0; 419 | ty = -Math.sin(this.angle) * this.width; 420 | } 421 | // 4th quadrant 422 | else if ((0 < this.angle && this.angle <= Math.PI/2) || (-Math.PI*2 <= this.angle && this.angle <= -Math.PI*3/2)) { 423 | tx = Math.sin(this.angle) * this.height; 424 | ty = 0; 425 | } 426 | // 2nd quadrant 427 | else if ((-Math.PI < this.angle && this.angle < -Math.PI/2) || (Math.PI <= this.angle && this.angle <= Math.PI*3/2)) { 428 | tx = -Math.cos(this.angle) * this.width; 429 | ty = -Math.sin(this.angle) * this.width - Math.cos(this.angle) * this.height; 430 | } 431 | // 3rd quadrant 432 | else if ((-Math.PI*3/2 < this.angle && this.angle < Math.PI) || (Math.PI/2 < this.angle && this.angle < Math.PI)) { 433 | tx = Math.sin(this.angle) * this.height - Math.cos(this.angle)*this.width; 434 | ty = -Math.cos(this.angle) * this.height; 435 | } 436 | ctx.strokeStyle = this.fillStyle; 437 | ctx.fillStyle = this.fillStyle; 438 | // var fstyle = this.fontStyle+' '+this.fontVariant+' '+this.fontWeight+' '+this.fontSize+' '+this.fontFamily; 439 | var fstyle = this.fontSize+' '+this.fontFamily; 440 | ctx.font = fstyle; 441 | ctx.translate(tx, ty); 442 | ctx.rotate(this.angle); 443 | ctx.fillText(str, x, y); 444 | // ctx.strokeText(str, x, y); 445 | 446 | ctx.restore(); 447 | }; 448 | 449 | })(jQuery); -------------------------------------------------------------------------------- /resources/jqplot.categoryAxisRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jqPlot 3 | * Pure JavaScript plotting plugin using jQuery 4 | * 5 | * Version: 1.0.8 6 | * Revision: 1250 7 | * 8 | * Copyright (c) 2009-2013 Chris Leonello 9 | * jqPlot is currently available for use in all personal or commercial projects 10 | * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 11 | * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 12 | * choose the license that best suits your project and use it accordingly. 13 | * 14 | * Although not required, the author would appreciate an email letting him 15 | * know of any substantial use of jqPlot. You can reach the author at: 16 | * chris at jqplot dot com or see http://www.jqplot.com/info.php . 17 | * 18 | * If you are feeling kind and generous, consider supporting the project by 19 | * making a donation at: http://www.jqplot.com/donate.php . 20 | * 21 | * sprintf functions contained in jqplot.sprintf.js by Ash Searle: 22 | * 23 | * version 2007.04.27 24 | * author Ash Searle 25 | * http://hexmen.com/blog/2007/03/printf-sprintf/ 26 | * http://hexmen.com/js/sprintf.js 27 | * The author (Ash Searle) has placed this code in the public domain: 28 | * "This code is unrestricted: you are free to use it however you like." 29 | * 30 | */ 31 | (function($) { 32 | /** 33 | * class: $.jqplot.CategoryAxisRenderer 34 | * A plugin for jqPlot to render a category style axis, with equal pixel spacing between y data values of a series. 35 | * 36 | * To use this renderer, include the plugin in your source 37 | * > 38 | * 39 | * and supply the appropriate options to your plot 40 | * 41 | * > {axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer}}} 42 | **/ 43 | $.jqplot.CategoryAxisRenderer = function(options) { 44 | $.jqplot.LinearAxisRenderer.call(this); 45 | // prop: sortMergedLabels 46 | // True to sort tick labels when labels are created by merging 47 | // x axis values from multiple series. That is, say you have 48 | // two series like: 49 | // > line1 = [[2006, 4], [2008, 9], [2009, 16]]; 50 | // > line2 = [[2006, 3], [2007, 7], [2008, 6]]; 51 | // If no label array is specified, tick labels will be collected 52 | // from the x values of the series. With sortMergedLabels 53 | // set to true, tick labels will be: 54 | // > [2006, 2007, 2008, 2009] 55 | // With sortMergedLabels set to false, tick labels will be: 56 | // > [2006, 2008, 2009, 2007] 57 | // 58 | // Note, this property is specified on the renderOptions for the 59 | // axes when creating a plot: 60 | // > axes:{xaxis:{renderer:$.jqplot.CategoryAxisRenderer, rendererOptions:{sortMergedLabels:true}}} 61 | this.sortMergedLabels = false; 62 | }; 63 | 64 | $.jqplot.CategoryAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer(); 65 | $.jqplot.CategoryAxisRenderer.prototype.constructor = $.jqplot.CategoryAxisRenderer; 66 | 67 | $.jqplot.CategoryAxisRenderer.prototype.init = function(options){ 68 | this.groups = 1; 69 | this.groupLabels = []; 70 | this._groupLabels = []; 71 | this._grouped = false; 72 | this._barsPerGroup = null; 73 | this.reverse = false; 74 | // prop: tickRenderer 75 | // A class of a rendering engine for creating the ticks labels displayed on the plot, 76 | // See <$.jqplot.AxisTickRenderer>. 77 | // this.tickRenderer = $.jqplot.AxisTickRenderer; 78 | // this.labelRenderer = $.jqplot.AxisLabelRenderer; 79 | $.extend(true, this, {tickOptions:{formatString:'%d'}}, options); 80 | var db = this._dataBounds; 81 | // Go through all the series attached to this axis and find 82 | // the min/max bounds for this axis. 83 | for (var i=0; i db.max || db.max == null) { 96 | db.max = d[j][0]; 97 | } 98 | } 99 | else { 100 | if (d[j][1] < db.min || db.min == null) { 101 | db.min = d[j][1]; 102 | } 103 | if (d[j][1] > db.max || db.max == null) { 104 | db.max = d[j][1]; 105 | } 106 | } 107 | } 108 | } 109 | 110 | if (this.groupLabels.length) { 111 | this.groups = this.groupLabels.length; 112 | } 113 | }; 114 | 115 | 116 | $.jqplot.CategoryAxisRenderer.prototype.createTicks = function() { 117 | // we're are operating on an axis here 118 | var ticks = this._ticks; 119 | var userTicks = this.ticks; 120 | var name = this.name; 121 | // databounds were set on axis initialization. 122 | var db = this._dataBounds; 123 | var dim, interval; 124 | var min, max; 125 | var pos1, pos2; 126 | var tt, i; 127 | 128 | // if we already have ticks, use them. 129 | if (userTicks.length) { 130 | // adjust with blanks if we have groups 131 | if (this.groups > 1 && !this._grouped) { 132 | var l = userTicks.length; 133 | var skip = parseInt(l/this.groups, 10); 134 | var count = 0; 135 | for (var i=skip; i 1 && !this._grouped) { 248 | var l = labels.length; 249 | var skip = parseInt(l/this.groups, 10); 250 | var count = 0; 251 | for (var i=skip; i0 && track'); 325 | 326 | if (this.name == 'xaxis' || this.name == 'x2axis') { 327 | this._elem.width(this._plotDimensions.width); 328 | } 329 | else { 330 | this._elem.height(this._plotDimensions.height); 331 | } 332 | 333 | // create a _label object. 334 | this.labelOptions.axis = this.name; 335 | this._label = new this.labelRenderer(this.labelOptions); 336 | if (this._label.show) { 337 | var elem = this._label.draw(ctx, plot); 338 | elem.appendTo(this._elem); 339 | } 340 | 341 | var t = this._ticks; 342 | for (var i=0; i'); 355 | elem.html(this.groupLabels[i]); 356 | this._groupLabels.push(elem); 357 | elem.appendTo(this._elem); 358 | } 359 | } 360 | return this._elem; 361 | }; 362 | 363 | // called with scope of axis 364 | $.jqplot.CategoryAxisRenderer.prototype.set = function() { 365 | var dim = 0; 366 | var temp; 367 | var w = 0; 368 | var h = 0; 369 | var lshow = (this._label == null) ? false : this._label.show; 370 | if (this.show) { 371 | var t = this._ticks; 372 | for (var i=0; i dim) { 382 | dim = temp; 383 | } 384 | } 385 | } 386 | 387 | var dim2 = 0; 388 | for (var i=0; i dim2) { 397 | dim2 = temp; 398 | } 399 | } 400 | 401 | if (lshow) { 402 | w = this._label._elem.outerWidth(true); 403 | h = this._label._elem.outerHeight(true); 404 | } 405 | if (this.name == 'xaxis') { 406 | dim += dim2 + h; 407 | this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'}); 408 | } 409 | else if (this.name == 'x2axis') { 410 | dim += dim2 + h; 411 | this._elem.css({'height':dim+'px', left:'0px', top:'0px'}); 412 | } 413 | else if (this.name == 'yaxis') { 414 | dim += dim2 + w; 415 | this._elem.css({'width':dim+'px', left:'0px', top:'0px'}); 416 | if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { 417 | this._label._elem.css('width', w+'px'); 418 | } 419 | } 420 | else { 421 | dim += dim2 + w; 422 | this._elem.css({'width':dim+'px', right:'0px', top:'0px'}); 423 | if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { 424 | this._label._elem.css('width', w+'px'); 425 | } 426 | } 427 | } 428 | }; 429 | 430 | // called with scope of axis 431 | $.jqplot.CategoryAxisRenderer.prototype.pack = function(pos, offsets) { 432 | var ticks = this._ticks; 433 | var max = this.max; 434 | var min = this.min; 435 | var offmax = offsets.max; 436 | var offmin = offsets.min; 437 | var lshow = (this._label == null) ? false : this._label.show; 438 | var i; 439 | 440 | for (var p in pos) { 441 | this._elem.css(p, pos[p]); 442 | } 443 | 444 | this._offsets = offsets; 445 | // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left. 446 | var pixellength = offmax - offmin; 447 | var unitlength = max - min; 448 | 449 | if (!this.reverse) { 450 | // point to unit and unit to point conversions references to Plot DOM element top left corner. 451 | 452 | this.u2p = function(u){ 453 | return (u - min) * pixellength / unitlength + offmin; 454 | }; 455 | 456 | this.p2u = function(p){ 457 | return (p - offmin) * unitlength / pixellength + min; 458 | }; 459 | 460 | if (this.name == 'xaxis' || this.name == 'x2axis'){ 461 | this.series_u2p = function(u){ 462 | return (u - min) * pixellength / unitlength; 463 | }; 464 | this.series_p2u = function(p){ 465 | return p * unitlength / pixellength + min; 466 | }; 467 | } 468 | 469 | else { 470 | this.series_u2p = function(u){ 471 | return (u - max) * pixellength / unitlength; 472 | }; 473 | this.series_p2u = function(p){ 474 | return p * unitlength / pixellength + max; 475 | }; 476 | } 477 | } 478 | 479 | else { 480 | // point to unit and unit to point conversions references to Plot DOM element top left corner. 481 | 482 | this.u2p = function(u){ 483 | return offmin + (max - u) * pixellength / unitlength; 484 | }; 485 | 486 | this.p2u = function(p){ 487 | return min + (p - offmin) * unitlength / pixellength; 488 | }; 489 | 490 | if (this.name == 'xaxis' || this.name == 'x2axis'){ 491 | this.series_u2p = function(u){ 492 | return (max - u) * pixellength / unitlength; 493 | }; 494 | this.series_p2u = function(p){ 495 | return p * unitlength / pixellength + max; 496 | }; 497 | } 498 | 499 | else { 500 | this.series_u2p = function(u){ 501 | return (min - u) * pixellength / unitlength; 502 | }; 503 | this.series_p2u = function(p){ 504 | return p * unitlength / pixellength + min; 505 | }; 506 | } 507 | 508 | } 509 | 510 | 511 | if (this.show) { 512 | if (this.name == 'xaxis' || this.name == 'x2axis') { 513 | for (i=0; i= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one. 577 | if (this._ticks[j]._elem && this._ticks[j].label != " ") { 578 | var t = this._ticks[j]._elem; 579 | var p = t.position(); 580 | mid += p.left + t.outerWidth(true)/2; 581 | count++; 582 | } 583 | } 584 | mid = mid/count; 585 | this._groupLabels[i].css({'left':(mid - this._groupLabels[i].outerWidth(true)/2)}); 586 | this._groupLabels[i].css(labeledge[0], labeledge[1]); 587 | } 588 | } 589 | else { 590 | for (i=0; i 0) { 610 | shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; 611 | } 612 | else { 613 | shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; 614 | } 615 | break; 616 | case 'middle': 617 | // if (t.angle > 0) { 618 | // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 619 | // } 620 | // else { 621 | // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 622 | // } 623 | shim = -t.getHeight()/2; 624 | break; 625 | default: 626 | shim = -t.getHeight()/2; 627 | break; 628 | } 629 | } 630 | else { 631 | shim = -t.getHeight()/2; 632 | } 633 | 634 | var val = this.u2p(t.value) + shim + 'px'; 635 | t._elem.css('top', val); 636 | t.pack(); 637 | } 638 | } 639 | 640 | var labeledge=['left', 0]; 641 | if (lshow) { 642 | var h = this._label._elem.outerHeight(true); 643 | this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px'); 644 | if (this.name == 'yaxis') { 645 | this._label._elem.css('left', '0px'); 646 | labeledge = ['left', this._label._elem.outerWidth(true)]; 647 | } 648 | else { 649 | this._label._elem.css('right', '0px'); 650 | labeledge = ['right', this._label._elem.outerWidth(true)]; 651 | } 652 | this._label.pack(); 653 | } 654 | 655 | // draw the group labels, position top here, do left after label position. 656 | 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 657 | for (i=0; i= this._ticks.length-1) continue; // the last tick does not exist as there is no other group in order to have an empty one. 662 | if (this._ticks[j]._elem && this._ticks[j].label != " ") { 663 | var t = this._ticks[j]._elem; 664 | var p = t.position(); 665 | mid += p.top + t.outerHeight()/2; 666 | count++; 667 | } 668 | } 669 | mid = mid/count; 670 | this._groupLabels[i].css({'top':mid - this._groupLabels[i].outerHeight()/2}); 671 | this._groupLabels[i].css(labeledge[0], labeledge[1]); 672 | 673 | } 674 | } 675 | } 676 | }; 677 | 678 | 679 | })(jQuery); 680 | -------------------------------------------------------------------------------- /resources/jqplot.highlighter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jqPlot 3 | * Pure JavaScript plotting plugin using jQuery 4 | * 5 | * Version: 1.0.8 6 | * Revision: 1250 7 | * 8 | * Copyright (c) 2009-2013 Chris Leonello 9 | * jqPlot is currently available for use in all personal or commercial projects 10 | * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 11 | * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 12 | * choose the license that best suits your project and use it accordingly. 13 | * 14 | * Although not required, the author would appreciate an email letting him 15 | * know of any substantial use of jqPlot. You can reach the author at: 16 | * chris at jqplot dot com or see http://www.jqplot.com/info.php . 17 | * 18 | * If you are feeling kind and generous, consider supporting the project by 19 | * making a donation at: http://www.jqplot.com/donate.php . 20 | * 21 | * sprintf functions contained in jqplot.sprintf.js by Ash Searle: 22 | * 23 | * version 2007.04.27 24 | * author Ash Searle 25 | * http://hexmen.com/blog/2007/03/printf-sprintf/ 26 | * http://hexmen.com/js/sprintf.js 27 | * The author (Ash Searle) has placed this code in the public domain: 28 | * "This code is unrestricted: you are free to use it however you like." 29 | * 30 | */ 31 | (function($) { 32 | $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]); 33 | 34 | /** 35 | * Class: $.jqplot.Highlighter 36 | * Plugin which will highlight data points when they are moused over. 37 | * 38 | * To use this plugin, include the js 39 | * file in your source: 40 | * 41 | * > 42 | * 43 | * A tooltip providing information about the data point is enabled by default. 44 | * To disable the tooltip, set "showTooltip" to false. 45 | * 46 | * You can control what data is displayed in the tooltip with various 47 | * options. The "tooltipAxes" option controls whether the x, y or both 48 | * data values are displayed. 49 | * 50 | * Some chart types (e.g. hi-low-close) have more than one y value per 51 | * data point. To display the additional values in the tooltip, set the 52 | * "yvalues" option to the desired number of y values present (3 for a hlc chart). 53 | * 54 | * By default, data values will be formatted with the same formatting 55 | * specifiers as used to format the axis ticks. A custom format code 56 | * can be supplied with the tooltipFormatString option. This will apply 57 | * to all values in the tooltip. 58 | * 59 | * For more complete control, the "formatString" option can be set. This 60 | * Allows conplete control over tooltip formatting. Values are passed to 61 | * the format string in an order determined by the "tooltipAxes" and "yvalues" 62 | * options. So, if you have a hi-low-close chart and you just want to display 63 | * the hi-low-close values in the tooltip, you could set a formatString like: 64 | * 65 | * > highlighter: { 66 | * > tooltipAxes: 'y', 67 | * > yvalues: 3, 68 | * > formatString:' 69 | * > 70 | * > 71 | * >
hi:%s
low:%s
close:%s
' 72 | * > } 73 | * 74 | */ 75 | $.jqplot.Highlighter = function(options) { 76 | // Group: Properties 77 | // 78 | //prop: show 79 | // true to show the highlight. 80 | this.show = $.jqplot.config.enablePlugins; 81 | // prop: markerRenderer 82 | // Renderer used to draw the marker of the highlighted point. 83 | // Renderer will assimilate attributes from the data point being highlighted, 84 | // so no attributes need set on the renderer directly. 85 | // Default is to turn off shadow drawing on the highlighted point. 86 | this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false}); 87 | // prop: showMarker 88 | // true to show the marker 89 | this.showMarker = true; 90 | // prop: lineWidthAdjust 91 | // Pixels to add to the lineWidth of the highlight. 92 | this.lineWidthAdjust = 2.5; 93 | // prop: sizeAdjust 94 | // Pixels to add to the overall size of the highlight. 95 | this.sizeAdjust = 5; 96 | // prop: showTooltip 97 | // Show a tooltip with data point values. 98 | this.showTooltip = true; 99 | // prop: tooltipLocation 100 | // Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw' 101 | this.tooltipLocation = 'nw'; 102 | // prop: fadeTooltip 103 | // true = fade in/out tooltip, flase = show/hide tooltip 104 | this.fadeTooltip = true; 105 | // prop: tooltipFadeSpeed 106 | // 'slow', 'def', 'fast', or number of milliseconds. 107 | this.tooltipFadeSpeed = "fast"; 108 | // prop: tooltipOffset 109 | // Pixel offset of tooltip from the highlight. 110 | this.tooltipOffset = 2; 111 | // prop: tooltipAxes 112 | // Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx' 113 | // 'both' and 'xy' are equivalent, 'yx' reverses order of labels. 114 | this.tooltipAxes = 'both'; 115 | // prop; tooltipSeparator 116 | // String to use to separate x and y axes in tooltip. 117 | this.tooltipSeparator = ', '; 118 | // prop; tooltipContentEditor 119 | // Function used to edit/augment/replace the formatted tooltip contents. 120 | // Called as str = tooltipContentEditor(str, seriesIndex, pointIndex) 121 | // where str is the generated tooltip html and seriesIndex and pointIndex identify 122 | // the data point being highlighted. Should return the html for the tooltip contents. 123 | this.tooltipContentEditor = null; 124 | // prop: useAxesFormatters 125 | // Use the x and y axes formatters to format the text in the tooltip. 126 | this.useAxesFormatters = true; 127 | // prop: tooltipFormatString 128 | // sprintf format string for the tooltip. 129 | // Uses Ash Searle's javascript sprintf implementation 130 | // found here: http://hexmen.com/blog/2007/03/printf-sprintf/ 131 | // See http://perldoc.perl.org/functions/sprintf.html for reference. 132 | // Additional "p" and "P" format specifiers added by Chris Leonello. 133 | this.tooltipFormatString = '%.5P'; 134 | // prop: formatString 135 | // alternative to tooltipFormatString 136 | // will format the whole tooltip text, populating with x, y values as 137 | // indicated by tooltipAxes option. So, you could have a tooltip like: 138 | // 'Date: %s, number of cats: %d' to format the whole tooltip at one go. 139 | // If useAxesFormatters is true, values will be formatted according to 140 | // Axes formatters and you can populate your tooltip string with 141 | // %s placeholders. 142 | this.formatString = null; 143 | // prop: yvalues 144 | // Number of y values to expect in the data point array. 145 | // Typically this is 1. Certain plots, like OHLC, will 146 | // have more y values in each data point array. 147 | this.yvalues = 1; 148 | // prop: bringSeriesToFront 149 | // This option requires jQuery 1.4+ 150 | // True to bring the series of the highlighted point to the front 151 | // of other series. 152 | this.bringSeriesToFront = false; 153 | this._tooltipElem; 154 | this.isHighlighting = false; 155 | this.currentNeighbor = null; 156 | 157 | $.extend(true, this, options); 158 | }; 159 | 160 | var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']; 161 | var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7}; 162 | var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e']; 163 | 164 | // axis.renderer.tickrenderer.formatter 165 | 166 | // called with scope of plot 167 | $.jqplot.Highlighter.init = function (target, data, opts){ 168 | var options = opts || {}; 169 | // add a highlighter attribute to the plot 170 | this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter); 171 | }; 172 | 173 | // called within scope of series 174 | $.jqplot.Highlighter.parseOptions = function (defaults, options) { 175 | // Add a showHighlight option to the series 176 | // and set it to true by default. 177 | this.showHighlight = true; 178 | }; 179 | 180 | // called within context of plot 181 | // create a canvas which we can draw on. 182 | // insert it before the eventCanvas, so eventCanvas will still capture events. 183 | $.jqplot.Highlighter.postPlotDraw = function() { 184 | // Memory Leaks patch 185 | if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) { 186 | this.plugins.highlighter.highlightCanvas.resetCanvas(); 187 | this.plugins.highlighter.highlightCanvas = null; 188 | } 189 | 190 | if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) { 191 | this.plugins.highlighter._tooltipElem.emptyForce(); 192 | this.plugins.highlighter._tooltipElem = null; 193 | } 194 | 195 | this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas(); 196 | 197 | this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this)); 198 | this.plugins.highlighter.highlightCanvas.setContext(); 199 | 200 | var elem = document.createElement('div'); 201 | this.plugins.highlighter._tooltipElem = $(elem); 202 | elem = null; 203 | this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip'); 204 | this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'}); 205 | 206 | this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem); 207 | }; 208 | 209 | $.jqplot.preInitHooks.push($.jqplot.Highlighter.init); 210 | $.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions); 211 | $.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw); 212 | 213 | function draw(plot, neighbor) { 214 | var hl = plot.plugins.highlighter; 215 | var s = plot.series[neighbor.seriesIndex]; 216 | var smr = s.markerRenderer; 217 | var mr = hl.markerRenderer; 218 | mr.style = smr.style; 219 | mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust; 220 | mr.size = smr.size + hl.sizeAdjust; 221 | var rgba = $.jqplot.getColorComponents(smr.color); 222 | var newrgb = [rgba[0], rgba[1], rgba[2]]; 223 | var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]); 224 | mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')'; 225 | mr.init(); 226 | mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx); 227 | } 228 | 229 | function showTooltip(plot, series, neighbor) { 230 | // neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]} 231 | // gridData should be x,y pixel coords on the grid. 232 | // add the plot._gridPadding to that to get x,y in the target. 233 | var hl = plot.plugins.highlighter; 234 | var elem = hl._tooltipElem; 235 | var serieshl = series.highlighter || {}; 236 | 237 | var opts = $.extend(true, {}, hl, serieshl); 238 | 239 | if (opts.useAxesFormatters) { 240 | var xf = series._xaxis._ticks[0].formatter; 241 | var yf = series._yaxis._ticks[0].formatter; 242 | var xfstr = series._xaxis._ticks[0].formatString; 243 | var yfstr = series._yaxis._ticks[0].formatString; 244 | var str; 245 | var xstr = xf(xfstr, neighbor.data[0]); 246 | var ystrs = []; 247 | for (var i=1; i 41 | * 42 | * By default, the last value in the data ponit array in the data series is used 43 | * for the label. For most series renderers, extra data can be added to the 44 | * data point arrays and the last value will be used as the label. 45 | * 46 | * For instance, 47 | * this series: 48 | * 49 | * > [[1,4], [3,5], [7,2]] 50 | * 51 | * Would, by default, use the y values in the labels. 52 | * Extra data can be added to the series like so: 53 | * 54 | * > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']] 55 | * 56 | * And now the point labels would be 'mid', 'low', and 'hi'. 57 | * 58 | * Options to the point labels and a custom labels array can be passed into the 59 | * "pointLabels" option on the series option like so: 60 | * 61 | * > series:[{pointLabels:{ 62 | * > labels:['mid', 'hi', 'low'], 63 | * > location:'se', 64 | * > ypadding: 12 65 | * > } 66 | * > }] 67 | * 68 | * A custom labels array in the options takes precendence over any labels 69 | * in the series data. If you have a custom labels array in the options, 70 | * but still want to use values from the series array as labels, set the 71 | * "labelsFromSeries" option to true. 72 | * 73 | * By default, html entities (<, >, etc.) are escaped in point labels. 74 | * If you want to include actual html markup in the labels, 75 | * set the "escapeHTML" option to false. 76 | * 77 | */ 78 | $.jqplot.PointLabels = function(options) { 79 | // Group: Properties 80 | // 81 | // prop: show 82 | // show the labels or not. 83 | this.show = $.jqplot.config.enablePlugins; 84 | // prop: location 85 | // compass location where to position the label around the point. 86 | // 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw' 87 | this.location = 'n'; 88 | // prop: labelsFromSeries 89 | // true to use labels within data point arrays. 90 | this.labelsFromSeries = false; 91 | // prop: seriesLabelIndex 92 | // array index for location of labels within data point arrays. 93 | // if null, will use the last element of the data point array. 94 | this.seriesLabelIndex = null; 95 | // prop: labels 96 | // array of arrays of labels, one array for each series. 97 | this.labels = []; 98 | // actual labels that will get displayed. 99 | // needed to preserve user specified labels in labels array. 100 | this._labels = []; 101 | // prop: stackedValue 102 | // true to display value as stacked in a stacked plot. 103 | // no effect if labels is specified. 104 | this.stackedValue = false; 105 | // prop: ypadding 106 | // vertical padding in pixels between point and label 107 | this.ypadding = 6; 108 | // prop: xpadding 109 | // horizontal padding in pixels between point and label 110 | this.xpadding = 6; 111 | // prop: escapeHTML 112 | // true to escape html entities in the labels. 113 | // If you want to include markup in the labels, set to false. 114 | this.escapeHTML = true; 115 | // prop: edgeTolerance 116 | // Number of pixels that the label must be away from an axis 117 | // boundary in order to be drawn. Negative values will allow overlap 118 | // with the grid boundaries. 119 | this.edgeTolerance = -5; 120 | // prop: formatter 121 | // A class of a formatter for the tick text. sprintf by default. 122 | this.formatter = $.jqplot.DefaultTickFormatter; 123 | // prop: formatString 124 | // string passed to the formatter. 125 | this.formatString = ''; 126 | // prop: hideZeros 127 | // true to not show a label for a value which is 0. 128 | this.hideZeros = false; 129 | this._elems = []; 130 | 131 | $.extend(true, this, options); 132 | }; 133 | 134 | var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']; 135 | var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7}; 136 | var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e']; 137 | 138 | // called with scope of a series 139 | $.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){ 140 | var options = $.extend(true, {}, seriesDefaults, opts); 141 | options.pointLabels = options.pointLabels || {}; 142 | if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) { 143 | options.pointLabels.location = 'e'; 144 | } 145 | // add a pointLabels attribute to the series plugins 146 | this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels); 147 | this.plugins.pointLabels.setLabels.call(this); 148 | }; 149 | 150 | // called with scope of series 151 | $.jqplot.PointLabels.prototype.setLabels = function() { 152 | var p = this.plugins.pointLabels; 153 | var labelIdx; 154 | if (p.seriesLabelIndex != null) { 155 | labelIdx = p.seriesLabelIndex; 156 | } 157 | else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') { 158 | labelIdx = (this._plotData[0].length < 3) ? 0 : this._plotData[0].length -1; 159 | } 160 | else { 161 | labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1; 162 | } 163 | p._labels = []; 164 | if (p.labels.length === 0 || p.labelsFromSeries) { 165 | if (p.stackedValue) { 166 | if (this._plotData.length && this._plotData[0].length){ 167 | // var idx = p.seriesLabelIndex || this._plotData[0].length -1; 168 | for (var i=0; i scr || elb + et > scb) { 358 | elem.remove(); 359 | } 360 | 361 | elem = null; 362 | helem = null; 363 | } 364 | 365 | // finally, animate them if the series is animated 366 | // if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) { 367 | // var sel = '.jqplot-point-label.jqplot-series-'+this.index; 368 | // $(sel).hide(); 369 | // $(sel).fadeIn(1000); 370 | // } 371 | 372 | } 373 | }; 374 | 375 | $.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init); 376 | $.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw); 377 | })(jQuery); -------------------------------------------------------------------------------- /resources/jquery.jqplot.css: -------------------------------------------------------------------------------- 1 | /*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/ 2 | .jqplot-target { 3 | position: relative; 4 | color: #666666; 5 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 6 | font-size: 1em; 7 | /* height: 300px; 8 | width: 400px;*/ 9 | } 10 | 11 | /*rules applied to all axes*/ 12 | .jqplot-axis { 13 | font-size: 0.75em; 14 | } 15 | 16 | .jqplot-xaxis { 17 | margin-top: 10px; 18 | } 19 | 20 | .jqplot-x2axis { 21 | margin-bottom: 10px; 22 | } 23 | 24 | .jqplot-yaxis { 25 | margin-right: 10px; 26 | } 27 | 28 | .jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis { 29 | margin-left: 10px; 30 | margin-right: 10px; 31 | } 32 | 33 | /*rules applied to all axis tick divs*/ 34 | .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 { 35 | position: absolute; 36 | white-space: pre; 37 | } 38 | 39 | 40 | .jqplot-xaxis-tick { 41 | top: 0px; 42 | /* initial position untill tick is drawn in proper place */ 43 | left: 15px; 44 | /* padding-top: 10px;*/ 45 | vertical-align: top; 46 | } 47 | 48 | .jqplot-x2axis-tick { 49 | bottom: 0px; 50 | /* initial position untill tick is drawn in proper place */ 51 | left: 15px; 52 | /* padding-bottom: 10px;*/ 53 | vertical-align: bottom; 54 | } 55 | 56 | .jqplot-yaxis-tick { 57 | right: 0px; 58 | /* initial position untill tick is drawn in proper place */ 59 | top: 15px; 60 | /* padding-right: 10px;*/ 61 | text-align: right; 62 | } 63 | 64 | .jqplot-yaxis-tick.jqplot-breakTick { 65 | right: -20px; 66 | margin-right: 0px; 67 | padding:1px 5px 1px 5px; 68 | /*background-color: white;*/ 69 | z-index: 2; 70 | font-size: 1.5em; 71 | } 72 | 73 | .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick { 74 | left: 0px; 75 | /* initial position untill tick is drawn in proper place */ 76 | top: 15px; 77 | /* padding-left: 10px;*/ 78 | /* padding-right: 15px;*/ 79 | text-align: left; 80 | } 81 | 82 | .jqplot-yMidAxis-tick { 83 | text-align: center; 84 | white-space: nowrap; 85 | } 86 | 87 | .jqplot-xaxis-label { 88 | margin-top: 10px; 89 | font-size: 11pt; 90 | position: absolute; 91 | } 92 | 93 | .jqplot-x2axis-label { 94 | margin-bottom: 10px; 95 | font-size: 11pt; 96 | position: absolute; 97 | } 98 | 99 | .jqplot-yaxis-label { 100 | margin-right: 10px; 101 | /* text-align: center;*/ 102 | font-size: 11pt; 103 | position: absolute; 104 | } 105 | 106 | .jqplot-yMidAxis-label { 107 | font-size: 11pt; 108 | position: absolute; 109 | } 110 | 111 | .jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label { 112 | /* text-align: center;*/ 113 | font-size: 11pt; 114 | margin-left: 10px; 115 | position: absolute; 116 | } 117 | 118 | .jqplot-meterGauge-tick { 119 | font-size: 0.75em; 120 | color: #999999; 121 | } 122 | 123 | .jqplot-meterGauge-label { 124 | font-size: 1em; 125 | color: #999999; 126 | } 127 | 128 | table.jqplot-table-legend { 129 | margin-top: 12px; 130 | margin-bottom: 12px; 131 | margin-left: 12px; 132 | margin-right: 12px; 133 | } 134 | 135 | table.jqplot-table-legend, table.jqplot-cursor-legend { 136 | background-color: rgba(255,255,255,0.6); 137 | border: 1px solid #cccccc; 138 | position: absolute; 139 | font-size: 0.75em; 140 | } 141 | 142 | td.jqplot-table-legend { 143 | vertical-align:middle; 144 | } 145 | 146 | /* 147 | These rules could be used instead of assigning 148 | element styles and relying on js object properties. 149 | */ 150 | 151 | /* 152 | td.jqplot-table-legend-swatch { 153 | padding-top: 0.5em; 154 | text-align: center; 155 | } 156 | 157 | tr.jqplot-table-legend:first td.jqplot-table-legend-swatch { 158 | padding-top: 0px; 159 | } 160 | */ 161 | 162 | td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active { 163 | cursor: pointer; 164 | } 165 | 166 | .jqplot-table-legend .jqplot-series-hidden { 167 | text-decoration: line-through; 168 | } 169 | 170 | div.jqplot-table-legend-swatch-outline { 171 | border: 1px solid #cccccc; 172 | padding:1px; 173 | } 174 | 175 | div.jqplot-table-legend-swatch { 176 | width:0px; 177 | height:0px; 178 | border-top-width: 5px; 179 | border-bottom-width: 5px; 180 | border-left-width: 6px; 181 | border-right-width: 6px; 182 | border-top-style: solid; 183 | border-bottom-style: solid; 184 | border-left-style: solid; 185 | border-right-style: solid; 186 | } 187 | 188 | .jqplot-title { 189 | top: 0px; 190 | left: 0px; 191 | padding-bottom: 0.5em; 192 | font-size: 1.2em; 193 | } 194 | 195 | table.jqplot-cursor-tooltip { 196 | border: 1px solid #cccccc; 197 | font-size: 0.75em; 198 | } 199 | 200 | 201 | .jqplot-cursor-tooltip { 202 | border: 1px solid #cccccc; 203 | font-size: 0.75em; 204 | white-space: nowrap; 205 | background: rgba(208,208,208,0.5); 206 | padding: 1px; 207 | } 208 | 209 | .jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip { 210 | border: 1px solid #cccccc; 211 | font-size: 0.75em; 212 | white-space: nowrap; 213 | background: rgba(208,208,208,0.5); 214 | padding: 1px; 215 | } 216 | 217 | .jqplot-point-label { 218 | font-size: 0.75em; 219 | z-index: 2; 220 | } 221 | 222 | td.jqplot-cursor-legend-swatch { 223 | vertical-align: middle; 224 | text-align: center; 225 | } 226 | 227 | div.jqplot-cursor-legend-swatch { 228 | width: 1.2em; 229 | height: 0.7em; 230 | } 231 | 232 | .jqplot-error { 233 | /* Styles added to the plot target container when there is an error go here.*/ 234 | text-align: center; 235 | } 236 | 237 | .jqplot-error-message { 238 | /* Styling of the custom error message div goes here.*/ 239 | position: relative; 240 | top: 46%; 241 | display: inline-block; 242 | } 243 | 244 | div.jqplot-bubble-label { 245 | font-size: 0.8em; 246 | /* background: rgba(90%, 90%, 90%, 0.15);*/ 247 | padding-left: 2px; 248 | padding-right: 2px; 249 | color: rgb(20%, 20%, 20%); 250 | } 251 | 252 | div.jqplot-bubble-label.jqplot-bubble-label-highlight { 253 | background: rgba(90%, 90%, 90%, 0.7); 254 | } 255 | 256 | div.jqplot-noData-container { 257 | text-align: center; 258 | background-color: rgba(96%, 96%, 96%, 0.3); 259 | } 260 | -------------------------------------------------------------------------------- /resources/patch-jquery.jqplot.js: -------------------------------------------------------------------------------- 1 | --- resources/jquery.jqplot.js.orig 2016-08-30 18:50:53.056335603 +0200 2 | +++ resources/jquery.jqplot.js 2016-08-30 18:51:33.211372314 +0200 3 | @@ -9139,7 +9139,7 @@ 4 | 5 | for (var i=0; i tagwidth) { 8 | + if (context.measureText(w).width > tagwidth && w.length > words[i].length) { 9 | breaks.push(i); 10 | w = ''; 11 | i--; 12 | -------------------------------------------------------------------------------- /resources/pgbadger.css: -------------------------------------------------------------------------------- 1 | /*Art is code*/ 2 | body { 3 | background-color:#cdd5da; 4 | background: -webkit-linear-gradient( bottom, #cdd5da, #e8e7e7); 5 | background: -moz-linear-gradient( bottom, #cdd5da, #e8e7e7); 6 | background: -ms-linear-gradient( bottom, #cdd5da, #e8e7e7); 7 | background: -o-linear-gradient( bottom, #cdd5da, #e8e7e7); 8 | background: linear-gradient( to bottom, #cdd5da, #e8e7e7); 9 | margin-top: 50px; 10 | } 11 | 12 | ul.nav li.dropdown:hover ul.dropdown-menu{ 13 | display: block; 14 | } 15 | 16 | ul.nav li.dropdown ul.dropdown-menu{ 17 | margin: 0; 18 | } 19 | 20 | #pgbadger-brand { 21 | font-size: 1.1em; 22 | font-weight: bold; 23 | } 24 | 25 | ul#slides li { 26 | list-style-type: none; 27 | } 28 | 29 | h1 { 30 | font-size: 2em; 31 | } 32 | 33 | h2 { 34 | font-size: 1.6em; 35 | } 36 | 37 | h3, h3 small { 38 | font-size: 1.1em; 39 | text-transform: uppercase; 40 | letter-spacing: .1em; 41 | } 42 | 43 | h3 small { 44 | font-size: 1em; 45 | } 46 | 47 | .analysis-item { 48 | background: #fff; 49 | margin-bottom:2em; 50 | padding-bottom:15px; 51 | } 52 | 53 | h3 { 54 | margin: 0; 55 | padding: 0; 56 | color: #5f5555; 57 | } 58 | 59 | h1.page-header { 60 | margin: 1em 0 1em 0; 61 | padding: 0; 62 | color: #5f5555; 63 | border-bottom: none; 64 | } 65 | 66 | h2 { 67 | color:#5f5555; 68 | } 69 | 70 | .nav-pills, .nav-tabs { 71 | margin: 0 1em; 72 | } 73 | 74 | footer { 75 | margin-top: 60px; 76 | } 77 | 78 | .col-md-8 .tabbable { 79 | margin-top: 1em; 80 | } 81 | 82 | #global-stats .tab-content { 83 | margin: 2em 0 3em 0; 84 | } 85 | 86 | #global-stats .tab-content li { 87 | display: block; 88 | width:12%; /*160/960*/ 89 | float:left; 90 | margin-left: 2.5%; 91 | } 92 | 93 | #global-stats .tab-content li.first { 94 | margin-left: 0; 95 | } 96 | 97 | .well { 98 | background: #f9f9f9; 99 | border-radius: 0; 100 | } 101 | 102 | .key-figures ul { 103 | margin: 0; 104 | padding: 0; 105 | } 106 | 107 | .key-figures li { 108 | list-style-type: none; 109 | margin-bottom: 2em; 110 | } 111 | 112 | .figure { 113 | font-weight: bold; 114 | font-size: 1.4em; 115 | color:#2e8aa5; 116 | } 117 | 118 | .figure-label { 119 | display: block; 120 | color: #666; 121 | } 122 | 123 | .mfigure { 124 | font-weight: bold; 125 | font-size: 1.4em; 126 | color:#d26115; 127 | margin-left: 5px; 128 | } 129 | 130 | .smfigure { 131 | font-weight: bold; 132 | font-size: 1.1em; 133 | color:#d26115; 134 | } 135 | 136 | .mfigure small { 137 | font-weight: bold; 138 | font-size: 0.6em; 139 | color:#ffffff; 140 | } 141 | 142 | .smfigure small { 143 | font-weight: bold; 144 | font-size: 0.6em; 145 | color:#ffffff; 146 | } 147 | 148 | .hfigure { 149 | font-weight: bold; 150 | font-size: 1.0em; 151 | color:#8dbd0f; 152 | } 153 | 154 | .hfigure small { 155 | font-weight: bold; 156 | font-size: 0.6em; 157 | color:#ffffff; 158 | } 159 | 160 | .navbar-inverse { 161 | background: #5f5555; 162 | border: none; 163 | } 164 | 165 | .navbar-inverse .brand, .navbar-inverse .nav > li > a { 166 | color:#eee; 167 | } 168 | 169 | .linegraph { 170 | width : 100%; 171 | height: 400px; 172 | } 173 | 174 | .piegraph { 175 | width : 100%; 176 | height: 400px; 177 | } 178 | .histo-graph { 179 | width : 100%; 180 | height: 140px; 181 | } 182 | .duration-histo-graph { 183 | width : 100%; 184 | height: 400px; 185 | } 186 | 187 | @media (min-width:750px) { 188 | #show-hide-menu { 189 | position: absolute; 190 | left: -9999px; 191 | } 192 | 193 | .navbar ul.collapse { 194 | overflow: visible; 195 | } 196 | } 197 | 198 | @media (max-width:749px) { 199 | #show-hide-menu { 200 | position: inherit; 201 | } 202 | 203 | #global-stats .tab-content li { 204 | display: block; 205 | width:auto; /*160/960*/ 206 | float:none; 207 | margin-left: 0; 208 | margin-bottom: 1em; 209 | } 210 | 211 | ul#slides { 212 | margin: 0; 213 | padding: 0; 214 | } 215 | 216 | ul#slides li div div { 217 | padding:0 1.5em; 218 | } 219 | 220 | .linegraph { 221 | width : 94.5%; 222 | } 223 | 224 | .piegraph { 225 | width : 94.5%; 226 | } 227 | 228 | .histo-graph { 229 | width : 84.5%; 230 | } 231 | 232 | .duration-histo-graph { 233 | width : 94.5%; 234 | } 235 | 236 | .key-figures ul { 237 | margin-top: 1.5em; 238 | } 239 | 240 | .navbar .nav { 241 | margin: 0 3em 2em 3em; 242 | } 243 | 244 | .navbar .nav > li { 245 | float: none; 246 | } 247 | 248 | .navbar ul.collapse:hover { 249 | overflow: visible; 250 | } 251 | } 252 | 253 | div#littleToc { display:none; } 254 | 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; } 255 | div#littleToc div#littleTocTitle { font-weight:bold; text-align:center;padding:2px; } 256 | div#littleToc ul { padding:0px; text-indent:0px; margin:0px; } 257 | div#littleToc li { font-size:11px; list-style-type:none; padding:0px; text-indent:0px; margin:0px; } 258 | div#littleToc a { color:#000000; padding:2px; margin:2px; display:block; text-decoration:none; border:1px solid #CCCCCC; } 259 | div#littleToc a:hover { text-decoration:none; background-color:#DDDDDD; } 260 | 261 | .jqplot-graph { color: #ac1316; z-index: 99;} 262 | .sql {font-family:monospace;} 263 | .sql .imp {font-weight: bold; color: red;} 264 | .sql .kw1 {color: #993333; font-weight: bold; text-transform: uppercase;} 265 | .sql .kw2 {color: #993333; font-style: italic;} 266 | .sql .kw3 {color: #993333; text-transform: uppercase;} 267 | .sql .co1 {color: #808080; font-style: italic;} 268 | .sql .co2 {color: #808080; font-style: italic;} 269 | .sql .coMULTI {color: #808080; font-style: italic;} 270 | .sql .es0 {color: #000099; font-weight: bold;} 271 | .sql .br0 {color: #66cc66;} 272 | .sql .sy0 {color: #000000;} 273 | .sql .st0 {color: #ff0000;} 274 | .sql .nu0 {color: #cc66cc;} 275 | .sql span.xtra { display:block; } 276 | .sql-smallsize {width: 450px; } 277 | .sql-mediumsize {width: 600px;max-height:200px;overflow:auto;} 278 | .sql-largesize {width: 950px;max-height:200px;overflow:auto;} 279 | .pre-smallfont {font-size:10px;} 280 | 281 | .jqplot-target { 282 | position: relative; 283 | color: #333; 284 | font-size: 1.2em; 285 | } 286 | 287 | .jqplot-highlighter-tooltip { 288 | border: 0px; 289 | font-size: 1.0em; 290 | white-space: nowrap; 291 | font-weight: bold; 292 | margin: 5px; 293 | padding: 5px; 294 | color:#ffffff; 295 | background: #5f5555; 296 | border:1px solid #5f5555; 297 | border:1px solid rgba(0, 0, 0, 0.2); 298 | -webkit-border-radius:6px; 299 | -moz-border-radius:6px; 300 | border-radius:6px; 301 | -webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2); 302 | -moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2); 303 | box-shadow:0 5px 10px rgba(0, 0, 0, 0.2); 304 | } 305 | 306 | .jqplot-title { 307 | font-size: 1.1em; 308 | text-transform: uppercase; 309 | letter-spacing: .1em; 310 | margin: 0; 311 | padding: 0; 312 | color: #5f5555; 313 | } 314 | 315 | #pgbadgerModal .modal-dialog 316 | { 317 | width: 850px; 318 | overflow-y: hidden; 319 | } 320 | 321 | #pgbadgerModal .modal-body 322 | { 323 | height: 100%; 324 | width: 830px; 325 | background-color: white; 326 | } 327 | 328 | .error-pre { 329 | display: block; 330 | padding: 9.5px; 331 | margin: 0 0 10px; 332 | font-size: 13px; 333 | line-height: 1.42857143; 334 | color: #333; 335 | word-break: break-all; 336 | word-wrap: break-word; 337 | background-color: #f5f5f5; 338 | border: 1px solid #ccc; 339 | border-radius: 4px; 340 | } 341 | -------------------------------------------------------------------------------- /resources/pgbadger.js: -------------------------------------------------------------------------------- 1 | 2 | /* Download'); 9 | } 10 | 11 | function add_download_button_event (buttonid, divid) { 12 | 13 | jQuery('#download'+buttonid).click( function() { 14 | $('#pgbadgerModal img').attr('src', $('#'+divid).jqplotToImageStr({})); 15 | $('#pgbadgerModal').modal('toggle'); 16 | }); 17 | 18 | } 19 | 20 | function create_linegraph (divid, charttitle, ylabel, arr_series, lineseries) { 21 | return $.jqplot(divid, lineseries, { 22 | seriesColors: ['#6e9dc9','#f4ab3a','#ac7fa8','#8dbd0f',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc"], 23 | seriesDefaults: { markerOptions: {show: false}, lineWidth:1 }, 24 | grid: { borderWidth: 1, background: '#ffffff'}, 25 | title: charttitle, 26 | series: arr_series, 27 | axes: { 28 | xaxis: { 29 | renderer:$.jqplot.DateAxisRenderer, 30 | tickOptions:{ angle: -30, textColor: '#333' }, 31 | }, 32 | yaxis: { 33 | renderer: $.jqplot.LogAxisRenderer, 34 | labelRenderer: $.jqplot.CanvasAxisLabelRenderer, 35 | tickRenderer: $.jqplot.CanvasAxisTickRenderer, 36 | tickOptions: { 37 | textColor: '#333', 38 | formatter: function(format, value) { return pretty_print_number(value, 0, ylabel); } 39 | }, 40 | } 41 | }, 42 | legend: { 43 | show: true, 44 | location: 'nw', 45 | }, 46 | cursor:{ 47 | show: true, 48 | zoom: true, 49 | showTooltip:false, 50 | looseZoom: true, 51 | followMouse: true, 52 | }, 53 | highlighter: { 54 | show: true, 55 | tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) { 56 | var dateToDisplay = new Date(plot.data[seriesIndex][pointIndex][0]); 57 | var textToShow = '
On '+dateToDisplay.toString(); 58 | for (var i=0; i'+plot.series[i].label+''; 60 | } 61 | textToShow += '
'; 62 | return textToShow; 63 | } 64 | } 65 | }); 66 | } 67 | 68 | function create_piechart(divid, title, data) { 69 | return $.jqplot(divid, [data], 70 | { 71 | 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"], 72 | grid: { borderWidth: 1, background: '#ffffff'}, 73 | title: title, 74 | seriesDefaults: { 75 | renderer: $.jqplot.PieRenderer, 76 | rendererOptions: { 77 | showDataLabels: true 78 | } 79 | }, 80 | legend: { show:true, location: 'e' }, 81 | highlighter: { 82 | show: true, 83 | tooltipLocation:'sw', 84 | useAxesFormatters:false, 85 | tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) { 86 | var textToShow = '
'; 87 | //textToShow += ''+pretty_print_number(plot.data[0][pointIndex][1], 2)+' '+plot.data[0][pointIndex][0]+''; 88 | textToShow += ''+format_number(plot.data[0][pointIndex][1])+' '+plot.data[0][pointIndex][0]+''; 89 | textToShow += '
'; 90 | return textToShow; 91 | } 92 | } 93 | } 94 | ); 95 | } 96 | 97 | function create_bargraph (divid, title, ytitle, data, y2title) { 98 | return $.jqplot(divid, data, { 99 | grid: { borderWidth: 1, background: '#ffffff'}, 100 | title: title, 101 | seriesDefaults: { 102 | rendererOptions: { 103 | barPadding: 4, 104 | barMargin: 5, 105 | } 106 | }, 107 | seriesColors: ['#6e9dc9','#8dbd0f','#f4ab3a','#ac7fa8',"#958c12","#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc"], 108 | series:[ 109 | { renderer:$.jqplot.BarRenderer, label: ytitle}, 110 | { yaxis:'y2axis', label: y2title, markerOptions: {show: false}, lineWidth:1 } 111 | ], 112 | axes: { 113 | xaxis: { 114 | renderer: $.jqplot.CategoryAxisRenderer, 115 | drawMajorGridlines: false, 116 | drawMajorTickMarks: false, 117 | tickRenderer: $.jqplot.CanvasAxisTickRenderer, 118 | tickOptions: { 119 | angle: -30, 120 | textColor: '#333', 121 | formatString:'%H:%M', 122 | fontFamily:'Helvetica', 123 | fontSize: '8pt' 124 | } 125 | }, 126 | yaxis: { 127 | autoscale:true, 128 | labelRenderer: $.jqplot.CanvasAxisLabelRenderer, 129 | tickOptions:{ textColor: '#333' }, 130 | tickRenderer: $.jqplot.CanvasAxisTickRenderer, 131 | tickOptions: { 132 | textColor: '#333', 133 | formatter: function(format, value) { return pretty_print_number(value, 1, ytitle); }, 134 | fontFamily:'Helvetica', 135 | fontSize: '8pt' 136 | }, 137 | label: ytitle 138 | }, 139 | y2axis: { 140 | autoscale:true, 141 | labelRenderer: $.jqplot.CanvasAxisLabelRenderer, 142 | tickRenderer: $.jqplot.CanvasAxisTickRenderer, 143 | tickOptions: { 144 | textColor: '#8dbd0f', 145 | formatter: function(format, value) { return pretty_print_number(value, 1, y2title); }, 146 | fontFamily:'Helvetica', 147 | fontSize: '8pt' 148 | }, 149 | rendererOptions: { 150 | alignTicks: true, 151 | }, 152 | label: y2title, 153 | 154 | } 155 | }, 156 | highlighter: { 157 | show: true, 158 | tooltipLocation:'ne', 159 | useAxesFormatters:false, 160 | tooltipContentEditor: function (str, seriesIndex, pointIndex, plot) { 161 | var textToShow = '
At '+plot.data[0][pointIndex][0]; 162 | for (var i=0; i'+plot.series[i].label+''; 164 | } 165 | textToShow += '
'; 166 | return textToShow; 167 | } 168 | } 169 | }); 170 | } 171 | 172 | function pretty_print_number(val, scale, type) 173 | { 174 | 175 | if ( (scale == undefined) || ((scale == 0) && (val != 0)) ) { 176 | scale = 1; 177 | }; 178 | 179 | if (type != undefined) { 180 | type = type.toLowerCase(); 181 | 182 | if (type.search('size') >= 0) { 183 | if (Math.abs(val) >= 1125899906842624) { 184 | val = (val / 1125899906842624); 185 | return val.toFixed(scale) + " PiB"; 186 | } else if (Math.abs(val) >= 1099511627776) { 187 | val = (val / 1099511627776); 188 | return val.toFixed(scale) + " TiB"; 189 | } else if (Math.abs(val) >= 1073741824) { 190 | val = (val / 1073741824); 191 | return val.toFixed(scale) + " GiB"; 192 | } else if (Math.abs(val) >= 1048576) { 193 | val = (val / 1048576); 194 | return val.toFixed(scale) + " MiB"; 195 | } else if (Math.abs(val) >= 1024) { 196 | val = (val / 1024); 197 | return val.toFixed(scale) + " KiB"; 198 | } else { 199 | return val + " B"; 200 | } 201 | } else if (type.search('duration') >= 0) { 202 | if (Math.abs(val) >= 1000) { 203 | val = (val / 1000); 204 | return val.toFixed(scale) + " sec"; 205 | } else { 206 | return val.toFixed(scale) + " ms"; 207 | } 208 | } else { 209 | if (Math.abs(val) >= 1000000000000000) { 210 | val = (val / 1000000000000000); 211 | return val.toFixed(scale) + " P"; 212 | } else if (Math.abs(val) >= 1000000000000) { 213 | val = (val / 1000000000000); 214 | return val.toFixed(scale) + " T"; 215 | } else if (Math.abs(val) >= 1000000000) { 216 | val = (val / 1000000000); 217 | return val.toFixed(scale) + " G"; 218 | } else if (Math.abs(val) >= 1000000) { 219 | val = (val / 1000000); 220 | return val.toFixed(scale) + " M"; 221 | } else if (Math.abs(val) >= 1000) { 222 | val = (val / 1000); 223 | return val.toFixed(scale) + " K"; 224 | } 225 | } 226 | } 227 | 228 | return val.toFixed(scale); 229 | } 230 | 231 | function format_number(val) { 232 | var decimal = 2; 233 | var msep = ','; 234 | var deci = Math.round( Math.pow(10,decimal)*(Math.abs(val)-Math.floor(Math.abs(val)))) ; 235 | val = Math.floor(Math.abs(val)); 236 | if ((decimal==0)||(deci==Math.pow(10,decimal))) {deci=0;} 237 | var val_format=val+""; 238 | var nb=val_format.length; 239 | for (var i=1;i<4;i++) { 240 | if (val>=Math.pow(10,(3*i))) { 241 | val_format=val_format.substring(0,nb-(3*i))+msep+val_format.substring(nb-(3*i)); 242 | } 243 | } 244 | if (decimal>0) { 245 | var decim=""; 246 | for (var j=0;j<(decimal-deci.toString().length);j++) {decim+="0";} 247 | deci=decim+deci.toString(); 248 | if (deci > 0) { 249 | val_format=val_format+"."+deci; 250 | } 251 | } 252 | if (parseFloat(val)<0) {val_format="-"+val_format;} 253 | return val_format; 254 | } 255 | 256 | 257 | function draw_charts() { 258 | window.charts = $.grep(window.charts, function(chart) { 259 | var chart_copy = chart.slice(); 260 | var type = chart_copy.shift(); 261 | var divid = chart[1]; 262 | if ($('#' + divid).is(':visible')) { 263 | window['create_' + type].apply(null, chart_copy); 264 | // chart loaded, don't keep it in the list 265 | return false; 266 | } 267 | return true; 268 | }); 269 | }; 270 | -------------------------------------------------------------------------------- /resources/pgbadger_slide.js: -------------------------------------------------------------------------------- 1 | 2 | jQuery(function (){ 3 | jQuery('#pgbadger-brand').tooltip(); 4 | 5 | jQuery("#pop-infos").popover('hide'); 6 | jQuery('.slide').hide(); 7 | jQuery('.active-slide').show(); 8 | 9 | /* Go to specific slide and report from external link call */ 10 | var activeMenu = location.hash; 11 | if (activeMenu) { 12 | activeMenu = activeMenu.substring(1); 13 | var lkobj = document.getElementById(activeMenu); 14 | var liId = jQuery(lkobj).parent().attr("id"); 15 | if (liId != undefined) { 16 | var slideId = '#'+liId; 17 | jQuery('#main-container li.slide').removeClass('active-slide').hide(); 18 | jQuery(slideId).addClass("active-slide").fadeIn(); 19 | window.location.hash = '#'+activeMenu; 20 | } 21 | } 22 | 23 | jQuery('.navbar li.dropdown').click(function() { 24 | var id = jQuery(this).attr("id"); 25 | id = id.substring(5); 26 | var slideId = '#'+id+'-slide'; 27 | var currentSlide = jQuery('#main-container .active-slide').attr("id"); 28 | currentSlide = '#'+currentSlide; 29 | 30 | if(slideId != currentSlide) { 31 | jQuery('#main-container li.slide').removeClass('active-slide').hide(); 32 | jQuery(slideId).addClass("active-slide").fadeIn(); 33 | } 34 | scrollTo(0,0); 35 | draw_charts(); 36 | }); 37 | 38 | jQuery('.navbar li ul li').click(function() { 39 | var liId = jQuery(this).parent().parent().attr("id"); 40 | var id = liId.substring(5); 41 | var slideId = '#'+id+'-slide'; 42 | jQuery('#main-container li.slide').removeClass('active-slide').hide(); 43 | jQuery(slideId).addClass("active-slide").fadeIn(); 44 | draw_charts(); 45 | }); 46 | 47 | draw_charts(); 48 | }); 49 | 50 | jQuery(document).ready(function () { 51 | jQuery('.sql').dblclick(function () { 52 | if (this.style == undefined || this.style.whiteSpace == 'pre') { 53 | this.style.whiteSpace ='normal'; 54 | } else { 55 | this.style.whiteSpace = 'pre'; 56 | } 57 | }); 58 | jQuery('.icon-copy').click(function () { 59 | var obj = $(this).parent()[0]; 60 | if (window.getSelection) { 61 | var sel = window.getSelection(); 62 | sel.removeAllRanges(); 63 | var range = document.createRange(); 64 | range.selectNodeContents(obj); 65 | sel.addRange(range); 66 | } else if (document.selection) { 67 | var textRange = document.body.createTextRange(); 68 | textRange.moveToElementText(obj); 69 | textRange.select(); 70 | } 71 | }); 72 | 73 | function shiftWindow() { 74 | scrollBy(0, -50); 75 | } 76 | 77 | if (location.hash) { 78 | shiftWindow(); 79 | } 80 | window.addEventListener("hashchange",shiftWindow); 81 | }); 82 | 83 | -------------------------------------------------------------------------------- /t/01_lint.t: -------------------------------------------------------------------------------- 1 | use Test::Simple tests => 3; 2 | 3 | my $ret = `perl -wc pgbadger 2>&1`; 4 | ok( $? == 0, "PERL syntax check"); 5 | 6 | $ret = `podchecker doc/*.pod 2>&1`; 7 | ok( $? == 0, "pod syntax check"); 8 | 9 | $ret = `perl -E 'use Storable qw(dclone); say "version=",\$Storable::VERSION; say "limit=",\$Storable::recursion_limit; my \@tt; for (1..1_000_000) { my \$t = [[[]]]; push \@tt, \$t } dclone \@tt' 2>&1 | grep "Max. recursion depth with nested structures exceeded"`; 10 | chomp($ret); 11 | ok( $ret eq '', "Storable version in this Perl install is buggy, need Perl 5.32 or greater"); 12 | 13 | -------------------------------------------------------------------------------- /t/02_basics.t: -------------------------------------------------------------------------------- 1 | use Test::Simple tests => 17; 2 | 3 | my $LOG = 't/fixtures/light.postgres.log.bz2'; 4 | my $SYSLOG = 't/fixtures/pg-syslog.1.bz2'; 5 | my $BIN = 't/fixtures/light.postgres.bin'; 6 | my $ANON = 't/fixtures/anonymize.log'; 7 | my $JSON = 't/out.json'; 8 | my $TEXT = 't/out.txt'; 9 | my $WEEKDAYLOG = 't/fixtures/weeknumber.log'; 10 | 11 | `rm -rf t/test_incr/ 2>/dev/null`; 12 | 13 | my $ret = `perl pgbadger --help`; 14 | ok( $? == 0, "Inline help"); 15 | 16 | $ret = `perl pgbadger -q -o - $LOG`; 17 | ok( $? == 0 && length($ret) > 0, "Light log report to stdout"); 18 | 19 | `rm -f out.html`; 20 | $ret = `perl pgbadger -q --outdir '.' $LOG`; 21 | ok( $? == 0 && -e "out.html", "Light log report to HTML"); 22 | 23 | $ret = `perl pgbadger -q -o $BIN $LOG`; 24 | ok( $? == 0 && -e "$BIN", "Light log to binary"); 25 | 26 | `rm -f $JSON`; 27 | $ret = `perl pgbadger -q -o $JSON --format binary $BIN`; 28 | $ret = `cat $JSON | perl -pe 's/.*"select":([1-9]+),.*/\$1/'`; 29 | ok( $? == 0 && $ret > 0, "From binary to JSON"); 30 | 31 | `mkdir t/test_incr/`; 32 | $ret = `perl pgbadger -q -O t/test_incr -I --extra-files $LOG`; 33 | ok( $? == 0 && -e "t/test_incr/2017/09/06/index.html" 34 | && -e "t/test_incr/2017/week-37/index.html", "Incremental mode report"); 35 | $ret = `grep 'src="../../../.*/bootstrap.min.js"' t/test_incr/2017/09/06/index.html`; 36 | ok( $? == 0 && substr($ret, 32, 14) eq 'src="../../../', "Ressources files in incremental mode"); 37 | 38 | `rm -f $JSON`; 39 | $ret = `bunzip2 -c $LOG | perl pgbadger -q -o $JSON -`; 40 | $ret = `cat $JSON | perl -pe 's/.*"select":([1-9]+),.*/\$1/'`; 41 | ok( $? == 0 && $ret > 0, "Light log from STDIN"); 42 | 43 | $ret = `perl pgbadger -q --outdir '.' -o $TEXT -o $JSON -o - -x json $LOG > t/ret.json`; 44 | my $ret2 = `stat --printf='%s' t/ret.json $TEXT $JSON`; 45 | chomp($ret); 46 | chomp($ret2); 47 | ok( $? == 0 && $ret2 eq '13518815900135188', "Multiple output format '$ret2' = '13518815900135188'"); 48 | 49 | `perl pgbadger -q -o /tmp/syslog.html $SYSLOG`; 50 | $ret = `perl pgbadger -q -o - $SYSLOG`; 51 | ok( $? == 0 && (length($ret) >= 23995), "syslog report to stdout: " . length($ret)); 52 | 53 | $ret = `perl pgbadger -q -f stderr -o /tmp/report$$.txt t/fixtures/stmt_type.log`; 54 | $ret = `grep -E "^(SELECT|INSERT|UPDATE|DELETE|COPY|CTE|DDL|TCL|CURSOR)" /tmp/report$$.txt > /tmp/stmt_type.out`; 55 | $ret = `diff t/exp/stmt_type.out /tmp/stmt_type.out`; 56 | ok( $? == 0 && ($ret eq ''), "statement type"); 57 | 58 | $ret = `grep "forêt & océan" /tmp/report$$.txt |wc -l`; 59 | chomp($ret); 60 | ok( $? == 0 && ($ret eq '7'), "French encoding"); 61 | 62 | $ret = `grep "камень & почка" /tmp/report$$.txt | wc -l`; 63 | chomp($ret); 64 | ok( $? == 0 && ($ret eq '7'), "Cyrillic encoding"); 65 | 66 | `cp /tmp/report$$.txt titi.txt`; 67 | `rm /tmp/report$$.txt`; 68 | 69 | # Test CSV and anonymization only if Text::CSV_XS is installed 70 | `perl -MText::CSV_XS -e 1 2>/dev/null`; 71 | if ($? == 0) 72 | { 73 | $ret = `perl pgbadger -m 1000 -q -f csv --anonymize -o /tmp/report$$.txt $ANON`; 74 | $ret = `grep "11111111" /tmp/report$$.txt | wc -l`; 75 | chomp($ret); 76 | ok( $? == 0 && ($ret eq '0'), "Anonymization #1"); 77 | $ret = `grep "'r','p','v','f','m'" /tmp/report$$.txt | wc -l`; 78 | chomp($ret); 79 | ok( $? == 0 && ($ret eq '0'), "Anonymization #2"); 80 | $ret = `grep "x255044" /tmp/report$$.txt | wc -l`; 81 | chomp($ret); 82 | ok( $? == 0 && ($ret eq '0'), "Anonymization #3"); 83 | } 84 | else 85 | { 86 | $ret = `perl pgbadger -q -f csv --anonymize -o /tmp/report$$.txt $SYSLOG`; 87 | $ret = `grep "aid = 62643" /tmp/report$$.txt | wc -l`; 88 | chomp($ret); 89 | ok( $? == 0 && ($ret eq '0'), "Anonymization #1"); 90 | $ret = `grep "tid = 1;" /tmp/report$$.txt | wc -l`; 91 | chomp($ret); 92 | ok( $? == 0 && ($ret eq '0'), "Anonymization #2"); 93 | $ret = `grep "8, 1, 27361, 529, CURRENT_TIMESTAMP" /tmp/report$$.txt | wc -l`; 94 | chomp($ret); 95 | ok( $? == 0 && ($ret eq '0'), "Anonymization #3"); 96 | } 97 | 98 | my $incr_outdir = "/tmp/pgbadger_data_tmp"; 99 | mkdir($incr_outdir); 100 | $ret = `perl pgbadger -q --iso-week-number -X -I -O $incr_outdir -f csv $WEEKDAYLOG && ls $incr_outdir/2021/ | grep -E '(week-27|week-53)' | wc -l`; 101 | chomp($ret); 102 | 103 | ok( $? == 0 && $ret eq '2'); 104 | # Remove files generated during the tests 105 | `rm -f out.html`; 106 | `rm -r $JSON`; 107 | `rm -r $TEXT`; 108 | `rm -f $BIN`; 109 | `rm -rf t/test_incr/`; 110 | `rm t/ret.json`; 111 | `rm /tmp/report$$.txt`; 112 | `rm /tmp/stmt_type.out`; 113 | `rm -rf $incr_outdir`; 114 | 115 | -------------------------------------------------------------------------------- /t/03_consistency.t: -------------------------------------------------------------------------------- 1 | use Test::Simple tests => 34; 2 | use JSON::XS; 3 | 4 | my $json = new JSON::XS; 5 | 6 | my $LOG = 't/fixtures/light.postgres.log.bz2 t/fixtures/pgbouncer.log.gz t/fixtures/pgbouncer.1.21.log.gz'; 7 | my $HLOG = 't/fixtures/logplex.gz'; 8 | my $RDSLOG = 't/fixtures/rds.log.bz2'; 9 | my $GCPLOG = 't/fixtures/cloudsql.log.gz'; 10 | my $CNPGLOG = 't/fixtures/cnpg.log.gz'; 11 | my $TIMELOG = 't/fixtures/begin_end.log'; 12 | my $VACUUMLOG = 't/fixtures/pg_vacuums.log.gz'; 13 | my $VACUUMJSON = 't/fixtures/pg_vacuums.json.gz'; 14 | my $BIN = 'out.bin'; 15 | my $OUT = 'out.json'; 16 | 17 | my $ret = `perl pgbadger -q -o $BIN $LOG`; 18 | ok( $? == 0, "Generate intermediate binary file from log"); 19 | 20 | $ret = `perl pgbadger -q -o $OUT --format binary $BIN`; 21 | ok( $? == 0, "Generate json report from binary file"); 22 | `cp $OUT /tmp/`; 23 | 24 | `rm -f $BIN`; 25 | 26 | my $json_ref = $json->decode(`cat $OUT`); 27 | 28 | # 29 | # Assert that analyzing json file provide the right results 30 | # 31 | ok( $json_ref->{database_info}{postgres}{postgres}{count} == 629, "Consistent count"); 32 | 33 | ok( $json_ref->{overall_stat}{postgres}{histogram}{query_total} == 629, "Consistent query_total"); 34 | 35 | ok( $json_ref->{overall_stat}{postgres}{peak}{"2017-09-06 08:48:45"}{write} == 1, "Consistent peak write"); 36 | 37 | ok( $json_ref->{pgb_session_info}{chronos}{20180912}{16}{count} == 63943, "pgBouncer connections"); 38 | 39 | $ret = `ls -la $OUT | awk '{print \$5}'`; 40 | chomp($ret); 41 | ok( $ret == 230257, "Consistent pgbouncer reports $ret != 230257"); 42 | 43 | `rm -f $OUT`; 44 | 45 | $ret = `perl pgbadger -q -o $OUT $HLOG`; 46 | ok( $? == 0, "Generate json report for heroku log file"); 47 | $json_ref = $json->decode(`cat $OUT`); 48 | ok( $json_ref->{database_info}{postgres}{GREEN}{"cte|duration"} eq "21761.546", "Consistent CTE duration"); 49 | 50 | # check logplex multiline and new format 51 | $ret = `grep -E "WHERE missions.checker_id = " out.json | wc -l`; 52 | chomp($ret); 53 | ok( $ret == 1, "logplex multiline and new format : $ret"); 54 | 55 | `rm -f $OUT`; 56 | 57 | $ret = `perl pgbadger -q -o $OUT --exclude-client 192.168.1.201 $RDSLOG`; 58 | ok( $? == 0, "Generate json report from RDS log file with --exclude-client"); 59 | $json_ref = $json->decode(`cat $OUT`); 60 | ok( $json_ref->{normalyzed_info}{postgres}{'select datname from pg_database where not datistemplate and datallowconn;'}{duration} eq "2.667", "Consistent RDS + exclude client"); 61 | 62 | `rm -f $OUT`; 63 | 64 | $ret = `perl pgbadger -q -o $OUT $GCPLOG`; 65 | ok( $? == 0, "Generate json report from CloudSQL log file"); 66 | $json_ref = $json->decode(`cat $OUT`); 67 | ok( $json_ref->{connection_info}{postgres}{database_user}{cloudsqladmin}{cloudsqladmin} eq "151", "Consistent CloudSQL log format"); 68 | 69 | `rm -f $OUT`; 70 | 71 | $ret = `perl pgbadger -q -o $OUT $CNPGLOG`; 72 | ok( $? == 0, "Generate json report from CloudNativePG log file"); 73 | $json_ref = $json->decode(`cat $OUT`); 74 | ok( $json_ref->{connection_info}{postgres}{database_user}{postgres}{postgres} eq "378", "Consistent CloudNativePG log format"); 75 | 76 | `rm -f $OUT`; 77 | 78 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --begin "09:00:00" | grep '^Total number of sessions:'`; 79 | chomp($ret); 80 | ok( $? == 0 && $ret eq 'Total number of sessions: 4', "Generate report from begin time"); 81 | 82 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --end "20:30:00" | grep '^Total number of sessions:'`; 83 | chomp($ret); 84 | ok( $? == 0 && $ret eq 'Total number of sessions: 6', "Generate report from end time"); 85 | 86 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --begin "09:00:00" --end "20:30:00" | grep '^Total number of sessions:'`; 87 | chomp($ret); 88 | ok( $? == 0 && $ret eq 'Total number of sessions: 2', "Generate report from begin->end time"); 89 | 90 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --end "2021-02-15 20:30:00" | grep '^Total number of sessions:'`; 91 | chomp($ret); 92 | ok( $? == 0 && $ret eq 'Total number of sessions: 7', "Generate report from full log before with end time"); 93 | 94 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --end "2021-02-14 20:30:00" | grep '^Total number of sessions:'`; 95 | chomp($ret); 96 | ok( $? == 0 && $ret eq 'Total number of sessions: 3', "Generate report from single day before with end time"); 97 | 98 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --include-time "2021-02-14 2[01]:.*" | grep '^Total number of sessions:'`; 99 | chomp($ret); 100 | ok( $? == 0 && $ret eq 'Total number of sessions: 2', "Generate report from include time"); 101 | 102 | $ret = `perl pgbadger -q -p "%t [%p]: db=%d,user=%u,app=%a,client=%h " -o - $TIMELOG --exclude-time "2021-02-14 2[01]:.*" | grep '^Total number of sessions:'`; 103 | chomp($ret); 104 | ok( $? == 0 && $ret eq 'Total number of sessions: 6', "Generate report from exclude time"); 105 | 106 | $ret = `perl pgbadger -q --log-timezone +5 -p "%t:%h:%u@%d:[%p]:" -o - t/fixtures/pg-timezones.log`; 107 | chomp($ret); 108 | ok( $? == 0 && $ret =~ m{Log start from 2021-05-27 12:00:00 to 2021-05-27 12:46:39} , "Correct first and last query timestamps on timezone adjusted log"); 109 | 110 | $ret = `perl pgbadger -f stderr -q -p "%t [%p]: [%l-1] user=%u,db=%d,host=%h,app=%a" -o $OUT $VACUUMLOG`; 111 | ok( $? == 0, "Generate vacuums report from stderr log"); 112 | $json_ref = $json->decode(`cat $OUT`); 113 | ok( $json_ref->{autoanalyze_info}{postgres}{count} eq "1450", "Autoanalyze count stderrlog"); 114 | ok( $json_ref->{autoanalyze_info}{postgres}{chronos}{"20240627"}{"01"}{count} eq "474", "Autoanalyze count at 01 am stderrlog"); 115 | ok( $json_ref->{autovacuum_info}{postgres}{count} eq "2094", "Autovacuum count stderrlog"); 116 | ok( $json_ref->{autovacuum_info}{postgres}{chronos}{"20240627"}{"02"}{count} eq "730", "Autovacuum count at 2 am stderrlog"); 117 | `rm -f $OUT`; 118 | 119 | $ret = `perl pgbadger -f jsonlog -q -x json -o $OUT $VACUUMJSON`; 120 | ok( $? == 0, "Generate vacuums report from jsonlog"); 121 | $json_ref = $json->decode(`cat $OUT`); 122 | ok( $json_ref->{autoanalyze_info}{postgres}{count} eq "1450", "Autoanalyze count jsonlog"); 123 | ok( $json_ref->{autoanalyze_info}{postgres}{chronos}{"20240627"}{"01"}{count} eq "474", "Autoanalyze count at 01 am jsonlog"); 124 | ok( $json_ref->{autovacuum_info}{postgres}{count} eq "2094", "Autovacuum count jsonlog"); 125 | ok( $json_ref->{autovacuum_info}{postgres}{chronos}{"20240627"}{"02"}{count} eq "730", "Autovacuum count at 2 am jsonlog"); 126 | `rm -f $OUT`; 127 | -------------------------------------------------------------------------------- /t/04_advanced.t: -------------------------------------------------------------------------------- 1 | use Test::Simple tests => 12; 2 | 3 | my $GCPLOG = 't/fixtures/cloudsql.log.gz'; 4 | my $SYSLOG1 = 't/fixtures/pg-syslog.1.bz2'; 5 | my $SYSLOG2 = 't/fixtures/pg-syslog.1.bz2'; 6 | my $STDERR1 = 't/fixtures/postgresql_param_range.log'; 7 | my $STDERR2 = 't/fixtures/multiline_param.log'; 8 | my $RAWCSV = 't/fixtures/pg_rawcsv.log'; 9 | my $JSON = 't/out.json'; 10 | my $TEXT = 't/out.txt'; 11 | 12 | `rm t/cluster1_day_*.bin t/file_cluster1 2>/dev/null`; 13 | `rm *.html 2>/dev/null`; 14 | my $ret = `perl pgbadger -q --exclude-db=pgbench --explode $SYSLOG1 && ls *.html`; 15 | chomp($ret); 16 | ok( $? == 0 && ($ret eq "out.html"), "Test --exclude-db + --explode"); 17 | 18 | $ret = `perl pgbadger -q --dbname=pgbench --explode $SYSLOG1 && ls *.html`; 19 | chomp($ret); 20 | ok( $? == 0 && ($ret eq "pgbench_out.html"), "Test --dbname + --explode"); 21 | 22 | `rm *.html`; 23 | 24 | my $incr_outdir = "/tmp/pgbadger_data_tmp"; 25 | mkdir($incr_outdir); 26 | 27 | $ret = `perl pgbadger -q --explode -I -O $incr_outdir $SYSLOG1 $SYSLOG2 && ls $incr_outdir | wc -l`; 28 | chomp($ret); 29 | ok( $? == 0 && ($ret == 4), "Test incremental mode with --explode"); 30 | 31 | `rm -rf $incr_outdir/*`; 32 | 33 | $ret = `perl pgbadger -q --dbname=pgbench --explode -I -O $incr_outdir $SYSLOG1 $SYSLOG2 && ls $incr_outdir | grep ">12,474 queries" $incr_outdir/pgbench/20*/week-0*/index.html`; 34 | chomp($ret); 35 | ok( $? == 0 && ($ret ne ''), "Test incremental mode with --explode and --dbname"); 36 | 37 | $ret = `perl pgbadger -q --exclude-db cloudsqladmin --explode $GCPLOG && ls *.html | wc -l`; 38 | chomp($ret); 39 | ok( $? == 0 && ($ret == 2), "Test database exclusion with jsonlog"); 40 | 41 | $ret = `perl pgbadger --disable-type --disable-session --disable-connection --disable-lock --disable-checkpoint --disable-autovacuum --disable-query -o $TEXT t/fixtures/tempfile_only.log.gz -q`; 42 | $ret = `grep "Example.*SELECT" $TEXT | wc -l`; 43 | chomp($ret); 44 | ok( $? == 0 && ($ret == 9), "Test log_temp_files only"); 45 | 46 | $ret = `perl pgbadger --last-parsed t/file_cluster1 -o t/cluster1_day_1.bin t/fixtures/tempfile_only.log.gz -q`; 47 | $ret = `md5sum t/file_cluster1 | awk '{print \$1}'`; 48 | chomp($ret); 49 | ok( $? == 0 && ($ret eq "7faeb101abf32de3bc4e14fcf525e005" ), "Test last parse file without incremental mode"); 50 | 51 | $ret = `perl pgbadger --last-parsed t/file_cluster1 -o t/cluster1_day_2.bin t/fixtures/tempfile_only.log.gz -q`; 52 | $ret = `ls -la t/cluster1_day_2.bin | awk '{print \$4}'`; 53 | chomp($ret); 54 | ok( $? == 0 && ($ret < 4000), "Second pass on last parse file without incremental mode"); 55 | $ret = `md5sum t/file_cluster1 | awk '{print \$1}'`; 56 | chomp($ret); 57 | ok( $? == 0 && ($ret eq "7faeb101abf32de3bc4e14fcf525e005" ), "Last parse file must not have changed"); 58 | 59 | `rm -f $JSON 2>/dev/null`; 60 | $ret = `perl pgbadger -f stderr -q $STDERR1 -o $JSON`; 61 | $ret = `grep "('D17756227', 'J16274127', 'IDA001127') and (public.t_plage_cloturee_utilisateur.pcu_range_cloture && '\\[2023-02-24T00:00,2023-02-25T00:00\\]'::tsrange" $JSON | wc -l`; 62 | chomp($ret); 63 | ok( $? == 0 && ($ret == 1), "Bind parameter with range: $ret"); 64 | 65 | $ret = `perl pgbadger -q -o t/out.txt $STDERR2`; 66 | $ret = `grep 'njvarchar\\\\nbye'* $TEXT | wc -l`; 67 | chomp($ret); 68 | ok( $? == 0 && ($ret == 4), "Multiline bind parameters: $ret"); 69 | 70 | $ret = `perl pgbadger -q -p '%m [%p] %q%u@%d ' --dump-raw-csv $RAWCSV`; 71 | ok( $? == 0 && length($ret) == 638, "Dump log as raw csv: " . length($ret)); 72 | 73 | # Remove files generated during the tests 74 | `rm -f *.html`; 75 | `rm -f $TEXT`; 76 | `rm -f $JSON`; 77 | `rm -rf $incr_outdir`; 78 | `rm t/cluster1_day_*.bin t/file_cluster1`; 79 | 80 | -------------------------------------------------------------------------------- /t/exp/stmt_type.out: -------------------------------------------------------------------------------- 1 | SELECT: 1 8.33% 2 | INSERT: 1 8.33% 3 | UPDATE: 1 8.33% 4 | DELETE: 1 8.33% 5 | COPY FROM: 1 8.33% 6 | COPY TO: 1 8.33% 7 | CTE: 1 8.33% 8 | DDL: 1 8.33% 9 | TCL: 2 16.67% 10 | CURSOR: 2 16.67% 11 | -------------------------------------------------------------------------------- /t/fixtures/anonymize.log: -------------------------------------------------------------------------------- 1 | 2021-01-22 10:03:22.785 CET,"aaaa","aaaa",1486,"127.0.0.1:52306",6009c305.5ce,32878,"SELECT",2021-01-21 19:08:05 CET,14/10903,0,LOG,00000,"duration: 26.120 ms execute S_65: select * from testZustaendigkeiten($1, $2, $3, $4, $5)","parameters: $1 = 'XXXXXXXX', $2 = NULL, $3 = 'XXXX', $4 = 'XXXXX', $5 = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'",,,,,,,,"PostgreSQL JDBC Driver" 2 | 2021-01-20 12:21:31.010 CET,"aaaa","aaaa",28797,"127.0.0.1:47268",6008123a.707d,17,"SELECT",2021-01-20 12:21:30 CET,14/971038,0,LOG,00000,"duration: 0.240 ms execute : SELECT * FROM (SELECT n.nspname,c.relname,a.attname,a.atttypid,a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod,a.attlen,t.typtypmod,row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, nullif(a.attidentity, '') as attidentity,pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS adsrc,dsc.description,t.typbasetype,t.typtype FROM pg_catalog.pg_namespace n JOIN pg_catalog.pg_class c ON (c.relnamespace = n.oid) JOIN pg_catalog.pg_attribute a ON (a.attrelid=c.oid) JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) LEFT JOIN pg_catalog.pg_description dsc ON (c.oid=dsc.objoid AND a.attnum = dsc.objsubid) LEFT JOIN pg_catalog.pg_class dc ON (dc.oid=dsc.classoid AND dc.relname='pg_class') LEFT JOIN pg_catalog.pg_namespace dn ON (dc.relnamespace=dn.oid AND dn.nspname='pg_catalog') WHERE c.relkind in ('r','p','v','f','m') and a.attnum > 0 AND NOT a.attisdropped AND n.nspname LIKE 'zeus' AND c.relname LIKE 'schema_version') c WHERE true AND attname LIKE 'version_rank' ORDER BY nspname,c.relname,attnum ",,,,,,,,,"PostgreSQL JDBC Driver" 3 | 2021-01-21 16:12:51.308 CET,"aaaa","aaaa",77553,"127.0.0.1:45908",60099770.12ef1,1643,"BIND",2021-01-21 16:02:08 CET,10/5684,136179,LOG,00000,"duration: 0.553 ms bind : update public.dokumentdata set data=$1 where id=$2","parameters: $1 = '\x255044......460a', $2 = '157'",,,,,,,,"PostgreSQL JDBC Driver" 4 | 2021-01-21 16:12:51.400 CET,"aaaa","aaaa",77553,"127.0.0.1:45908",60099770.12ef1,1644,"UPDATE",2021-01-21 16:02:08 CET,10/5684,136179,LOG,00000,"duration: 36.388 ms execute : update public.dokumentdata set data=$1 where id=$2","parameters: $1 = '\x255044......460a', $2 = '157'",,,,,,,,"PostgreSQL JDBC Driver" 5 | 2021-01-21 16:12:57.127 CET,"aaaa","aaaa",77553,"127.0.0.1:45908",60099770.12ef1,1728,"BIND",2021-01-21 16:02:08 CET,10/5692,136185,LOG,00000,"duration: 0.814 ms bind S_29: update public.dokumentdata set data=$1 where id=$2","parameters: $1 = '\x255044......460a', $2 = '157'",,,,,,,,"PostgreSQL JDBC Driver" 6 | 2021-01-21 16:12:57.186 CET,"aaaa","aaaa",77553,"127.0.0.1:45908",60099770.12ef1,1729,"UPDATE",2021-01-21 16:02:08 CET,10/5692,136185,LOG,00000,"duration: 21.083 ms execute S_29: update public.dokumentdata set data=$1 where id=$2","parameters: $1 = '\x255044......460a', $2 = '157'",,,,,,,,"PostgreSQL JDBC Driver" 7 | 2021-01-21 16:12:58.257 CET,"aaaa","aaaa",77556,"127.0.0.1:45908",60099770.12ef1,1728,"BIND",2021-01-21 16:22:08 CET,10/5692,136185,LOG,00000,"duration: 230.814 ms bind S_29: update public.dokumentdata set data=$1 where id=$2","parameters: $1 = '\x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111......460a', $2 = '157'",,,,,,,,"PostgreSQL JDBC Driver" 8 | 2021-01-21 16:12:58.453 CET,"aaaa","aaaa",77556,"127.0.0.1:45908",60099770.12ef1,1729,"UPDATE",2021-01-21 16:02:08 CET,10/5692,136185,LOG,00000,"duration: 1221.083 ms execute S_29: update public.dokumentdata set data=$1 where id=$2","parameters: $1 = '\x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111......460a', $2 = '157'",,,,,,,,"PostgreSQL JDBC Driver" 9 | -------------------------------------------------------------------------------- /t/fixtures/begin_end.log: -------------------------------------------------------------------------------- 1 | 2021-02-14 01:34:02 CET [30291]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 2 | 2021-02-14 01:34:02 CET [30291]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 3 | 2021-02-14 01:34:02 CET [30291]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.007 user=postgres database=template1 host=[local] 4 | 2021-02-14 08:00:02 CET [28538]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 5 | 2021-02-14 08:00:02 CET [28538]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 6 | 2021-02-14 08:00:02 CET [28538]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.007 user=postgres database=template1 host=[local] 7 | 2021-02-14 20:29:52 CET [3202]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 8 | 2021-02-14 20:29:52 CET [3202]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 9 | 2021-02-14 20:29:52 CET [3202]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.017 user=postgres database=template1 host=[local] 10 | 2021-02-14 21:48:40 CET [22313]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 11 | 2021-02-14 21:48:40 CET [22313]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 12 | 2021-02-14 21:48:40 CET [22313]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.007 user=postgres database=template1 host=[local] 13 | 2021-02-15 01:20:04 CET [1974]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 14 | 2021-02-15 01:20:05 CET [1974]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 15 | 2021-02-15 01:20:05 CET [1974]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.008 user=postgres database=template1 host=[local] 16 | 2021-02-15 08:07:45 CET [12772]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 17 | 2021-02-15 08:07:45 CET [12772]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 18 | 2021-02-15 08:07:45 CET [12772]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.004 user=postgres database=template1 host=[local] 19 | 2021-02-15 20:29:06 CET [23197]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 20 | 2021-02-15 20:29:06 CET [23197]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 21 | 2021-02-15 20:29:06 CET [23197]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.007 user=postgres database=template1 host=[local] 22 | 2021-02-15 21:02:06 CET [13032]: db=[unknown],user=[unknown],app=[unknown],client=[local] LOG: connection received: host=[local] 23 | 2021-02-15 21:02:06 CET [13032]: db=template1,user=postgres,app=[unknown],client=[local] LOG: connection authorized: user=postgres database=template1 application_name=psql 24 | 2021-02-15 21:02:06 CET [13032]: db=template1,user=postgres,app=psql,client=[local] LOG: disconnection: session time: 0:00:00.007 user=postgres database=template1 host=[local] 25 | -------------------------------------------------------------------------------- /t/fixtures/cloudsql.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/cloudsql.log.gz -------------------------------------------------------------------------------- /t/fixtures/cnpg.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/cnpg.log.gz -------------------------------------------------------------------------------- /t/fixtures/light.postgres.log.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/light.postgres.log.bz2 -------------------------------------------------------------------------------- /t/fixtures/logplex.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/logplex.gz -------------------------------------------------------------------------------- /t/fixtures/multiline_param.log: -------------------------------------------------------------------------------- 1 | 2021-12-03 07:14:48.591 UTC [4913] user=user1,db=mydb LOG: duration: 124.928 ms statement: select njprocs.njIsVersionOk(16777216) 2 | 2021-12-03 07:14:48.591 UTC [4913] user=user1,db=mydb LOG: duration: 0.211 ms statement: SELECT current_setting('nj.maint') 3 | 2021-12-03 07:14:48.591 UTC [4913] user=user1,db=mydb LOG: duration: 0.032 ms statement: BEGIN WORK 4 | 2021-12-03 07:14:48.593 UTC [4913] user=user1,db=mydb LOG: duration: 1.163 ms parse njTypeOidQuery_Name: SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE (n.nspname = 'njcat') AND (t.typnamespace = $1) AND (t.typname = $2) AND t.test = $3 5 | 2021-12-03 07:14:48.594 UTC [4913] user=user1,db=mydb LOG: duration: 0.700 ms bind njTypeOidQuery_Name: SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE (n.nspname = 'njcat') AND (t.typnamespace = $1) AND (t.typname = $2) AND t.test = $3 6 | 2021-12-03 07:14:48.594 UTC [4913] user=user1,db=mydb DETAIL: parameters: $1 = '1234', $2 = 'njchar 7 | hello', $3 = 'aaaa' 8 | 2021-12-03 07:14:48.594 UTC [4913] user=user1,db=mydb LOG: duration: 0.215 ms execute njTypeOidQuery_Name: SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE (n.nspname = 'njcat') AND (t.typnamespace = $1) AND (t.typname = $2) AND t.test = $3 9 | 2021-12-03 07:14:48.594 UTC [4913] user=user1,db=mydb DETAIL: parameters: $1 = '1234', $2 = 'njchar 10 | hello', $3 = 'aaaa' 11 | 2021-12-03 07:14:48.595 UTC [4913] user=user1,db=mydb LOG: duration: 0.145 ms bind njTypeOidQuery_Name: SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE (n.nspname = 'njcat') AND (t.typnamespace = $1) AND (t.typname = $2) AND t.test = $3 12 | 2021-12-03 07:14:48.595 UTC [4913] user=user1,db=mydb DETAIL: parameters: $1 = '1234', $2 = 'njvarchar 13 | bye', $3 = 'iiii' 14 | 2021-12-03 07:14:48.595 UTC [4913] user=user1,db=mydb LOG: duration: 0.131 ms execute njTypeOidQuery_Name: SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n WHERE (n.nspname = 'njcat') AND (t.typnamespace = $1) AND (t.typname = $2) AND t.test = $3 15 | 2021-12-03 07:14:48.595 UTC [4913] user=user1,db=mydb DETAIL: parameters: $1 = '1234', $2 = 'njvarchar 16 | bye', $3 = 'iiii' 17 | -------------------------------------------------------------------------------- /t/fixtures/pg-syslog.1.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/pg-syslog.1.bz2 -------------------------------------------------------------------------------- /t/fixtures/pg-syslog.2.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/pg-syslog.2.gz -------------------------------------------------------------------------------- /t/fixtures/pg-timezones.log: -------------------------------------------------------------------------------- 1 | 2021-05-27 17:00:00 UTC:10.1.91.65:user@my_db:[24743]:LOG: duration: 0.054 ms statement: SELECT * FROM t1; 2 | 2021-05-27 17:46:39 UTC:10.1.17.194:user@my_db:[19003]:LOG: duration: 0.060 ms statement: UPDATE t1 SET c1=1; 3 | -------------------------------------------------------------------------------- /t/fixtures/pg_rawcsv.log: -------------------------------------------------------------------------------- 1 | 2025-01-21 18:32:48.288 CET [77898] LOG: invalid value for parameter "lc_messages": "en_US.UTF-8" 2 | 2025-01-21 18:32:48.288 CET [77898] FATAL: configuration file "/etc/postgresql/17/main/postgresql.conf" contains errors 3 | pg_ctl: could not start server 4 | Examine the log output. 5 | 2025-01-21 18:37:49.782 CET [78776] LOG: starting PostgreSQL 17.2 (Ubuntu 17.2-1.pgdg24.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, 64-bit 6 | 2025-01-21 18:37:49.783 CET [78776] LOG: listening on IPv4 address "127.0.0.1", port 5432 7 | 2025-01-21 18:37:49.784 CET [78776] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" 8 | 2025-01-21 18:37:49.793 CET [78779] LOG: database system was shut down at 2025-01-21 18:32:48 CET 9 | 2025-01-21 18:37:49.802 CET [78776] LOG: database system is ready to accept connections 10 | 2025-01-21 18:38:06.453 CET [78798] gilles@gilles ERROR: conflicting values for "Y" field in formatting string 11 | 2025-01-21 18:38:06.453 CET [78798] gilles@gilles DETAIL: This value contradicts a previous setting for the same field type. 12 | 2025-01-21 18:38:06.453 CET [78798] gilles@gilles STATEMENT: SELECT TO_DATE('311299999','DDMMYYYYY'); 13 | 2025-01-21 18:38:09.749 CET [78798] gilles@gilles ERROR: relation "table_is_not_here" does not exist at character 15 14 | 2025-01-21 18:38:09.749 CET [78798] gilles@gilles STATEMENT: select * from table_is_not_here order by total_time desc limit 20; 15 | -------------------------------------------------------------------------------- /t/fixtures/pg_vacuums.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/pg_vacuums.json.gz -------------------------------------------------------------------------------- /t/fixtures/pg_vacuums.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/pg_vacuums.log.gz -------------------------------------------------------------------------------- /t/fixtures/pgbouncer.1.21.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/pgbouncer.1.21.log.gz -------------------------------------------------------------------------------- /t/fixtures/pgbouncer.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/pgbouncer.log.gz -------------------------------------------------------------------------------- /t/fixtures/postgresql_param_range.log: -------------------------------------------------------------------------------- 1 | 2023-03-07 09:06:08 CET [130096]: [247-1] [0] usr=usr6,db=progdb,appli=76ae131687c458a8 LOG: duration: 778.166 ms execute : select public.t_plage_cloturee_utilisateur.pcu_utilisateur_nni_fk, public.t_plage_cloturee_utilisateur.pcu_range_cloture from public.t_plage_cloturee_utilisateur where (public.t_plage_cloturee_utilisateur.pcu_utilisateur_nni_fk in ($1, $2, $3, $4, $5, $6, $7) and (public.t_plage_cloturee_utilisateur.pcu_range_cloture && $8::tsrange)) 2 | 2023-03-07 09:06:08 CET [130096]: [248-1] [0] usr=usr6,db=progdb,appli=76ae131687c458a8 DETAIL: parameters: $1 = 'F99278492', $2 = 'H44277392', $3 = 'B38147492', $4 = 'D65357492', $5 = 'A32557392', $6 = 'A29970192', $7 = 'A32557492', $8 = '[2023-02-23T00:00,2023-02-24T00:00]' 3 | 2023-03-07 09:05:37 CET [2052]: [97-1] [0] usr=usr6,db=progdb,appli=8a2a6ffa3c3a18d5 LOG: duration: 747.825 ms execute : select public.t_plage_cloturee_utilisateur.pcu_utilisateur_nni_fk, public.t_plage_cloturee_utilisateur.pcu_range_cloture from public.t_plage_cloturee_utilisateur where (public.t_plage_cloturee_utilisateur.pcu_utilisateur_nni_fk in ($1, $2, $3) and (public.t_plage_cloturee_utilisateur.pcu_range_cloture && $4::tsrange)) 4 | 2023-03-07 09:05:37 CET [2052]: [98-1] [0] usr=usr6,db=progdb,appli=8a2a6ffa3c3a18d5 DETAIL: parameters: $1 = 'D17756227', $2 = 'J16274127', $3 = 'IDA001127', $4 = '[2023-02-24T00:00,2023-02-25T00:00]' 5 | 2023-03-07 09:07:12 CET [115852]: [1562-1] [0] usr=usr6,db=progdb,appli=8f8363625ebc309c LOG: duration: 133.424 ms execute : select public.t_plage_cloturee_utilisateur.pcu_utilisateur_nni_fk, public.t_plage_cloturee_utilisateur.pcu_range_cloture from public.t_plage_cloturee_utilisateur where (public.t_plage_cloturee_utilisateur.pcu_utilisateur_nni_fk in ($1, $2) and (public.t_plage_cloturee_utilisateur.pcu_range_cloture && $3::tsrange)) 6 | 2023-03-07 09:07:12 CET [115852]: [1563-1] [0] usr=usr6,db=progdb,appli=8f8363625ebc309c DETAIL: parameters: $1 = 'C24240348', $2 = 'B24557248', $3 = '[2023-02-25T00:00,2023-02-26T00:00]' 7 | -------------------------------------------------------------------------------- /t/fixtures/queryid.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/queryid.log.gz -------------------------------------------------------------------------------- /t/fixtures/rds.log.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/rds.log.bz2 -------------------------------------------------------------------------------- /t/fixtures/stmt_type.log: -------------------------------------------------------------------------------- 1 | 2019-10-21 12:03:31.311 MSK [5133] LOG: duration: 0.007 ms statement: BEGIN; 2 | 2019-10-21 12:03:33.151 MSK [5134] LOG: duration: 7.209 ms statement: CREATE TABLE test (data text); 3 | 2019-10-21 12:03:33.186 MSK [5138] LOG: duration: 2.286 ms statement: INSERT INTO test SELECT 'data' FROM generate_series(1, 100); 4 | 2019-10-21 12:03:33.219 MSK [5142] LOG: duration: 0.942 ms statement: SELECT * FROM test; 5 | 2019-10-21 12:03:34.123 MSK [5143] LOG: duration: 1.452 ms statement: UPDATE test SET col1='forêt & océan' WHERE col2 = 1; 6 | 2019-10-21 12:03:36.569 MSK [5144] LOG: duration: 0.367 ms statement: DELETE FROM test; 7 | 2019-10-21 12:03:38.321 MSK [5145] LOG: duration: 3.448 ms statement: WITH wf1 AS ( SELECT id FROM t1) DELETE FROM test WHERE col1 = wf1.id; 8 | 2019-10-21 12:03:40.612 MSK [5146] LOG: duration: 9.109 ms statement: DECLARE CURSOR test_curs1 AS SELECT * FROM test WHERE (k0_.tokens @@ to_tsquery('камень & почка & !" & ![ & !]')) = true ORDER BY k0_.updated_at DESC LIMIT 1 9 | 2019-10-21 12:03:42.567 MSK [5147] LOG: duration: 2.549 ms statement: FETCH 1 FROM test_curs1; 10 | 2019-10-21 12:03:44.231 MSK [5148] LOG: duration: 7.378 ms statement: COPY country TO STDOUT (DELIMITER '|'); 11 | 2019-10-21 12:03:48.231 MSK [5149] LOG: duration: 1.690 ms statement: COPY country FROM '/usr1/proj/bray/sql/country_data'; 12 | 2019-10-21 12:03:51.311 MSK [5153] LOG: duration: 1.007 ms statement: COMMIT; 13 | -------------------------------------------------------------------------------- /t/fixtures/tempfile_only.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darold/pgbadger/34b4bed1a1f65e9aba7773100d84f479b984bc9e/t/fixtures/tempfile_only.log.gz -------------------------------------------------------------------------------- /t/fixtures/weeknumber.log: -------------------------------------------------------------------------------- 1 | 2021-01-01 07:13:44.120 GMT,"foobar","foobar",77962,"10.200.48.82:39364",60e2b128.1308a,13,"SELECT",2021-07-05 07:13:44 GMT,4/187623,0,LOG,00000,"duration: 0.576 ms execute pdo_stmt_00000003: select * from ""users"" where ""cn"" = $1 and ""users"".""deleted_at"" is null limit 1","parameters: $1 = 'foo.bar'",,,,,,,,"" 2 | 2021-01-02 07:13:44.120 GMT,"foobar","foobar",77962,"10.200.48.82:39364",60e2b128.1308a,13,"SELECT",2021-07-05 07:13:44 GMT,4/187623,0,LOG,00000,"duration: 0.576 ms execute pdo_stmt_00000003: select * from ""users"" where ""cn"" = $1 and ""users"".""deleted_at"" is null limit 1","parameters: $1 = 'foo.bar'",,,,,,,,"" 3 | 2021-01-03 07:13:44.120 GMT,"foobar","foobar",77962,"10.200.48.82:39364",60e2b128.1308a,13,"SELECT",2021-07-05 07:13:44 GMT,4/187623,0,LOG,00000,"duration: 0.576 ms execute pdo_stmt_00000003: select * from ""users"" where ""cn"" = $1 and ""users"".""deleted_at"" is null limit 1","parameters: $1 = 'foo.bar'",,,,,,,,"" 4 | 2021-01-04 07:13:44.120 GMT,"foobar","foobar",77962,"10.200.48.82:39364",60e2b128.1308a,13,"SELECT",2021-07-05 07:13:44 GMT,4/187623,0,LOG,00000,"duration: 0.576 ms execute pdo_stmt_00000003: select * from ""users"" where ""cn"" = $1 and ""users"".""deleted_at"" is null limit 1","parameters: $1 = 'foo.bar'",,,,,,,,"" 5 | 2021-01-05 07:13:44.120 GMT,"foobar","foobar",77962,"10.200.48.82:39364",60e2b128.1308a,13,"SELECT",2021-07-05 07:13:44 GMT,4/187623,0,LOG,00000,"duration: 0.576 ms execute pdo_stmt_00000003: select * from ""users"" where ""cn"" = $1 and ""users"".""deleted_at"" is null limit 1","parameters: $1 = 'foo.bar'",,,,,,,,"" 6 | 2021-07-05 07:13:44.120 GMT,"foobar","foobar",77962,"10.200.48.82:39364",60e2b128.1308a,13,"SELECT",2021-07-05 07:13:44 GMT,4/187623,0,LOG,00000,"duration: 0.576 ms execute pdo_stmt_00000003: select * from ""users"" where ""cn"" = $1 and ""users"".""deleted_at"" is null limit 1","parameters: $1 = 'foo.bar'",,,,,,,,"" 7 | -------------------------------------------------------------------------------- /tools/README.pgbadger_tools: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | pgbadger_tools - Tools based on pgBadger binary files 3 | 4 | This program is open source, licensed under the PostgreSQL Licence. 5 | For license terms, see the LICENSE file. 6 | ------------------------------------------------------------------------------ 7 | 8 | This program is first used to demonstrate how to deal with pgBadger binary 9 | files to build your own tool. If you don't want to rewrite all, you can 10 | post pull request of this tool with your own option so that pgbadger_tools 11 | will do what your need. 12 | 13 | All statistics and data gathered by pgBadger binary file is load into memory 14 | using method load_stat(). To generate a binary file you simply have to set 15 | the output file extension to .bin. Note that if you use incremental mode with 16 | pgbadger it already generate binary file in daily directories. 17 | 18 | example: pgbadger -o out.bin /var/log/postgresql/postgresql.log 19 | 20 | PGBADGER_TOOLS OPTIONS AND TOOLS 21 | -------------------------------- 22 | 23 | **Auto explain** 24 | 25 | The first option added is --explain-slowest that dump top slowest queries in 26 | an explain analyze statement, ready to be executed. An extended version could 27 | be created to automatically execute those explain statement on the database. 28 | 29 | ./pgbadger_tools --explain-slowest out.bin 30 | 31 | You can use several binary file as input, for example from an incremental 32 | output directory: 33 | 34 | ./pgbadger_tools --explain-slowest /var/www/pgbadger/2014/09/03/*.bin 35 | 36 | The explain tool is base on an original work of Rodolphe Quiedeville, that was 37 | first sent as a pull request on pgbadger. But we need to keep pgBadger simple 38 | so the place of this kind of addons are more in peripheral tools. It is possible 39 | that in future, the tsung and json output will be removed from pgbadger and put 40 | in pgbadger_tools 41 | 42 | If you want to chain tools and HTML report, you can proceed as follow: 43 | 44 | pgbadger -o out.bin /var/log/postgresql/postgresql.log 45 | ./pgbadger_tools --explain-slowest out.bin > explain_top_slowest.sql 46 | pgbadger -o report.html out.bin 47 | 48 | **CSV output** 49 | _ 50 | When using the following option, pgbadger_tools will export top queries section 51 | results in csv format, for example for later analysis. This is an original work 52 | of bricklen. 53 | 54 | Here are the supported options, only one of the following is mandatory: 55 | 56 | --csv-time-consuming : generate a CSV file with top time consuming queries 57 | --csv-slowest : generate a CSV file with top slowest queries 58 | --csv-normalized : generate a CSV file with top normalized queries 59 | 60 | ./pgbadger_tools --csv-time-consuming out.bin 61 | 62 | Those options can not be used together. By default the output file is named 63 | out.csv, you can use the --csv-filename option to renamed this file. Ex: 64 | 65 | ./pgbadger_tools --csv-slowest --csv-filename slowest.csv out.bin 66 | 67 | Option to limit top queries to minimum duration: 68 | 69 | --max-duration MS : set the number of milliseconds above which queries 70 | will not be reported. Use it if you want to auto 71 | execute explain statements. 72 | 73 | If you want to chain tools and HTML report, you can proceed as follow: 74 | 75 | pgbadger -o out.bin /var/log/postgresql/postgresql.log 76 | ./pgbadger_tools --csv-slowest out.bin --csv-filename top_slowest.csv 77 | pgbadger -o report.html out.bin 78 | 79 | 80 | CONTRIBUTION: 81 | ------------- 82 | 83 | Feel free to extend pgbadger_tools of new features. To see how to integrate 84 | you Perl code in this program, search for "Add your own" string in the code 85 | and look at the example searching on explain_slowest. 86 | 87 | Regards, 88 | 89 | -- 90 | Gilles Darold 91 | 92 | -------------------------------------------------------------------------------- /tools/README.updt_embedded_rsc: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------ 2 | updt_embedded_rsc.pl - script to update all embedded javascript and css 3 | snippets into the pgbadger Perl script. 4 | 5 | This program is open source, licensed under the PostgreSQL Licence. 6 | For license terms, see the LICENSE file. 7 | ------------------------------------------------------------------------------ 8 | -------------------------------------------------------------------------------- /tools/updt_embedded_rsc.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | #----------------------------------------------------------------------------- 3 | # 4 | # Script used to update javascript and css files embedded into pgbadger. 5 | # It will replace all content after the __DATA__ in the right order with 6 | # the files content stored into the resources/min/ directory. 7 | # 8 | # This script must be executed from the main source repository as follow: 9 | #  10 | # This script must be executed from the main source repository as follow: 11 | #  ./tools/updt_embedded_rsc.pl 12 | # 13 | # The fontawesome.css must also embedded the TrueType font, this is done 14 | #----------------------------------------------------------------------------- 15 | use strict; 16 | 17 | my $RSC_DIR = 'resources'; 18 | my $PGBADGER_PROG = 'pgbadger'; 19 | my $DEST_TMP_FILE = 'pgbadger.new'; 20 | 21 | # Ordered resources files list 22 | 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); 23 | my @min_rsc_list = (); 24 | 25 | if (!-d $RSC_DIR) { 26 | die "FATAL: can't find directory: $RSC_DIR.\n"; 27 | } 28 | 29 | # Apply patch on jquery.jqplot.js to fix infinite loop 30 | # May be removed with next jqplot release update 31 | `patch -r - -s -N resources/jquery.jqplot.js -i resources/patch-jquery.jqplot.js`; 32 | 33 | # Generate all minified resources files 34 | mkdir "$RSC_DIR/min"; 35 | foreach my $f (@rsc_list) { 36 | my $dest = $f; 37 | $dest =~ s/\.(js|css)$/.min.$1/; 38 | push(@min_rsc_list, $dest); 39 | # minify resources files 40 | `yui-compressor $RSC_DIR/$f -o $RSC_DIR/min/$dest`; 41 | } 42 | 43 | # Embedded fontawesome webfont into the CSS file as base64 data 44 | print `base64 -w 0 $RSC_DIR/font/FontAwesome.otf > $RSC_DIR/font/FontAwesome.otf.b64`; 45 | open(IN, "$RSC_DIR/font/FontAwesome.otf.b64") or die "FATAL: can't open file $RSC_DIR/font/FontAwesome.otf.b64, $!\n"; 46 | my $b64_font = ; 47 | close(IN); 48 | 49 | # Update minimized fontawesome.css file 50 | open(IN, "$RSC_DIR/min/fontawesome.min.css") or die "FATAL: can't open file $RSC_DIR/min/fontawesome.min.css\n"; 51 | my @content = ; 52 | close(IN); 53 | open(OUT, ">$RSC_DIR/min/fontawesome.min.css") or die "FATAL: can't write to file $RSC_DIR/min/fontawesome.min.css\n"; 54 | foreach my $l (@content) { 55 | $l =~ s|;src:url.* format.* format\('svg'\);|;src: url('data:font\/opentype;charset=utf-8;base64,$b64_font') format('truetype');|; 56 | print OUT $l; 57 | } 58 | close(OUT); 59 | 60 | if (!-e $PGBADGER_PROG) { 61 | die "FATAL: can't find pgbadger script: $PGBADGER_PROG\n"; 62 | } 63 | 64 | # Extract content of pgbadger script until __DATA__ is found 65 | my $content = ''; 66 | open(IN, $PGBADGER_PROG) or die "FATAL: can't open file $PGBADGER_PROG, $!\n"; 67 | while () { 68 | last if (/^__DATA__$/); 69 | $content .= $_; 70 | } 71 | close(IN); 72 | 73 | # Write script base to destination file 74 | open(OUT, ">$DEST_TMP_FILE") or die "FATAL: can't write to file $DEST_TMP_FILE, $!\n"; 75 | print OUT $content; 76 | print OUT "__DATA__\n"; 77 | 78 | # Append each minified resources file 79 | foreach my $f (@min_rsc_list) { 80 | print OUT "\nWRFILE: $f\n\n"; 81 | open(IN, "$RSC_DIR/min/$f") or die "FATAL: can't open file $RSC_DIR/min/$f, $!\n"; 82 | my @tmp = ; 83 | close(IN); 84 | print OUT @tmp; 85 | } 86 | close(OUT); 87 | 88 | # Clobber original pgbadger file. 89 | rename($DEST_TMP_FILE, $PGBADGER_PROG) or die "FATAL: can't rename $DEST_TMP_FILE into $PGBADGER_PROG\n"; 90 | 91 | exit 0; 92 | --------------------------------------------------------------------------------