24use Psr\Log\LoggerAwareInterface;
25use Psr\Log\LoggerInterface;
26use Psr\Log\NullLogger;
28use Wikimedia\ScopedCallback;
96 $this->dbTrxHoldingLocks = [];
97 $this->dbTrxMethodTimes = [];
99 $this->silenced =
false;
115 $this->silenced = $value;
126 return new ScopedCallback(
function () use ( $oldSilenced ) {
142 if ( !isset( $this->expect[$event] ) ) {
146 if ( $limit <= $this->expect[$event][self::FLD_LIMIT] ) {
148 $this->expect[$event] = [
149 self::FLD_LIMIT => $limit,
150 self::FLD_FNAME => $fname
167 foreach ( $expects as $event => $value ) {
214 "[connect to $server ($db)]",
223 "[connect to $server ($db)]",
224 $this->hits[
'masterConns']
239 $name =
"{$server} ({$db}) (TRX#$id)";
240 if ( isset( $this->dbTrxHoldingLocks[$name] ) ) {
241 $this->logger->warning(
"Nested transaction for '$name' - out of sync." );
243 $this->dbTrxHoldingLocks[$name] = [
244 'start' => microtime(
true ),
247 $this->dbTrxMethodTimes[$name] = [];
249 foreach ( $this->dbTrxHoldingLocks as $name => &$info ) {
251 $info[
'conns'][$name] = 1;
266 $eTime = microtime(
true );
267 $elapsed = ( $eTime - $sTime );
271 } elseif ( !$isWrite && $this->
isAboveThreshold( $rowCount,
'readQueryRows' ) ) {
290 if ( !$this->dbTrxHoldingLocks ) {
293 } elseif ( !$isWrite && $elapsed < self::EVENT_THRESHOLD_SEC ) {
298 foreach ( $this->dbTrxHoldingLocks as $name => $info ) {
299 $lastQuery = end( $this->dbTrxMethodTimes[$name] );
302 $lastEnd = $lastQuery[2];
303 if ( $sTime >= $lastEnd ) {
304 if ( ( $sTime - $lastEnd ) > self::EVENT_THRESHOLD_SEC ) {
306 $this->dbTrxMethodTimes[$name][] = [
'...delay...', $lastEnd, $sTime ];
308 $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
312 if ( $sTime >= $info[
'start'] ) {
313 $this->dbTrxMethodTimes[$name][] = [ $query, $sTime, $eTime ];
339 $name =
"{$server} ({$db}) (TRX#$id)";
340 if ( !isset( $this->dbTrxMethodTimes[$name] ) ) {
341 $this->logger->warning(
"Detected no transaction for '$name' - out of sync." );
351 "[transaction $id writes to {$server} ({$db})]",
360 "[transaction $id writes to {$server} ({$db})]",
365 $lastQuery = end( $this->dbTrxMethodTimes[$name] );
367 $now = microtime(
true );
368 $lastEnd = $lastQuery[2];
369 if ( ( $now - $lastEnd ) > self::EVENT_THRESHOLD_SEC ) {
370 $this->dbTrxMethodTimes[$name][] = [
'...delay...', $lastEnd, $now ];
374 foreach ( $this->dbTrxMethodTimes[$name] as $info ) {
375 $elapsed = ( $info[2] - $info[1] );
376 if ( $elapsed >= self::DB_LOCK_THRESHOLD_SEC ) {
383 foreach ( $this->dbTrxMethodTimes[$name] as $i => [ $query, $sTime, $end ] ) {
385 "%d\t%.6f\t%s\n", $i, ( $end - $sTime ), self::queryString( $query ) );
387 $this->logger->warning(
"Sub-optimal transaction on DB(s) [{dbs}]: \n{trace}", [
388 'dbs' => implode(
', ', array_keys( $this->dbTrxHoldingLocks[$name][
'conns'] ) ),
392 unset( $this->dbTrxHoldingLocks[$name] );
393 unset( $this->dbTrxMethodTimes[$name] );
397 $this->expect = array_fill_keys(
399 [ self::FLD_LIMIT => INF, self::FLD_FNAME =>
null ]
402 $this->hits = array_fill_keys( self::COUNTER_EVENT_NAMES, 0 );
411 return ( $value > $this->expect[$expectation][self::FLD_LIMIT] );
419 $newValue = ++$this->hits[$expectation];
421 return ( $newValue > $this->expect[$expectation][self::FLD_LIMIT] );
430 if ( $this->silenced ) {
436 $this->logger->warning(
437 "Expectation ($expectation <=) $max by $by not met (actual: {actual}):\n{query}\n",
439 'measure' => $expectation,
443 'query' => self::queryString( $query ),
444 'exception' =>
new RuntimeException()