PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Profiler/Storage/RedisProfilerStorage.php

http://github.com/snc/SncRedisBundle
PHP | 395 lines | 203 code | 67 blank | 125 comment | 34 complexity | 8182cd2aecf256985403acf47c70bea2 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the SncRedisBundle package.
  4. *
  5. * (c) Henrik Westphal <henrik.westphal@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Snc\RedisBundle\Profiler\Storage;
  11. use Symfony\Component\HttpKernel\Profiler\Profile;
  12. use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface;
  13. /**
  14. * @deprecated since version 3.2.0 as \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface is now marked as internal
  15. *
  16. * RedisProfilerStorage stores profiling information in Redis.
  17. *
  18. * This class is a reimplementation of
  19. * the RedisProfilerStorage class from Symfony 2.8
  20. *
  21. * @author Andrej Hudec <pulzarraider@gmail.com>
  22. * @author Stephane PY <py.stephane1@gmail.com>
  23. * @author Gijs van Lammeren <gijsvanlammeren@gmail.com>
  24. */
  25. class RedisProfilerStorage implements ProfilerStorageInterface
  26. {
  27. /**
  28. * Key prefix
  29. *
  30. * @var string
  31. */
  32. const TOKEN_PREFIX = 'sf_prof_';
  33. /**
  34. * Index token name
  35. *
  36. * @var string
  37. */
  38. const INDEX_NAME = 'index';
  39. const REDIS_SERIALIZER_NONE = 0;
  40. const REDIS_SERIALIZER_PHP = 1;
  41. /**
  42. * The redis client.
  43. *
  44. * @var \Predis\Client|\Redis
  45. */
  46. protected $redis;
  47. /**
  48. * TTL for profiler data (in seconds).
  49. *
  50. * @var int
  51. */
  52. protected $lifetime;
  53. /**
  54. * Constructor.
  55. *
  56. * @param \Predis\Client|\Redis $redis Redis database connection
  57. * @param int $lifetime The lifetime to use for the purge
  58. */
  59. public function __construct($redis, $lifetime = 86400)
  60. {
  61. $this->redis = $redis;
  62. $this->lifetime = (int) $lifetime;
  63. }
  64. /**
  65. * {@inheritdoc}
  66. */
  67. public function find($ip, $url, $limit, $method, $start = null, $end = null)
  68. {
  69. $indexName = $this->getIndexName();
  70. if (!$indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE)) {
  71. return array();
  72. }
  73. $profileList = array_reverse(explode("\n", $indexContent));
  74. $result = array();
  75. foreach ($profileList as $item) {
  76. if ($limit === 0) {
  77. break;
  78. }
  79. if ($item == '') {
  80. continue;
  81. }
  82. $values = explode("\t", $item, 7);
  83. list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = $values;
  84. $statusCode = isset($values[6]) ? $values[6] : null;
  85. $itemTime = (int) $itemTime;
  86. if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) {
  87. continue;
  88. }
  89. if (!empty($start) && $itemTime < $start) {
  90. continue;
  91. }
  92. if (!empty($end) && $itemTime > $end) {
  93. continue;
  94. }
  95. $result[] = array(
  96. 'token' => $itemToken,
  97. 'ip' => $itemIp,
  98. 'method' => $itemMethod,
  99. 'url' => $itemUrl,
  100. 'time' => $itemTime,
  101. 'parent' => $itemParent,
  102. 'status_code' => $statusCode,
  103. );
  104. --$limit;
  105. }
  106. return $result;
  107. }
  108. /**
  109. * {@inheritdoc}
  110. */
  111. public function purge()
  112. {
  113. // delete only items from index
  114. $indexName = $this->getIndexName();
  115. $indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE);
  116. if (!$indexContent) {
  117. return false;
  118. }
  119. $profileList = explode("\n", $indexContent);
  120. $result = array();
  121. foreach ($profileList as $item) {
  122. if ($item == '') {
  123. continue;
  124. }
  125. if (false !== $pos = strpos($item, "\t")) {
  126. $result[] = $this->getItemName(substr($item, 0, $pos));
  127. }
  128. }
  129. $result[] = $indexName;
  130. return $this->delete($result);
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function read($token)
  136. {
  137. if (empty($token)) {
  138. return false;
  139. }
  140. $profile = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP);
  141. if ($profile) {
  142. $profile = $this->createProfileFromData($token, $profile);
  143. }
  144. return $profile;
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function write(Profile $profile)
  150. {
  151. $data = array(
  152. 'token' => $profile->getToken(),
  153. 'parent' => $profile->getParentToken(),
  154. 'children' => array_map(function ($p) {
  155. return $p->getToken();
  156. }, $profile->getChildren()),
  157. 'data' => $profile->getCollectors(),
  158. 'ip' => $profile->getIp(),
  159. 'method' => $profile->getMethod(),
  160. 'url' => $profile->getUrl(),
  161. 'time' => $profile->getTime(),
  162. );
  163. $profileIndexed = $this->getValue($this->getItemName($profile->getToken()));
  164. if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime, self::REDIS_SERIALIZER_PHP)) {
  165. if (!$profileIndexed) {
  166. // Add to index
  167. $indexName = $this->getIndexName();
  168. $indexRow = implode("\t", array(
  169. $profile->getToken(),
  170. $profile->getIp(),
  171. $profile->getMethod(),
  172. $profile->getUrl(),
  173. $profile->getTime(),
  174. $profile->getParentToken(),
  175. $profile->getStatusCode(),
  176. )) . "\n";
  177. return $this->appendValue($indexName, $indexRow, $this->lifetime);
  178. }
  179. return true;
  180. }
  181. return false;
  182. }
  183. /**
  184. * Creates a Profile.
  185. *
  186. * @param string $token
  187. * @param array $data
  188. * @param Profile $parent
  189. * @return Profile
  190. */
  191. protected function createProfileFromData($token, $data, $parent = null)
  192. {
  193. $profile = new Profile($token);
  194. $profile->setIp($data['ip']);
  195. $profile->setMethod($data['method']);
  196. $profile->setUrl($data['url']);
  197. $profile->setTime($data['time']);
  198. $profile->setCollectors($data['data']);
  199. if (!$parent && $data['parent']) {
  200. $parent = $this->read($data['parent']);
  201. }
  202. if ($parent) {
  203. $profile->setParent($parent);
  204. }
  205. foreach ($data['children'] as $token) {
  206. if (!$token) {
  207. continue;
  208. }
  209. if (!$childProfileData = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP)) {
  210. continue;
  211. }
  212. $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile));
  213. }
  214. return $profile;
  215. }
  216. /**
  217. * Gets the item name.
  218. *
  219. * @param string $token
  220. *
  221. * @return string
  222. */
  223. protected function getItemName($token)
  224. {
  225. $name = $this->prefixKey($token);
  226. if ($this->isItemNameValid($name)) {
  227. return $name;
  228. }
  229. return false;
  230. }
  231. /**
  232. * Gets the name of the index.
  233. *
  234. * @return string
  235. */
  236. protected function getIndexName()
  237. {
  238. $name = $this->prefixKey(self::INDEX_NAME);
  239. if ($this->isItemNameValid($name)) {
  240. return $name;
  241. }
  242. return false;
  243. }
  244. /**
  245. * Check if the item name is valid.
  246. *
  247. * @param string $name
  248. * @throws \RuntimeException
  249. * @return bool
  250. */
  251. protected function isItemNameValid($name)
  252. {
  253. $length = strlen($name);
  254. if ($length > 2147483648) {
  255. throw new \RuntimeException(sprintf('The Redis item key "%s" is too long (%s bytes). Allowed maximum size is 2^31 bytes.', $name, $length));
  256. }
  257. return true;
  258. }
  259. /**
  260. * Retrieves an item from the Redis server.
  261. *
  262. * @param string $key
  263. * @param int $serializer
  264. *
  265. * @return mixed
  266. */
  267. protected function getValue($key, $serializer = self::REDIS_SERIALIZER_NONE)
  268. {
  269. $value = $this->redis->get($key);
  270. if ($value && (self::REDIS_SERIALIZER_PHP === $serializer)) {
  271. $value = unserialize($value);
  272. }
  273. return $value;
  274. }
  275. /**
  276. * Stores an item on the Redis server under the specified key.
  277. *
  278. * @param string $key
  279. * @param mixed $value
  280. * @param int $expiration
  281. * @param int $serializer
  282. *
  283. * @return bool
  284. */
  285. protected function setValue($key, $value, $expiration = 0, $serializer = self::REDIS_SERIALIZER_NONE)
  286. {
  287. if (self::REDIS_SERIALIZER_PHP === $serializer) {
  288. $value = serialize($value);
  289. }
  290. return $this->redis->setex($key, $expiration, $value);
  291. }
  292. /**
  293. * Appends data to an existing item on the Redis server.
  294. *
  295. * @param string $key
  296. * @param string $value
  297. * @param int $expiration
  298. * @return bool
  299. */
  300. protected function appendValue($key, $value, $expiration = 0)
  301. {
  302. if ($this->redis->exists($key)) {
  303. $this->redis->append($key, $value);
  304. return $this->redis->expire($key, $expiration);
  305. }
  306. return $this->redis->setex($key, $expiration, $value);
  307. }
  308. /**
  309. * Removes the specified keys.
  310. *
  311. * @param array $keys
  312. * @return bool
  313. */
  314. protected function delete(array $keys)
  315. {
  316. return (bool) $this->redis->del($keys);
  317. }
  318. /**
  319. * Prefixes the key.
  320. *
  321. * @param string $key
  322. * @return string
  323. */
  324. protected function prefixKey($key)
  325. {
  326. return self::TOKEN_PREFIX . $key;
  327. }
  328. }