3v4l.org

run code in 300+ PHP versions simultaneously
<?php /* * This file is part of the SensioLabsProfiler SDK package. * * (c) SensioLabs <contact@sensiolabs.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * This is a PHP 5.2 compatible fallback implementation of the Sprofiler extension. * The interfaces and behavior are the same, or as close as possible. * It uses xhprof or uprofiler to gather profiling metrics and push them to SensioLabsProfiler. * When the extension is loaded, this PHP fallback is not loaded. * * A general rule of design is that this fallback (as the extension) does not generate any exception * nor any PHP notice/warning/etc. Instead, a log facility is provided where all messages shall be written. */ class SprofilerProbe { private $fileFormat = 'SensioLabs\Profiler\Probe'; private $profiler; private $outputUrl; private $outputTimeout; private $outputStream; private $logLevel = 1; private $logUrl = 'php://stderr'; private $isEnabled = false; private $responseLine = ''; private $challenge; private $signature; private $flags; private $options = array( 'server_keys' => array( 'HTTP_HOST', 'HTTP_USER_AGENT', 'HTTPS', 'REQUEST_METHOD', 'REQUEST_URI', 'SERVER_ADDR', 'SERVER_SOFTWARE', '_', 'argv', ), 'ignored_functions' => array( 'array_map', 'array_filter', 'array_reduce', 'array_walk', 'array_walk_recursive', 'call_user_func', 'call_user_func_array', 'call_user_method', 'call_user_method_array', 'forward_static_call', 'forward_static_call_array', 'iterator_apply', ), ); private static $probe; private static $profilerIsEnabled = false; private static $defaultOutputUrl = 'unix:///var/run/sprofiler/agent.sock'; private static $urlEncMap = array( '%21' => '!', '%22' => '"', '%23' => '#', '%24' => '$', '%27' => "'", '%28' => '(', '%29' => ')', '%2A' => '*', '%2C' => ',', '%2F' => '/', '%3A' => ':', '%3B' => ';', '%3C' => '<', '%3D' => '=', '%3E' => '>', '%40' => '@', '%5B' => '[', '%5C' => '\\','%5D' => ']', '%5E' => '^', '%60' => '`', '%7B' => '{', '%7C' => '|', '%7D' => '}', '%7E' => '~', ); /** * Returns a global singleton and enables it by default. * * Uses an HTTP header or an env vars to create this singleton on its first use: * 1. the value of the X-SensioLabsProfiler-Query HTTP header (when applicable) * 2. or the SPROFILER_QUERY environment var * 3. or the empty string otherwise * is used as the first argument of the constructor below to instantiate the main probe. * * Additionally, this function enables the probe, except when the just said string * contains an auto_enable=0 URL parameter. * * @return self * * @api */ public static function getMainInstance() { if (isset($_SERVER['HTTP_X_SENSIOLABSPROFILER_QUERY'])) { $query = $_SERVER['HTTP_X_SENSIOLABSPROFILER_QUERY']; } elseif (isset($_SERVER['SPROFILER_QUERY'])) { $query = $_SERVER['SPROFILER_QUERY']; } else { $query = ''; } if (null !== self::$probe) { return self::$probe; } if (!file_exists(substr(self::$defaultOutputUrl, 7))) { self::$defaultOutputUrl = null; } $probe = new self($query); parse_str($query, $query); if (!isset($query['auto_enable']) || $query['auto_enable']) { if ($probe->isVerified()) { self::boxPostEnable($probe, $probe->enable()); $probe->debug($probe->getResponseLine()); } } return self::$probe = $probe; } /** * Instantiate a probe object. * * @param string $query An URL-encoded string that configures the probe. Part of the string is signed. * @param string $probeId An id that is given to the agent for signature impersonation. * @param string $probeToken The token associated to $probeId. * @param string $outputUrl The URL where profiles will be written (directory, socket or TCP destination). * * @api */ public function __construct($query, $probeId = null, $probeToken = null, $outputUrl = null) { $query = preg_split('/(?:^|&)signature=(.+?)(?:&|$)/', $query, 2, PREG_SPLIT_DELIM_CAPTURE); list($this->challenge, $this->signature, $args) = $query + array(1 => '', ''); $this->signature = rawurldecode($this->signature); parse_str($args, $args); $query = array( 'SPROFILER_PROBE_ID' => null, 'SPROFILER_PROBE_TOKEN' => null, 'SPROFILER_OUTPUT_URL' => null, 'SPROFILER_OUTPUT_TIMEOUT' => null, 'SPROFILER_LOG_LEVEL' => null, 'SPROFILER_LOG_URL' => null, ); foreach ($query as $k => $v) { if (isset($_ENV[$k])) { $query[$k] = $_ENV[$k]; } elseif (isset($_SERVER[$k])) { $query[$k] = $_SERVER[$k]; } } $this->probeId = $probeId ? $probeId : $query['SPROFILER_PROBE_ID']; $this->probeToken = $probeToken ? $probeToken : $query['SPROFILER_PROBE_TOKEN']; $this->outputUrl = $outputUrl; $this->outputUrl or $this->outputUrl = $query['SPROFILER_OUTPUT_URL']; $this->outputUrl or $this->outputUrl = self::$defaultOutputUrl; $this->outputUrl or $this->outputUrl = ini_get('uprofiler.output_dir'); $this->outputUrl or $this->outputUrl = ini_get('xhprof.output_dir'); $this->outputTimeout = 1000000 * $query['SPROFILER_OUTPUT_TIMEOUT']; $this->outputTimeout or $this->outputTimeout = 0.25; $query['SPROFILER_LOG_LEVEL'] and $this->logLevel = $query['SPROFILER_LOG_LEVEL']; $query['SPROFILER_LOG_URL'] and $this->logUrl = $query['SPROFILER_LOG_URL']; $this->aggregSamples = isset($args['aggreg_samples']) && is_string($args['aggreg_samples']) ? max((int) $args['aggreg_samples'], 1) : 1; if (!$this->logUrl || 'stderr' === $this->logUrl) { $this->logUrl = 'php://stderr'; } empty($args['flag_cpu']) or $this->flags |= UPROFILER_FLAGS_CPU; empty($args['flag_memory']) or $this->flags |= UPROFILER_FLAGS_MEMORY; empty($args['flag_no_builtins']) or $this->flags |= UPROFILER_FLAGS_NO_BUILTINS; if (function_exists('uprofiler_enable')) { $this->profiler = 'uprofiler'; } elseif (function_exists('xhprof_enable')) { $this->profiler = 'xhprof'; } if ($this->logLevel >= 4) { $this->debug('New probe instanciated'); foreach ($this as $k => $v) { if ('options' !== $k) { if ('' !== $v = (string) $v) { $this->debug(' '.$k.': '.$v); } } } } } /** * Tells if the probe is cryptographically verified, i.e. if the signature in $query is valid. * * @return bool * * @api */ public function isVerified() { return $this->box('doVerify', false); } /** * Gets the response message/status/line * * This lines gives details about the status of the probe. That can be: * - an error: `SensioLabsProfiler-Error: $errNumber $urlEncodedErrorMessage` * - or not: `SensioLabsProfiler-Response: $rfc1738EncodedMessage` * * @return string The response line * * @api */ public function getResponseLine() { return $this->responseLine; } /** * Enables profiling instrumentation and data aggregation. * * One and only one probe can be enabled at the same time. * * @see getResponseLine() for error/status reporting * * @return bool False if enabling failed. * * @api */ public function enable() { return $this->box('doEnable', false, $this->getErrorHandler('error', array(__CLASS__, 'onError')) .$this->getErrorHandler('exception', array($this, 'onException')) ); } /** * Disables profiling instrumentation and data aggregation. * * As a side-effect, flushes the collected profile to the output. * * @param bool $close Not closing allows to re-enable the probe later and aggregate data in the same profile. Closing means that a later enable() will create a new profile on the output. * * @return bool False if the probe was not enabled. * * @api */ public function disable($close = false) { return $this->box('doDisable', true, $close); } // XXX // XXX - END OF PUBLIC API - XXX // XXX /** * @internal */ private static function boxPostEnable($probe, $isEnabled) { if ($isEnabled) { register_shutdown_function(array($probe, 'onShutdown')); } $probe->box('sendHeaderLine', null); } /** * @internal */ private static function restoreErrorHandler() { restore_error_handler(); } /** * @internal */ public function __destruct() { $this->disable(true); } /** * Wraps internal functions and handles any error/exception. * * @internal */ private function box($method, $returnValue) { set_error_handler(__CLASS__.'::onInternalError'); try { $args = func_get_args(); unset($args[0], $args[1]); $this->debug('Boxing '.$method); $returnValue = call_user_func_array(array($this, $method), $args); } catch (Exception $e) { $this->warn(get_class($e).': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine()); restore_error_handler(); $this->profilerDisable(); $this->responseLine = 'SensioLabsProfiler-Error: 101 '.rawurlencode($e->getMessage().' in '.$e->getFile().':'.$e->getLine()); } self::restoreErrorHandler(); return $returnValue; } /** * @internal */ private function doVerify() { if (null === $this->outputStream) { $signature = strtr($this->signature, '-_', '+/'); $signature = base64_decode($signature, true); // XXX Crypto checks are done here in the C version. // In the PHP version, this is delegated to the agent, // no verification occurs when the output is a directory. if ($signature) { $this->debug('Signature looks OK'); $this->openOutput(); } else { $this->info('Invalid signature'); } } return (bool) $this->outputStream; } /** * @internal */ private function doEnable($extra) { if ($this->isEnabled) { return true; } if (self::$profilerIsEnabled) { $this->responseLine = "SensioLabsProfiler-Error: 101 An other probe is already profiling"; return false; } if ($this->doVerify()) { $this->writeChunkProlog($extra); $this->profilerEnable(); $this->isEnabled = true; } return $this->isEnabled; } /** * @internal */ private function doDisable($close = false) { if (!$this->isEnabled) { return false; } $this->isEnabled = false; $this->profilerWrite(true); if ($close && $this->outputStream) { $this->debug('Closing output stream'); flock($this->outputStream, LOCK_UN); fclose($this->outputStream); $this->outputStream = null; } return true; } /** * @internal */ private function sendHeaderLine() { header('X-'.$this->getResponseLine()); } /** * @internal */ private function openOutput() { if (null !== $this->outputStream) { return $this->outputStream; } $this->outputStream = false; $url = $this->outputUrl; if (($i = strpos($url, '://')) && in_array(substr($url, 0, $i), stream_get_transports(), true)) { $this->debug('Lets open '.$url); if ($h = stream_socket_client($url, $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT)) { stream_set_timeout($h, 0, $this->outputTimeout); stream_set_write_buffer($h, 0); $i = array(null, array($h), null); if (stream_select($i[0], $i[1], $i[2], 0, $this->outputTimeout)) { $this->writeHelloProlog($h); $response = rtrim(fgets($h, 4096)); while ('' !== rtrim(fgets($h, 4096))) { // No-op } if (0 !== strpos($response, 'SensioLabsProfiler-Response: ')) { fclose($h); $h = false; if (0 !== strpos($response, 'SensioLabsProfiler-Error: ')) { $response = "SensioLabsProfiler-Error: 102 Invalid agent response ($response)"; } } } else { fclose($h); $h = false; $response = "SensioLabsProfiler-Error: 101 Agent connection timeout"; } } else { $response = "SensioLabsProfiler-Error: 101 $errstr ($errno)"; } } else { $i = sprintf('%019.6F', microtime(true)).'-'; $i .= substr(str_replace(array('+', '/'), array('', ''), base64_encode(md5(mt_rand(), true))), 0, 6); $url .= '/'.$i.'.log'; $this->debug('Lets open '.$url); $h = fopen($url, 'wb'); if (stream_is_local($h)) { flock($h, LOCK_SH); // This shared lock allows readers to wait for the end of the stream stream_set_write_buffer($h, 0); } else { $this->writeHelloProlog($h); } $response = "SensioLabsProfiler-Response: continue=false"; } $this->responseLine = $response; $this->outputStream = $h; if ($h) { $this->writeMainProlog(); } return $h; } /** * @internal */ private function writeHelloProlog($h) { $hello = ''; if ($this->probeId && $this->probeToken) { $line = $this->probeId.':'.$this->probeToken; if (strlen($line) !== strcspn($line, "\r\n") || 1 < substr_count($line, ':')) { $this->warn('Invalid probe_id/probe_token'); } else { $hello .= 'SensioLabsProfiler-Auth: '.$line."\n"; } } $line = 'signature='.$this->signature.'&aggreg_samples='.$this->aggregSamples."\n"; isset($this->challenge[0]) and $line = $this->challenge.'&'.$line; $hello .= 'SensioLabsProfiler-Query: '.$line."\n"; self::fwrite($h, $hello); } /** * @internal */ private function writeMainProlog() { // Loaded extensions list helps understanding runtime behavior $extensions = array(); foreach (get_loaded_extensions() as $e) { $extensions[$e] = phpversion($e); } // Keep only keys from $_COOKIE $cookies = array_keys($_COOKIE); // Keep selected keys from $_SERVER $servers = array(); foreach ($this->options['server_keys'] as $e) { if (isset($_SERVER[$e])) { $servers[$e] = $_SERVER[$e]; } } // Get request's URI if (isset($_SERVER['HTTP_X_ORIGINAL_URL'])) { $e = $_SERVER['HTTP_X_ORIGINAL_URL']; } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { $e = $_SERVER['HTTP_X_REWRITE_URL']; } elseif (!empty($_SERVER['IIS_WasUrlRewritten']) && !empty($_SERVER['UNENCODED_URL'])) { $e = $_SERVER['UNENCODED_URL']; } elseif (isset($_SERVER['REQUEST_URI'][0])) { $e = $_SERVER['REQUEST_URI']; if ('/' !== $e[0]) { $e = preg_replace('#^https?://[^/]+#', '', $e); } } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { $e = $_SERVER['ORIG_PATH_INFO']; if (!empty($_SERVER['QUERY_STRING'])) { $e .= '?'.$_SERVER['QUERY_STRING']; } } else { $e = ''; } if (!empty($e)) { $servers['REQUEST_URI'] = $e; } self::fwrite($this->outputStream, 'file-format: '.$this->fileFormat."\n" .'php-os: '.PHP_OS."\n" .'php-sapi: '.PHP_SAPI."\n" .'php-version: '.PHP_VERSION_ID."\n" .'php-extensions: '.strtr(http_build_query($extensions, '', '&'), self::$urlEncMap)."\n" .'_COOKIE: '.strtr(http_build_query($cookies, '', '&'), self::$urlEncMap)."\n" .'_SERVER: '.strtr(http_build_query($servers, '', '&'), self::$urlEncMap)."\n" ."\nmain()//1 0 0 0 0\n\n" ); $this->debug('Main prolog pushed'); } /** * @internal */ private function writeChunkProlog($extra) { $data = 'request-mu: '.memory_get_usage(true)."\n" .'request-pmu: '.memory_get_peak_usage(true)."\n" .'request-start: '.microtime(true)."\n" .$extra; if (function_exists('sys_getloadavg')) { $data .= 'sys-load-avg: '.implode(' ', sys_getloadavg())."\n"; } self::fwrite($this->outputStream, $data); } /** * @internal */ private function getErrorHandler($type, $default = 'var_dump') { $s = "set_{$type}_handler"; if ($h = $s($default)) { $s = "restore_{$type}_handler"; $s(); } elseif ('var_dump' !== $default) { $h = $default; } $type .= '-handler: '; if ($h instanceof Closure) { $h = new ReflectionFunction($h); if (PHP_VERSION_ID >= 50400 && $s = $h->getClosureScopeClass()) { $h = $s->name.'::{closure}/'.$h->getStartLine().'-'.$h->getEndLine(); } else { $h = $h->name.'::'.implode('/', array_slice(explode('/', $h->getFileName()), -2)).'/'.$h->getStartLine().'-'.$h->getEndLine(); } } else { if (!is_array($h)) { if (is_object($h)) { $h = array($h, '__invoke'); } else { $h = explode('::', $h, 2); } } if (isset($h[1])) { $h = new ReflectionMethod($h[0], $h[1]); $h = $h->getDeclaringClass()->name.'::'.$h->name; } else { $h = $h[0]; } } $type .= $h; $this->debug('Extracted '.$type); return $type."\n"; } /** * @internal */ private function profilerEnable() { self::$profilerIsEnabled = true; if (is_string($this->profiler)) { $p = $this->profiler.'_enable'; $this->debug($p); $p($this->flags, $this->options); } else { $this->info('No profiler to enable'); } } /** * @internal */ private function profilerDisable() { self::$profilerIsEnabled = false; if (is_string($this->profiler)) { $p = $this->profiler.'_disable'; $this->debug($p); return $p(); } elseif (is_array($this->profiler)) { $this->debug('data array profiler_disable'); return $this->profiler; } else { $this->info('No profiler to disable'); return array(); } } /** * @internal */ private function profilerWrite($disable, $chunk = '') { $data = $this->profilerDisable(); $chunk .= "request-end: ".microtime(true) ."\nrequest-mu: ".memory_get_usage(true) ."\nrequest-pmu: ".memory_get_peak_usage(true) ."\n\n"; $this->debug('Pushing '.count($data).' call pairs'); if (!$disable) { $this->profilerEnable(); } $h = $this->outputStream; $i = 50; // 50 ~= 4Ko chunks // Speed optimized paths if (!$data) { // No-op } elseif ((UPROFILER_FLAGS_CPU & $this->flags) && (UPROFILER_FLAGS_MEMORY & $this->flags)) { foreach ($data as $k => $v) { $chunk .= "{$k}//{$v['ct']} {$v['wt']} {$v['cpu']} {$v['mu']} {$v['pmu']}\n"; if (0 === --$i) { self::fwrite($h, $chunk); $chunk = ''; $i = 50; } } } elseif (UPROFILER_FLAGS_MEMORY & $this->flags) { foreach ($data as $k => $v) { $chunk .= "{$k}//{$v['ct']} {$v['wt']} 0 {$v['mu']} {$v['pmu']}\n"; if (0 === --$i) { self::fwrite($h, $chunk); $chunk = ''; $i = 50; } } } elseif (UPROFILER_FLAGS_CPU & $this->flags) { foreach ($data as $k => $v) { $chunk .= "{$k}//{$v['ct']} {$v['wt']} {$v['cpu']} 0 0\n"; if (0 === --$i) { self::fwrite($h, $chunk); $chunk = ''; $i = 50; } } } else { foreach ($data as $k => $v) { $chunk .= "{$k}//{$v['ct']} {$v['wt']} 0 0 0\n"; if (0 === --$i) { self::fwrite($h, $chunk); $chunk = ''; $i = 50; } } } if (isset($data['main()'])) { $chunk .= "main()//-{$data['main()']['ct']} 0 0 0 0\n"; } $chunk .= "\n"; return self::fwrite($h, $chunk); } /** * @internal */ private static function fwrite($stream, $data) { $len = strlen($data); $written = fwrite($stream, $data); if (false !== $written) { while ($written < $len) { fflush($stream); $w = fwrite($stream, substr($data, $written)); $written += $w ? $w : $len + 1; } if ($written === $len) { return true; } } } /** * @internal */ public static function onInternalError($type, $message, $file, $line) { throw new ErrorException($message, 0, $type, $file, $line); } /** * @internal */ public static function onError() { return false; // Delegate error handling to the internal handler, but adds a line in profiler's data } /** * @internal */ public function onException($e) { // Rethrow only, but adds a line in profiler's data $this->box('profilerWrite', null, true); // Prevents a crash with XHProf throw $e; } /** * @internal */ public function onShutdown() { $this->box('doShutdown', null, $this->getErrorHandler('error') .$this->getErrorHandler('exception') ); } /** * @internal */ private function doShutdown($extra) { // Get and write data now so that any later fatal error // does not prevent collecting what we already have. if (!$this->isEnabled) { return; } $e = error_get_last(); if (function_exists('http_response_code')) { $extra .= 'response-code: '.http_response_code()."\n"; } if (isset($e['type'])) { switch ($e['type']) { case E_ERROR: case E_PARSE: case E_USER_ERROR: case E_CORE_ERROR: case E_COMPILE_ERROR: case E_RECOVERABLE_ERROR: $h = explode("\r", $e['message'], 2); $h = explode("\n", $h[0], 2); $h[1] = " in {$e['file']}:{$e['line']}"; $h[0] = str_replace($h[1], '', $h[0]); $h = "fatal-error: {$h[0]}{$h[1]}\n"; $this->info('Got '.$h); self::fwrite($this->outputStream, $h); break; } } $this->profilerWrite(false, $extra); } /** * @internal */ private function warn($msg) { if ($this->logLevel >= 2) { file_put_contents($this->logUrl, 'WARN: '.$msg."\n", FILE_APPEND); } } /** * @internal */ private function info($msg) { if ($this->logLevel >= 3) { file_put_contents($this->logUrl, 'Info: '.$msg."\n", FILE_APPEND); } } /** * @internal */ private function debug($msg) { if ($this->logLevel >= 4) { file_put_contents($this->logUrl, 'dbug: '.$msg."\n", FILE_APPEND); } } }
Output for git.master, git.master_jit, rfc.property-hooks

This tab shows result from various feature-branches currently under review by the php developers. Contact me to have additional branches featured.

Active branches

Archived branches

Once feature-branches are merged or declined, they are no longer available. Their functionality (when merged) can be viewed from the main output page


preferences:
34.81 ms | 401 KiB | 8 Q