Skip to content

Instantly share code, notes, and snippets.

@julp
Created March 8, 2011 16:24
Show Gist options
  • Save julp/860485 to your computer and use it in GitHub Desktop.
Save julp/860485 to your computer and use it in GitHub Desktop.
(v2) Profiling MySQL/PDO queries
<?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