Profiler.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <?php
  2. /**
  3. * Provides simple benchmarking and profiling. To display the statistics that
  4. * have been collected, load the `profiler/stats` [View]:
  5. *
  6. * echo View::factory('profiler/stats');
  7. *
  8. * @package Kohana
  9. * @category Helpers
  10. * @author Kohana Team
  11. * @copyright (c) Kohana Team
  12. * @license https://koseven.ga/LICENSE.md
  13. */
  14. class Kohana_Profiler {
  15. /**
  16. * @var integer maximum number of application stats to keep
  17. */
  18. public static $rollover = 1000;
  19. /**
  20. * @var array collected benchmarks
  21. */
  22. protected static $_marks = [];
  23. /**
  24. * Starts a new benchmark and returns a unique token. The returned token
  25. * _must_ be used when stopping the benchmark.
  26. *
  27. * $token = Profiler::start('test', 'profiler');
  28. *
  29. * @param string $group group name
  30. * @param string $name benchmark name
  31. * @return string
  32. */
  33. public static function start($group, $name)
  34. {
  35. static $counter = 0;
  36. // Create a unique token based on the counter
  37. $token = 'kp/'.base_convert($counter++, 10, 32);
  38. Profiler::$_marks[$token] = [
  39. 'group' => strtolower($group),
  40. 'name' => (string) $name,
  41. // Start the benchmark
  42. 'start_time' => microtime(TRUE),
  43. 'start_memory' => memory_get_usage(),
  44. // Set the stop keys without values
  45. 'stop_time' => FALSE,
  46. 'stop_memory' => FALSE,
  47. ];
  48. return $token;
  49. }
  50. /**
  51. * Stops a benchmark.
  52. *
  53. * Profiler::stop($token);
  54. *
  55. * @param string $token
  56. * @return void
  57. */
  58. public static function stop($token)
  59. {
  60. // Stop the benchmark
  61. Profiler::$_marks[$token]['stop_time'] = microtime(TRUE);
  62. Profiler::$_marks[$token]['stop_memory'] = memory_get_usage();
  63. }
  64. /**
  65. * Deletes a benchmark. If an error occurs during the benchmark, it is
  66. * recommended to delete the benchmark to prevent statistics from being
  67. * adversely affected.
  68. *
  69. * Profiler::delete($token);
  70. *
  71. * @param string $token
  72. * @return void
  73. */
  74. public static function delete($token)
  75. {
  76. // Remove the benchmark
  77. unset(Profiler::$_marks[$token]);
  78. }
  79. /**
  80. * Returns all the benchmark tokens by group and name as an array.
  81. *
  82. * $groups = Profiler::groups();
  83. *
  84. * @return array
  85. */
  86. public static function groups()
  87. {
  88. $groups = [];
  89. foreach (Profiler::$_marks as $token => $mark)
  90. {
  91. // Sort the tokens by the group and name
  92. $groups[$mark['group']][$mark['name']][] = $token;
  93. }
  94. return $groups;
  95. }
  96. /**
  97. * Gets the min, max, average and total of a set of tokens as an array.
  98. *
  99. * $stats = Profiler::stats($tokens);
  100. *
  101. * @param array $tokens profiler tokens
  102. * @return array min, max, average, total
  103. * @uses Profiler::total
  104. */
  105. public static function stats(array $tokens)
  106. {
  107. // Min and max are unknown by default
  108. $min = $max = [
  109. 'time' => NULL,
  110. 'memory' => NULL];
  111. // Total values are always integers
  112. $total = [
  113. 'time' => 0,
  114. 'memory' => 0];
  115. foreach ($tokens as $token)
  116. {
  117. // Get the total time and memory for this benchmark
  118. list($time, $memory) = Profiler::total($token);
  119. if ($max['time'] === NULL OR $time > $max['time'])
  120. {
  121. // Set the maximum time
  122. $max['time'] = $time;
  123. }
  124. if ($min['time'] === NULL OR $time < $min['time'])
  125. {
  126. // Set the minimum time
  127. $min['time'] = $time;
  128. }
  129. // Increase the total time
  130. $total['time'] += $time;
  131. if ($max['memory'] === NULL OR $memory > $max['memory'])
  132. {
  133. // Set the maximum memory
  134. $max['memory'] = $memory;
  135. }
  136. if ($min['memory'] === NULL OR $memory < $min['memory'])
  137. {
  138. // Set the minimum memory
  139. $min['memory'] = $memory;
  140. }
  141. // Increase the total memory
  142. $total['memory'] += $memory;
  143. }
  144. // Determine the number of tokens
  145. $count = count($tokens);
  146. // Determine the averages
  147. $average = [
  148. 'time' => $total['time'] / $count,
  149. 'memory' => $total['memory'] / $count];
  150. return [
  151. 'min' => $min,
  152. 'max' => $max,
  153. 'total' => $total,
  154. 'average' => $average];
  155. }
  156. /**
  157. * Gets the min, max, average and total of profiler groups as an array.
  158. *
  159. * $stats = Profiler::group_stats('test');
  160. *
  161. * @param mixed $groups single group name string, or array with group names; all groups by default
  162. * @return array min, max, average, total
  163. * @uses Profiler::groups
  164. * @uses Profiler::stats
  165. */
  166. public static function group_stats($groups = NULL)
  167. {
  168. // Which groups do we need to calculate stats for?
  169. $groups = ($groups === NULL)
  170. ? Profiler::groups()
  171. : array_intersect_key(Profiler::groups(), array_flip( (array) $groups));
  172. // All statistics
  173. $stats = [];
  174. foreach ($groups as $group => $names)
  175. {
  176. foreach ($names as $name => $tokens)
  177. {
  178. // Store the stats for each subgroup.
  179. // We only need the values for "total".
  180. $_stats = Profiler::stats($tokens);
  181. $stats[$group][$name] = $_stats['total'];
  182. }
  183. }
  184. // Group stats
  185. $groups = [];
  186. foreach ($stats as $group => $names)
  187. {
  188. // Min and max are unknown by default
  189. $groups[$group]['min'] = $groups[$group]['max'] = [
  190. 'time' => NULL,
  191. 'memory' => NULL];
  192. // Total values are always integers
  193. $groups[$group]['total'] = [
  194. 'time' => 0,
  195. 'memory' => 0];
  196. foreach ($names as $total)
  197. {
  198. if ( ! isset($groups[$group]['min']['time']) OR $groups[$group]['min']['time'] > $total['time'])
  199. {
  200. // Set the minimum time
  201. $groups[$group]['min']['time'] = $total['time'];
  202. }
  203. if ( ! isset($groups[$group]['min']['memory']) OR $groups[$group]['min']['memory'] > $total['memory'])
  204. {
  205. // Set the minimum memory
  206. $groups[$group]['min']['memory'] = $total['memory'];
  207. }
  208. if ( ! isset($groups[$group]['max']['time']) OR $groups[$group]['max']['time'] < $total['time'])
  209. {
  210. // Set the maximum time
  211. $groups[$group]['max']['time'] = $total['time'];
  212. }
  213. if ( ! isset($groups[$group]['max']['memory']) OR $groups[$group]['max']['memory'] < $total['memory'])
  214. {
  215. // Set the maximum memory
  216. $groups[$group]['max']['memory'] = $total['memory'];
  217. }
  218. // Increase the total time and memory
  219. $groups[$group]['total']['time'] += $total['time'];
  220. $groups[$group]['total']['memory'] += $total['memory'];
  221. }
  222. // Determine the number of names (subgroups)
  223. $count = count($names);
  224. // Determine the averages
  225. $groups[$group]['average']['time'] = $groups[$group]['total']['time'] / $count;
  226. $groups[$group]['average']['memory'] = $groups[$group]['total']['memory'] / $count;
  227. }
  228. return $groups;
  229. }
  230. /**
  231. * Gets the total execution time and memory usage of a benchmark as a list.
  232. *
  233. * list($time, $memory) = Profiler::total($token);
  234. *
  235. * @param string $token
  236. * @return array execution time, memory
  237. */
  238. public static function total($token)
  239. {
  240. // Import the benchmark data
  241. $mark = Profiler::$_marks[$token];
  242. if ($mark['stop_time'] === FALSE)
  243. {
  244. // The benchmark has not been stopped yet
  245. $mark['stop_time'] = microtime(TRUE);
  246. $mark['stop_memory'] = memory_get_usage();
  247. }
  248. return [
  249. // Total time in seconds
  250. $mark['stop_time'] - $mark['start_time'],
  251. // Amount of memory in bytes
  252. $mark['stop_memory'] - $mark['start_memory'],
  253. ];
  254. }
  255. /**
  256. * Gets the total application run time and memory usage. Caches the result
  257. * so that it can be compared between requests.
  258. *
  259. * list($time, $memory) = Profiler::application();
  260. *
  261. * @return array execution time, memory
  262. * @uses Kohana::cache
  263. */
  264. public static function application()
  265. {
  266. // Load the stats from cache, which is valid for 1 day
  267. $stats = Kohana::cache('profiler_application_stats', NULL, 3600 * 24);
  268. if ( ! is_array($stats) OR $stats['count'] > Profiler::$rollover)
  269. {
  270. // Initialize the stats array
  271. $stats = [
  272. 'min' => [
  273. 'time' => NULL,
  274. 'memory' => NULL],
  275. 'max' => [
  276. 'time' => NULL,
  277. 'memory' => NULL],
  278. 'total' => [
  279. 'time' => NULL,
  280. 'memory' => NULL],
  281. 'count' => 0];
  282. }
  283. // Get the application run time
  284. $time = microtime(TRUE) - KOHANA_START_TIME;
  285. // Get the total memory usage
  286. $memory = memory_get_usage() - KOHANA_START_MEMORY;
  287. // Calculate max time
  288. if ($stats['max']['time'] === NULL OR $time > $stats['max']['time'])
  289. {
  290. $stats['max']['time'] = $time;
  291. }
  292. // Calculate min time
  293. if ($stats['min']['time'] === NULL OR $time < $stats['min']['time'])
  294. {
  295. $stats['min']['time'] = $time;
  296. }
  297. // Add to total time
  298. $stats['total']['time'] += $time;
  299. // Calculate max memory
  300. if ($stats['max']['memory'] === NULL OR $memory > $stats['max']['memory'])
  301. {
  302. $stats['max']['memory'] = $memory;
  303. }
  304. // Calculate min memory
  305. if ($stats['min']['memory'] === NULL OR $memory < $stats['min']['memory'])
  306. {
  307. $stats['min']['memory'] = $memory;
  308. }
  309. // Add to total memory
  310. $stats['total']['memory'] += $memory;
  311. // Another mark has been added to the stats
  312. $stats['count']++;
  313. // Determine the averages
  314. $stats['average'] = [
  315. 'time' => $stats['total']['time'] / $stats['count'],
  316. 'memory' => $stats['total']['memory'] / $stats['count']];
  317. // Cache the new stats
  318. Kohana::cache('profiler_application_stats', $stats);
  319. // Set the current application execution time and memory
  320. // Do NOT cache these, they are specific to the current request only
  321. $stats['current']['time'] = $time;
  322. $stats['current']['memory'] = $memory;
  323. // Return the total application run time and memory usage
  324. return $stats;
  325. }
  326. }