Completed
Branch master (4dc390)
by Fabio
30:44
created

Benchmark_Profiler   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0
Metric Value
wmc 47
lcom 1
cbo 0
dl 0
loc 369
rs 8.439

11 Methods

Rating   Name   Duplication   Size   Complexity  
A Benchmark_Profiler() 0 9 2
A _Benchmark_Profiler() 0 6 3
B getSectionInformations() 0 37 6
A getAllSectionsInformations() 0 9 2
F _getOutput() 0 92 18
A display() 0 3 1
A start() 0 3 1
A stop() 0 3 1
B enterSection() 0 27 6
B leaveSection() 0 31 6
A _getMicrotime() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Benchmark_Profiler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Benchmark_Profiler, and based on these observations, apply Extract Interface, too.

1
<?php
2
//
3
// +----------------------------------------------------------------------+
4
// | PEAR :: Benchmark                                                    |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 2002-2006 Matthias Englert <[email protected]>.  |
7
// +----------------------------------------------------------------------+
8
// | This source file is subject to the New BSD license, That is bundled  |
9
// | with this package in the file LICENSE, and is available through      |
10
// | the world-wide-web at                                                |
11
// | http://www.opensource.org/licenses/bsd-license.php                   |
12
// | If you did not receive a copy of the new BSDlicense and are unable   |
13
// | to obtain it through the world-wide-web, please send a note to       |
14
// | [email protected] so we can mail you a copy immediately.               |
15
// +----------------------------------------------------------------------+
16
//
17
// $Id: Profiler.php,v 1.19 2006/03/01 19:26:09 anant Exp $
18
//
19
20
require_once 'PEAR.php';
21
22
/**
23
 * Provides timing and profiling information.
24
 *
25
 * Example 1: Automatic profiling start, stop, and output.
26
 *
27
 * <code>
28
 * <?php
29
 * require_once 'Benchmark/Profiler.php';
30
 *
31
 * $profiler = new Benchmark_Profiler(TRUE);
32
 *
33
 * function myFunction() {
34
 *     global $profiler;
35
 *     $profiler->enterSection('myFunction');
36
 *     //do something
37
 *     $profiler->leaveSection('myFunction');
38
 *     return;
39
 * }
40
 *
41
 * //do something
42
 * myFunction();
43
 * //do more
44
 * ?>
45
 * </code>
46
 *
47
 * Example 2: Manual profiling start, stop, and output.
48
 *
49
 * <code>
50
 * <?php
51
 * require_once 'Benchmark/Profiler.php';
52
 *
53
 * $profiler = new Benchmark_Profiler();
54
 *
55
 * function myFunction() {
56
 *     global $profiler;
57
 *     $profiler->enterSection('myFunction');
58
 *     //do something
59
 *     $profiler->leaveSection('myFunction');
60
 *     return;
61
 * }
62
 *
63
 * $profiler->start();
64
 * //do something
65
 * myFunction();
66
 * //do more
67
 * $profiler->stop();
68
 * $profiler->display();
69
 * ?>
70
 * </code>
71
 *
72
 * @author    Matthias Englert <[email protected]>
73
 * @copyright Copyright &copy; 2002-2005 Matthias Englert <[email protected]>
74
 * @license   http://www.php.net/license/3_0.txt The PHP License, Version 3.0
75
 * @category  Benchmarking
76
 * @package   Benchmark
77
 * @since     1.2.0
78
 */
79
class Benchmark_Profiler extends PEAR {
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
80
    /**
81
     * Contains the total ex. time of each section
82
     *
83
     * @var    array
84
     * @access private
85
     */
86
    var $_sections = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_sections.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
87
88
    /**
89
     * Calling stack
90
     *
91
     * @var    array
92
     * @access private
93
     */
94
    var $_stack = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_stack.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
95
96
    /**
97
     * Notes how often a section was entered
98
     *
99
     * @var    array
100
     * @access private
101
     */
102
    var $_numberOfCalls = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_numberOfCalls.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
103
104
    /**
105
     * Notes for each section how much time is spend in sub-sections
106
     *
107
     * @var    array
108
     * @access private
109
     */
110
    var $_subSectionsTime = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_subSectionsTime.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
111
112
    /**
113
     * Notes for each section how often it calls which section
114
     *
115
     * @var    array
116
     * @access private
117
     */
118
    var $_calls = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_calls.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
119
120
    /**
121
     * Notes for each section how often it was called by which section
122
     *
123
     * @var    array
124
     * @access private
125
     */
126
    var $_callers = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_callers.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
127
128
    /**
129
     * Auto-starts and stops profiler
130
     *
131
     * @var    boolean
132
     * @access private
133
     */
134
    var $_auto = FALSE;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_auto.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
135
136
    /**
137
     * Max marker name length for non-html output
138
     *
139
     * @var    integer
140
     * @access private
141
     */
142
    var $_maxStringLength = 0;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $_maxStringLength.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
143
144
    /**
145
     * Constructor, starts profiling recording
146
     *
147
     * @access public
148
     */
149
    function Benchmark_Profiler($auto = FALSE) {
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
150
        $this->_auto = $auto;
151
152
        if ($this->_auto) {
153
            $this->start();
154
        }
155
156
        $this->PEAR();
157
    }
158
159
    /**
160
     * Destructor, stops profiling recording
161
     *
162
     * @access private
163
     */
164
    function _Benchmark_Profiler() {
165
        if (isset($this->_auto) && $this->_auto) {
166
            $this->stop();
167
            $this->display();
168
        }
169
    }
170
171
    /**
172
     * Returns profiling informations for a given section.
173
     *
174
     * @param  string $section
175
     * @return array
176
     * @access public
177
     */
178
    function getSectionInformations($section = 'Global') {
179
        if (isset($this->_sections[$section])) {
180
            $calls = array();
181
182
            if (isset($this->_calls[$section])) {
183
                $calls = $this->_calls[$section];
184
            }
185
186
            $callers = array();
187
188
            if (isset($this->_callers[$section])) {
189
                $callers = $this->_callers[$section];
190
            }
191
192
            $informations = array();
193
194
            $informations['time']       = $this->_sections[$section];
195
            if (isset($this->_sections['Global'])) {
196
                $informations['percentage'] = number_format(100 * $this->_sections[$section] / $this->_sections['Global'], 2, '.', '');
197
            } else {
198
                $informations['percentage'] = 'N/A';
199
            }
200
            $informations['calls']      = $calls;
201
            $informations['num_calls']  = $this->_numberOfCalls[$section];
202
            $informations['callers']    = $callers;
203
204
      	    if (isset($this->_subSectionsTime[$section])) {
205
                $informations['netto_time'] = $this->_sections[$section] - $this->_subSectionsTime[$section];
206
      	    } else {
207
                $informations['netto_time'] = $this->_sections[$section];
208
      	    }
209
210
            return $informations;
211
        } else {
212
            $this->raiseError("The section '$section' does not exists.\n", NULL, PEAR_ERROR_TRIGGER, E_USER_WARNING);
213
        }
214
    }
215
216
    /**
217
     * Returns profiling informations for all sections.
218
     *
219
     * @return array
220
     * @access public
221
     */
222
    function getAllSectionsInformations() {
223
        $informations = array();
224
225
        foreach($this->_sections as $section => $time) {
226
            $informations[$section] = $this->getSectionInformations($section);
227
        }
228
229
        return $informations;
230
    }
231
232
    /**
233
     * Returns formatted profiling information.
234
     *
235
     * @param  string output format (auto, plain or html), default auto
236
     * @see    display()
237
     * @access private
238
     */
239
    function _getOutput($format) {
0 ignored issues
show
Coding Style introduced by
_getOutput uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
_getOutput uses the super-global variable $HTTP_SERVER_VARS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
240
        
241
        /* Quickly find out the maximun length: Ineffecient, but will do for now! */
242
        $informations = $this->getAllSectionsInformations();
243
        $names = array_keys($informations);
244
        
245
        $maxLength = 0;
246
        foreach ($names as $name)
247
        {
248
            if ($maxLength < strlen($name)) {
249
                $maxLength = strlen($name);
250
            }
251
        }
252
        $this->_maxStringLength = $maxLength;
253
254
        if ($format == 'auto') {
255
            if (function_exists('version_compare') &&
256
                version_compare(phpversion(), '4.1', 'ge')) {
257
                $format = isset($_SERVER['SERVER_PROTOCOL']) ? 'html' : 'plain';
258
            } else {
259
                global $HTTP_SERVER_VARS;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
260
                $format = isset($HTTP_SERVER_VARS['SERVER_PROTOCOL']) ? 'html' : 'plain';
261
            }
262
        }
263
264
        if ($format == 'html') {
265
            $out = '<table style="border: 1px solid #000000; ">'."\n";
266
            $out .=
267
                '<tr><td>&nbsp;</td><td align="center"><b>total ex. time</b></td>'.
268
                '<td align="center"><b>netto ex. time</b></td>'.
269
                '<td align="center"><b>#calls</b></td><td align="center"><b>%</b></td>'.
270
                '<td align="center"><b>calls</b></td><td align="center"><b>callers</b></td></tr>'.
271
                "\n";
272
        } else {
273
            $dashes = $out = str_pad("\n", ($this->_maxStringLength + 75), '-', STR_PAD_LEFT);
274
            $out .= str_pad('Section', $this->_maxStringLength + 10);
275
            $out .= str_pad("Total Ex Time", 22);
276
            $out .= str_pad("Netto Ex Time", 22);
277
            $out .= str_pad("#Calls", 10);
278
            $out .= "Percentage\n";
279
            $out .= $dashes;
280
        }
281
           
282
        foreach($informations as $name => $values) {
283
            $percentage = $values['percentage'];
0 ignored issues
show
Unused Code introduced by
$percentage is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
284
            $calls_str = "";
285
286
            foreach($values['calls'] as $key => $val) {
287
                if ($calls_str) {
288
                    $calls_str .= ", ";
289
                }
290
291
                $calls_str .= "$key ($val)";
292
            }
293
294
            $callers_str = "";
295
296
            foreach($values['callers'] as $key => $val) {
297
                if ($callers_str) {
298
                    $callers_str .= ", ";
299
    		        }
300
301
                $callers_str .= "$key ($val)";
302
            }
303
304
            if ($format == 'html') {
305
                $out .= "<tr><td><b>$name</b></td><td>{$values['time']}</td><td>{$values['netto_time']}</td><td>{$values['num_calls']}</td>";
306
                if (is_numeric($values['percentage'])) {
307
                    $out .= "<td align=\"right\">{$values['percentage']}%</td>\n";
308
                } else {
309
                    $out .= "<td align=\"right\">{$values['percentage']}</td>\n";
310
                }
311
                $out .= "<td>$calls_str</td><td>$callers_str</td></tr>";
312
            } else {
313
                $out .= str_pad($name, $this->_maxStringLength + 10);
314
                $out .= str_pad($values['time'], 22);
315
                $out .= str_pad($values['netto_time'], 22);
316
                $out .= str_pad($values['num_calls'], 10);
317
                if (is_numeric($values['percentage'])) {
318
                    $out .= str_pad($values['percentage']."%\n", 8, ' ', STR_PAD_LEFT);
319
                } else {
320
                    $out .= str_pad($values['percentage']."\n", 8, ' ', STR_PAD_LEFT);
321
                }
322
            }
323
        }
324
        
325
        if ($format == 'html') {
326
            return $out . '</table>';
327
        } else {
328
            return $out;
329
        }
330
    }
331
332
    /**
333
     * Returns formatted profiling information.
334
     *
335
     * @param  string output format (auto, plain or html), default auto
336
     * @access public
337
     */
338
    function display($format = 'auto') {
339
        echo $this->_getOutput($format);
340
    }
341
342
    /**
343
     * Enters "Global" section.
344
     *
345
     * @see    enterSection(), stop()
346
     * @access public
347
     */
348
    function start() {
349
        $this->enterSection('Global');
350
    }
351
352
    /**
353
     * Leaves "Global" section.
354
     *
355
     * @see    leaveSection(), start()
356
     * @access public
357
     */
358
    function stop() {
359
        $this->leaveSection('Global');
360
    }
361
362
    /**
363
     * Enters code section.
364
     *
365
     * @param  string  name of the code section
366
     * @see    start(), leaveSection()
367
     * @access public
368
     */
369
    function enterSection($name) {
370
        if (count($this->_stack)) {
371
            if (isset($this->_callers[$name][$this->_stack[count($this->_stack) - 1]["name"]])) {
372
                $this->_callers[$name][$this->_stack[count($this->_stack) - 1]["name"]]++;
373
            } else {
374
                $this->_callers[$name][$this->_stack[count($this->_stack) - 1]["name"]] = 1;
375
            }
376
377
            if (isset($this->_calls[$this->_stack[count($this->_stack) - 1]["name"]][$name])) {
378
                $this->_calls[$this->_stack[count($this->_stack) - 1]["name"]][$name]++;
379
            } else {
380
                $this->_calls[$this->_stack[count($this->_stack) - 1]["name"]][$name] = 1;
381
            }
382
        } else {
383
            if ($name != 'Global') {
384
                $this->raiseError("tried to enter section ".$name." but profiling was not started\n", NULL, PEAR_ERROR_DIE);
385
            }
386
        }
387
388
        if (isset($this->_numberOfCalls[$name])) {
389
            $this->_numberOfCalls[$name]++;
390
        } else {
391
            $this->_numberOfCalls[$name] = 1;
392
        }
393
394
        array_push($this->_stack, array("name" => $name, "time" => $this->_getMicrotime()));
395
    }
396
397
    /**
398
     * Leaves code section.
399
     *
400
     * @param  string  name of the marker to be set
401
     * @see     stop(), enterSection()
402
     * @access public
403
     */
404
    function leaveSection($name) {
405
        $microtime = $this->_getMicrotime();
406
407
        if (!count($this->_stack)) {
408
            $this->raiseError("tried to leave section ".$name." but profiling was not started\n", NULL, PEAR_ERROR_DIE);
409
        }
410
411
        $x = array_pop($this->_stack);
412
413
        if ($x["name"] != $name) {
414
            $this->raiseError("reached end of section $name but expecting end of " . $x["name"]."\n", NULL, PEAR_ERROR_DIE);
415
        }
416
417
        if (isset($this->_sections[$name])) {
418
            $this->_sections[$name] += $microtime - $x["time"];
419
        } else {
420
            $this->_sections[$name] = $microtime - $x["time"];
421
        }
422
423
        $parent = array_pop($this->_stack);
424
425
      	if (isset($parent)) {
426
            if (isset($this->_subSectionsTime[$parent['name']])) {
427
                $this->_subSectionsTime[$parent['name']] += $microtime - $x['time'];
428
            } else {
429
                $this->_subSectionsTime[$parent['name']] = $microtime - $x['time'];
430
            }
431
432
            array_push($this->_stack, $parent);
433
        }
434
    }
435
436
    /**
437
     * Wrapper for microtime().
438
     *
439
     * @return float
440
     * @access private
441
     * @since  1.3.0
442
     */
443
    function _getMicrotime() {
444
        $microtime = explode(' ', microtime());
445
        return $microtime[1] . substr($microtime[0], 1);
446
    }
447
}
448