-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCallSiteStats.php
114 lines (102 loc) · 3.57 KB
/
CallSiteStats.php
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
<?php
/*************************************************************************
* Allows easy collection of arbitrary line-level stats about
* function calls into a particular class in a production environment.
************************************************************************/
trait CallSiteStats {
protected static $callSiteStatsEnabled = true;
protected static $_callSiteStats = [];
/**
* The number of stack frames below the current one to capture (more ==
* slower, higher == greater risk of missingsome stats)
*/
protected static $stackCaptureDepth = 10;
public static $_callSiteSeconds = 0;
/**
* Enable or disable call site stats collection.
*
* Intended to allow deploying this library with no execution time cost,
* and selectively enabling it.
*/
public static function toggleCallSiteStats($enabled) {
self::$callSiteStatsEnabled = $enabled;
}
/**
* Returns a multiline string representing all the collected stats
* (one line per call to `recordCallSite()`) in the format:
* FILE:LINE ARG1 ARGN...
*
* Example:
* /path/to/file.php:21 arg1 arg2 ...
* /path/to/file.php:21 arg1 arg2 ...
* /path/to/other.php:37 arg1 arg2 ...
*
* Where each arg passed to recordCallSite() is written out spearated by
* spaces.
* Note: This format is designed to be post-processed with summarize.php
*
* If CallSiteStats collection has been disabled, this returns null.
*/
public static function getCallSiteStats() {
if (!self::$callSiteStatsEnabled) {
return null;
}
$time = microtime(true);
$outStats = [];
foreach(self::$_callSiteStats as $site => $stats) {
foreach($stats as $statLine) {
$outStats[] = "{$site} {$statLine}";
}
}
$results = implode("\n", $outStats);
self::$_callSiteSeconds += microtime(true) - $time;
return $results;
}
/**
* Records the passed arguments for the function
* call-site that called into this class.
*/
protected static function recordCallSite() {
if (!self::$callSiteStatsEnabled) {
return null;
}
$time = microtime(true);
$callSite = self::getCallSite();
$data = implode(' ', func_get_args());
// If we don't have a call-site it's because we reached the end of our
// partial stack before isExternalCallSite() returned true, meaning all
// the frames we captured were inside the class we were trying to
// measure.
if (!$callSite) {
$callSite = "end-of-captured-stack";
$data = "1";
}
if (isset(self::$_callSiteStats[$callSite])) {
self::$_callSiteStats[$callSite][] = $data;
} else {
self::$_callSiteStats[$callSite] = [$data];
}
self::$_callSiteSeconds += microtime(true) - $time;
}
/**
* Returns a string like "path/to/file.php:123" for the first stack frame
* down the stack that is NOT inside this file.
*
* Returns null if one can't be found.
*/
protected static function getCallSite() {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,
static::$stackCaptureDepth);
$length = count($trace);
for ($i=0; $i < $length; $i++) {
$frame = $trace[$i];
if (isset($frame['file']) &&
$frame['file'] != __FILE__ &&
self::isExternalCallSite($frame['file'])) {
return $frame['file'] . ":" . $frame['line'];
}
}
return null;
}
abstract protected function isExternalCallSite($file);
}