Profiler.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <?php
  2. /**
  3. * Static class that represents profiling tool
  4. *
  5. * Copyright © Magento, Inc. All rights reserved.
  6. * See COPYING.txt for license details.
  7. */
  8. namespace Magento\Framework;
  9. use Magento\Framework\Profiler\Driver\Factory;
  10. use Magento\Framework\Profiler\DriverInterface;
  11. /**
  12. * @api
  13. * @since 100.0.2
  14. */
  15. class Profiler
  16. {
  17. /**
  18. * Separator literal to assemble timer identifier from timer names
  19. */
  20. const NESTING_SEPARATOR = '->';
  21. /**
  22. * Whether profiling is active or not
  23. *
  24. * @var bool
  25. */
  26. private static $_enabled = false;
  27. /**
  28. * Nesting path that represents namespace to resolve timer names
  29. *
  30. * @var string[]
  31. */
  32. private static $_currentPath = [];
  33. /**
  34. * Count of elements in $_currentPath
  35. *
  36. * @var int
  37. */
  38. private static $_pathCount = 0;
  39. /**
  40. * Index for counting of $_pathCount for timer names
  41. *
  42. * @var array
  43. */
  44. private static $_pathIndex = [];
  45. /**
  46. * Collection for profiler drivers.
  47. *
  48. * @var DriverInterface[]
  49. */
  50. private static $_drivers = [];
  51. /**
  52. * List of default tags.
  53. *
  54. * @var array
  55. */
  56. private static $_defaultTags = [];
  57. /**
  58. * Collection of tag filters.
  59. *
  60. * @var array
  61. */
  62. private static $_tagFilters = [];
  63. /**
  64. * Has tag filters flag for faster checks of filters availability.
  65. *
  66. * @var bool
  67. */
  68. private static $_hasTagFilters = false;
  69. /**
  70. * Set default tags
  71. *
  72. * @param array $tags
  73. * @return void
  74. */
  75. public static function setDefaultTags(array $tags)
  76. {
  77. self::$_defaultTags = $tags;
  78. }
  79. /**
  80. * Add tag filter.
  81. *
  82. * @param string $tagName
  83. * @param string $tagValue
  84. * @return void
  85. */
  86. public static function addTagFilter($tagName, $tagValue)
  87. {
  88. if (!isset(self::$_tagFilters[$tagName])) {
  89. self::$_tagFilters[$tagName] = [];
  90. }
  91. self::$_tagFilters[$tagName][] = $tagValue;
  92. self::$_hasTagFilters = true;
  93. }
  94. /**
  95. * Check tags with tag filters.
  96. *
  97. * @param array|null $tags
  98. * @return bool
  99. */
  100. private static function _checkTags(array $tags = null)
  101. {
  102. if (self::$_hasTagFilters) {
  103. if (is_array($tags)) {
  104. $keysToCheck = array_intersect(array_keys(self::$_tagFilters), array_keys($tags));
  105. if ($keysToCheck) {
  106. foreach ($keysToCheck as $keyToCheck) {
  107. if (in_array($tags[$keyToCheck], self::$_tagFilters[$keyToCheck])) {
  108. return true;
  109. }
  110. }
  111. }
  112. }
  113. return false;
  114. }
  115. return true;
  116. }
  117. /**
  118. * Add profiler driver.
  119. *
  120. * @param DriverInterface $driver
  121. * @return void
  122. */
  123. public static function add(DriverInterface $driver)
  124. {
  125. self::$_drivers[] = $driver;
  126. self::enable();
  127. }
  128. /**
  129. * Retrieve unique identifier among all timers
  130. *
  131. * @param string|null $timerName Timer name
  132. * @return string
  133. */
  134. private static function _getTimerId($timerName = null)
  135. {
  136. if (!self::$_currentPath) {
  137. return (string)$timerName;
  138. } elseif ($timerName) {
  139. return implode(self::NESTING_SEPARATOR, self::$_currentPath) . self::NESTING_SEPARATOR . $timerName;
  140. } else {
  141. return implode(self::NESTING_SEPARATOR, self::$_currentPath);
  142. }
  143. }
  144. /**
  145. * Get tags list.
  146. *
  147. * @param array|null $tags
  148. * @return array|null
  149. */
  150. private static function _getTags(array $tags = null)
  151. {
  152. if (self::$_defaultTags) {
  153. return (array)$tags + self::$_defaultTags;
  154. } else {
  155. return $tags;
  156. }
  157. }
  158. /**
  159. * Enable profiling.
  160. *
  161. * Any call to profiler does nothing until profiler is enabled.
  162. *
  163. * @return void
  164. */
  165. public static function enable()
  166. {
  167. self::$_enabled = true;
  168. }
  169. /**
  170. * Disable profiling.
  171. *
  172. * Any call to profiler is silently ignored while profiler is disabled.
  173. *
  174. * @return void
  175. */
  176. public static function disable()
  177. {
  178. self::$_enabled = false;
  179. }
  180. /**
  181. * Get profiler enable status.
  182. *
  183. * @return bool
  184. */
  185. public static function isEnabled()
  186. {
  187. return self::$_enabled;
  188. }
  189. /**
  190. * Clear collected statistics for specified timer or for whole profiler if timer id is omitted
  191. *
  192. * @param string|null $timerName
  193. * @return void
  194. * @throws \InvalidArgumentException
  195. */
  196. public static function clear($timerName = null)
  197. {
  198. if (strpos($timerName, self::NESTING_SEPARATOR) !== false) {
  199. throw new \InvalidArgumentException('Timer name must not contain a nesting separator.');
  200. }
  201. $timerId = self::_getTimerId($timerName);
  202. /** @var DriverInterface $driver */
  203. foreach (self::$_drivers as $driver) {
  204. $driver->clear($timerId);
  205. }
  206. }
  207. /**
  208. * Reset profiler to initial state
  209. *
  210. * @return void
  211. */
  212. public static function reset()
  213. {
  214. self::clear();
  215. self::$_enabled = false;
  216. self::$_currentPath = [];
  217. self::$_tagFilters = [];
  218. self::$_defaultTags = [];
  219. self::$_hasTagFilters = false;
  220. self::$_drivers = [];
  221. self::$_pathCount = 0;
  222. self::$_pathIndex = [];
  223. }
  224. /**
  225. * Start collecting statistics for specified timer
  226. *
  227. * @param string $timerName
  228. * @param array|null $tags
  229. * @return void
  230. * @throws \InvalidArgumentException
  231. */
  232. public static function start($timerName, array $tags = null)
  233. {
  234. if (!self::$_enabled) {
  235. return;
  236. }
  237. $tags = self::_getTags($tags);
  238. if (!self::_checkTags($tags)) {
  239. return;
  240. }
  241. if (strpos($timerName, self::NESTING_SEPARATOR) !== false) {
  242. throw new \InvalidArgumentException('Timer name must not contain a nesting separator.');
  243. }
  244. $timerId = self::_getTimerId($timerName);
  245. /** @var DriverInterface $driver */
  246. foreach (self::$_drivers as $driver) {
  247. $driver->start($timerId, $tags);
  248. }
  249. /* Continue collecting timers statistics under the latest started one */
  250. self::$_currentPath[] = $timerName;
  251. self::$_pathCount++;
  252. self::$_pathIndex[$timerName][] = self::$_pathCount;
  253. }
  254. /**
  255. * Stop recording statistics for specified timer.
  256. *
  257. * Call with no arguments to stop the recently started timer.
  258. * Only the latest started timer can be stopped.
  259. *
  260. * @param string|null $timerName
  261. * @return void
  262. * @throws \InvalidArgumentException
  263. */
  264. public static function stop($timerName = null)
  265. {
  266. if (!self::$_enabled || !self::_checkTags(self::_getTags())) {
  267. return;
  268. }
  269. if ($timerName === null) {
  270. $timersToStop = 1;
  271. } else {
  272. $timerPosition = false;
  273. if (!empty(self::$_pathIndex[$timerName])) {
  274. $timerPosition = array_pop(self::$_pathIndex[$timerName]);
  275. }
  276. if ($timerPosition === false) {
  277. throw new \InvalidArgumentException(sprintf('Timer "%s" has not been started.', $timerName));
  278. } elseif ($timerPosition === 1) {
  279. $timersToStop = 1;
  280. } else {
  281. $timersToStop = self::$_pathCount + 1 - $timerPosition;
  282. }
  283. }
  284. for ($i = 0; $i < $timersToStop; $i++) {
  285. $timerId = self::_getTimerId();
  286. /** @var DriverInterface $driver */
  287. foreach (self::$_drivers as $driver) {
  288. $driver->stop($timerId);
  289. }
  290. /* Move one level up in timers nesting tree */
  291. array_pop(self::$_currentPath);
  292. self::$_pathCount--;
  293. }
  294. }
  295. /**
  296. * Init profiler
  297. *
  298. * @param array|string $config
  299. * @param string $baseDir
  300. * @param bool $isAjax
  301. * @return void
  302. */
  303. public static function applyConfig($config, $baseDir, $isAjax = false)
  304. {
  305. $config = self::_parseConfig($config, $baseDir, $isAjax);
  306. if ($config['driverConfigs']) {
  307. foreach ($config['driverConfigs'] as $driverConfig) {
  308. self::add($config['driverFactory']->create($driverConfig));
  309. }
  310. }
  311. foreach ($config['tagFilters'] as $tagName => $tagValue) {
  312. self::addTagFilter($tagName, $tagValue);
  313. }
  314. }
  315. /**
  316. * Parses config
  317. *
  318. * @param array|string $profilerConfig
  319. * @param string $baseDir
  320. * @param bool $isAjax
  321. * @return array
  322. * @SuppressWarnings(PHPMD.NPathComplexity)
  323. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  324. */
  325. protected static function _parseConfig($profilerConfig, $baseDir, $isAjax)
  326. {
  327. $config = ['baseDir' => $baseDir, 'tagFilters' => []];
  328. if (is_scalar($profilerConfig)) {
  329. $config['drivers'] = [
  330. ['output' => is_numeric($profilerConfig) ? 'html' : $profilerConfig],
  331. ];
  332. } else {
  333. $config = array_merge($config, $profilerConfig);
  334. }
  335. $driverConfigs = (array)(isset($config['drivers']) ? $config['drivers'] : []);
  336. $driverFactory = isset($config['driverFactory']) ? $config['driverFactory'] : new Factory();
  337. $tagFilters = (array)(isset($config['tagFilters']) ? $config['tagFilters'] : []);
  338. $result = [
  339. 'driverConfigs' => self::_parseDriverConfigs($driverConfigs, $config['baseDir']),
  340. 'driverFactory' => $driverFactory,
  341. 'tagFilters' => $tagFilters,
  342. 'baseDir' => $config['baseDir'],
  343. ];
  344. return $result;
  345. }
  346. /**
  347. * Parses list of driver configurations
  348. *
  349. * @param array $driverConfigs
  350. * @param string $baseDir
  351. * @return array
  352. */
  353. protected static function _parseDriverConfigs(array $driverConfigs, $baseDir)
  354. {
  355. $result = [];
  356. foreach ($driverConfigs as $code => $driverConfig) {
  357. $driverConfig = self::_parseDriverConfig($driverConfig);
  358. if ($driverConfig === false) {
  359. continue;
  360. }
  361. if (!isset($driverConfig['type']) && !is_numeric($code)) {
  362. $driverConfig['type'] = $code;
  363. }
  364. if (!isset($driverConfig['baseDir']) && $baseDir) {
  365. $driverConfig['baseDir'] = $baseDir;
  366. }
  367. $result[] = $driverConfig;
  368. }
  369. return $result;
  370. }
  371. /**
  372. * Parses driver config
  373. *
  374. * @param mixed $driverConfig
  375. * @return array|false
  376. */
  377. protected static function _parseDriverConfig($driverConfig)
  378. {
  379. $result = false;
  380. if (is_array($driverConfig)) {
  381. $result = $driverConfig;
  382. } elseif (is_scalar($driverConfig) && $driverConfig) {
  383. if (is_numeric($driverConfig)) {
  384. $result = [];
  385. } else {
  386. $result = ['type' => $driverConfig];
  387. }
  388. }
  389. return $result;
  390. }
  391. }