Profiler.php 9.2 KB

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