annotate vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\HttpKernel\Profiler;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Storage for profiler using files.
Chris@0 16 *
Chris@0 17 * @author Alexandre Salomé <alexandre.salome@gmail.com>
Chris@0 18 */
Chris@0 19 class FileProfilerStorage implements ProfilerStorageInterface
Chris@0 20 {
Chris@0 21 /**
Chris@0 22 * Folder where profiler data are stored.
Chris@0 23 *
Chris@0 24 * @var string
Chris@0 25 */
Chris@0 26 private $folder;
Chris@0 27
Chris@0 28 /**
Chris@0 29 * Constructs the file storage using a "dsn-like" path.
Chris@0 30 *
Chris@0 31 * Example : "file:/path/to/the/storage/folder"
Chris@0 32 *
Chris@0 33 * @param string $dsn The DSN
Chris@0 34 *
Chris@0 35 * @throws \RuntimeException
Chris@0 36 */
Chris@0 37 public function __construct($dsn)
Chris@0 38 {
Chris@0 39 if (0 !== strpos($dsn, 'file:')) {
Chris@0 40 throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn));
Chris@0 41 }
Chris@0 42 $this->folder = substr($dsn, 5);
Chris@0 43
Chris@0 44 if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) {
Chris@0 45 throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
Chris@0 46 }
Chris@0 47 }
Chris@0 48
Chris@0 49 /**
Chris@0 50 * {@inheritdoc}
Chris@0 51 */
Chris@0 52 public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null)
Chris@0 53 {
Chris@0 54 $file = $this->getIndexFilename();
Chris@0 55
Chris@0 56 if (!file_exists($file)) {
Chris@17 57 return [];
Chris@0 58 }
Chris@0 59
Chris@0 60 $file = fopen($file, 'r');
Chris@0 61 fseek($file, 0, SEEK_END);
Chris@0 62
Chris@17 63 $result = [];
Chris@17 64 while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
Chris@0 65 $values = str_getcsv($line);
Chris@0 66 list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values;
Chris@0 67 $csvTime = (int) $csvTime;
Chris@0 68
Chris@0 69 if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) {
Chris@0 70 continue;
Chris@0 71 }
Chris@0 72
Chris@0 73 if (!empty($start) && $csvTime < $start) {
Chris@0 74 continue;
Chris@0 75 }
Chris@0 76
Chris@0 77 if (!empty($end) && $csvTime > $end) {
Chris@0 78 continue;
Chris@0 79 }
Chris@0 80
Chris@17 81 $result[$csvToken] = [
Chris@0 82 'token' => $csvToken,
Chris@0 83 'ip' => $csvIp,
Chris@0 84 'method' => $csvMethod,
Chris@0 85 'url' => $csvUrl,
Chris@0 86 'time' => $csvTime,
Chris@0 87 'parent' => $csvParent,
Chris@0 88 'status_code' => $csvStatusCode,
Chris@17 89 ];
Chris@0 90 }
Chris@0 91
Chris@0 92 fclose($file);
Chris@0 93
Chris@0 94 return array_values($result);
Chris@0 95 }
Chris@0 96
Chris@0 97 /**
Chris@0 98 * {@inheritdoc}
Chris@0 99 */
Chris@0 100 public function purge()
Chris@0 101 {
Chris@0 102 $flags = \FilesystemIterator::SKIP_DOTS;
Chris@0 103 $iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
Chris@0 104 $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
Chris@0 105
Chris@0 106 foreach ($iterator as $file) {
Chris@0 107 if (is_file($file)) {
Chris@0 108 unlink($file);
Chris@0 109 } else {
Chris@0 110 rmdir($file);
Chris@0 111 }
Chris@0 112 }
Chris@0 113 }
Chris@0 114
Chris@0 115 /**
Chris@0 116 * {@inheritdoc}
Chris@0 117 */
Chris@0 118 public function read($token)
Chris@0 119 {
Chris@0 120 if (!$token || !file_exists($file = $this->getFilename($token))) {
Chris@0 121 return;
Chris@0 122 }
Chris@0 123
Chris@0 124 return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
Chris@0 125 }
Chris@0 126
Chris@0 127 /**
Chris@0 128 * {@inheritdoc}
Chris@0 129 *
Chris@0 130 * @throws \RuntimeException
Chris@0 131 */
Chris@0 132 public function write(Profile $profile)
Chris@0 133 {
Chris@0 134 $file = $this->getFilename($profile->getToken());
Chris@0 135
Chris@0 136 $profileIndexed = is_file($file);
Chris@0 137 if (!$profileIndexed) {
Chris@0 138 // Create directory
Chris@17 139 $dir = \dirname($file);
Chris@0 140 if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
Chris@0 141 throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
Chris@0 142 }
Chris@0 143 }
Chris@0 144
Chris@14 145 $profileToken = $profile->getToken();
Chris@14 146 // when there are errors in sub-requests, the parent and/or children tokens
Chris@14 147 // may equal the profile token, resulting in infinite loops
Chris@14 148 $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
Chris@14 149 $childrenToken = array_filter(array_map(function ($p) use ($profileToken) {
Chris@14 150 return $profileToken !== $p->getToken() ? $p->getToken() : null;
Chris@14 151 }, $profile->getChildren()));
Chris@14 152
Chris@0 153 // Store profile
Chris@17 154 $data = [
Chris@14 155 'token' => $profileToken,
Chris@14 156 'parent' => $parentToken,
Chris@14 157 'children' => $childrenToken,
Chris@0 158 'data' => $profile->getCollectors(),
Chris@0 159 'ip' => $profile->getIp(),
Chris@0 160 'method' => $profile->getMethod(),
Chris@0 161 'url' => $profile->getUrl(),
Chris@0 162 'time' => $profile->getTime(),
Chris@0 163 'status_code' => $profile->getStatusCode(),
Chris@17 164 ];
Chris@0 165
Chris@0 166 if (false === file_put_contents($file, serialize($data))) {
Chris@0 167 return false;
Chris@0 168 }
Chris@0 169
Chris@0 170 if (!$profileIndexed) {
Chris@0 171 // Add to index
Chris@0 172 if (false === $file = fopen($this->getIndexFilename(), 'a')) {
Chris@0 173 return false;
Chris@0 174 }
Chris@0 175
Chris@17 176 fputcsv($file, [
Chris@0 177 $profile->getToken(),
Chris@0 178 $profile->getIp(),
Chris@0 179 $profile->getMethod(),
Chris@0 180 $profile->getUrl(),
Chris@0 181 $profile->getTime(),
Chris@0 182 $profile->getParentToken(),
Chris@0 183 $profile->getStatusCode(),
Chris@17 184 ]);
Chris@0 185 fclose($file);
Chris@0 186 }
Chris@0 187
Chris@0 188 return true;
Chris@0 189 }
Chris@0 190
Chris@0 191 /**
Chris@0 192 * Gets filename to store data, associated to the token.
Chris@0 193 *
Chris@0 194 * @param string $token
Chris@0 195 *
Chris@0 196 * @return string The profile filename
Chris@0 197 */
Chris@0 198 protected function getFilename($token)
Chris@0 199 {
Chris@0 200 // Uses 4 last characters, because first are mostly the same.
Chris@0 201 $folderA = substr($token, -2, 2);
Chris@0 202 $folderB = substr($token, -4, 2);
Chris@0 203
Chris@0 204 return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token;
Chris@0 205 }
Chris@0 206
Chris@0 207 /**
Chris@0 208 * Gets the index filename.
Chris@0 209 *
Chris@0 210 * @return string The index filename
Chris@0 211 */
Chris@0 212 protected function getIndexFilename()
Chris@0 213 {
Chris@0 214 return $this->folder.'/index.csv';
Chris@0 215 }
Chris@0 216
Chris@0 217 /**
Chris@0 218 * Reads a line in the file, backward.
Chris@0 219 *
Chris@0 220 * This function automatically skips the empty lines and do not include the line return in result value.
Chris@0 221 *
Chris@0 222 * @param resource $file The file resource, with the pointer placed at the end of the line to read
Chris@0 223 *
Chris@0 224 * @return mixed A string representing the line or null if beginning of file is reached
Chris@0 225 */
Chris@0 226 protected function readLineFromFile($file)
Chris@0 227 {
Chris@0 228 $line = '';
Chris@0 229 $position = ftell($file);
Chris@0 230
Chris@0 231 if (0 === $position) {
Chris@0 232 return;
Chris@0 233 }
Chris@0 234
Chris@0 235 while (true) {
Chris@0 236 $chunkSize = min($position, 1024);
Chris@0 237 $position -= $chunkSize;
Chris@0 238 fseek($file, $position);
Chris@0 239
Chris@0 240 if (0 === $chunkSize) {
Chris@0 241 // bof reached
Chris@0 242 break;
Chris@0 243 }
Chris@0 244
Chris@0 245 $buffer = fread($file, $chunkSize);
Chris@0 246
Chris@0 247 if (false === ($upTo = strrpos($buffer, "\n"))) {
Chris@0 248 $line = $buffer.$line;
Chris@0 249 continue;
Chris@0 250 }
Chris@0 251
Chris@0 252 $position += $upTo;
Chris@0 253 $line = substr($buffer, $upTo + 1).$line;
Chris@0 254 fseek($file, max(0, $position), SEEK_SET);
Chris@0 255
Chris@0 256 if ('' !== $line) {
Chris@0 257 break;
Chris@0 258 }
Chris@0 259 }
Chris@0 260
Chris@0 261 return '' === $line ? null : $line;
Chris@0 262 }
Chris@0 263
Chris@0 264 protected function createProfileFromData($token, $data, $parent = null)
Chris@0 265 {
Chris@0 266 $profile = new Profile($token);
Chris@0 267 $profile->setIp($data['ip']);
Chris@0 268 $profile->setMethod($data['method']);
Chris@0 269 $profile->setUrl($data['url']);
Chris@0 270 $profile->setTime($data['time']);
Chris@0 271 $profile->setStatusCode($data['status_code']);
Chris@0 272 $profile->setCollectors($data['data']);
Chris@0 273
Chris@0 274 if (!$parent && $data['parent']) {
Chris@0 275 $parent = $this->read($data['parent']);
Chris@0 276 }
Chris@0 277
Chris@0 278 if ($parent) {
Chris@0 279 $profile->setParent($parent);
Chris@0 280 }
Chris@0 281
Chris@0 282 foreach ($data['children'] as $token) {
Chris@0 283 if (!$token || !file_exists($file = $this->getFilename($token))) {
Chris@0 284 continue;
Chris@0 285 }
Chris@0 286
Chris@0 287 $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
Chris@0 288 }
Chris@0 289
Chris@0 290 return $profile;
Chris@0 291 }
Chris@0 292 }