summaryrefslogtreecommitdiff
path: root/includes/debug/Debug.php
blob: de50ccac1deb9f649d808238fbdfede064007510 (plain)
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
<?php

/**
 * New debugger system that outputs a toolbar on page view
 *
 * By default, most methods do nothing ( self::$enabled = false ). You have
 * to explicitly call MWDebug::init() to enabled them.
 *
 * @todo Profiler support
 */
class MWDebug {

	/**
	 * Log lines
	 *
	 * @var array
	 */
	protected static $log = array();

	/**
	 * Debug messages from wfDebug()
	 *
	 * @var array
	 */
	protected static $debug = array();

	/**
	 * Queries
	 *
	 * @var array
	 */
	protected static $query = array();

	/**
	 * Is the debugger enabled?
	 *
	 * @var bool
	 */
	protected static $enabled = false;

	/**
	 * Array of functions that have already been warned, formatted
	 * function-caller to prevent a buttload of warnings
	 *
	 * @var array
	 */
	protected static $deprecationWarnings = array();

	/**
	 * Enabled the debugger and load resource module.
	 * This is called by Setup.php when $wgDebugToolbar is true.
	 */
	public static function init() {
		self::$enabled = true;
	}

	/**
	 * Add ResourceLoader modules to the OutputPage object if debugging is
	 * enabled.
	 *
	 * @param $out OutputPage
	 */
	public static function addModules( OutputPage $out ) {
		if ( self::$enabled ) {
			$out->addModules( 'mediawiki.debug.init' );
		}
	}

	/**
	 * Adds a line to the log
	 *
	 * @todo Add support for passing objects
	 *
	 * @param $str string
	 */
	public static function log( $str ) {
		if ( !self::$enabled ) {
			return;
		}

		self::$log[] = array(
			'msg' => htmlspecialchars( $str ),
			'type' => 'log',
			'caller' => wfGetCaller(),
		);
	}

	/**
	 * Returns internal log array
	 */
	public static function getLog() {
		return self::$log;
	}

	/**
	 * Clears internal log array and deprecation tracking
	 */
	public static function clearLog() {
		self::$log = array();
		self::$deprecationWarnings = array();
	}

	/**
	 * Adds a warning entry to the log
	 *
	 * @param $msg
	 * @param int $callerOffset
	 * @return mixed
	 */
	public static function warning( $msg, $callerOffset = 1 ) {
		if ( !self::$enabled ) {
			return;
		}

		// Check to see if there was already a deprecation notice, so not to
		// get a duplicate warning
		$logCount = count( self::$log );
		if ( $logCount ) {
			$lastLog = self::$log[ $logCount - 1 ];
			if ( $lastLog['type'] == 'deprecated' && $lastLog['caller'] == wfGetCaller( $callerOffset + 1 ) ) {
				return;
			}
		}

		self::$log[] = array(
			'msg' => htmlspecialchars( $msg ),
			'type' => 'warn',
			'caller' => wfGetCaller( $callerOffset ),
		);
	}

	/**
	 * Adds a depreciation entry to the log, along with a backtrace
	 *
	 * @param $function
	 * @param $version
	 * @param $component
	 * @return mixed
	 */
	public static function deprecated( $function, $version, $component ) {
		if ( !self::$enabled ) {
			return;
		}

		// Chain: This function -> wfDeprecated -> deprecatedFunction -> caller
		$caller = wfGetCaller( 4 );

		// Check to see if there already was a warning about this function
		$functionString = "$function-$caller";
		if ( in_array( $functionString, self::$deprecationWarnings ) ) {
			return;
		}

		$version = $version === false ? '(unknown version)' : $version;
		$component = $component === false ? 'MediaWiki' : $component;
		$msg = htmlspecialchars( "Use of function $function was deprecated in $component $version" );
		$msg .= Html::rawElement( 'div', array( 'class' => 'mw-debug-backtrace' ),
			Html::element( 'span', array(), 'Backtrace:' )
			 . wfBacktrace()
		);

		self::$deprecationWarnings[] = $functionString;
		self::$log[] = array(
			'msg' => $msg,
			'type' => 'deprecated',
			'caller' => $caller,
		);
	}

	/**
	 * This is a method to pass messages from wfDebug to the pretty debugger.
	 * Do NOT use this method, use MWDebug::log or wfDebug()
	 *
	 * @param $str string
	 */
	public static function debugMsg( $str ) {
		if ( !self::$enabled ) {
			return;
		}

		self::$debug[] = trim( $str );
	}

	/**
	 * Begins profiling on a database query
	 *
	 * @param $sql string
	 * @param $function string
	 * @param $isMaster bool
	 * @return int ID number of the query to pass to queryTime or -1 if the
	 *  debugger is disabled
	 */
	public static function query( $sql, $function, $isMaster ) {
		if ( !self::$enabled ) {
			return -1;
		}

		self::$query[] = array(
			'sql' => $sql,
			'function' => $function,
			'master' => (bool) $isMaster,
			'time' => 0.0,
			'_start' => microtime( true ),
		);

		return count( self::$query ) - 1;
	}

	/**
	 * Calculates how long a query took.
	 *
	 * @param $id int
	 */
	public static function queryTime( $id ) {
		if ( $id === -1 || !self::$enabled ) {
			return;
		}

		self::$query[$id]['time'] = microtime( true ) - self::$query[$id]['_start'];
		unset( self::$query[$id]['_start'] );
	}

	/**
	 * Returns a list of files included, along with their size
	 *
	 * @param $context IContextSource
	 * @return array
	 */
	protected static function getFilesIncluded( IContextSource $context ) {
		$files = get_included_files();
		$fileList = array();
		foreach ( $files as $file ) {
			$size = filesize( $file );
			$fileList[] = array(
				'name' => $file,
				'size' => $context->getLanguage()->formatSize( $size ),
			);
		}

		return $fileList;
	}

	/**
	 * Returns the HTML to add to the page for the toolbar
	 *
	 * @param $context IContextSource
	 * @return string
	 */
	public static function getDebugHTML( IContextSource $context ) {
		if ( !self::$enabled ) {
			return '';
		}

		global $wgVersion, $wgRequestTime;
		MWDebug::log( 'MWDebug output complete' );
		$request = $context->getRequest();
		$debugInfo = array(
			'mwVersion' => $wgVersion,
			'phpVersion' => PHP_VERSION,
			'time' => microtime( true ) - $wgRequestTime,
			'log' => self::$log,
			'debugLog' => self::$debug,
			'queries' => self::$query,
			'request' => array(
				'method' => $_SERVER['REQUEST_METHOD'],
				'url' => $request->getRequestURL(),
				'headers' => $request->getAllHeaders(),
				'params' => $request->getValues(),
			),
			'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
			'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
			'includes' => self::getFilesIncluded( $context ),
		);

		// Cannot use OutputPage::addJsConfigVars because those are already outputted
		// by the time this method is called.
		$html = Html::inlineScript(
			ResourceLoader::makeLoaderConditionalScript(
				ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) )
			)
		);

		return $html;
	}
}