1#! /usr/bin/php -qC 2<?php 3 4/* 5 * This file is part of pgFouine. 6 * 7 * pgFouine - a PostgreSQL log analyzer 8 * Copyright (c) 2005-2008 Guillaume Smet 9 * 10 * pgFouine is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or (at 13 * your option) any later version. 14 * 15 * pgFouine is distributed in the hope that it will be useful, but 16 * WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with pgFouine; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. 23 */ 24 25ini_set('max_execution_time', 18000); 26ini_set('log_errors', true); 27ini_set('display_errors', false); 28 29if(strpos(phpversion(), '4.4') === 0) { 30 error_reporting(E_ALL - E_NOTICE); 31} else { 32 error_reporting(E_ALL); 33} 34 35ini_set('include_path', '!!PREFIX!!/share/pgfouine'); 36 37include('version.php'); 38require_once('include/lib/common.lib.php'); 39require_once('include/base.lib.php'); 40require_once('include/listeners/listeners.lib.php'); 41require_once('include/postgresql/postgresql.lib.php'); 42require_once('include/reporting/reports.lib.php'); 43 44$stderr = fopen('php://stderr', 'w'); 45 46function usage($error = false) { 47 if($error) { 48 stderr('Error: '.$error); 49 echo "\n"; 50 } 51 echo 'Usage: '.$GLOBALS['executable'].' -file <file> [-top <n>] [-format <format>] [-logtype <logtype>] [-report [outputfile=]<block1,block2>] 52 -file <file> log file to analyze 53 - read the log from stdin instead of -file 54 -top <n> number of queries in lists. Default is 20. 55 -format <format> output format: html, html-with-graphs or text. Default is html. 56 -logtype <logtype> log type: syslog, stderr or csvlog. Default is syslog. 57 for stderr, you have to use the following log_line_prefix: \'%t [%p]: [%l-1] \' 58 -report [outputfile=]<block1,block2> list of report blocks separated by a comma 59 report blocks can be: overall, hourly, bytype, slowest, n-mosttime, 60 n-mostfrequent, n-slowestaverage, history, n-mostfrequenterrors, 61 tsung, csv-query 62 you can add several -report options if you want to generate several reports at once 63 -examples <n> maximum number of examples for a normalized query 64 -onlyselect ignore all queries but SELECT 65 -from "<date>" ignore lines logged before this date (uses strtotime) 66 -to "<date>" ignore lines logged after this date (uses strtotime) 67 -database <database> consider only queries on this database 68 (supports db1,db2 and /regexp/) 69 -user <user> consider only queries executed by this user 70 (supports user1,user2 and /regexp/) 71 -keepformatting keep the formatting of the query 72 -maxquerylength <length> maximum length of a query: we cut it if it exceeds this length 73 -durationunit <s|ms> unit used to display the durations. Default is s(econds). 74 -title <title> define the title of the reports 75 -syslogident <ident> PostgreSQL syslog identity. Default is postgres. 76 -memorylimit <n> PHP memory limit in MB. Default is 512. 77 -quiet quiet mode 78 -debug debug mode 79 -profile profile mode 80 -help this help 81'; 82 if($error) { 83 exit(1); 84 } else { 85 exit(0); 86 } 87} 88 89function checkOutputFilePath($filePath) { 90 if(!$filePath) { 91 return false; 92 } 93 94 $tmpOutputFilePath = $filePath; 95 $tmpOutputDirectory = dirname($tmpOutputFilePath); 96 $tmpOutputFileName = basename($tmpOutputFilePath); 97 98 if(file_exists($tmpOutputFilePath) && (!is_file($tmpOutputFilePath) || !is_writable($tmpOutputFilePath))) { 99 usage($tmpOutputFilePath.' already exists and is not a file or is not writable'); 100 return false; 101 } elseif(!is_dir($tmpOutputDirectory) || !is_writable($tmpOutputDirectory)) { 102 usage($tmpOutputDirectory.' is not a directory, does not exist or is not writable'); 103 return false; 104 } elseif(!$tmpOutputFileName) { 105 usage('cannot find a valid basename in '.$tmpOutputFilePath); 106 return false; 107 } else { 108 $outputFilePath = realpath($tmpOutputDirectory).'/'.$tmpOutputFileName; 109 return $outputFilePath; 110 } 111} 112 113if(isset($_SERVER['argv']) && (!isset($argv) || empty($argv))) { 114 $argv = $_SERVER['argv']; 115} 116if(is_array($argv)) { 117 $executable = array_shift($argv); 118} else { 119 $argv = array(); 120 $executable = 'unknown'; 121} 122 123$options = array(); 124$argvCount = count($argv); 125for($i = 0; $i < $argvCount; $i++) { 126 if(strpos($argv[$i], '-') === 0) { 127 if($argv[$i] == '-') { 128 define('CONFIG_STDIN', true); 129 } else { 130 $optionKey = substr($argv[$i], 1); 131 $value = false; 132 if(($i+1 < $argvCount) && (strpos($argv[$i+1], '-') !== 0)) { 133 $value = $argv[$i+1]; 134 $i++; 135 } 136 if($optionKey == 'report' || $optionKey == 'reports') { 137 if(!isset($options['reports'])) { 138 $options['reports'] = array(); 139 } 140 $options['reports'][] = $value; 141 } else { 142 $options[$optionKey] = $value; 143 } 144 } 145 } else { 146 usage('invalid options format'); 147 } 148} 149 150if(isset($options['memorylimit']) && ((int) $options['memorylimit']) > 0) { 151 $memoryLimit = (int) $options['memorylimit']; 152} else { 153 $memoryLimit = 512; 154} 155ini_set('memory_limit', $memoryLimit.'M'); 156 157if(!defined('CONFIG_STDIN')) { 158 define('CONFIG_STDIN', false); 159} 160 161if(isset($options['help']) || isset($options['h']) || isset($options['-help'])) { 162 usage(); 163} 164 165if(isset($options['debug'])) { 166 define('DEBUG', 1); 167} else { 168 define('DEBUG', 0); 169} 170if(isset($options['profile'])) { 171 define('PROFILE', 1); 172} else { 173 define('PROFILE', 0); 174} 175 176define('CONFIG_FILTER', false); 177 178if(!CONFIG_STDIN) { 179 if(!isset($options['file'])) { 180 usage('the -file option is required'); 181 } elseif(!$options['file']) { 182 usage('you have to specify a file path'); 183 } elseif(!is_readable($options['file'])) { 184 usage('file '.$options['file'].' cannot be read'); 185 } else { 186 $filePath = realpath($options['file']); 187 } 188} else { 189 $filePath = 'php://stdin'; 190} 191 192if(isset($options['title'])) { 193 define('CONFIG_REPORT_TITLE', $options['title']); 194} else { 195 define('CONFIG_REPORT_TITLE', 'pgFouine: PostgreSQL log analysis report'); 196} 197 198if(isset($options['top'])) { 199 if((int) $options['top'] > 0) { 200 $top = (int) $options['top']; 201 } else { 202 usage('top option should be a valid integer'); 203 } 204} else { 205 $top = 20; 206} 207define('CONFIG_TOP_QUERIES_NUMBER', $top); 208 209$outputToFiles = false; 210$supportedReportBlocks = array( 211 'overall' => 'OverallStatsReport', 212 'bytype' => 'QueriesByTypeReport', 213 'hourly' => 'HourlyStatsReport', 214 'slowest' => 'SlowestQueriesReport', 215 'n-mosttime' => 'NormalizedQueriesMostTimeReport', 216 'n-mostfrequent' => 'NormalizedQueriesMostFrequentReport', 217 'n-slowestaverage' => 'NormalizedQueriesSlowestAverageReport', 218 'history' => 'QueriesHistoryReport', 219 'historyperpid' => 'QueriesHistoryPerPidReport', 220 'n-mostfrequenterrors' => 'NormalizedErrorsMostFrequentReport', 221 'tsung' => 'TsungSessionsReport', 222 'csv-query' => 'CsvQueriesHistoryReport' 223); 224$defaultReportBlocks = array('overall', 'bytype', 'n-mosttime', 'slowest', 'n-mostfrequent', 'n-slowestaverage'); 225 226$reports = array(); 227if(isset($options['reports'])) { 228 foreach($options['reports'] AS $report) { 229 if(strpos($report, '=') !== false) { 230 list($outputFilePath, $blocks) = explode('=', $report); 231 $outputToFiles = true; 232 } elseif(strpos($report, '.') !== false) { 233 $outputFilePath = $report; 234 $blocks = 'default'; 235 $outputToFiles = true; 236 } else { 237 $outputFilePath = false; 238 $blocks = $report; 239 $outputToFiles = false; 240 } 241 if($blocks == 'default') { 242 $selectedBlocks = $defaultReportBlocks; 243 $notSupportedBlocks = array(); 244 } elseif($blocks == 'all') { 245 $selectedBlocks = array_keys($supportedReportBlocks); 246 $notSupportedBlocks = array(); 247 } else { 248 $selectedBlocks = explode(',', $blocks); 249 $notSupportedBlocks = array_diff($selectedBlocks, array_keys($supportedReportBlocks)); 250 } 251 252 if(empty($notSupportedBlocks)) { 253 $outputFilePath = checkOutputFilePath($outputFilePath); 254 $reports[] = array( 255 'blocks' => $selectedBlocks, 256 'file' => $outputFilePath 257 ); 258 } else { 259 usage('report types not supported: '.implode(',', $notSupportedBlocks)); 260 } 261 } 262} else { 263 $reports[] = array( 264 'blocks' => $defaultReportBlocks, 265 'file' => false 266 ); 267} 268 269$supportedFormats = array('text' => 'TextReportAggregator', 'html' => 'HtmlReportAggregator', 'html-with-graphs' => 'HtmlWithGraphsReportAggregator'); 270if(isset($options['format'])) { 271 if(array_key_exists($options['format'], $supportedFormats)) { 272 if($options['format'] == 'html-with-graphs') { 273 if(!function_exists('imagegd2')) { 274 usage('HTML with graphs format requires GD2 library and extension'); 275 } 276 if(!function_exists('imagettfbbox')) { 277 usage('HTML with graphs format requires Freetype support'); 278 } 279 if(!$outputToFiles) { 280 usage('you need to define an output file to use HTML with graphs format (use -report outputfile=block1,block2,...)'); 281 } 282 } 283 $aggregator = $supportedFormats[$options['format']]; 284 } else { 285 usage('format not supported'); 286 } 287} else { 288 $aggregator = $supportedFormats['html']; 289} 290 291$supportedLogTypes = array( 292 'syslog' => 'SyslogPostgreSQLParser', 293 'stderr' => 'StderrPostgreSQLParser', 294 'csvlog' => 'CsvlogPostgreSQLParser', 295); 296$logtype = ''; 297if(isset($options['logtype'])) { 298 if(array_key_exists($options['logtype'], $supportedLogTypes)) { 299 $parser = $supportedLogTypes[$options['logtype']]; 300 $logtype = $options['logtype']; 301 } else { 302 usage('log type not supported'); 303 } 304} else { 305 $parser = $supportedLogTypes['syslog']; 306 $logtype = 'syslog'; 307} 308 309if(isset($options['examples'])) { 310 $maxExamples = (int) $options['examples']; 311} else { 312 $maxExamples = 3; 313} 314define('CONFIG_MAX_NUMBER_OF_EXAMPLES', $maxExamples); 315 316if(isset($options['onlyselect'])) { 317 define('CONFIG_ONLY_SELECT', true); 318} else { 319 define('CONFIG_ONLY_SELECT', false); 320} 321 322if(isset($options['database']) && !empty($options['database'])) { 323 $options['database'] = trim($options['database']); 324 if(substr($options['database'], 0, 1) == '/' && substr($options['database'], -1, 1) == '/') { 325 // the filter is probably a regexp 326 if(@preg_match($options['database'], $value) === false) { 327 usage('database filter regexp is not valid'); 328 } else { 329 define('CONFIG_DATABASE_REGEXP', $options['database']); 330 } 331 } elseif(strpos($options['database'], ',') !== false) { 332 // the filter is a list 333 $databases = explode(',', $options['database']); 334 $databases = array_map('trim', $databases); 335 define('CONFIG_DATABASE_LIST', implode(',', $databases)); 336 } else { 337 define('CONFIG_DATABASE', $options['database']); 338 } 339} 340if(!defined('CONFIG_DATABASE')) define('CONFIG_DATABASE', false); 341if(!defined('CONFIG_DATABASE_LIST')) define('CONFIG_DATABASE_LIST', false); 342if(!defined('CONFIG_DATABASE_REGEXP')) define('CONFIG_DATABASE_REGEXP', false); 343 344if(isset($options['user']) && !empty($options['user'])) { 345 $options['user'] = trim($options['user']); 346 if(substr($options['user'], 0, 1) == '/' && substr($options['user'], -1, 1) == '/') { 347 // the filter is probably a regexp 348 if(@preg_match($options['user'], $value) === false) { 349 usage('user filter regexp is not valid'); 350 } else { 351 define('CONFIG_USER_REGEXP', $options['user']); 352 } 353 } elseif(strpos($options['user'], ',') !== false) { 354 // the filter is a list 355 $users = explode(',', $options['user']); 356 $users = array_map('trim', $users); 357 define('CONFIG_USER_LIST', implode(',', $users)); 358 } else { 359 define('CONFIG_USER', $options['user']); 360 } 361} 362if(!defined('CONFIG_USER')) define('CONFIG_USER', false); 363if(!defined('CONFIG_USER_LIST')) define('CONFIG_USER_LIST', false); 364if(!defined('CONFIG_USER_REGEXP')) define('CONFIG_USER_REGEXP', false); 365 366if(isset($options['keepformatting'])) { 367 define('CONFIG_KEEP_FORMATTING', true); 368} else { 369 define('CONFIG_KEEP_FORMATTING', false); 370} 371 372if(isset($options['maxquerylength']) && is_numeric($options['maxquerylength'])) { 373 define('CONFIG_MAX_QUERY_LENGTH', $options['maxquerylength']); 374} else { 375 define('CONFIG_MAX_QUERY_LENGTH', 0); 376} 377 378if(isset($options['durationunit']) && $options['durationunit'] == 'ms') { 379 define('CONFIG_DURATION_UNIT', 'ms'); 380} else { 381 define('CONFIG_DURATION_UNIT', 's'); 382} 383 384if(isset($options['from']) && !empty($options['from'])) { 385 $fromTimestamp = strtotime($options['from']); 386 if($fromTimestamp <= 0) { 387 $fromTimestamp = false; 388 } 389} else { 390 $fromTimestamp = false; 391} 392 393if(isset($options['to']) && !empty($options['to'])) { 394 $toTimestamp = strtotime($options['to']); 395 if($toTimestamp <= 0) { 396 $toTimestamp = false; 397 } 398} else { 399 $toTimestamp = false; 400} 401 402if($fromTimestamp || $toTimestamp) { 403 define('CONFIG_TIMESTAMP_FILTER', true); 404 if($fromTimestamp) { 405 define('CONFIG_FROM_TIMESTAMP', $fromTimestamp); 406 } else { 407 define('CONFIG_FROM_TIMESTAMP', MIN_TIMESTAMP); 408 } 409 if($toTimestamp) { 410 define('CONFIG_TO_TIMESTAMP', $toTimestamp); 411 } else { 412 define('CONFIG_TO_TIMESTAMP', MAX_TIMESTAMP); 413 } 414} else { 415 define('CONFIG_TIMESTAMP_FILTER', false); 416} 417 418if(isset($options['syslogident'])) { 419 define('CONFIG_SYSLOG_IDENTITY', $options['syslogident']); 420} else { 421 define('CONFIG_SYSLOG_IDENTITY', 'postgres'); 422} 423 424if(isset($options['quiet'])) { 425 define('CONFIG_QUIET', true); 426} else { 427 define('CONFIG_QUIET', false); 428} 429 430if($logtype == 'csvlog') { 431 $logReader = new CsvlogLogReader($filePath, $parser, 'PostgreSQLAccumulator'); 432} else { 433 $logReader = new GenericLogReader($filePath, $parser, 'PostgreSQLAccumulator'); 434} 435 436foreach($reports AS $report) { 437 $reportAggregator = new $aggregator($logReader, $report['file']); 438 foreach($report['blocks'] AS $block) { 439 $reportAggregator->addReportBlock($supportedReportBlocks[$block]); 440 } 441 $logReader->addReportAggregator($reportAggregator); 442 unset($reportAggregator); 443} 444 445$logReader->parse(); 446$logReader->output(); 447 448fclose($stderr); 449 450exit(0); 451 452?>