MediaWiki  1.34.0
XhprofData.php
Go to the documentation of this file.
1 <?php
21 use Wikimedia\RunningStat;
22 
31 class XhprofData {
32 
36  protected $config;
37 
42  protected $hieraData;
43 
48  protected $inclusive;
49 
54  protected $complete;
55 
64  public function __construct( array $data, array $config = [] ) {
65  $this->config = array_merge( [
66  'include' => null,
67  'sort' => 'wt',
68  ], $config );
69 
70  $this->hieraData = $this->pruneData( $data );
71  }
72 
99  public function getRawData() {
100  return $this->hieraData;
101  }
102 
113  public static function splitKey( $key ) {
114  return array_pad( explode( '==>', $key, 2 ), -2, null );
115  }
116 
124  protected function pruneData( $data ) {
125  if ( !$this->config['include'] ) {
126  return $data;
127  }
128 
129  $want = array_fill_keys( $this->config['include'], true );
130  $want['main()'] = true;
131 
132  $keep = [];
133  foreach ( $data as $key => $stats ) {
134  list( $parent, $child ) = self::splitKey( $key );
135  if ( isset( $want[$parent] ) || isset( $want[$child] ) ) {
136  $keep[$key] = $stats;
137  }
138  }
139  return $keep;
140  }
141 
160  public function getInclusiveMetrics() {
161  if ( $this->inclusive === null ) {
162  $main = $this->hieraData['main()'];
163  $hasCpu = isset( $main['cpu'] );
164  $hasMu = isset( $main['mu'] );
165  $hasAlloc = isset( $main['alloc'] );
166 
167  $this->inclusive = [];
168  foreach ( $this->hieraData as $key => $stats ) {
169  list( $parent, $child ) = self::splitKey( $key );
170  if ( !isset( $this->inclusive[$child] ) ) {
171  $this->inclusive[$child] = [
172  'ct' => 0,
173  'wt' => new RunningStat(),
174  ];
175  if ( $hasCpu ) {
176  $this->inclusive[$child]['cpu'] = new RunningStat();
177  }
178  if ( $hasMu ) {
179  $this->inclusive[$child]['mu'] = new RunningStat();
180  $this->inclusive[$child]['pmu'] = new RunningStat();
181  }
182  if ( $hasAlloc ) {
183  $this->inclusive[$child]['alloc'] = new RunningStat();
184  $this->inclusive[$child]['free'] = new RunningStat();
185  }
186  }
187 
188  $this->inclusive[$child]['ct'] += $stats['ct'];
189  foreach ( $stats as $stat => $value ) {
190  if ( $stat === 'ct' ) {
191  continue;
192  }
193 
194  if ( !isset( $this->inclusive[$child][$stat] ) ) {
195  // Ignore unknown stats
196  continue;
197  }
198 
199  for ( $i = 0; $i < $stats['ct']; $i++ ) {
200  $this->inclusive[$child][$stat]->addObservation(
201  $value / $stats['ct']
202  );
203  }
204  }
205  }
206 
207  // Convert RunningStat instances to static arrays and add
208  // percentage stats.
209  foreach ( $this->inclusive as $func => $stats ) {
210  foreach ( $stats as $name => $value ) {
211  if ( $value instanceof RunningStat ) {
212  $total = $value->getMean() * $value->getCount();
213  $percent = ( isset( $main[$name] ) && $main[$name] )
214  ? 100 * $total / $main[$name]
215  : 0;
216  $this->inclusive[$func][$name] = [
217  'total' => $total,
218  'min' => $value->min,
219  'mean' => $value->getMean(),
220  'max' => $value->max,
221  'variance' => $value->m2,
222  'percent' => $percent,
223  ];
224  }
225  }
226  }
227 
228  uasort( $this->inclusive, self::makeSortFunction(
229  $this->config['sort'], 'total'
230  ) );
231  }
232  return $this->inclusive;
233  }
234 
246  public function getCompleteMetrics() {
247  if ( $this->complete === null ) {
248  // Start with inclusive data
249  $this->complete = $this->getInclusiveMetrics();
250 
251  foreach ( $this->complete as $func => $stats ) {
252  foreach ( $stats as $stat => $value ) {
253  if ( $stat === 'ct' ) {
254  continue;
255  }
256  // Initialize exclusive data with inclusive totals
257  $this->complete[$func][$stat]['exclusive'] = $value['total'];
258  }
259  // Add sapce for call tree information to be filled in later
260  $this->complete[$func]['calls'] = [];
261  $this->complete[$func]['subcalls'] = [];
262  }
263 
264  foreach ( $this->hieraData as $key => $stats ) {
265  list( $parent, $child ) = self::splitKey( $key );
266  if ( $parent !== null ) {
267  // Track call tree information
268  $this->complete[$child]['calls'][$parent] = $stats;
269  $this->complete[$parent]['subcalls'][$child] = $stats;
270  }
271 
272  if ( isset( $this->complete[$parent] ) ) {
273  // Deduct child inclusive data from exclusive data
274  foreach ( $stats as $stat => $value ) {
275  if ( $stat === 'ct' ) {
276  continue;
277  }
278 
279  if ( !isset( $this->complete[$parent][$stat] ) ) {
280  // Ignore unknown stats
281  continue;
282  }
283 
284  $this->complete[$parent][$stat]['exclusive'] -= $value;
285  }
286  }
287  }
288 
289  uasort( $this->complete, self::makeSortFunction(
290  $this->config['sort'], 'exclusive'
291  ) );
292  }
293  return $this->complete;
294  }
295 
303  public function getCallers( $function ) {
304  $edges = $this->getCompleteMetrics();
305  if ( isset( $edges[$function]['calls'] ) ) {
306  return array_keys( $edges[$function]['calls'] );
307  } else {
308  return [];
309  }
310  }
311 
319  public function getCallees( $function ) {
320  $edges = $this->getCompleteMetrics();
321  if ( isset( $edges[$function]['subcalls'] ) ) {
322  return array_keys( $edges[$function]['subcalls'] );
323  } else {
324  return [];
325  }
326  }
327 
334  public function getCriticalPath( $metric = 'wt' ) {
335  $func = 'main()';
336  $path = [
337  $func => $this->hieraData[$func],
338  ];
339  while ( $func ) {
340  $callees = $this->getCallees( $func );
341  $maxCallee = null;
342  $maxCall = null;
343  foreach ( $callees as $callee ) {
344  $call = "{$func}==>{$callee}";
345  if ( $maxCall === null ||
346  $this->hieraData[$call][$metric] >
347  $this->hieraData[$maxCall][$metric]
348  ) {
349  $maxCallee = $callee;
350  $maxCall = $call;
351  }
352  }
353  if ( $maxCall !== null ) {
354  $path[$maxCall] = $this->hieraData[$maxCall];
355  }
356  $func = $maxCallee;
357  }
358  return $path;
359  }
360 
369  public static function makeSortFunction( $key, $sub ) {
370  return function ( $a, $b ) use ( $key, $sub ) {
371  if ( isset( $a[$key] ) && isset( $b[$key] ) ) {
372  // Descending sort: larger values will be first in result.
373  // Values for 'main()' will not have sub keys
374  $valA = is_array( $a[$key] ) ? $a[$key][$sub] : $a[$key];
375  $valB = is_array( $b[$key] ) ? $b[$key][$sub] : $b[$key];
376  return $valB <=> $valA;
377  } else {
378  // Sort datum with the key before those without
379  return isset( $a[$key] ) ? -1 : 1;
380  }
381  };
382  }
383 }
XhprofData\getCriticalPath
getCriticalPath( $metric='wt')
Find the critical path for the given metric.
Definition: XhprofData.php:334
XhprofData\splitKey
static splitKey( $key)
Convert an xhprof data key into an array of ['parent', 'child'] function names.
Definition: XhprofData.php:113
XhprofData\$config
$config
Definition: XhprofData.php:36
XhprofData\getRawData
getRawData()
Get raw data collected by xhprof.
Definition: XhprofData.php:99
XhprofData\getInclusiveMetrics
getInclusiveMetrics()
Get the inclusive metrics for each function call.
Definition: XhprofData.php:160
XhprofData\pruneData
pruneData( $data)
Remove data for functions that are not included in the 'include' configuration array.
Definition: XhprofData.php:124
XhprofData\getCallees
getCallees( $function)
Get a list of all callees from a given function.
Definition: XhprofData.php:319
XhprofData\makeSortFunction
static makeSortFunction( $key, $sub)
Make a closure to use as a sort function.
Definition: XhprofData.php:369
XhprofData\getCallers
getCallers( $function)
Get a list of all callers of a given function.
Definition: XhprofData.php:303
XhprofData\getCompleteMetrics
getCompleteMetrics()
Get the inclusive and exclusive metrics for each function call.
Definition: XhprofData.php:246
XhprofData\$hieraData
$hieraData
Hierarchical profiling data returned by xhprof.
Definition: XhprofData.php:42
XhprofData\__construct
__construct(array $data, array $config=[])
Configuration data can contain:
Definition: XhprofData.php:64
XhprofData\$inclusive
$inclusive
Per-function inclusive data.
Definition: XhprofData.php:48
XhprofData
Convenience class for working with XHProf profiling data https://github.com/phacility/xhprof.
Definition: XhprofData.php:31
$path
$path
Definition: NoLocalSettings.php:25
XhprofData\$complete
$complete
Per-function inclusive and exclusive data.
Definition: XhprofData.php:54