-
Notifications
You must be signed in to change notification settings - Fork 2
/
Tracer.php
137 lines (120 loc) · 3.13 KB
/
Tracer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?php
/**
* Galahad Query Tracer
* @author Chris Morrell <http://cmorrell.com/>
*/
class Galahad_Query_Tracer
{
/**
* Allows faux-singleton access
* @var Galahad_Query_Tracer
*/
protected static $_instance = null;
/**
* Stored Backtrace Data
* @var array
*/
protected $_data = array();
/**
* Whether data has been parsed yet
* @var bool
*/
protected $_parsed = false;
/**
* Faux-Singleton Accessor
* Use this method to get a consistent instance of the class
*
* @return Galahad_Query_Tracer
*/
public static function instance()
{
if (!self::$_instance) {
self::$_instance = new self;
}
return self::$_instance;
}
/**
* Constructor
* Adds Wordpress filters necessary
*/
public function __construct()
{
add_filter('query', array($this, 'traceFilter'));
}
/**
* Filters wpdb::query
* This filter stores all queries and their backtraces for later use
*
* @param string $query
* @return string
*/
public function traceFilter($query)
{
$trace = debug_backtrace();
array_splice($trace, 0, 3); // Get rid of the tracer's fingerprint (and wpdb::query)
$this->_data[] = array('query' => $query, 'backtrace' => $trace);
return $query;
}
/**
* Get and optionally parse the data
*
* @return array
*/
public function getData()
{
// Parse if necessary
if (!$this->_parsed) {
$pluginsPath = WP_CONTENT_DIR . '/plugins/'; // Have to do this due to symlink issue
$rawData = $this->_data;
$this->_data = array();
// Gather data about existing plugins
$rootData = array();
foreach (get_plugins() as $filename => $data) {
list($root) = explode('/', $filename, 2);
$rootData[$root] = array_change_key_case($data);
}
// Parse each query's backtrace
foreach ($rawData as $query) {
$functionChain = array();
foreach ($query['backtrace'] as $call) {
// Add to function chain
$functionChain[] = (isset($call['class']) ? "{$call['class']}::" : '') . $call['function'];
// We've got a plugin
if ( isset($call['file']) && false !== strpos($call['file'], $pluginsPath)) {
list($root) = explode('/', plugin_basename($call['file']), 2);
$file = str_replace($pluginsPath, '', $call['file']);
// Make sure the array is set up
if (!isset($this->_data[$root])) {
$this->_data[$root] = $rootData[$root];
$this->_data[$root]['backtrace'] = array();
}
// Make sure the backtrace for this file is set up
if (!isset($this->_data[$root]['backtrace'][$file])) {
$this->_data[$root]['backtrace'][$file] = array();
}
// Save parsed data
$this->_data[$root]['backtrace'][$file][] = array(
'line' => $call['line'],
'query' => $query['query'],
'function_chain' => array_reverse($functionChain),
);
}
}
}
$this->_parsed = true;
usort($this->_data, array($this, '_sortByName'));
}
return $this->_data;
}
/**
* Faux-private function for sorting data
*
* @param array $a
* @param array $b
* @return int
*/
public function _sortByName($a, $b)
{
return strcmp($a['name'], $b['name']);
}
}