Skip to content

[WIP] Master profiler component #20502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
284 changes: 284 additions & 0 deletions src/Symfony/Component/Console/Profiler/FileProfilerStorage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\Profiler;

/**
* Storage for profiler using files.
*
* @author Alexandre Salomé <alexandre.salome@gmail.com>
*/
class FileProfilerStorage implements ProfilerStorageInterface
{
/**
* Folder where profiler data are stored.
*
* @var string
*/
private $folder;

/**
* Constructs the file storage using a "dsn-like" path.
*
* Example : "file:/path/to/the/storage/folder"
*
* @param string $dsn The DSN
*
* @throws \RuntimeException
*/
public function __construct($dsn)
{
if (0 !== strpos($dsn, 'file:')) {
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));
}
$this->folder = substr($dsn, 5);

if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) {
throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
}
}

/**
* {@inheritdoc}
*/
public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null)
{
$file = $this->getIndexFilename();

if (!file_exists($file)) {
return array();
}

$file = fopen($file, 'r');
fseek($file, 0, SEEK_END);

$result = array();
while (count($result) < $limit && $line = $this->readLineFromFile($file)) {
$values = str_getcsv($line);
list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values;
$csvTime = (int) $csvTime;

if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) {
continue;
}

if (!empty($start) && $csvTime < $start) {
continue;
}

if (!empty($end) && $csvTime > $end) {
continue;
}

$result[$csvToken] = array(
'token' => $csvToken,
'ip' => $csvIp,
'method' => $csvMethod,
'url' => $csvUrl,
'time' => $csvTime,
'parent' => $csvParent,
'status_code' => $csvStatusCode,
);
}

fclose($file);

return array_values($result);
}

/**
* {@inheritdoc}
*/
public function purge()
{
$flags = \FilesystemIterator::SKIP_DOTS;
$iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);

foreach ($iterator as $file) {
if (is_file($file)) {
unlink($file);
} else {
rmdir($file);
}
}
}

/**
* {@inheritdoc}
*/
public function read($token)
{
if (!$token || !file_exists($file = $this->getFilename($token))) {
return;
}

return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
}

/**
* {@inheritdoc}
*
* @throws \RuntimeException
*/
public function write(Profile $profile)
{
$file = $this->getFilename($profile->getToken());

$profileIndexed = is_file($file);
if (!$profileIndexed) {
// Create directory
$dir = dirname($file);
if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
}
}

// Store profile
$data = array(
'token' => $profile->getToken(),
'parent' => $profile->getParentToken(),
'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()),
'data' => $profile->getCollectors(),
'ip' => $profile->getIp(),
'method' => $profile->getMethod(),
'url' => $profile->getUrl(),
'time' => $profile->getTime(),
'status_code' => $profile->getStatusCode(),
);

if (false === file_put_contents($file, serialize($data))) {
return false;
}

if (!$profileIndexed) {
// Add to index
if (false === $file = fopen($this->getIndexFilename(), 'a')) {
return false;
}

fputcsv($file, array(
$profile->getToken(),
$profile->getIp(),
$profile->getMethod(),
$profile->getUrl(),
$profile->getTime(),
$profile->getParentToken(),
$profile->getStatusCode(),
));
fclose($file);
}

return true;
}

/**
* Gets filename to store data, associated to the token.
*
* @param string $token
*
* @return string The profile filename
*/
protected function getFilename($token)
{
// Uses 4 last characters, because first are mostly the same.
$folderA = substr($token, -2, 2);
$folderB = substr($token, -4, 2);

return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token;
}

/**
* Gets the index filename.
*
* @return string The index filename
*/
protected function getIndexFilename()
{
return $this->folder.'/index.csv';
}

/**
* Reads a line in the file, backward.
*
* This function automatically skips the empty lines and do not include the line return in result value.
*
* @param resource $file The file resource, with the pointer placed at the end of the line to read
*
* @return mixed A string representing the line or null if beginning of file is reached
*/
protected function readLineFromFile($file)
{
$line = '';
$position = ftell($file);

if (0 === $position) {
return;
}

while (true) {
$chunkSize = min($position, 1024);
$position -= $chunkSize;
fseek($file, $position);

if (0 === $chunkSize) {
// bof reached
break;
}

$buffer = fread($file, $chunkSize);

if (false === ($upTo = strrpos($buffer, "\n"))) {
$line = $buffer.$line;
continue;
}

$position += $upTo;
$line = substr($buffer, $upTo + 1).$line;
fseek($file, max(0, $position), SEEK_SET);

if ('' !== $line) {
break;
}
}

return '' === $line ? null : $line;
}

protected function createProfileFromData($token, $data, $parent = null)
{
$profile = new Profile($token);
$profile->setIp($data['ip']);
$profile->setMethod($data['method']);
$profile->setUrl($data['url']);
$profile->setTime($data['time']);
$profile->setStatusCode($data['status_code']);
$profile->setCollectors($data['data']);

if (!$parent && $data['parent']) {
$parent = $this->read($data['parent']);
}

if ($parent) {
$profile->setParent($parent);
}

foreach ($data['children'] as $token) {
if (!$token || !file_exists($file = $this->getFilename($token))) {
continue;
}

$profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
}

return $profile;
}
}
Loading