From 04b105d6be19ea82513266c99d369f4976da27f6 Mon Sep 17 00:00:00 2001 From: Kristoffer Lindqvist Date: Sat, 23 Feb 2013 17:02:34 +0200 Subject: [PATCH 1/2] .gitignore IntelliJ IDEA project files --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 239c9b9f..344ac0a0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ # Netbeans project files # /nbproject + +# +# IntelliJ IDEA project files +# +.idea +*.iml From ea7696bf40201c61c21a3bd0928c13c44ed9cbaf Mon Sep 17 00:00:00 2001 From: Kristoffer Lindqvist Date: Sat, 23 Feb 2013 18:21:02 +0200 Subject: [PATCH 2/2] Add support for PostgreSQL using the database abstraction layer. Removes remaining MySQL specifics found here and there. MySQL and PostgreSQL seem to be working nicely now, further testing is encouraged. MS SQL and SQL Server seemed broken from the outset and no effort has been made to fix them as I do not have access to either. Inconsistent line endings in the code base and having a mix of spaces and tabs in some places makes the diff a little less clean than one would wish. Main changes: * added PostgreSQL database abstraction class * MySQL purging: abstract out the identifier quote style to remove hardcoded MySQL backticks, abstract out FROM_UNIXTIME, remove MySQL schema doc from XHProfRuns_Default, use explicit aliases in getRunComparativeData() to not rely on implicit MySQL aggregate column naming. * Fix method declarations and some variable issues in the database abstractions which leads me to believe that not all of the database classes actually work. Added notes about remaining portability issues for other databases than MySQL and PostgreSQL. * Remove unused dir property and constructor param from XHProfRuns_Default. This is a left-over from the Facebook file backend and there should be no compelling reason to retain it anymore. Cosmetics/refactorings: * centralized (un)serializing, made things a bit DRYer * Use XHPROF_LIB_ROOT everywhere in index.php. * fix typos here and there. * declare xhprof global in the header and footer includes too to fix scope when including files. --- INSTALL | 2 +- external/footer.php | 2 + external/header.php | 4 +- xhprof_html/index.php | 23 +- .../templates/diff_run_header_block.phtml | 26 +- .../templates/single_run_header_block.phtml | 40 +- xhprof_lib/utils/Db/Abstract.php | 18 +- xhprof_lib/utils/Db/Mssql.php | 140 +++--- xhprof_lib/utils/Db/Mysql.php | 118 +++--- xhprof_lib/utils/Db/Mysqli.php | 38 +- xhprof_lib/utils/Db/Pdo.php | 53 ++- xhprof_lib/utils/Db/Postgresql.php | 121 ++++++ xhprof_lib/utils/Db/Sqlsrv.php | 45 +- xhprof_lib/utils/xhprof_runs.php | 398 +++++++++--------- 14 files changed, 629 insertions(+), 399 deletions(-) create mode 100644 xhprof_lib/utils/Db/Postgresql.php diff --git a/INSTALL b/INSTALL index ba0258b7..9afb463e 100644 --- a/INSTALL +++ b/INSTALL @@ -18,7 +18,7 @@ 3. Copy xhprof_lib/config.sample.php to xhprof_lib/config.php 4. Edit xhprof_lib/config.php with your database type and credentials. Set control IPs, and other fun stuff Control IPs allow you to specify which IPs will be shown the footer linking directly to the results, and also which IPs will be able to turn profiling on and off - You can set this value to FALSE, which disables that protection all togethor. + You can set this value to FALSE, which disables that protection altogether. 5. Create a table as required for your database, SQL is available inside utils/Db/.php. If you're prefixing runs IDs, note that it will affect the length requried for the ID column. 5. Load up index.php, ensure happiness 7. Edit the virtual host configuration for the site you'd like to profile to diff --git a/external/footer.php b/external/footer.php index fea2d224..53c334ff 100755 --- a/external/footer.php +++ b/external/footer.php @@ -1,4 +1,6 @@ getDbInstance(); $domainFilter = getFilter('domain_filter'); $serverFilter = getFilter('server_filter'); @@ -80,7 +81,7 @@ $_xh_header = ""; if(isset($_GET['run1']) || isset($_GET['run'])) { - include ("../xhprof_lib/templates/header.phtml"); + include (XHPROF_LIB_ROOT . "/templates/header.phtml"); displayXHProfReport($xhprof_runs_impl, $params, $source, $run, $wts, $symbol, $sort, $run1, $run2); }elseif (isset($_GET['geturl'])) @@ -94,9 +95,9 @@ list($header, $body) = showChart($rs, true); $_xh_header .= $header; - include ("../xhprof_lib/templates/header.phtml"); + include (XHPROF_LIB_ROOT . "/templates/header.phtml"); $rs = $xhprof_runs_impl->getRuns($criteria); - include ("../xhprof_lib/templates/emptyBody.phtml"); + include (XHPROF_LIB_ROOT . "/templates/emptyBody.phtml"); $url = htmlentities($_GET['geturl'], ENT_QUOTES, "UTF-8"); displayRuns($rs, "Runs with URL: $url"); @@ -111,15 +112,15 @@ $rs = $xhprof_runs_impl->getUrlStats($criteria); list($header, $body) = showChart($rs, true); $_xh_header .= $header; - include ("../xhprof_lib/templates/header.phtml"); + include (XHPROF_LIB_ROOT . "/templates/header.phtml"); $url = htmlentities($_GET['getcurl'], ENT_QUOTES, "UTF-8"); $rs = $xhprof_runs_impl->getRuns($criteria); - include("../xhprof_lib/templates/emptyBody.phtml"); + include(XHPROF_LIB_ROOT . "/templates/emptyBody.phtml"); displayRuns($rs, "Runs with Simplified URL: $url"); }elseif (isset($_GET['getruns'])) { - include ("../xhprof_lib/templates/header.phtml"); + include (XHPROF_LIB_ROOT . "/templates/header.phtml"); $days = (int) $_GET['days']; switch ($_GET['getruns']) @@ -137,12 +138,14 @@ $criteria['order by'] = $load; $criteria['limit'] = "500"; - $criteria['where'] = "DATE_SUB(CURDATE(), INTERVAL $days DAY) <= `timestamp`"; + // @TODO kludgy, should plug the database stuff leaking into here altogether + $criteria['where'] = $xhprof_db->dateSub($days) . " <= " . $xhprof_db->quote("timestamp"); + $rs = $xhprof_runs_impl->getRuns($criteria); displayRuns($rs, "Worst runs by $load"); }elseif(isset($_GET['hit'])) { - include ("../xhprof_lib/templates/header.phtml"); + include (XHPROF_LIB_ROOT . "/templates/header.phtml"); $last = (isset($_GET['hit'])) ? $_GET['hit'] : 25; $last = (int) $last; $days = (isset($_GET['days'])) ? $_GET['days'] : 1; @@ -203,7 +206,7 @@ CODESE; }else { - include ("../xhprof_lib/templates/header.phtml"); + include (XHPROF_LIB_ROOT . "/templates/header.phtml"); $last = (isset($_GET['last'])) ? $_GET['last'] : 25; $last = (int) $last; $criteria['order by'] = "timestamp"; @@ -212,4 +215,4 @@ displayRuns($rs, "Last $last Runs"); } -include ("../xhprof_lib/templates/footer.phtml"); +include (XHPROF_LIB_ROOT . "/templates/footer.phtml"); diff --git a/xhprof_lib/templates/diff_run_header_block.phtml b/xhprof_lib/templates/diff_run_header_block.phtml index 044fbacd..2dc63777 100644 --- a/xhprof_lib/templates/diff_run_header_block.phtml +++ b/xhprof_lib/templates/diff_run_header_block.phtml @@ -25,24 +25,14 @@ print('
'); print("
"); print('
'); - // cookie diff data from array position 0 - if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer']) == 'php') { - $cookieArr0 = unserialize($xhprof_runs_impl->run_details[0]['cookie']); - $cookieArr1 = unserialize($xhprof_runs_impl->run_details[1]['cookie']); - $getArr0 = unserialize($xhprof_runs_impl->run_details[0]['get']); - $getArr1 = unserialize($xhprof_runs_impl->run_details[1]['get']); - $postArr0 = unserialize($xhprof_runs_impl->run_details[0]['post']); - $postArr1 = unserialize($xhprof_runs_impl->run_details[1]['post']); - } else { - $cookieArr0 = json_decode($xhprof_runs_impl->run_details[0]['cookie'], true); - $cookieArr1 = json_decode($xhprof_runs_impl->run_details[1]['cookie'], true); - $getArr0 = json_decode($xhprof_runs_impl->run_details[0]['get'], true); - $getArr1 = json_decode($xhprof_runs_impl->run_details[1]['get'], true); - $postArr0 = json_decode($xhprof_runs_impl->run_details[0]['post'], true); - $postArr1 = json_decode($xhprof_runs_impl->run_details[1]['post'], true); - } - - + // cookie diff data from array position 0 + $cookieArr0 = $xhprof_runs_impl->run_details[0]['cookie']; + $cookieArr1 = $xhprof_runs_impl->run_details[1]['cookie']; + $getArr0 = $xhprof_runs_impl->run_details[0]['get']; + $getArr1 = $xhprof_runs_impl->run_details[1]['get']; + $postArr0 = $xhprof_runs_impl->run_details[0]['post']; + $postArr1 = $xhprof_runs_impl->run_details[1]['post']; + print ('
'); print ''; print ""; diff --git a/xhprof_lib/templates/single_run_header_block.phtml b/xhprof_lib/templates/single_run_header_block.phtml index e6bc8d0d..c3604581 100644 --- a/xhprof_lib/templates/single_run_header_block.phtml +++ b/xhprof_lib/templates/single_run_header_block.phtml @@ -11,16 +11,10 @@ if ($display_calls) { $format_total= number_format($totals['ct']); } - - if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer'] == 'php')) { - $cookieArr = unserialize($xhprof_runs_impl->run_details['cookie']); - $getArr = unserialize($xhprof_runs_impl->run_details['get']); - $postArr = unserialize($xhprof_runs_impl->run_details['post']); - } else { - $cookieArr = json_decode($xhprof_runs_impl->run_details['cookie'], true); - $getArr = json_decode($xhprof_runs_impl->run_details['get'], true); - $postArr = json_decode($xhprof_runs_impl->run_details['post'], true); - } + + $cookieArr = $xhprof_runs_impl->run_details['cookie']; + $getArr = $xhprof_runs_impl->run_details['get']; + $postArr = $xhprof_runs_impl->run_details['post']; //TODO This is lame global $comparative; @@ -38,21 +32,21 @@ - - - - - + + + + + - - - - + + + + - - - - + + + + diff --git a/xhprof_lib/utils/Db/Abstract.php b/xhprof_lib/utils/Db/Abstract.php index ab36025b..5f50f7f2 100644 --- a/xhprof_lib/utils/Db/Abstract.php +++ b/xhprof_lib/utils/Db/Abstract.php @@ -12,14 +12,26 @@ public function __construct($config) abstract public function connect(); abstract public function query($sql); abstract public function escape($str); - abstract public function affectedRows(); + abstract public function escapeBinary($data); + abstract public function unescapeBinary($data); + abstract public function affectedRows($resultSet); - public static function unixTimestamp($field) + public function quote($identifier) { throw new RuntimeException("Method '".get_called_class()."::".__FUNCTION__."' not implemented"); } - public static function dateSub($days) + public function unixTimestamp($field) + { + throw new RuntimeException("Method '".get_called_class()."::".__FUNCTION__."' not implemented"); + } + + public function fromUnixTimestamp($field) + { + throw new RuntimeException("Method '".get_called_class()."::".__FUNCTION__."' not implemented"); + } + + public function dateSub($days) { throw new RuntimeException("Method '".get_called_class()."::".__FUNCTION__."' not implemented"); } diff --git a/xhprof_lib/utils/Db/Mssql.php b/xhprof_lib/utils/Db/Mssql.php index b6d1e73d..ce46f188 100644 --- a/xhprof_lib/utils/Db/Mssql.php +++ b/xhprof_lib/utils/Db/Mssql.php @@ -1,52 +1,54 @@ prefix - * - * - CREATE TABLE dbo.details - ( - id nchar(17) NOT NULL, - url nvarchar(255) NULL DEFAULT NULL, - c_url nvarchar(255) NULL DEFAULT NULL, - timestamp datetime NOT NULL DEFAULT getdate(), - [server name] nvarchar(64) NULL DEFAULT NULL, - perfdata nvarchar(max) NULL, - type smallint NULL DEFAULT NULL, - cookie nvarchar(max) NULL, - post nvarchar(max) NULL, - get nvarchar(max) NULL, - pmu int NULL DEFAULT NULL, - wt int NULL DEFAULT NULL, - cpu int NULL DEFAULT NULL, - server_id nchar(3) NOT NULL DEFAULT N't11', - aggregateCalls_include nvarchar(255) NULL DEFAULT NULL, - CONSTRAINT PK_details_id PRIMARY KEY (id) - ) - GO - CREATE NONCLUSTERED INDEX dbo.url - ON dbo.details (url ASC) - GO - CREATE NONCLUSTERED INDEX dbo.c_url - ON dbo.details (c_url ASC) - GO - CREATE NONCLUSTERED INDEX dbo.cpu - ON dbo.details (cpu ASC) - GO - CREATE NONCLUSTERED INDEX dbo.wt - ON dbo.details (wt ASC) - GO - CREATE NONCLUSTERED INDEX dbo.pmu - ON dbo.details (pmu ASC) - GO - CREATE NONCLUSTERED INDEX dbo.timestamp - ON dbo.details (timestamp +/** + * When setting the `id` column, consider the length of the prefix you're specifying in $this->prefix + * + * + CREATE TABLE dbo.details + ( + id nchar(17) NOT NULL, + url nvarchar(255) NULL DEFAULT NULL, + c_url nvarchar(255) NULL DEFAULT NULL, + timestamp datetime NOT NULL DEFAULT getdate(), + [server name] nvarchar(64) NULL DEFAULT NULL, + perfdata nvarchar(max) NULL, + type smallint NULL DEFAULT NULL, + cookie nvarchar(max) NULL, + post nvarchar(max) NULL, + get nvarchar(max) NULL, + pmu int NULL DEFAULT NULL, + wt int NULL DEFAULT NULL, + cpu int NULL DEFAULT NULL, + server_id nchar(3) NOT NULL DEFAULT N't11', + aggregateCalls_include nvarchar(255) NULL DEFAULT NULL, + CONSTRAINT PK_details_id PRIMARY KEY (id) + ) + GO + CREATE NONCLUSTERED INDEX dbo.url + ON dbo.details (url ASC) + GO + CREATE NONCLUSTERED INDEX dbo.c_url + ON dbo.details (c_url ASC) + GO + CREATE NONCLUSTERED INDEX dbo.cpu + ON dbo.details (cpu ASC) + GO + CREATE NONCLUSTERED INDEX dbo.wt + ON dbo.details (wt ASC) + GO + CREATE NONCLUSTERED INDEX dbo.pmu + ON dbo.details (pmu ASC) + GO + CREATE NONCLUSTERED INDEX dbo.timestamp + ON dbo.details (timestamp */ +// @TODO MS SQL does most likely not work, e.g. see TODOs in XHProfRuns_Default about the use of LIMIT and OFFSET + require_once XHPROF_LIB_ROOT.'/utils/Db/Abstract.php'; class Db_Mssql extends Db_Abstract { - + public function connect() { $linkid = mssql_connect($this->config['dbhost'], $this->config['dbuser'], $this->config['dbpass']); @@ -59,34 +61,58 @@ public function connect() mssql_select_db($this->config['dbname'], $linkid); $this->linkID = $linkid; } - + public function query($sql) { return mssql_query($sql); } - + public static function getNextAssoc($resultSet) { return mssql_fetch_assoc($resultSet); } - - public static function escape($str) + + public function escape($str) { return addslashes($str); } - - public function affectedRows() + + public function escapeBinary($data) + { + // @TODO is this correct behavior? + return $this->escape($data); + } + + public function unescapeBinary($data) + { + // did not have any binary unescaping before introducing this method to Db_Abstract, needed? + return $data; + } + + public function affectedRows($resultSet) { + // NOTE: MS SQL uses the link identifier so $resultSet is unused on purpose return mssql_rows_affected($this->linkID); } - - public static function unixTimestamp($field) - { - return 'UNIX_TIMESTAMP('.$field.')'; + + public function quote($identifier) + { + return '"' . $identifier . '"'; + } + + public function unixTimestamp($field) + { + return 'UNIX_TIMESTAMP('.$field.')'; } - - public static function dateSub($days) - { - return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; + + public function fromUnixTimestamp($field) + { + // @TODO is this correct syntax? + return 'FROM_UNIXTIME(' . $field . ')'; + } + + public function dateSub($days) + { + return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; } -} \ No newline at end of file +} diff --git a/xhprof_lib/utils/Db/Mysql.php b/xhprof_lib/utils/Db/Mysql.php index 7b343505..b05a2899 100644 --- a/xhprof_lib/utils/Db/Mysql.php +++ b/xhprof_lib/utils/Db/Mysql.php @@ -1,34 +1,34 @@ prefix - * - * - CREATE TABLE `details` ( - `id` char(17) NOT NULL, - `url` varchar(255) default NULL, - `c_url` varchar(255) default NULL, - `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, - `server name` varchar(64) default NULL, - `perfdata` MEDIUMBLOB, - `type` tinyint(4) default NULL, - `cookie` BLOB, - `post` BLOB, - `get` BLOB, - `pmu` int(11) unsigned default NULL, - `wt` int(11) unsigned default NULL, - `cpu` int(11) unsigned default NULL, - `server_id` char(3) NOT NULL default 't11', - `aggregateCalls_include` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `url` (`url`), - KEY `c_url` (`c_url`), - KEY `cpu` (`cpu`), - KEY `wt` (`wt`), - KEY `pmu` (`pmu`), - KEY `timestamp` (`timestamp`) - ) ENGINE=MyISAM DEFAULT CHARSET=utf8; - +/** + * When setting the `id` column, consider the length of the prefix you're specifying in $this->prefix + * + * + CREATE TABLE `details` ( + `id` char(17) NOT NULL, + `url` varchar(255) default NULL, + `c_url` varchar(255) default NULL, + `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + `server name` varchar(64) default NULL, + `perfdata` MEDIUMBLOB, + `type` tinyint(4) default NULL, + `cookie` BLOB, + `post` BLOB, + `get` BLOB, + `pmu` int(11) unsigned default NULL, + `wt` int(11) unsigned default NULL, + `cpu` int(11) unsigned default NULL, + `server_id` char(3) NOT NULL default 't11', + `aggregateCalls_include` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `url` (`url`), + KEY `c_url` (`c_url`), + KEY `cpu` (`cpu`), + KEY `wt` (`wt`), + KEY `pmu` (`pmu`), + KEY `timestamp` (`timestamp`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + */ require_once XHPROF_LIB_ROOT.'/utils/Db/Abstract.php'; @@ -37,18 +37,18 @@ class Db_Mysql extends Db_Abstract public function connect() { - $linkid = mysql_connect($this->config['dbhost'], $this->config['dbuser'], $this->config['dbpass']); - if ($linkid === FALSE) - { - xhprof_error("Could not connect to db"); - throw new Exception("Unable to connect to database"); - return false; - } - $this->query("SET NAMES utf8"); - mysql_select_db($this->config['dbname'], $linkid); + $linkid = mysql_connect($this->config['dbhost'], $this->config['dbuser'], $this->config['dbpass']); + if ($linkid === FALSE) + { + xhprof_error("Could not connect to db"); + throw new Exception("Unable to connect to database"); + return false; + } + $this->query("SET NAMES utf8"); + mysql_select_db($this->config['dbname'], $linkid); $this->linkID = $linkid; } - + public function query($sql) { return mysql_query($sql); @@ -63,19 +63,41 @@ public function escape($str) { return mysql_real_escape_string($str); } - - public function affectedRows() + + public function escapeBinary($data) + { + return $this->escape($data); + } + + public function unescapeBinary($data) + { + // did not have any binary unescaping before introducing this method to Db_Abstract, needed? + return $data; + } + + public function affectedRows($resultSet) { + // NOTE: MySQL uses the link identifier so $resultSet is unused on purpose return mysql_affected_rows($this->linkID); } - - public static function unixTimestamp($field) - { - return 'UNIX_TIMESTAMP('.$field.')'; + + public function quote($identifier) + { + return "`$identifier`"; + } + + public function unixTimestamp($field) + { + return 'UNIX_TIMESTAMP('.$field.')'; + } + + public function fromUnixTimestamp($field) + { + return 'FROM_UNIXTIME(' . $field . ')'; } - public static function dateSub($days) - { - return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; + public function dateSub($days) + { + return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; } } \ No newline at end of file diff --git a/xhprof_lib/utils/Db/Mysqli.php b/xhprof_lib/utils/Db/Mysqli.php index 786ef931..345cdf83 100644 --- a/xhprof_lib/utils/Db/Mysqli.php +++ b/xhprof_lib/utils/Db/Mysqli.php @@ -63,19 +63,41 @@ public function escape($str) { return mysqli_real_escape_string($str); } - - public function affectedRows() + + public function escapeBinary($data) + { + return $this->escape($data); + } + + public function unescapeBinary($data) + { + // did not have any binary unescaping before introducing this method to Db_Abstract, needed? + return $data; + } + + public function affectedRows($resultSet) { + // NOTE: MySQLi uses the link identifier so $resultSet is unused on purpose return mysqli_affected_rows($this->linkID); } + + public function quote($identifier) + { + return "`$identifier`"; + } - public static function unixTimestamp($field) - { - return 'UNIX_TIMESTAMP('.$field.')'; + public function unixTimestamp($field) + { + return 'UNIX_TIMESTAMP('.$field.')'; + } + + public function fromUnixTimestamp($field) + { + return 'FROM_UNIXTIME(' . $field . ')'; } - public static function dateSub($days) - { - return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; + public function dateSub($days) + { + return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; } } \ No newline at end of file diff --git a/xhprof_lib/utils/Db/Pdo.php b/xhprof_lib/utils/Db/Pdo.php index 48c95efc..10d2b623 100644 --- a/xhprof_lib/utils/Db/Pdo.php +++ b/xhprof_lib/utils/Db/Pdo.php @@ -35,19 +35,20 @@ class Db_Pdo extends Db_Abstract { protected $curStmt; + protected $db; public function connect() { - $connectionString = $this->config['dbtype'] . ':host=' . $this->config['dbhost'] . ';dbname=' . $this->config['dbname']; - $db = new PDO($connectionString, $this->config['dbuser'], $this->config['dbpass']); - if ($db === FALSE) - { - xhprof_error("Could not connect to db"); - $run_desc = "could not connect to db"; - throw new Exception("Unable to connect to database"); - return false; - } - $this->db = $db; + $connectionString = $this->config['dbtype'] . ':host=' . $this->config['dbhost'] . ';dbname=' . $this->config['dbname']; + $db = new PDO($connectionString, $this->config['dbuser'], $this->config['dbpass']); + if ($db === FALSE) + { + xhprof_error("Could not connect to db"); + $run_desc = "could not connect to db"; + throw new Exception("Unable to connect to database"); + return false; + } + $this->db = $db; $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); } @@ -71,21 +72,45 @@ public function escape($str) $str = substr($str, 1); return $str; } - - public function affectedRows() + + public function escapeBinary($data) + { + return $this->escape($data); + } + + public function unescapeBinary($data) { + // did not have any binary unescaping before introducing this method to Db_Abstract, needed? + return $data; + } + + public function affectedRows($resultSet) + { + // NOTE: PDO does not need $resultSet, unused on purpose if ($this->curStmt === false) { return 0; } return $this->curStmt->rowCount(); } + + public function quote($identifier) + { + // @TODO how should we handle this in PDO? https://bugs.php.net/bug.php?id=38196 + return "`$identifier`"; + } - public static function unixTimestamp($field) + public function unixTimestamp($field) { return 'UNIX_TIMESTAMP('.$field.')'; } - public static function dateSub($days) + public function fromUnixTimestamp($field) + { + // @TODO is this correct syntax? + return 'FROM_UNIXTIME(' . $field . ')'; + } + + public function dateSub($days) { return 'DATE_SUB(CURDATE(), INTERVAL '.$days.' DAY)'; } diff --git a/xhprof_lib/utils/Db/Postgresql.php b/xhprof_lib/utils/Db/Postgresql.php new file mode 100644 index 00000000..fc31acfc --- /dev/null +++ b/xhprof_lib/utils/Db/Postgresql.php @@ -0,0 +1,121 @@ +config['dbhost'], $this->config['dbname'], $this->config['dbuser'] + ); + + // @TODO migrate dbport to the other abstractions too! + if (isset($this->config['dbport'])) { + $connectionString .= sprintf(' port=%s', $this->config['dbport']); + } + + // allow passwordless login, typical in dev + if (strlen($this->config['dbpass'])) { + $connectionString .= sprintf(' password=%s', $this->config['dbpass']); + } + + $linkid = pg_connect($connectionString); + if ($linkid === FALSE) + { + xhprof_error("Could not connect to db"); + throw new Exception("Unable to connect to database"); + } + $this->linkID = $linkid; + pg_set_client_encoding($this->linkID, "UTF8"); + } + + public function query($sql) + { + return pg_query($this->linkID, $sql); + } + + public static function getNextAssoc($resultSet) + { + return pg_fetch_assoc($resultSet); + } + + public function escape($str) + { + return pg_escape_string($this->linkID, $str); + } + + public function escapeBinary($data) + { + // @see http://www.php.net/manual/en/function.pg-escape-bytea.php#89036 + return str_replace(array("\\\\", "''"), array("\\", "'"), pg_escape_bytea($this->linkID, $data)); + } + + public function unescapeBinary($data) + { + return pg_unescape_bytea($data); + } + + public function affectedRows($resultSet) + { + return pg_affected_rows($resultSet); + } + + public function quote($identifier) + { + return '"' . $identifier . '"'; + } + + public function unixTimestamp($field) + { + return "extract(epoch from $field)"; + } + + public function fromUnixTimestamp($field) + { + return "to_timestamp($field)"; + } + + public function dateSub($days) + { + return "(current_date - '$days days'::interval)"; + } + +} \ No newline at end of file diff --git a/xhprof_lib/utils/Db/Sqlsrv.php b/xhprof_lib/utils/Db/Sqlsrv.php index 6133f1dc..a0839649 100644 --- a/xhprof_lib/utils/Db/Sqlsrv.php +++ b/xhprof_lib/utils/Db/Sqlsrv.php @@ -51,7 +51,7 @@ class Db_Sqlsrv extends Db_Abstract public function connect() { - $connectionInfo = array("UID" => $this->config['dbuser'], "PWD" => $this->config['dbpass'], "Database"=>$this->config['dbname'], "ReturnDatesAsStrings" => TRUE); + $connectionInfo = array("UID" => $this->config['dbuser'], "PWD" => $this->config['dbpass'], "Database"=>$this->config['dbname'], "ReturnDatesAsStrings" => TRUE); $linkid = sqlsrv_connect($this->config['dbhost'], $connectionInfo); if ($linkid === FALSE) { @@ -64,7 +64,7 @@ public function connect() public function query($sql) { - $this->curStmt = sqlsrv_prepare($this->linkID, $query, array()); + $this->curStmt = sqlsrv_prepare($this->linkID, $sql, array()); return sqlsrv_execute($this->curStmt); } @@ -77,19 +77,42 @@ public function escape($str) { return addslashes($str); } - - public function affectedRows() + + public function escapeBinary($data) + { + // did not have any binary escaping before introducing this method to Db_Abstract, needed? + return $data; + } + + public function unescapeBinary($data) + { + // did not have any binary unescaping before introducing this method to Db_Abstract, needed? + return $data; + } + + public function affectedRows($resultSet) { return sqlsrv_rows_affected($this->curStmt); } - - public static function unixTimestamp($field) - { - return 'DATEDIFF(s, \'1970-01-01 00:00:00\', '.$field.')'; + + public function quote($identifier) + { + return '"' . $identifier . '"'; + } + + public function unixTimestamp($field) + { + return 'DATEDIFF(s, \'1970-01-01 00:00:00\', '.$field.')'; + } + + public function fromUnixTimestamp($field) + { + // @TODO is this correct syntax? + return 'FROM_UNIXTIME(' . $field . ')'; } - public static function dateSub($days) - { - return 'dateadd(d, -'.$days.', getdate())'; + public function dateSub($days) + { + return 'dateadd(d, -'.$days.', getdate())'; } } \ No newline at end of file diff --git a/xhprof_lib/utils/xhprof_runs.php b/xhprof_lib/utils/xhprof_runs.php index 017e2c35..56ca3744 100644 --- a/xhprof_lib/utils/xhprof_runs.php +++ b/xhprof_lib/utils/xhprof_runs.php @@ -60,26 +60,25 @@ public function save_run($xhprof_data, $type, $run_id = null); * XHProfRuns_Default is the default implementation of the * iXHProfRuns interface for saving/fetching XHProf runs. * - * This modified version of the file uses a MySQL backend to store + * This modified version of the file uses a database backend to store * the data, it also stores additional information outside the run * itself (beyond simply the run id) to make comparisons and run * location easier - * + * * @author Kannan * @author Paul Reinheimer (http://blog.preinheimer.com) */ class XHProfRuns_Default implements iXHProfRuns { - private $dir = ''; public $prefix = 't11_'; public $run_details = null; /** - * + * * @var Db_Abstract */ protected $db; - public function __construct($dir = null) + public function __construct() { $this->db(); } @@ -88,81 +87,49 @@ protected function db() { global $_xhprof; require_once XHPROF_LIB_ROOT.'/utils/Db/'.$_xhprof['dbadapter'].'.php'; - + $class = self::getDbClass(); $this->db = new $class($_xhprof); $this->db->connect(); } - + public static function getDbClass() { global $_xhprof; - + $class = 'Db_'.$_xhprof['dbadapter']; return $class; } - - /** - * When setting the `id` column, consider the length of the prefix you're specifying in $this->prefix - * - * -CREATE TABLE `details` ( - `id` char(17) NOT NULL, - `url` varchar(255) default NULL, - `c_url` varchar(255) default NULL, - `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, - `server name` varchar(64) default NULL, - `perfdata` MEDIUMBLOB, - `type` tinyint(4) default NULL, - `cookie` BLOB, - `post` BLOB, - `get` BLOB, - `pmu` int(11) unsigned default NULL, - `wt` int(11) unsigned default NULL, - `cpu` int(11) unsigned default NULL, - `server_id` char(3) NOT NULL default 't11', - `aggregateCalls_include` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `url` (`url`), - KEY `c_url` (`c_url`), - KEY `cpu` (`cpu`), - KEY `wt` (`wt`), - KEY `pmu` (`pmu`), - KEY `timestamp` (`timestamp`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -*/ - - - private function gen_run_id($type) + + public function getDbInstance() + { + return $this->db; + } + + private function gen_run_id($type) { return uniqid(); } - + /** * This function gets runs based on passed parameters, column data as key, value as the value. Values * are escaped automatically. You may also pass limit, order by, group by, or "where" to add those values, - * all of which are used as is, no escaping. - * + * all of which are used as is, no escaping. + * * @param array $stats Criteria by which to select columns * @return resource */ public function getRuns($stats) { - if (isset($stats['select'])) - { - $query = "SELECT {$stats['select']} FROM `details` "; - }else - { - $query = "SELECT * FROM `details` "; - } - + $queryScope = (isset($stats['select'])) ? $stats['select'] : '*'; + $query = "SELECT $queryScope FROM " . $this->db->quote('details'); + $skippers = array("limit", "order by", "group by", "where", "select"); $hasWhere = false; - + foreach($stats AS $column => $value) { - + if (in_array($column, $skippers)) { continue; @@ -179,11 +146,10 @@ public function getRuns($stats) { $query .= $column; } - - $value = $this->db->escape($value); - $query .= " `$column` = '$value' "; + + $query .= " " . $this->db->quote($column) . " = '" . $this->db->escape($value) . "' "; } - + if (isset($stats['where'])) { if ($hasWhere === false) @@ -196,26 +162,27 @@ public function getRuns($stats) } $query .= $stats['where']; } - + if (isset($stats['group by'])) { - $query .= " GROUP BY `{$stats['group by']}` "; + $query .= " GROUP BY " . $this->db->quote($stats['group by']) . " "; } - + if (isset($stats['order by'])) { - $query .= " ORDER BY `{$stats['order by']}` DESC"; + $query .= " ORDER BY " . $this->db->quote($stats['order by']) . " DESC"; } - + + // @TODO this is not portable, i.e. MS SQL would require TOP instead and placed first if (isset($stats['limit'])) { $query .= " LIMIT {$stats['limit']} "; } - + $resultSet = $this->db->query($query); return $resultSet; } - + /** * Obtains the pages that have been the hardest hit over the past N days, utalizing the getRuns() method. * @@ -224,57 +191,62 @@ public function getRuns($stats) */ public function getHardHit($criteria) { - //call thing to get runs - $criteria['select'] = "distinct(`{$criteria['type']}`), count(`{$criteria['type']}`) AS `count` , sum(`wt`) as total_wall, avg(`wt`) as avg_wall"; + $criteria['select'] = + "distinct(" . $this->db->quote($criteria['type']) . "), " . + "count(" . $this->db->quote($criteria['type']) . ") AS " . $this->db->quote("count") . ", " . + "sum(" . $this->db->quote("wt") . ") AS " . $this->db->quote("total_wall") . ", " . + "avg(" . $this->db->quote("wt") . ") AS " . $this->db->quote("avg_wall"); unset($criteria['type']); - $criteria['where'] = $this->db->dateSub($criteria['days']) . " <= `timestamp`"; + $criteria['where'] = $this->db->dateSub($criteria['days']) . " <= " . $this->db->quote("timestamp"); unset($criteria['days']); $criteria['group by'] = "url"; $criteria['order by'] = "count"; $resultSet = $this->getRuns($criteria); - + return $resultSet; } - + public function getDistinct($data) { $sql['column'] = $this->db->escape($data['column']); - $query = "SELECT DISTINCT(`{$sql['column']}`) FROM `details`"; + $query = "SELECT DISTINCT(" . $this->db->quote($sql['column']) . ") FROM " . $this->db->quote("details"); $rs = $this->db->query($query); return $rs; } - + public static function getNextAssoc($resultSet) { $class = self::getDbClass(); return $class::getNextAssoc($resultSet); } - + /** - * Retreives a run from the database, - * + * Retrieves a run from the database, + * * @param string $run_id unique identifier for the run being requested * @param mixed $type * @param mixed $run_desc - * @return mixed + * @return array */ - public function get_run($run_id, $type, &$run_desc) + public function get_run($run_id, $type, &$run_desc) { $run_id = $this->db->escape($run_id); - $query = "SELECT * FROM `details` WHERE `id` = '$run_id'"; + $query = "SELECT * FROM " . $this->db->quote("details") . " WHERE " . $this->db->quote("id") . " = '$run_id'"; $resultSet = $this->db->query($query); $data = $this->db->getNextAssoc($resultSet); - + if (!$data) { + return array(array(), array()); + } + //The Performance data is compressed lightly to avoid max row length - if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer'] == 'php')) { - $contents = unserialize(gzuncompress($data['perfdata'])); - } else { - $contents = json_decode(gzuncompress($data['perfdata']), true); - } - + $contents = $this->unserialize($data['perfdata'], true); + //This data isnt' needed for display purposes, there's no point in keeping it in this array unset($data['perfdata']); + $data['cookie'] = $this->unserialize($data['cookie']); + $data['get'] = $this->unserialize($data['get']); + $data['post'] = $this->unserialize($data['post']); // The same function is called twice when diff'ing runs. In this case we'll populate the global scope with an array if (is_null($this->run_details)) @@ -282,109 +254,124 @@ public function get_run($run_id, $type, &$run_desc) $this->run_details = $data; }else { - $this->run_details[0] = $this->run_details; + $this->run_details[0] = $this->run_details; $this->run_details[1] = $data; } - + $run_desc = "XHProf Run (Namespace=$type)"; $this->getRunComparativeData($data['url'], $data['c_url']); - + return array($contents, $data); } - + /** * Get stats (pmu, ct, wt) on a url or c_url - * - * @param array $data An associative array containing the limit you'd like to set for the queyr, as well as either c_url or url for the desired element. + * + * @param array $data An associative array containing the limit you'd like to set for the query, as well as either c_url or url for the desired element. * @return resource result set from the database query */ public function getUrlStats($data) { - $data['select'] = '`id`, '.$this->db->unixTimestamp(`timestamp`).' as `timestamp`, `pmu`, `wt`, `cpu`'; + $data['select'] = + $this->db->quote("id") . ", " . + $this->db->unixTimestamp($this->db->quote("timestamp")) . " as " . $this->db->quote("timestamp") . ", " . + $this->db->quote("pmu") . ", " . + $this->db->quote("wt") . ", " . + $this->db->quote("cpu"); + $rs = $this->getRuns($data); return $rs; } - + /** * Get comparative information for a given URL and c_url, this information will be used to display stats like how many calls a URL has, - * average, min, max execution time, etc. This information is pushed into the global namespace, which is horribly hacky. - * + * average, min, max execution time, etc. This information is pushed into the global namespace, which is horribly hacky. + * * @param string $url * @param string $c_url * @return array */ public function getRunComparativeData($url, $c_url) { - $url = $this->db->escape($url); - $c_url = $this->db->escape($c_url); - //Runs same URL - // count, avg/min/max for wt, cpu, pmu - $query = "SELECT count(`id`), avg(`wt`), min(`wt`), max(`wt`), avg(`cpu`), min(`cpu`), max(`cpu`), avg(`pmu`), min(`pmu`), max(`pmu`) FROM `details` WHERE `url` = '$url'"; - $rs = $this->db->query($query); - $row = $this->db->getNextAssoc($rs); - $row['url'] = $url; - - $row['95(`wt`)'] = $this->calculatePercentile(array('count' => $row['count(`id`)'], 'column' => 'wt', 'type' => 'url', 'url' => $url)); - $row['95(`cpu`)'] = $this->calculatePercentile(array('count' => $row['count(`id`)'], 'column' => 'cpu', 'type' => 'url', 'url' => $url)); - $row['95(`pmu`)'] = $this->calculatePercentile(array('count' => $row['count(`id`)'], 'column' => 'pmu', 'type' => 'url', 'url' => $url)); - global $comparative; - $comparative['url'] = $row; - unset($row); - - //Runs same c_url - // count, avg/min/max for wt, cpu, pmu - $query = "SELECT count(`id`), avg(`wt`), min(`wt`), max(`wt`), avg(`cpu`), min(`cpu`), max(`cpu`), avg(`pmu`), min(`pmu`), max(`pmu`) FROM `details` WHERE `c_url` = '$c_url'"; - $rs = $this->db->query($query); - $row = $this->db->getNextAssoc($rs); - $row['url'] = $c_url; - $row['95(`wt`)'] = $this->calculatePercentile(array('count' => $row['count(`id`)'], 'column' => 'wt', 'type' => 'c_url', 'url' => $c_url)); - $row['95(`cpu`)'] = $this->calculatePercentile(array('count' => $row['count(`id`)'], 'column' => 'cpu', 'type' => 'c_url', 'url' => $c_url)); - $row['95(`pmu`)'] = $this->calculatePercentile(array('count' => $row['count(`id`)'], 'column' => 'pmu', 'type' => 'c_url', 'url' => $c_url)); - $comparative['c_url'] = $row; - unset($row); + $queryTemplate = + "SELECT " . + "count(" . $this->db->quote("id") . ") AS " . $this->db->quote("count") . ", " . + "avg(" . $this->db->quote("wt") . ") AS " . $this->db->quote("wt_avg") . ", " . + "min(" . $this->db->quote("wt") . ") AS " . $this->db->quote("wt_min") . ", " . + "max(" . $this->db->quote("wt") . ") AS " . $this->db->quote("wt_max") . ", " . + "avg(" . $this->db->quote("cpu") . ") AS " . $this->db->quote("cpu_avg") . ", " . + "min(" . $this->db->quote("cpu") . ") AS " . $this->db->quote("cpu_min") . ", " . + "max(" . $this->db->quote("cpu") . ") AS " . $this->db->quote("cpu_max") . ", " . + "avg(" . $this->db->quote("pmu") . ") AS " . $this->db->quote("pmu_avg") . ", " . + "min(" . $this->db->quote("pmu") . ") AS " . $this->db->quote("pmu_min") . ", " . + "max(" . $this->db->quote("pmu") . ") AS " . $this->db->quote("pmu_max") . " " . + "FROM " . $this->db->quote("details") . " WHERE "; + + $fields = array('url' => $url, 'c_url' => $c_url); + foreach ($fields as $urlType => $urlVal) + { + $urlVal = $this->db->escape($urlVal); + $rs = $this->db->query($queryTemplate . $this->db->quote($urlType) . " = '$urlVal'"); + $row = $this->db->getNextAssoc($rs); + $row['url'] = $urlVal; + + $row['wt_95'] = $this->calculatePercentile(array('count' => $row['count'], 'column' => 'wt', 'type' => $urlType, 'url' => $url)); + $row['cpu_95'] = $this->calculatePercentile(array('count' => $row['count'], 'column' => 'cpu', 'type' => $urlType, 'url' => $url)); + $row['pmu_95'] = $this->calculatePercentile(array('count' => $row['count'], 'column' => 'pmu', 'type' => $urlType, 'url' => $url)); + + $comparative[$urlType] = $row; + unset($row); + } + return $comparative; } - + protected function calculatePercentile($details) { - $limit = (int) ($details['count'] / 20); - $query = "SELECT `{$details['column']}` as `value` FROM `details` WHERE `{$details['type']}` = '{$details['url']}' ORDER BY `{$details['column']}` DESC LIMIT $limit, 1"; - $rs = $this->db->query($query); - $row = $this->db->getNextAssoc($rs); - return $row['value']; + // @TODO this query is not portable due to its use of LIMIT and OFFSET, i.e. MS SQL should use TOP instead which + // also goes in straight after the SELECT. + $offset = (int) ($details['count'] / 20); + $query = + "SELECT " . $this->db->quote($details['column']) . " as " . $this->db->quote("value") . " " . + "FROM " . $this->db->quote("details") . " " . + "WHERE " . $this->db->quote($details['type']) . " = '{$details['url']}' " . + "ORDER BY " . $this->db->quote($details['column']) . " DESC " . + "LIMIT 1 OFFSET $offset"; + $rs = $this->db->query($query); + $row = $this->db->getNextAssoc($rs); + return $row['value']; } - + /** - * Save the run in the database. - * + * Save the run in the database. + * * @param string $xhprof_data * @param mixed $type * @param string $run_id * @param mixed $xhprof_details * @return string */ - public function save_run($xhprof_data, $type, $run_id = null, $xhprof_details = null) + public function save_run($xhprof_data, $type, $run_id = null, $xhprof_details = null) { global $_xhprof; - $sql = array(); if ($run_id === null) { $run_id = $this->gen_run_id($type); } - + /* Session data is ommitted purposefully, mostly because it's not likely that the data that resides in $_SESSION at this point is the same as the data that the application started off with (for most apps, it's likely that session data is manipulated on most pageloads). - + The goal of storing get, post and cookie is to help explain why an application chose - a particular code execution path, pehaps it was a poorly filled out form, or a cookie that + a particular code execution path, perhaps it was a poorly filled out form, or a cookie that overwrote some default parameters. So having them helps. Most applications don't push data - back into those super globals, so we're safe(ish) storing them now. - + back into those super globals, so we're safe(ish) storing them now. + We can't just clone the session data in header.php to be sneaky either, starting the session is an application decision, and we don't want to go starting sessions where none are needed (not good performance wise). We could be extra sneaky and do something like: @@ -392,79 +379,80 @@ public function save_run($xhprof_data, $type, $run_id = null, $xhprof_details = { session_start(); $_xhprof['session_data'] = $_SESSION; - } + } but starting session support really feels like an application level decision, not one that - a supposedly unobtrusive profiler makes for you. - + a supposedly unobtrusive profiler makes for you. + */ - if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer'] == 'php')) { - $sql['get'] = $this->db->escape(serialize($_GET)); - $sql['cookie'] = $this->db->escape(serialize($_COOKIE)); - - //This code has not been tested - if (isset($_xhprof['savepost']) && $_xhprof['savepost']) - { - $sql['post'] = $this->db->escape(serialize($_POST)); - } else { - $sql['post'] = $this->db->escape(serialize(array("Skipped" => "Post data omitted by rule"))); - } - } else { - $sql['get'] = $this->db->escape(json_encode($_GET)); - $sql['cookie'] = $this->db->escape(json_encode($_COOKIE)); - - //This code has not been tested - if (isset($_xhprof['savepost']) && $_xhprof['savepost']) - { - $sql['post'] = $this->db->escape(json_encode($_POST)); - } else { - $sql['post'] = $this->db->escape(json_encode(array("Skipped" => "Post data omitted by rule"))); - } - } - - - $sql['pmu'] = isset($xhprof_data['main()']['pmu']) ? $xhprof_data['main()']['pmu'] : ''; - $sql['wt'] = isset($xhprof_data['main()']['wt']) ? $xhprof_data['main()']['wt'] : ''; - $sql['cpu'] = isset($xhprof_data['main()']['cpu']) ? $xhprof_data['main()']['cpu'] : ''; - - - // The value of 2 seems to be light enugh that we're not killing the server, but still gives us lots of breathing room on - // full production code. - if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer'] == 'php')) { - $sql['data'] = $this->db->escape(gzcompress(serialize($xhprof_data), 2)); - } else { - $sql['data'] = $this->db->escape(gzcompress(json_encode($xhprof_data), 2)); - } - - - $url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF']; - $sname = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''; - - $sql['url'] = $this->db->escape($url); - $sql['c_url'] = $this->db->escape(_urlSimilartor($_SERVER['REQUEST_URI'])); - $sql['servername'] = $this->db->escape($sname); - $sql['type'] = (int) (isset($xhprof_details['type']) ? $xhprof_details['type'] : 0); - $sql['timestamp'] = $this->db->escape($_SERVER['REQUEST_TIME']); - $sql['server_id'] = $this->db->escape($_xhprof['servername']); - $sql['aggregateCalls_include'] = getenv('xhprof_aggregateCalls_include') ? getenv('xhprof_aggregateCalls_include') : ''; - - $query = "INSERT INTO `details` (`id`, `url`, `c_url`, `timestamp`, `server name`, `perfdata`, `type`, `cookie`, `post`, `get`, `pmu`, `wt`, `cpu`, `server_id`, `aggregateCalls_include`) VALUES('$run_id', '{$sql['url']}', '{$sql['c_url']}', FROM_UNIXTIME('{$sql['timestamp']}'), '{$sql['servername']}', '{$sql['data']}', '{$sql['type']}', '{$sql['cookie']}', '{$sql['post']}', '{$sql['get']}', '{$sql['pmu']}', '{$sql['wt']}', '{$sql['cpu']}', '{$sql['server_id']}', '{$sql['aggregateCalls_include']}')"; - - $this->db->query($query); - if ($this->db->affectedRows($this->db->linkID) == 1) + $postData = (isset($_xhprof['savepost']) && $_xhprof['savepost']) ? $_POST : array("Skipped" => "Post data omitted by rule"); + $sql = array( + 'get' => $this->serialize($_GET), + 'cookie' => $this->serialize($_COOKIE), + 'post' => $this->serialize($postData), + 'data' => $this->serialize($xhprof_data, true) + ); + + $q = "'"; + $insertData = array( + "id" => $q . $this->db->escape($run_id) . $q, + "url" => $q . $this->db->escape(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['PHP_SELF']) . $q, + "c_url" => $q . $this->db->escape(_urlSimilartor($_SERVER['REQUEST_URI'])) . $q, + "timestamp" => $this->db->fromUnixTimestamp($this->db->escape($_SERVER['REQUEST_TIME'])), + "server name" => $q . $this->db->escape(isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '') . $q, + "perfdata" => $q . $sql['data'] . $q, + "type" => (int)(isset($xhprof_details['type']) ? $xhprof_details['type'] : 0), + "cookie" => $q . $sql['cookie'] . $q, + "post" => $q . $sql['post'] . $q, + "get" => $q . $sql['get'] . $q, + "pmu" => $q . (isset($xhprof_data['main()']['pmu']) ? $xhprof_data['main()']['pmu'] : '') . $q, + "wt" => $q . (isset($xhprof_data['main()']['wt']) ? $xhprof_data['main()']['wt'] : '') . $q, + "cpu" => $q . (isset($xhprof_data['main()']['cpu']) ? $xhprof_data['main()']['cpu'] : '') . $q, + "server_id" => $q . $this->db->escape($_xhprof['servername']) . $q, + "aggregateCalls_include" => $q . (getenv('xhprof_aggregateCalls_include') ? getenv('xhprof_aggregateCalls_include') : '') . $q + ); + $quotedFields = implode(", " , array_map(array($this->db, 'quote'), array_keys($insertData))); + + $query = "INSERT INTO " . $this->db->quote("details") . "($quotedFields) VALUES (" . implode(", ", $insertData) . ')'; + $resultSet = $this->db->query($query); + if ($this->db->affectedRows($resultSet) == 1) { return $run_id; - }else + } + + global $_xhprof; + if ($_xhprof['display'] === true) { - global $_xhprof; - if ($_xhprof['display'] === true) - { - echo "Failed to insert: $query
\n"; - } - return -1; + echo "Failed to insert: $query
\n"; + } + return -1; + } + + protected function serialize($data, $compress = false) + { + $serializer = 'json_encode'; + if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer'] == 'php')) { + $serializer = 'serialize'; + } + + // The value of 2 seems to be light enough if compressing that we're not killing the server, but still gives us + // lots of breathing room on full production code. + return $this->db->escapeBinary( + ($compress ? gzcompress($serializer($data), 2) : $serializer($data)) + ); + } + + protected function unserialize($data, $is_compressed = false) + { + if (!isset($GLOBALS['_xhprof']['serializer']) || strtolower($GLOBALS['_xhprof']['serializer'] == 'php')) { + return unserialize( + ($is_compressed ? gzuncompress($this->db->unescapeBinary($data)) : $this->db->unescapeBinary($data)) + ); } + return json_decode( + ($is_compressed ? gzuncompress($this->db->unescapeBinary($data)) : $this->db->unescapeBinary($data)), + true + ); } - - }
StatExact URLSimilar URLs
Count
Min Wall Time
Max Wall Time
Avg Wall Time
95% Wall Time
Count
Min Wall Time
Max Wall Time
Avg Wall Time
95% Wall Time
 
Min CPU Ticks
Max CPU Ticks
Avg CPU Ticks
95% CPU Ticks
Min CPU Ticks
Max CPU Ticks
Avg CPU Ticks
95% CPU Ticks
 
Min Peak Memory Usage
Max Peak Memory Usage
Avg Peak Memory Usage
95% Peak Memory Usage
Min Peak Memory Usage
Max Peak Memory Usage
Avg Peak Memory Usage
95% Peak Memory Usage
 
Number of Function Calls: