Implement extension registration from an extension.json file
[mediawiki.git] / includes / profiler / TransactionProfiler.php
blob886bc5a0b4bb420f3d5facd2f22638ef3a28e746
1 <?php
2 /**
3 * Transaction profiling for contention
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
20 * @file
21 * @ingroup Profiler
22 * @author Aaron Schulz
25 /**
26 * Helper class that detects high-contention DB queries via profiling calls
28 * This class is meant to work with a DatabaseBase object, which manages queries
30 * @since 1.24
32 class TransactionProfiler {
33 /** @var float Seconds */
34 protected $dbLockThreshold = 3.0;
35 /** @var float Seconds */
36 protected $eventThreshold = .25;
38 /** @var array transaction ID => (write start time, list of DBs involved) */
39 protected $dbTrxHoldingLocks = array();
40 /** @var array transaction ID => list of (query name, start time, end time) */
41 protected $dbTrxMethodTimes = array();
43 /**
44 * Mark a DB as in a transaction with one or more writes pending
46 * Note that there can be multiple connections to a single DB.
48 * @param string $server DB server
49 * @param string $db DB name
50 * @param string $id ID string of transaction
52 public function transactionWritingIn( $server, $db, $id ) {
53 $name = "{$server} ({$db}) (TRX#$id)";
54 if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
55 wfDebugLog( 'DBPerformance', "Nested transaction for '$name' - out of sync." );
57 $this->dbTrxHoldingLocks[$name] = array(
58 'start' => microtime( true ),
59 'conns' => array(), // all connections involved
61 $this->dbTrxMethodTimes[$name] = array();
63 foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
64 // Track all DBs in transactions for this transaction
65 $info['conns'][$name] = 1;
69 /**
70 * Register the name and time of a method for slow DB trx detection
72 * This assumes that all queries are synchronous (non-overlapping)
74 * @param string $query Function name
75 * @param float $sTime Starting UNIX wall time
76 * @param bool $isWrite Whether this is a write query
78 public function recordQueryCompletion( $query, $sTime, $isWrite = false ) {
79 $eTime = microtime( true );
80 $elapsed = ( $eTime - $sTime );
82 if ( !$this->dbTrxHoldingLocks ) {
83 // Short-circuit
84 return;
85 } elseif ( !$isWrite && $elapsed < $this->eventThreshold ) {
86 // Not an important query nor slow enough
87 return;
90 foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
91 $lastQuery = end( $this->dbTrxMethodTimes[$name] );
92 if ( $lastQuery ) {
93 // Additional query in the trx...
94 $lastEnd = $lastQuery[2];
95 if ( $sTime >= $lastEnd ) { // sanity check
96 if ( ( $sTime - $lastEnd ) > $this->eventThreshold ) {
97 // Add an entry representing the time spent doing non-queries
98 $this->dbTrxMethodTimes[$name][] = array( '...delay...', $lastEnd, $sTime );
100 $this->dbTrxMethodTimes[$name][] = array( $query, $sTime, $eTime );
102 } else {
103 // First query in the trx...
104 if ( $sTime >= $info['start'] ) { // sanity check
105 $this->dbTrxMethodTimes[$name][] = array( $query, $sTime, $eTime );
112 * Mark a DB as no longer in a transaction
114 * This will check if locks are possibly held for longer than
115 * needed and log any affected transactions to a special DB log.
116 * Note that there can be multiple connections to a single DB.
118 * @param string $server DB server
119 * @param string $db DB name
120 * @param string $id ID string of transaction
122 public function transactionWritingOut( $server, $db, $id ) {
123 $name = "{$server} ({$db}) (TRX#$id)";
124 if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
125 wfDebugLog( 'DBPerformance', "Detected no transaction for '$name' - out of sync." );
126 return;
128 // Fill in the last non-query period...
129 $lastQuery = end( $this->dbTrxMethodTimes[$name] );
130 if ( $lastQuery ) {
131 $now = microtime( true );
132 $lastEnd = $lastQuery[2];
133 if ( ( $now - $lastEnd ) > $this->eventThreshold ) {
134 $this->dbTrxMethodTimes[$name][] = array( '...delay...', $lastEnd, $now );
137 // Check for any slow queries or non-query periods...
138 $slow = false;
139 foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
140 $elapsed = ( $info[2] - $info[1] );
141 if ( $elapsed >= $this->dbLockThreshold ) {
142 $slow = true;
143 break;
146 if ( $slow ) {
147 $dbs = implode( ', ', array_keys( $this->dbTrxHoldingLocks[$name]['conns'] ) );
148 $msg = "Sub-optimal transaction on DB(s) [{$dbs}]:\n";
149 foreach ( $this->dbTrxMethodTimes[$name] as $i => $info ) {
150 list( $query, $sTime, $end ) = $info;
151 $msg .= sprintf( "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), $query );
153 wfDebugLog( 'DBPerformance', $msg );
155 unset( $this->dbTrxHoldingLocks[$name] );
156 unset( $this->dbTrxMethodTimes[$name] );