MediaWiki master
ProfilerXhprof.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Profiler;
10
11use RuntimeException;
13
25class ProfilerXhprof extends Profiler {
29 protected $xhprofData;
30
35 protected $sprofiler;
36
58 public function __construct( array $params = [] ) {
59 parent::__construct( $params );
60
61 // See T180183 and T247332 for why we need the 'running' option.
62 if ( empty( $params['running'] ) ) {
63 $flags = $params['flags'] ?? 0;
64 if ( function_exists( 'xhprof_enable' ) ) {
65 $options = isset( $params['exclude'] )
66 ? [ 'ignored_functions' => $params['exclude'] ]
67 : [];
68 xhprof_enable( $flags, $options );
69 } elseif ( function_exists( 'tideways_xhprof_enable' ) ) {
70 if ( isset( $params['exclude'] ) ) {
71 throw new RuntimeException( 'The exclude option is not supported in tideways_xhprof' );
72 }
73 tideways_xhprof_enable( $flags );
74 } else {
75 throw new RuntimeException( 'Neither xhprof nor tideways_xhprof is installed' );
76 }
77 }
78
79 $this->sprofiler = new SectionProfiler();
80 }
81
85 public function getXhprofData() {
86 if ( !$this->xhprofData ) {
87 if ( function_exists( 'xhprof_disable' ) ) {
88 $data = xhprof_disable();
89 } elseif ( function_exists( 'tideways_xhprof_disable' ) ) {
90 $data = tideways_xhprof_disable();
91 } else {
92 throw new RuntimeException( 'Neither xhprof nor tideways_xhprof is installed' );
93 }
94 $this->xhprofData = new XhprofData( $data, $this->params );
95 }
96 return $this->xhprofData;
97 }
98
100 #[\NoDiscard]
101 public function scopedProfileIn( $section ): ?SectionProfileCallback {
102 $key = 'section.' . ltrim( $section, '.' );
103 return $this->sprofiler->scopedProfileIn( $key );
104 }
105
109 public function close() {
110 }
111
118 private function shouldExclude( $name ) {
119 if ( $name === '-total' ) {
120 return true;
121 }
122 if ( !empty( $this->params['include'] ) ) {
123 foreach ( $this->params['include'] as $pattern ) {
124 if ( fnmatch( $pattern, $name, FNM_NOESCAPE ) ) {
125 return false;
126 }
127 }
128 return true;
129 }
130 if ( !empty( $this->params['exclude'] ) ) {
131 foreach ( $this->params['exclude'] as $pattern ) {
132 if ( fnmatch( $pattern, $name, FNM_NOESCAPE ) ) {
133 return true;
134 }
135 }
136 }
137 return false;
138 }
139
141 public function getFunctionStats() {
142 $metrics = $this->getXhprofData()->getCompleteMetrics();
143 $profile = [];
144
145 $main = null; // units in ms
146 foreach ( $metrics as $fname => $stats ) {
147 if ( $this->shouldExclude( $fname ) ) {
148 continue;
149 }
150 // Convert elapsed times from μs to ms to match interface
151 $entry = [
152 'name' => $fname,
153 'calls' => $stats['ct'],
154 'real' => $stats['wt']['total'] / 1000,
155 '%real' => $stats['wt']['percent'],
156 'cpu' => ( $stats['cpu']['total'] ?? 0 ) / 1000,
157 '%cpu' => $stats['cpu']['percent'] ?? 0,
158 'memory' => $stats['mu']['total'] ?? 0,
159 '%memory' => $stats['mu']['percent'] ?? 0,
160 'min_real' => $stats['wt']['min'] / 1000,
161 'max_real' => $stats['wt']['max'] / 1000
162 ];
163 $profile[] = $entry;
164 if ( $fname === 'main()' ) {
165 $main = $entry;
166 }
167 }
168
169 // Merge in all of the custom profile sections
170 foreach ( $this->sprofiler->getFunctionStats() as $stats ) {
171 if ( $this->shouldExclude( $stats['name'] ) ) {
172 continue;
173 }
174
175 // @note: getFunctionStats() values already in ms
176 $stats['%real'] = $main['real'] ? $stats['real'] / $main['real'] * 100 : 0;
177 $stats['%cpu'] = $main['cpu'] ? $stats['cpu'] / $main['cpu'] * 100 : 0;
178 $stats['%memory'] = $main['memory'] ? $stats['memory'] / $main['memory'] * 100 : 0;
179 $profile[] = $stats; // assume no section names collide with $metrics
180 }
181
182 return $profile;
183 }
184
190 public function getOutput() {
191 return $this->getFunctionReport();
192 }
193
210 protected function getFunctionReport() {
211 $data = $this->getFunctionStats();
212 usort( $data, static function ( $a, $b ) {
213 return $b['real'] <=> $a['real']; // descending
214 } );
215
216 $width = 140;
217 $nameWidth = $width - 65;
218 $format = "%-{$nameWidth}s %6d %9d %9d %9d %9d %7.3f%% %9d";
219 $out = [];
220 $out[] = sprintf( "%-{$nameWidth}s %6s %9s %9s %9s %9s %7s %9s",
221 'Name', 'Calls', 'Total', 'Min', 'Each', 'Max', '%', 'Mem'
222 );
223 foreach ( $data as $stats ) {
224 $out[] = sprintf( $format,
225 $stats['name'],
226 $stats['calls'],
227 $stats['real'] * 1000,
228 $stats['min_real'] * 1000,
229 $stats['real'] / $stats['calls'] * 1000,
230 $stats['max_real'] * 1000,
231 $stats['%real'],
232 $stats['memory']
233 );
234 }
235 return implode( "\n", $out );
236 }
237
242 public function getRawData() {
243 return $this->getXhprofData()->getRawData();
244 }
245}
246
248class_alias( ProfilerXhprof::class, 'ProfilerXhprof' );
Profiler that captures all function calls from the XHProf PHP extension.
getRawData()
Retrieve raw data from xhprof.
scopedProfileIn( $section)
Mark the start of a custom profiling frame (e.g.DB queries). The frame ends when the result of this m...
getFunctionReport()
Get a report of profiled functions sorted by inclusive wall clock time in descending order.
getFunctionStats()
Get the aggregated inclusive profiling data for each method.The percent time for each time is based o...
SectionProfiler $sprofiler
Profiler for explicit, arbitrary, frame labels.
getOutput()
Returns a profiling output to be stored in debug file.
close()
No-op for xhprof profiling.
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:26
array $params
All of the params passed from $wgProfiler.
Definition Profiler.php:30
Subclass ScopedCallback to avoid call_user_func_array(), which is slow.
Arbitrary section name based PHP profiling.
Convenience class for working with XHProf profiling data https://github.com/phacility/xhprof.