MediaWiki  master
profileinfo.php
Go to the documentation of this file.
1 <?php
39 // This endpoint is supposed to be independent of request cookies and other
40 // details of the session. Enforce this constraint with respect to session use.
41 define( 'MW_NO_SESSION', 1 );
42 
43 ini_set( 'zlib.output_compression', 'off' );
44 
45 require __DIR__ . '/includes/WebStart.php';
46 
47 header( 'Content-Type: text/html; charset=utf-8' );
48 
49 ?>
50 <!DOCTYPE html>
51 <html>
52 <head>
53  <meta charset="UTF-8" />
54  <title>Profiling data</title>
55  <style>
56  /* noc.wikimedia.org/base.css */
57 
58  * {
59  margin: 0;
60  padding: 0;
61  }
62 
63  body {
64  padding: 0.5em 1em;
65  background: #fff;
66  font: 14px/1.6 sans-serif;
67  color: #333;
68  }
69 
70  p, ul, ol, table {
71  margin: 0.5em 0;
72  }
73 
74  a {
75  color: #0645AD;
76  text-decoration: none;
77  }
78 
79  a:hover {
80  text-decoration: underline;
81  }
82 
93  table {
94  max-width: 100%;
95  background-color: transparent;
96  border-collapse: collapse;
97  border-spacing: 0;
98  }
99 
100  .table {
101  width: 100%;
102  margin-bottom: 20px;
103  }
104 
105  .table th,
106  .table td {
107  padding: 0.1em;
108  text-align: left;
109  vertical-align: top;
110  border-top: 1px solid #ddd;
111  }
112 
113  .table th {
114  font-weight: bold;
115  }
116 
117  .table thead th {
118  vertical-align: bottom;
119  }
120 
121  .table thead:first-child tr:first-child th,
122  .table thead:first-child tr:first-child td {
123  border-top: 0;
124  }
125 
126  .table tbody + tbody {
127  border-top: 2px solid #ddd;
128  }
129 
130  .table-condensed th,
131  .table-condensed td {
132  padding: 4px 5px;
133  }
134 
135  .table-striped tbody tr:nth-child(odd) td,
136  .table-striped tbody tr:nth-child(odd) th {
137  background-color: #f9f9f9;
138  }
139 
140  .table-hover tbody tr:hover td,
141  .table-hover tbody tr:hover th {
142  background-color: #f5f5f5;
143  }
144 
145  hr {
146  margin: 20px 0;
147  border: 0;
148  border-top: 1px solid #eee;
149  border-bottom: 1px solid #fff;
150  }
151  </style>
152 </head>
153 <body>
154 <?php
155 
156 if ( !$wgEnableProfileInfo ) {
157  echo '<p>Disabled</p>'
158  . '</body></html>';
159  exit( 1 );
160 }
161 
163 
164 if ( !$dbr->tableExists( 'profiling' ) ) {
165  echo '<p>No <code>profiling</code> table exists, so we can\'t show you anything.</p>'
166  . '<p>If you want to log profiling data, enable <code>$wgProfiler[\'output\'] = \'db\'</code>'
167  . ' in LocalSettings.php and run <code>maintenance/update.php</code> to'
168  . ' create the profiling table.'
169  . '</body></html>';
170  exit( 1 );
171 }
172 
173 $expand = [];
174 if ( isset( $_REQUEST['expand'] ) ) {
175  foreach ( explode( ',', $_REQUEST['expand'] ) as $f ) {
176  $expand[$f] = true;
177  }
178 }
179 
180 // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
182 
183  public $name;
184  public $count;
185  public $time;
186  public $children;
187 
189 
190  public function __construct( $name, $count, $time, $memory ) {
191  $this->name = $name;
192  $this->count = $count;
193  $this->time = $time;
194  $this->memory = $memory;
195  $this->children = [];
196  }
197 
198  public function add_child( $child ) {
199  $this->children[] = $child;
200  }
201 
202  public function display( $expand, $indent = 0.0 ) {
203  usort( $this->children, 'compare_point' );
204 
205  $ex = isset( $expand[$this->name()] );
206 
207  $anchor = str_replace( '"', '', $this->name() );
208 
209  if ( !$ex ) {
210  if ( count( $this->children ) ) {
211  $url = getEscapedProfileUrl( false, false, $expand + [ $this->name() => true ] );
212  $extet = " <a id=\"{$anchor}\" href=\"{$url}#{$anchor}\">[+]</a>";
213  } else {
214  $extet = '';
215  }
216  } else {
217  $e = [];
218  foreach ( $expand as $name => $ep ) {
219  if ( $name != $this->name() ) {
220  $e += [ $name => $ep ];
221  }
222  }
223  $url = getEscapedProfileUrl( false, false, $e );
224  $extet = " <a id=\"{$anchor}\" href=\"{$url}#{$anchor}\">[–]</a>";
225  }
226  ?>
227  <tr>
228  <th>
229  <div style="margin-left: <?php echo (int)$indent; ?>em;">
230  <?php echo htmlspecialchars( str_replace( ',', ', ', $this->name() ) ) . $extet ?>
231  </div>
232  </th>
233  <?php // phpcs:disable Generic.Files.LineLength,Generic.PHP.NoSilencedErrors ?>
234  <td class="mw-profileinfo-timep"><?php echo @wfPercent( $this->time() / self::$totaltime * 100 ); ?></td>
235  <td class="mw-profileinfo-memoryp"><?php echo @wfPercent( $this->memory() / self::$totalmemory * 100 ); ?></td>
236  <td class="mw-profileinfo-count"><?php echo $this->count(); ?></td>
237  <td class="mw-profileinfo-cpr"><?php echo round( sprintf( '%.2f', $this->callsPerRequest() ), 2 ); ?></td>
238  <td class="mw-profileinfo-tpc"><?php echo round( sprintf( '%.2f', $this->timePerCall() ), 2 ); ?></td>
239  <td class="mw-profileinfo-mpc"><?php echo round( sprintf( '%.2f', $this->memoryPerCall() / 1024 ), 2 ); ?></td>
240  <td class="mw-profileinfo-tpr"><?php echo @round( sprintf( '%.2f', $this->time() / self::$totalcount ), 2 ); ?></td>
241  <td class="mw-profileinfo-mpr"><?php echo @round( sprintf( '%.2f', $this->memory() / self::$totalcount / 1024 ), 2 ); ?></td>
242  <?php // phpcs:enable ?>
243  </tr>
244  <?php
245  if ( $ex ) {
246  foreach ( $this->children as $child ) {
247  $child->display( $expand, $indent + 2 );
248  }
249  }
250  }
251 
252  public function name() {
253  return $this->name;
254  }
255 
256  public function count() {
257  return $this->count;
258  }
259 
260  public function time() {
261  return $this->time;
262  }
263 
264  public function memory() {
265  return $this->memory;
266  }
267 
268  public function timePerCall() {
269  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
270  return @( $this->time / $this->count );
271  }
272 
273  public function memoryPerCall() {
274  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
275  return @( $this->memory / $this->count );
276  }
277 
278  public function callsPerRequest() {
279  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
280  return @( $this->count / self::$totalcount );
281  }
282 
283  public function timePerRequest() {
284  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
285  return @( $this->time / self::$totalcount );
286  }
287 
288  public function memoryPerRequest() {
289  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
290  return @( $this->memory / self::$totalcount );
291  }
292 
293  public function fmttime() {
294  return sprintf( '%5.02f', $this->time );
295  }
296 }
297 
299  // phpcs:ignore MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
300  global $sort;
301 
302  switch ( $sort ) {
303  // Sorted ascending:
304  case 'name':
305  return strcmp( $a->name(), $b->name() );
306  // Sorted descending:
307  case 'time':
308  return $b->time() <=> $a->time();
309  case 'memory':
310  return $b->memory() <=> $a->memory();
311  case 'count':
312  return $b->count() <=> $a->count();
313  case 'time_per_call':
314  return $b->timePerCall() <=> $a->timePerCall();
315  case 'memory_per_call':
316  return $b->memoryPerCall() <=> $a->memoryPerCall();
317  case 'calls_per_req':
318  return $b->callsPerRequest() <=> $a->callsPerRequest();
319  case 'time_per_req':
320  return $b->timePerRequest() <=> $a->timePerRequest();
321  case 'memory_per_req':
322  return $b->memoryPerRequest() <=> $a->memoryPerRequest();
323  }
324 }
325 
326 $sorts = [ 'time', 'memory', 'count', 'calls_per_req', 'name',
327  'time_per_call', 'memory_per_call', 'time_per_req', 'memory_per_req' ];
328 $sort = 'time';
329 if ( isset( $_REQUEST['sort'] ) && in_array( $_REQUEST['sort'], $sorts ) ) {
330  $sort = $_REQUEST['sort'];
331 }
332 
333 $res = $dbr->select(
334  'profiling',
335  '*',
336  [],
337  'profileinfo.php',
338  [ 'ORDER BY' => 'pf_name ASC' ]
339 );
340 
341 $filter = $_REQUEST['filter'] ?? '';
342 
343 ?>
344 <form method="get" action="profileinfo.php">
345  <p>
346  <input type="text" name="filter" value="<?php echo htmlspecialchars( $filter ); ?>">
347  <input type="hidden" name="sort" value="<?php echo htmlspecialchars( $sort ); ?>">
348  <input type="hidden" name="expand" value="<?php
349  echo htmlspecialchars( implode( ",", array_keys( $expand ) ) );
350  ?>">
351  <input type="submit" value="Filter">
352  </p>
353 </form>
354 
355 <table class="mw-profileinfo-table table table-striped table-hover">
356  <thead>
357  <tr>
358  <th><a href="<?php
359  echo getEscapedProfileUrl( false, 'name' );
360  ?>">Name</a></th>
361  <th><a href="<?php
362  echo getEscapedProfileUrl( false, 'time' );
363  ?>">Time (%)</a></th>
364  <th><a href="<?php
365  echo getEscapedProfileUrl( false, 'memory' );
366  ?>">Memory (%)</a></th>
367  <th><a href="<?php
368  echo getEscapedProfileUrl( false, 'count' );
369  ?>">Count</a></th>
370  <th><a href="<?php
371  echo getEscapedProfileUrl( false, 'calls_per_req' );
372  ?>">Calls/req</a></th>
373  <th><a href="<?php
374  echo getEscapedProfileUrl( false, 'time_per_call' );
375  ?>">ms/call</a></th>
376  <th><a href="<?php
377  echo getEscapedProfileUrl( false, 'memory_per_call' );
378  ?>">kb/call</a></th>
379  <th><a href="<?php
380  echo getEscapedProfileUrl( false, 'time_per_req' );
381  ?>">ms/req</a></th>
382  <th><a href="<?php
383  echo getEscapedProfileUrl( false, 'memory_per_req' );
384  ?>">kb/req</a></th>
385  </tr>
386  </thead>
387  <tbody>
388  <?php
389  profile_point::$totaltime = 0.0;
392 
393  function getEscapedProfileUrl( $_filter = false, $_sort = false, $_expand = false ) {
394  // phpcs:ignore MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
395  global $filter, $sort, $expand;
396 
397  if ( $_expand === false ) {
398  $_expand = $expand;
399  }
400 
401  return htmlspecialchars(
402  '?' .
403  wfArrayToCgi( [
404  'filter' => $_filter ?: $filter,
405  'sort' => $_sort ?: $sort,
406  'expand' => implode( ',', array_keys( $_expand ) )
407  ] )
408  );
409  }
410 
411  $points = [];
412  $queries = [];
413  $sqltotal = 0.0;
414 
416  $last = false;
417  foreach ( $res as $o ) {
418  $next = new profile_point( $o->pf_name, $o->pf_count, $o->pf_time, $o->pf_memory );
419  if ( $next->name() == '-total' || $next->name() == 'main()' ) {
420  profile_point::$totaltime = $next->time();
421  profile_point::$totalcount = $next->count();
422  profile_point::$totalmemory = $next->memory();
423  }
424  if ( $last !== false ) {
425  if ( preg_match( '/^' . preg_quote( $last->name(), '/' ) . '/', $next->name() ) ) {
426  $last->add_child( $next );
427  continue;
428  }
429  }
430  $last = $next;
431  if ( preg_match( '/^query: /', $next->name() ) || preg_match( '/^query-m: /', $next->name() ) ) {
432  $sqltotal += $next->time();
433  $queries[] = $next;
434  } else {
435  $points[] = $next;
436  }
437  }
438 
439  $s = new profile_point( 'SQL Queries', 0, $sqltotal, 0 );
440  foreach ( $queries as $q ) {
441  $s->add_child( $q );
442  }
443  $points[] = $s;
444 
445  // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
446  @usort( $points, 'compare_point' );
447 
448  foreach ( $points as $point ) {
449  if ( strlen( $filter ) && !strstr( $point->name(), $filter ) ) {
450  continue;
451  }
452 
453  $point->display( $expand );
454  }
455  ?>
456  </tbody>
457 </table>
458 <hr />
459 <p>Total time: <code><?php printf( '%5.02f', profile_point::$totaltime ); ?></code></p>
460 
461 <p>Total memory: <code><?php printf( '%5.02f', profile_point::$totalmemory / 1024 ); ?></code></p>
462 <hr />
463 </body>
464 </html>
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of data
Definition: hooks.txt:6
__construct( $name, $count, $time, $memory)
null means default in associative array form
Definition: hooks.txt:1982
foreach( $res as $o) $s
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
width
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of or an object and a method hook function The function part of a third party developers and local administrators to define code that will be run at certain points in the mainline code
Definition: hooks.txt:23
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
$sort
add_child( $child)
title
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
getEscapedProfileUrl( $_filter=false, $_sort=false, $_expand=false)
wfPercent( $nr, $acc=2, $round=true)
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
$last
$points
if(! $dbr->tableExists( 'profiling')) $expand
$sorts
static $totalmemory
$sqltotal
$filter
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
f9f9f9
static $totalcount
in the sidebar</td >< td > font color
if(! $wgEnableProfileInfo) $dbr
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
static $totaltime
display( $expand, $indent=0.0)
Bar style
$f
Definition: router.php:79
compare_point(profile_point $a, profile_point $b)
const DB_REPLICA
Definition: defines.php:25
$queries
$wgEnableProfileInfo
Allow the profileinfo.php entrypoint to be used.
if(isset( $_REQUEST['sort']) &&in_array( $_REQUEST['sort'], $sorts)) $res