2012-02-26 00:09:51 +00:00
< ? 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 ;
use Redis ;
/**
2012-03-03 00:44:43 +00:00
* RedisProfilerStorage stores profiling information in Redis .
2012-02-26 00:09:51 +00:00
*
* @ author Andrej Hudec < pulzarraider @ gmail . com >
*/
class RedisProfilerStorage implements ProfilerStorageInterface
{
const TOKEN_PREFIX = 'sf_profiler_' ;
protected $dsn ;
protected $lifetime ;
/**
* @ var Redis
*/
private $redis ;
/**
* Constructor .
*
2012-03-03 00:44:43 +00:00
* @ param string $dsn A data source name
* @ param string $username Not used
* @ param string $password Not used
* @ param int $lifetime The lifetime to use for the purge
2012-02-26 00:09:51 +00:00
*/
public function __construct ( $dsn , $username = '' , $password = '' , $lifetime = 86400 )
{
$this -> dsn = $dsn ;
$this -> lifetime = ( int ) $lifetime ;
}
/**
* { @ inheritdoc }
*/
public function find ( $ip , $url , $limit , $method )
{
$indexName = $this -> getIndexName ();
2012-03-03 00:44:43 +00:00
if ( ! $indexContent = $this -> getValue ( $indexName , Redis :: SERIALIZER_NONE )) {
2012-02-26 00:09:51 +00:00
return array ();
}
$profileList = explode ( " \n " , $indexContent );
$result = array ();
foreach ( $profileList as $item ) {
if ( $limit === 0 ) {
break ;
}
if ( $item == '' ) {
continue ;
}
list ( $itemToken , $itemIp , $itemMethod , $itemUrl , $itemTime , $itemParent ) = explode ( " \t " , $item , 6 );
if ( $ip && false === strpos ( $itemIp , $ip ) || $url && false === strpos ( $itemUrl , $url ) || $method && false === strpos ( $itemMethod , $method )) {
continue ;
}
$result [ $itemToken ] = array (
'token' => $itemToken ,
'ip' => $itemIp ,
'method' => $itemMethod ,
'url' => $itemUrl ,
'time' => $itemTime ,
'parent' => $itemParent ,
);
-- $limit ;
}
usort ( $result , function ( $a , $b ) {
if ( $a [ 'time' ] === $b [ 'time' ]) {
return 0 ;
}
2012-03-03 00:44:43 +00:00
2012-02-26 00:09:51 +00:00
return $a [ 'time' ] > $b [ 'time' ] ? - 1 : 1 ;
});
return $result ;
}
/**
* { @ inheritdoc }
*/
public function purge ()
{
2012-03-03 00:44:43 +00:00
// delete only items from index
2012-02-26 00:09:51 +00:00
$indexName = $this -> getIndexName ();
$indexContent = $this -> getValue ( $indexName , Redis :: SERIALIZER_NONE );
if ( ! $indexContent ) {
return false ;
}
$profileList = explode ( " \n " , $indexContent );
$result = array ();
foreach ( $profileList as $item ) {
if ( $item == '' ) {
continue ;
}
2012-03-03 00:44:43 +00:00
if ( false !== $pos = strpos ( $item , " \t " )) {
2012-02-26 00:09:51 +00:00
$result [] = $this -> getItemName ( substr ( $item , 0 , $pos ));
}
}
$result [] = $indexName ;
return $this -> delete ( $result );
}
/**
* { @ inheritdoc }
*/
public function read ( $token )
{
if ( empty ( $token )) {
return false ;
}
$profile = $this -> getValue ( $this -> getItemName ( $token ), Redis :: SERIALIZER_PHP );
if ( false !== $profile ) {
$profile = $this -> createProfileFromData ( $token , $profile );
}
return $profile ;
}
/**
* { @ inheritdoc }
*/
public function write ( Profile $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 (),
);
if ( $this -> setValue ( $this -> getItemName ( $profile -> getToken ()), $data , $this -> lifetime , Redis :: SERIALIZER_PHP )) {
// Add to index
$indexName = $this -> getIndexName ();
$indexRow = implode ( " \t " , array (
$profile -> getToken (),
$profile -> getIp (),
$profile -> getMethod (),
$profile -> getUrl (),
$profile -> getTime (),
$profile -> getParentToken (),
2012-03-03 00:44:43 +00:00
)) . " \n " ;
2012-02-26 00:09:51 +00:00
return $this -> appendValue ( $indexName , $indexRow , $this -> lifetime );
}
return false ;
}
/**
2012-03-03 00:44:43 +00:00
* Internal convenience method that returns the instance of Redis .
2012-02-26 00:09:51 +00:00
*
* @ return Redis
*/
protected function getRedis ()
{
if ( null === $this -> redis ) {
if ( ! preg_match ( '#^redis://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#' , $this -> dsn , $matches )) {
2012-03-04 18:43:39 +00:00
throw new \RuntimeException ( sprintf ( 'Please check your configuration. You are trying to use Redis with an invalid dsn "%s". The expected format is "redis://[host]:port".' , $this -> dsn ));
2012-02-26 00:09:51 +00:00
}
2012-03-03 00:44:43 +00:00
$host = $matches [ 1 ] ? : $matches [ 2 ];
2012-02-26 00:09:51 +00:00
$port = $matches [ 3 ];
if ( ! extension_loaded ( 'redis' )) {
2012-03-03 00:44:43 +00:00
throw new \RuntimeException ( 'RedisProfilerStorage requires that the redis extension is loaded.' );
2012-02-26 00:09:51 +00:00
}
$redis = new Redis ;
$redis -> connect ( $host , $port );
$redis -> setOption ( Redis :: OPT_PREFIX , self :: TOKEN_PREFIX );
$this -> redis = $redis ;
}
return $this -> redis ;
}
private 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 -> setCollectors ( $data [ 'data' ]);
if ( ! $parent && $data [ 'parent' ]) {
$parent = $this -> read ( $data [ 'parent' ]);
}
if ( $parent ) {
$profile -> setParent ( $parent );
}
foreach ( $data [ 'children' ] as $token ) {
if ( ! $token ) {
continue ;
}
if ( ! $childProfileData = $this -> getValue ( $this -> getItemName ( $token ), Redis :: SERIALIZER_PHP )) {
continue ;
}
$profile -> addChild ( $this -> createProfileFromData ( $token , $childProfileData , $profile ));
}
return $profile ;
}
/**
2012-03-03 00:44:43 +00:00
* Gets the item name .
2012-02-26 00:09:51 +00:00
*
* @ param string $token
*
* @ return string
*/
private function getItemName ( $token )
{
$name = $token ;
if ( $this -> isItemNameValid ( $name )) {
return $name ;
}
return false ;
}
/**
2012-03-03 00:44:43 +00:00
* Gets the name of the index .
2012-02-26 00:09:51 +00:00
*
* @ return string
*/
private function getIndexName ()
{
$name = 'index' ;
if ( $this -> isItemNameValid ( $name )) {
return $name ;
}
return false ;
}
private function isItemNameValid ( $name )
{
$length = strlen ( $name );
if ( $length > 2147483648 ) {
throw new \RuntimeException ( sprintf ( 'The Redis item key "%s" is too long (%s bytes). Allowed maximum size is 2^31 bytes.' , $name , $length ));
}
return true ;
}
/**
2012-03-03 00:44:43 +00:00
* Retrieves an item from the Redis server .
2012-02-26 00:09:51 +00:00
*
* @ param string $key
2012-03-03 00:44:43 +00:00
* @ param int $serializer
2012-02-26 00:09:51 +00:00
*
* @ return mixed
*/
private function getValue ( $key , $serializer = Redis :: SERIALIZER_NONE )
{
$redis = $this -> getRedis ();
$redis -> setOption ( Redis :: OPT_SERIALIZER , $serializer );
return $redis -> get ( $key );
}
/**
2012-03-03 00:44:43 +00:00
* Stores an item on the Redis server under the specified key .
2012-02-26 00:09:51 +00:00
*
* @ param string $key
2012-03-03 00:44:43 +00:00
* @ param mixed $value
* @ param int $expiration
* @ param int $serializer
2012-02-26 00:09:51 +00:00
*
2012-03-03 00:44:43 +00:00
* @ return Boolean
2012-02-26 00:09:51 +00:00
*/
private function setValue ( $key , $value , $expiration = 0 , $serializer = Redis :: SERIALIZER_NONE )
{
$redis = $this -> getRedis ();
$redis -> setOption ( Redis :: OPT_SERIALIZER , $serializer );
return $redis -> setex ( $key , $expiration , $value );
}
/**
2012-03-03 00:44:43 +00:00
* Appends data to an existing item on the Redis server .
2012-02-26 00:09:51 +00:00
*
* @ param string $key
* @ param string $value
2012-03-03 00:44:43 +00:00
* @ param int $expiration
2012-02-26 00:09:51 +00:00
*
2012-03-03 00:44:43 +00:00
* @ return Boolean
2012-02-26 00:09:51 +00:00
*/
private function appendValue ( $key , $value , $expiration = 0 )
{
$redis = $this -> getRedis ();
$redis -> setOption ( Redis :: OPT_SERIALIZER , Redis :: SERIALIZER_NONE );
if ( $redis -> exists ( $key )) {
$redis -> append ( $key , $value );
2012-03-03 00:44:43 +00:00
2012-02-26 00:09:51 +00:00
return $redis -> setTimeout ( $key , $expiration );
}
return $redis -> setex ( $key , $expiration , $value );
}
/**
2012-03-03 00:44:43 +00:00
* Removes the specified keys .
2012-02-26 00:09:51 +00:00
*
* @ param array $key
*
2012-03-03 00:44:43 +00:00
* @ return Boolean
2012-02-26 00:09:51 +00:00
*/
private function delete ( array $keys )
{
return ( bool ) $this -> getRedis () -> delete ( $keys );
}
}