Stat.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. /**
  3. * Storage for timers statistics
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework\Profiler\Driver\Standard;
  9. use Magento\Framework\Profiler;
  10. class Stat
  11. {
  12. /**
  13. * #@+ Timer statistics data keys
  14. */
  15. const ID = 'id';
  16. const START = 'start';
  17. const TIME = 'sum';
  18. const COUNT = 'count';
  19. const AVG = 'avg';
  20. const REALMEM = 'realmem';
  21. const REALMEM_START = 'realmem_start';
  22. const EMALLOC = 'emalloc';
  23. const EMALLOC_START = 'emalloc_start';
  24. /**#@-*/
  25. /**#@-*/
  26. protected $_timers = [];
  27. /**
  28. * Starts timer
  29. *
  30. * @param string $timerId
  31. * @param int $time
  32. * @param int $realMemory Real size of memory allocated from system
  33. * @param int $emallocMemory Memory used by emalloc()
  34. * @return void
  35. */
  36. public function start($timerId, $time, $realMemory, $emallocMemory)
  37. {
  38. if (empty($this->_timers[$timerId])) {
  39. $this->_timers[$timerId] = [
  40. self::START => false,
  41. self::TIME => 0,
  42. self::COUNT => 0,
  43. self::REALMEM => 0,
  44. self::EMALLOC => 0,
  45. ];
  46. }
  47. $this->_timers[$timerId][self::REALMEM_START] = $realMemory;
  48. $this->_timers[$timerId][self::EMALLOC_START] = $emallocMemory;
  49. $this->_timers[$timerId][self::START] = $time;
  50. $this->_timers[$timerId][self::COUNT]++;
  51. }
  52. /**
  53. * Stops timer
  54. *
  55. * @param string $timerId
  56. * @param int $time
  57. * @param int $realMemory Real size of memory allocated from system
  58. * @param int $emallocMemory Memory used by emalloc()
  59. * @return void
  60. * @throws \InvalidArgumentException if timer doesn't exist
  61. */
  62. public function stop($timerId, $time, $realMemory, $emallocMemory)
  63. {
  64. if (empty($this->_timers[$timerId])) {
  65. throw new \InvalidArgumentException(sprintf('Timer "%s" doesn\'t exist.', $timerId));
  66. }
  67. $this->_timers[$timerId][self::TIME] += $time - $this->_timers[$timerId]['start'];
  68. $this->_timers[$timerId][self::START] = false;
  69. $this->_timers[$timerId][self::REALMEM] += $realMemory;
  70. $this->_timers[$timerId][self::REALMEM] -= $this->_timers[$timerId][self::REALMEM_START];
  71. $this->_timers[$timerId][self::EMALLOC] += $emallocMemory;
  72. $this->_timers[$timerId][self::EMALLOC] -= $this->_timers[$timerId][self::EMALLOC_START];
  73. }
  74. /**
  75. * Get timer statistics data by timer id
  76. *
  77. * @param string $timerId
  78. * @return array
  79. * @throws \InvalidArgumentException if timer doesn't exist
  80. */
  81. public function get($timerId)
  82. {
  83. if (empty($this->_timers[$timerId])) {
  84. throw new \InvalidArgumentException(sprintf('Timer "%s" doesn\'t exist.', $timerId));
  85. }
  86. return $this->_timers[$timerId];
  87. }
  88. /**
  89. * Retrieve statistics on specified timer
  90. *
  91. * @param string $timerId
  92. * @param string $key Information to return
  93. * @return string|bool|int|float
  94. * @throws \InvalidArgumentException
  95. */
  96. public function fetch($timerId, $key)
  97. {
  98. if ($key === self::ID) {
  99. return $timerId;
  100. }
  101. if (empty($this->_timers[$timerId])) {
  102. throw new \InvalidArgumentException(sprintf('Timer "%s" doesn\'t exist.', $timerId));
  103. }
  104. /* AVG = TIME / COUNT */
  105. $isAvg = $key == self::AVG;
  106. if ($isAvg) {
  107. $key = self::TIME;
  108. }
  109. if (!isset($this->_timers[$timerId][$key])) {
  110. throw new \InvalidArgumentException(sprintf('Timer "%s" doesn\'t have value for "%s".', $timerId, $key));
  111. }
  112. $result = $this->_timers[$timerId][$key];
  113. if ($key == self::TIME && $this->_timers[$timerId][self::START] !== false) {
  114. $result += microtime(true) - $this->_timers[$timerId][self::START];
  115. }
  116. if ($isAvg) {
  117. $count = $this->_timers[$timerId][self::COUNT];
  118. if ($count) {
  119. $result = $result / $count;
  120. }
  121. }
  122. return $result;
  123. }
  124. /**
  125. * Clear collected statistics for specified timer or for all timers if timer id is omitted
  126. *
  127. * @param string|null $timerId
  128. * @return void
  129. */
  130. public function clear($timerId = null)
  131. {
  132. if ($timerId) {
  133. unset($this->_timers[$timerId]);
  134. } else {
  135. $this->_timers = [];
  136. }
  137. }
  138. /**
  139. * Get ordered list of timer ids filtered by thresholds and pcre pattern
  140. *
  141. * @param array|null $thresholds
  142. * @param string|null $filterPattern
  143. * @return array
  144. */
  145. public function getFilteredTimerIds(array $thresholds = null, $filterPattern = null)
  146. {
  147. $timerIds = $this->_getOrderedTimerIds();
  148. if (!$thresholds && !$filterPattern) {
  149. return $timerIds;
  150. }
  151. $thresholds = (array)$thresholds;
  152. $result = [];
  153. foreach ($timerIds as $timerId) {
  154. /* Filter by pattern */
  155. if ($filterPattern && !preg_match($filterPattern, $timerId)) {
  156. continue;
  157. }
  158. /* Filter by thresholds */
  159. $match = true;
  160. foreach ($thresholds as $fetchKey => $minMatchValue) {
  161. $match = $this->fetch($timerId, $fetchKey) >= $minMatchValue;
  162. if ($match) {
  163. break;
  164. }
  165. }
  166. if ($match) {
  167. $result[] = $timerId;
  168. }
  169. }
  170. return $result;
  171. }
  172. /**
  173. * Get ordered list of timer ids
  174. *
  175. * @return array
  176. */
  177. protected function _getOrderedTimerIds()
  178. {
  179. $timerIds = array_keys($this->_timers);
  180. if (count($timerIds) <= 2) {
  181. /* No sorting needed */
  182. return $timerIds;
  183. }
  184. /* Prepare PCRE once to use it inside the loop body */
  185. $nestingSep = preg_quote(Profiler::NESTING_SEPARATOR, '/');
  186. $patternLastTimerId = '/' . $nestingSep . '(?:.(?!' . $nestingSep . '))+$/';
  187. $prevTimerId = $timerIds[0];
  188. $result = [$prevTimerId];
  189. for ($i = 1; $i < count($timerIds); $i++) {
  190. $timerId = $timerIds[$i];
  191. /* Skip already added timer */
  192. if (!$timerId) {
  193. continue;
  194. }
  195. /* Loop over all timers that need to be closed under previous timer */
  196. while (strpos($timerId, $prevTimerId . Profiler::NESTING_SEPARATOR) !== 0) {
  197. /* Add to result all timers nested in the previous timer */
  198. for ($j = $i + 1; $j < count($timerIds); $j++) {
  199. if (strpos($timerIds[$j], $prevTimerId . Profiler::NESTING_SEPARATOR) === 0) {
  200. $result[] = $timerIds[$j];
  201. /* Mark timer as already added */
  202. $timerIds[$j] = null;
  203. }
  204. }
  205. /* Go to upper level timer */
  206. $count = 0;
  207. $prevTimerId = preg_replace($patternLastTimerId, '', $prevTimerId, -1, $count);
  208. /* Break the loop if no replacements was done. It is possible when we are */
  209. /* working with top level (root) item */
  210. if (!$count) {
  211. break;
  212. }
  213. }
  214. /* Add current timer to the result */
  215. $result[] = $timerId;
  216. $prevTimerId = $timerId;
  217. }
  218. return $result;
  219. }
  220. }