PageRenderTime 38ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/test/filter/Profiler.php

https://bitbucket.org/d1rk/lithium
PHP | 246 lines | 160 code | 28 blank | 58 comment | 20 complexity | 5b9d6f4faf6086af34513a9c807a863c MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2012, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\test\filter;
  9. /**
  10. * The `Profiler` filter tracks timing and memory usage information for each test method, and
  11. * presents aggregate reports across single test runs. Used for performance-tuning classes and
  12. * methods.
  13. */
  14. class Profiler extends \lithium\test\Filter {
  15. /**
  16. * Contains the list of profiler checks to run against each test. Values can be string
  17. * function names, arrays containing function names as the first key and function parameters
  18. * as subsequent keys, or closures.
  19. *
  20. * @var array
  21. * @see lithium\test\Profiler::check()
  22. */
  23. protected static $_metrics = array(
  24. 'Time' => array(
  25. 'function' => array('microtime', true),
  26. 'format' => 'seconds'
  27. ),
  28. 'Current Memory' => array(
  29. 'function' => 'memory_get_usage',
  30. 'format' => 'bytes'
  31. ),
  32. 'Peak Memory' => array(
  33. 'function' => 'memory_get_peak_usage',
  34. 'format' => 'bytes'
  35. ),
  36. 'Current Memory (Xdebug)' => array(
  37. 'function' => 'xdebug_memory_usage',
  38. 'format' => 'bytes'
  39. ),
  40. 'Peak Memory (Xdebug)' => array(
  41. 'function' => 'xdebug_peak_memory_usage',
  42. 'format' => 'bytes'
  43. )
  44. );
  45. protected static $_formatters = array();
  46. /**
  47. * Verifies that the corresponding function exists for each built-in profiler check.
  48. * Initializes display formatters.
  49. *
  50. * @return void
  51. */
  52. public static function __init() {
  53. foreach (static::$_metrics as $name => $check) {
  54. $function = current((array) $check['function']);
  55. if (is_string($check['function']) && !function_exists($check['function'])) {
  56. unset(static::$_metrics[$name]);
  57. }
  58. }
  59. static::$_formatters = array(
  60. 'seconds' => function($value) { return number_format($value, 4) . 's'; },
  61. 'bytes' => function($value) { return number_format($value / 1024, 3) . 'k'; }
  62. );
  63. }
  64. /**
  65. * Takes an instance of an object (usually a Collection object) containing test
  66. * instances. Allows for preparing tests before they are run.
  67. *
  68. * @param object $report Instance of Report which is calling apply.
  69. * @param array $tests The test to apply this filter on
  70. * @param array $options Options for how this filter should be applied. Available options are:
  71. * - `'method'`
  72. * - `'run'`
  73. * - `'checks'`
  74. * @return object Returns the instance of `$tests`.
  75. */
  76. public static function apply($report, $tests, array $options = array()) {
  77. $defaults = array('method' => 'run', 'checks' => static::$_metrics);
  78. $options += $defaults;
  79. $m = $options['method'];
  80. $filter = function($self, $params, $chain) use ($report, $options) {
  81. $start = $results = array();
  82. $runCheck = function($check) {
  83. switch (true) {
  84. case (is_object($check) || is_string($check)):
  85. return $check();
  86. break;
  87. case (is_array($check)):
  88. $function = array_shift($check);
  89. $result = !$check ? $check() : call_user_func_array($function, $check);
  90. break;
  91. }
  92. return $result;
  93. };
  94. foreach ($options['checks'] as $name => $check) {
  95. $start[$name] = $runCheck($check['function']);
  96. }
  97. $methodResult = $chain->next($self, $params, $chain);
  98. foreach ($options['checks'] as $name => $check) {
  99. $results[$name] = $runCheck($check['function']) - $start[$name];
  100. }
  101. $report->collect(
  102. __CLASS__,
  103. array(
  104. $self->subject() => $results,
  105. 'options' => $options + array('test' => get_class($self)),
  106. 'method' => $params['method']
  107. )
  108. );
  109. return $methodResult;
  110. };
  111. $tests->invoke('applyFilter', array($m, $filter));
  112. return $tests;
  113. }
  114. /**
  115. * Analyzes the results of a test run and returns the result of the analysis.
  116. *
  117. * @param object $report The report instance running this filter and aggregating results
  118. * @param array $options Not used.
  119. * @return array The results of the analysis.
  120. */
  121. public static function analyze($report, array $options = array()) {
  122. $results = $report->results['group'];
  123. $collectedResults = static::collect($report->results['filters'][__CLASS__]);
  124. extract($collectedResults, EXTR_OVERWRITE);
  125. $metrics = array();
  126. foreach ($results as $testCase) {
  127. foreach ((array) $testCase as $assertion) {
  128. if ($assertion['result'] != 'pass' && $assertion['result'] != 'fail') {
  129. continue;
  130. }
  131. $class = $classMap[$assertion['class']];
  132. if (!isset($metrics[$class])) {
  133. $metrics[$class] = array('assertions' => 0);
  134. }
  135. $metrics[$class]['assertions']++;
  136. }
  137. }
  138. foreach ($filterResults as $class => $methods) {
  139. foreach ($methods as $methodName => $timers) {
  140. foreach ($timers as $title => $value) {
  141. if (!isset($metrics[$class][$title])) {
  142. $metrics[$class][$title] = 0;
  143. }
  144. $metrics[$class][$title] += $value;
  145. }
  146. }
  147. }
  148. $totals = array();
  149. foreach ($metrics as $class => $data) {
  150. foreach ($data as $title => $value) {
  151. if (isset(static::$_metrics[$title])) {
  152. if (isset($totals[$title]['value'])) {
  153. $totals[$title]['value'] += $value;
  154. } else {
  155. $totals[$title]['value'] = $value;
  156. }
  157. if (!isset($totals[$title]['format'])) {
  158. $format = static::$_metrics[$title]['format'];
  159. $totals[$title]['formatter'] = static::$_formatters[$format];
  160. }
  161. }
  162. }
  163. }
  164. $metrics['totals'] = $totals;
  165. return $metrics;
  166. }
  167. /**
  168. * Add, remove, or modify a profiler check.
  169. *
  170. * @see lithium\test\Profiler::$_metrics
  171. * @param mixed $name
  172. * @param string $value
  173. * @return mixed
  174. */
  175. public function check($name, $value = null) {
  176. if (is_null($value) && !is_array($name)) {
  177. return isset(static::$_metrics[$name]) ? static::$_metrics[$name] : null;
  178. }
  179. if ($value === false) {
  180. unset(static::$_metrics[$name]);
  181. return;
  182. }
  183. if (!empty($value)) {
  184. static::$_metrics[$name] = $value;
  185. }
  186. if (is_array($name)) {
  187. static::$_metrics = $name + static::$_metrics;
  188. }
  189. }
  190. /**
  191. * Collects the raw filter results and packages them for analysis.
  192. *
  193. * @param array $filterResults The results of the filter on the test run.
  194. * @return array The packaged filter results prepared for analysis.
  195. */
  196. public static function collect($filterResults) {
  197. $defaults = array('test' => null);
  198. $classMap = array();
  199. $packagedResults = array();
  200. foreach ($filterResults as $results) {
  201. $class = key($results);
  202. $options = $results['options'];
  203. $options += $defaults;
  204. $method = $results['method'];
  205. $classMap[$options['test']] = $class;
  206. if (!isset($packagedResults[$class])) {
  207. $packagedResults[$class] = array();
  208. }
  209. $packagedResults[$class][$method] = $results[$class];
  210. }
  211. $filterResults = $packagedResults;
  212. return array(
  213. 'filterResults' => $filterResults,
  214. 'classMap' => $classMap
  215. );
  216. }
  217. }
  218. ?>