Created
March 8, 2011 16:24
-
-
Save julp/860485 to your computer and use it in GitHub Desktop.
(v2) Profiling MySQL/PDO queries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Julp; | |
if (isset($_SERVER['PHP_ENV']) && $_SERVER['PHP_ENV'] == 'development' && function_exists('ob_tidyhandler')) { | |
/* | |
tidy.default_config is: | |
indent: true | |
indent-spaces: 4 | |
output-xhtml: yes | |
wrap: 0 | |
tidy-mark: no | |
input-encoding: utf8 | |
output-bom: no | |
output-encoding: utf8 | |
*/ | |
ob_start('ob_tidyhandler'); | |
} | |
?> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | |
<title>XXX</title> | |
<script type="text/javascript" src="http://code.jquery.com/jquery-1.5.min.js"></script> | |
<script type="text/javascript"> | |
<!-- | |
function id2num(id) { | |
return id.substring(1); | |
} | |
$( | |
function () { | |
$('.query_execution').hide(); | |
$('.query').click( | |
function () { | |
var qe_id = '#QE' + id2num($(this).attr('id')); | |
$('.query_execution').not(qe_id).hide(); | |
$(qe_id).toggle(); | |
} | |
); | |
} | |
); | |
--> | |
</script> | |
<style type="text/css"> | |
<!-- | |
#profiling table { | |
border-collapse: separate; | |
border: #3399CC 1px solid; | |
margin: auto; | |
width: 100%; | |
} | |
#profiling table table { | |
width: 80%; | |
margin: 16px auto; | |
} | |
#profiling th { | |
background-color: #FFCC66; | |
border: #FFCC66 1px solid; | |
font-variant: small-caps; | |
font-size: 0.8em; | |
letter-spacing: 1px; | |
} | |
#profiling td { | |
border: #DDEEFF 1px solid; | |
} | |
--> | |
</style> | |
</head> | |
<body> | |
<?php | |
//use \PDO; | |
use \Exception; | |
require_once('pdo_constantes.php'); | |
function h($text) { | |
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); | |
} | |
class Timer | |
{ | |
protected $_precision; | |
protected $_start_time; | |
protected $_stop_time; | |
public function __construct($precision = 5) { | |
$this->_precision = $precision; | |
} | |
public function start() { | |
$this->_start_time = microtime(TRUE); | |
return $this; | |
} | |
public function stop() { | |
$this->_stop_time = microtime(TRUE); | |
return $this; | |
} | |
public function getTime() { | |
return round($this->_stop_time - $this->_start_time, $this->_precision); | |
} | |
public function getPrecision() { | |
return $this->_precision; | |
} | |
} | |
class Query_Execution | |
{ | |
protected $_query; | |
protected $_id; | |
public function __construct(Query $query, $id) { | |
$this->_query = $query; | |
$this->_id = $id; | |
} | |
public function getQuery() { | |
return $this->_query; | |
} | |
public function getId() { | |
return $this->_id; | |
} | |
} | |
class Query implements \Countable, \IteratorAggregate | |
{ | |
protected $_id; | |
protected $_prepared; | |
protected $_query_string; | |
protected $_executions = array(); | |
public function __construct($query_string, $prepared = FALSE) { | |
$this->_id = md5($query_string); | |
$this->_query_string = $query_string; | |
$this->_prepared = $prepared; | |
} | |
public function addQueryExecution($id) { | |
$this->_executions[] = new Query_Execution($this, $id); | |
} | |
public function count() { | |
return count($this->_executions); | |
} | |
public function getIterator() { | |
return new \ArrayIterator($this->_executions); | |
} | |
public function getQueryString() { | |
return $this->_query_string; | |
} | |
public function getId() { | |
return $this->_id; | |
} | |
public function isPrepared() { | |
return $this->_prepared; | |
} | |
} | |
class PDO/*_MySQL_Profiler*/ extends \PDO implements \Countable, \IteratorAggregate | |
{ | |
protected $_counter = 0; | |
protected $_queries = array(); | |
public function __construct($dsn, $username = NULL, $password = NULL, $driver_options = array()) { | |
parent::__construct($dsn, $username, $password, $driver_options); | |
parent::setAttribute(self::ATTR_STATEMENT_CLASS, array(__NAMESPACE__ . '\PDOStatement', array($this))); | |
parent::exec('SET SESSION query_cache_type = OFF, PROFILING = 1'); | |
} | |
public function setAttribute($attribute, $value) { | |
/*if (defined('__NAMESPACE__') && $attribute == self::ATTR_STATEMENT_CLASS) { | |
if ($value[0][0] != '\\') { | |
$value[0] = __NAMESPACE__ . '\\' . $value[0]; | |
} | |
}*/ | |
if ($attribute == self::ATTR_STATEMENT_CLASS) { | |
throw new LogicException('PDO::ATTR_STATEMENT_CLASS overwrite forbidden: a specific class is already in place.'); | |
} | |
return parent::setAttribute($attribute, $value); | |
} | |
public function _query($statement, $prepared = FALSE) { | |
if (!isset($this->_queries[$statement])) { | |
$this->_queries[$statement] = new Query($statement, $prepared); | |
} else { | |
if ($prepared) { | |
trigger_error('Prepared statement created more than once: ' . h($statement), E_USER_WARNING); | |
} | |
} | |
return $this->_queries[$statement]; | |
} | |
public function _register_query_execution($statement) { | |
$this->_query($statement)->addQueryExecution(++$this->_counter); | |
} | |
public function exec($statement) { | |
$affected = parent::exec($statement); | |
$this->_register_query_execution($statement); | |
return $affected; | |
} | |
public function query($statement) { | |
$args = func_get_args(); | |
if (func_num_args() >= 3) { | |
if ($args[1] == self::FETCH_CLASS) { | |
if ($args[2][0] != '\\') { | |
$args[2] = __NAMESPACE__ . '\\' . $args[2]; | |
} | |
} | |
} | |
$stmt = call_user_func_array(array(get_parent_class(), __FUNCTION__), $args); | |
$this->_register_query_execution($statement); | |
return $stmt; | |
} | |
public function prepare($statement, $driver_options = array()) { | |
$this->_query($statement, TRUE); | |
return parent::prepare($statement, $driver_options); | |
} | |
public function count() { | |
return $this->_counter; | |
} | |
public function getIterator() { | |
return new \ArrayIterator($this->_queries); | |
} | |
public function __destruct() { | |
parent::exec('SET PROFILING = 0'); | |
$prof = parent::query('SHOW PROFILES', self::FETCH_OBJ); | |
if ($prof->rowCount()) { | |
echo '<div id="profiling">'; | |
echo '<table>'; | |
echo '<thead>'; | |
echo '<th>Query</th>'; | |
echo '<th>Prepared ?</th>'; | |
echo '<th>Executed</th>'; | |
echo '<th>Total Duration</th>'; | |
echo '<th>Average Duration</th>'; | |
echo '</thead>'; | |
echo '<tbody>'; | |
$queries = $prof->fetchAll(PDO::FETCH_UNIQUE); | |
foreach ($this->_queries as $q) { | |
echo '<tr class="query" id="Q', $q->getId(), '">'; | |
$duration = 0; | |
foreach ($q as $qe) { | |
$duration += $queries[$qe->getId()]->Duration; | |
} | |
echo '<td>', h($q->getQueryString()), '</td>'; | |
echo '<td>', $q->isPrepared() ? 'Yes' : 'No', '</td>'; | |
echo '<td>', count($q), ' time(s)</td>'; | |
echo '<td>', number_format($duration, 8), '</td>'; | |
echo '<td>', number_format($duration / count($q), 8), '</td>'; | |
echo '</tr>'; | |
echo '<tr class="query_execution" id="QE', $q->getId(), '">'; | |
echo '<td colspan="99">'; | |
echo '<table>'; | |
echo '<thead>'; | |
echo '<th>ID</th>'; | |
echo '<th>Real final query</th>'; | |
echo '<th>Duration</th>'; | |
echo '</thead>'; | |
echo '<tbody>'; | |
foreach ($q as $qe) { | |
$qp = $queries[$qe->getId()]; | |
echo '<tr>'; | |
echo '<td>', $qe->getId(), '</td>'; | |
echo '<td>', h($qp->Query), '</td>'; | |
echo '<td>', number_format($qp->Duration, 8), '</td>'; | |
echo '</tr>'; | |
} | |
echo '</tbody>'; | |
echo '</table>'; | |
echo '</td>'; | |
echo '</tr>'; | |
} | |
echo '</tbody>'; | |
echo '</table>'; | |
echo '</div>'; | |
} | |
} | |
} | |
class PDOStatement/*_MySQL_Profiler*/ extends \PDOStatement | |
{ | |
protected $_pdo; | |
protected function __construct(PDO $pdo) { | |
$this->_pdo = $pdo; | |
} | |
public function execute($input_parameters = NULL) { | |
$bool = parent::execute($input_parameters); | |
$this->_pdo->_register_query_execution($this->queryString); | |
return $bool; | |
} | |
public function fetchObject($class_name = '\stdClass', $ctor_args = array()) { | |
if ($class_name[0] != '\\') { | |
$class_name = __NAMESPACE__ . '\\' . $class_name; | |
} | |
return parent::fetchObject($class_name, $ctor_args); | |
} | |
public function setFetchMode($mode, $class_name = NULL) { | |
if ($mode == \PDO::FETCH_CLASS) { | |
$ctor_args = func_num_args() >= 3 ? func_get_arg(2) : array(); | |
if ($class_name[0] != '\\') { | |
$class_name = __NAMESPACE__ . '\\' . $class_name; | |
} | |
return parent::setFetchMode($mode, $class_name, $ctor_args); | |
} else { | |
return call_user_func_array(array(get_parent_class(), __FUNCTION__), func_get_args()); | |
} | |
} | |
} | |
/* TESTING */ | |
//function var_dump() {} | |
try { | |
$dbh = new PDO(DSN, LOGIN, MOT_DE_PASSE); | |
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); | |
/*$dbh->exec('SET SESSION query_cache_type = OFF'); | |
$dbh->exec('SET PROFILING = 1');*/ | |
$utils = $dbh->prepare('SELECT COUNT(*) FROM utilisateurs WHERE id = :id'); | |
$utils->bindParam('id', $id, PDO::PARAM_INT); | |
$id = 3; | |
$utils->execute(); | |
$utils->fetch(); | |
$dbh->exec('UPDATE utilisateurs SET verrouille = 1 WHERE login = "toto"'); | |
$id = 6; | |
$utils->execute(); | |
$utils->fetch(); | |
$dbh->exec('UPDATE utilisateurs SET verrouille = 1 WHERE login = "toto"'); | |
$id = 1; | |
$utils->execute(); | |
$utils->fetch(); | |
$utils = $dbh->prepare('SELECT COUNT(*) FROM utilisateurs WHERE id = :id'); | |
$utils->execute(array('id' => '42')); | |
} catch (Exception $e) { | |
die(sprintf("%s dans %s à la ligne %d : %s", get_class($e), $e->getFile(), $e->getLine(), $e->getMessage())); | |
} | |
?> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment