@ 2014-10-02T12:50:43Z <?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);
}
}
}
Enable javascript to submit You have javascript disabled. You will not be able to edit any code.
Here you find the average performance (time & memory) of each version. A grayed out version indicates it didn't complete successfully (based on exit-code).
Version System time (s) User time (s) Memory (MiB) 8.3.6 0.010 0.013 16.50 8.3.5 0.010 0.011 21.93 8.3.4 0.012 0.004 18.92 8.3.3 0.011 0.004 19.26 8.3.2 0.000 0.010 20.47 8.3.1 0.009 0.000 20.36 8.3.0 0.004 0.004 19.38 8.2.18 0.006 0.015 17.00 8.2.17 0.010 0.007 22.96 8.2.16 0.009 0.006 20.60 8.2.15 0.000 0.008 24.18 8.2.14 0.000 0.008 24.66 8.2.13 0.005 0.005 26.16 8.2.12 0.008 0.000 22.24 8.2.11 0.003 0.006 22.20 8.2.10 0.006 0.006 18.03 8.2.9 0.004 0.004 19.34 8.2.8 0.004 0.004 17.97 8.2.7 0.003 0.007 17.75 8.2.6 0.005 0.005 18.15 8.2.5 0.003 0.007 18.07 8.2.4 0.005 0.003 18.28 8.2.3 0.006 0.003 19.79 8.2.2 0.004 0.004 17.88 8.2.1 0.000 0.009 18.27 8.2.0 0.005 0.003 17.79 8.1.28 0.007 0.014 25.92 8.1.27 0.000 0.008 23.83 8.1.26 0.008 0.000 26.35 8.1.25 0.008 0.000 28.09 8.1.24 0.010 0.000 23.91 8.1.23 0.004 0.007 19.26 8.1.22 0.000 0.009 17.78 8.1.21 0.007 0.003 18.77 8.1.20 0.007 0.004 17.48 8.1.19 0.006 0.003 17.38 8.1.18 0.003 0.006 18.10 8.1.17 0.006 0.003 18.83 8.1.16 0.008 0.000 22.31 8.1.15 0.000 0.008 18.95 8.1.14 0.009 0.000 17.55 8.1.13 0.000 0.008 17.59 8.1.12 0.005 0.003 17.58 8.1.11 0.003 0.006 17.63 8.1.10 0.004 0.004 17.67 8.1.9 0.000 0.008 17.62 8.1.8 0.004 0.004 17.62 8.1.7 0.000 0.007 17.61 8.1.6 0.000 0.009 17.63 8.1.5 0.006 0.003 17.68 8.1.4 0.000 0.008 17.63 8.1.3 0.000 0.008 17.72 8.1.2 0.003 0.006 17.67 8.1.1 0.005 0.003 17.59 8.1.0 0.009 0.000 17.60 8.0.30 0.004 0.004 18.77 8.0.29 0.003 0.006 16.88 8.0.28 0.000 0.008 18.45 8.0.27 0.002 0.005 17.46 8.0.26 0.000 0.007 17.09 8.0.25 0.000 0.007 17.11 8.0.24 0.003 0.006 17.01 8.0.23 0.007 0.000 17.03 8.0.22 0.004 0.004 16.93 8.0.21 0.000 0.008 17.07 8.0.20 0.000 0.007 17.03 8.0.19 0.007 0.000 17.00 8.0.18 0.000 0.008 17.07 8.0.17 0.004 0.004 17.05 8.0.16 0.000 0.008 17.05 8.0.15 0.006 0.003 17.00 8.0.14 0.006 0.003 16.86 8.0.13 0.003 0.006 13.52 8.0.12 0.008 0.000 16.92 8.0.11 0.004 0.004 17.01 8.0.10 0.000 0.008 17.01 8.0.9 0.004 0.004 16.96 8.0.8 0.007 0.017 17.05 8.0.7 0.008 0.000 17.05 8.0.6 0.003 0.006 17.02 8.0.5 0.000 0.008 16.93 8.0.3 0.011 0.009 17.27 8.0.2 0.016 0.009 17.40 8.0.1 0.003 0.005 17.07 8.0.0 0.017 0.007 16.83 7.4.33 0.000 0.005 15.00 7.4.32 0.000 0.007 16.72 7.4.30 0.004 0.004 16.60 7.4.29 0.004 0.004 16.65 7.4.28 0.003 0.006 16.73 7.4.27 0.007 0.000 16.75 7.4.26 0.004 0.004 16.67 7.4.25 0.004 0.004 16.55 7.4.24 0.000 0.008 16.76 7.4.23 0.004 0.004 16.88 7.4.22 0.017 0.003 16.70 7.4.21 0.003 0.014 16.63 7.4.20 0.004 0.004 16.55 7.4.16 0.006 0.012 16.47 7.4.15 0.000 0.020 17.40 7.4.14 0.007 0.016 17.86 7.4.13 0.007 0.013 16.67 7.4.12 0.009 0.012 16.71 7.4.11 0.011 0.007 16.58 7.4.10 0.013 0.006 16.54 7.4.9 0.003 0.015 16.62 7.4.8 0.006 0.016 16.55 7.4.7 0.008 0.015 16.82 7.4.6 0.013 0.006 16.70 7.4.5 0.003 0.006 16.56 7.4.4 0.016 0.008 16.60 7.4.3 0.009 0.009 16.67 7.4.0 0.003 0.013 15.20 7.3.33 0.004 0.004 13.45 7.3.32 0.003 0.003 13.45 7.3.31 0.007 0.000 16.41 7.3.30 0.004 0.004 16.45 7.3.29 0.012 0.006 16.50 7.3.28 0.011 0.009 16.40 7.3.27 0.007 0.011 17.40 7.3.26 0.007 0.011 16.63 7.3.25 0.012 0.013 16.40 7.3.24 0.007 0.011 16.49 7.3.23 0.003 0.014 16.68 7.3.21 0.012 0.006 16.53 7.3.20 0.003 0.019 19.39 7.3.19 0.006 0.013 16.51 7.3.18 0.014 0.004 16.67 7.3.17 0.007 0.011 16.64 7.3.16 0.012 0.006 16.36 7.2.33 0.003 0.016 16.74 7.2.32 0.007 0.017 16.58 7.2.31 0.012 0.013 16.54 7.2.30 0.007 0.011 16.80 7.2.29 0.015 0.003 16.45 7.2.6 0.007 0.010 16.47 7.2.0 0.006 0.006 18.88 7.1.20 0.003 0.006 15.71 7.1.10 0.007 0.007 17.53 7.1.7 0.006 0.003 16.95 7.1.6 0.009 0.015 19.32 7.1.5 0.010 0.013 16.77 7.1.0 0.010 0.063 22.30 7.0.20 0.000 0.010 16.61 7.0.14 0.007 0.070 21.95 7.0.10 0.050 0.063 20.27 7.0.9 0.030 0.080 20.29 7.0.8 0.020 0.077 20.16 7.0.7 0.013 0.080 20.25 7.0.6 0.010 0.080 20.23 7.0.5 0.020 0.063 20.64 7.0.4 0.007 0.090 20.20 7.0.3 0.003 0.090 20.16 7.0.2 0.007 0.087 20.11 7.0.1 0.007 0.053 20.05 7.0.0 0.003 0.067 20.13 5.6.28 0.003 0.077 21.20 5.6.25 0.007 0.077 20.78 5.6.24 0.010 0.070 20.82 5.6.23 0.020 0.067 20.83 5.6.22 0.007 0.070 20.80 5.6.21 0.013 0.043 20.93 5.6.20 0.017 0.063 21.20 5.6.19 0.007 0.080 21.23 5.6.18 0.020 0.070 21.23 5.6.17 0.000 0.080 21.14 5.6.16 0.010 0.073 21.21 5.6.15 0.013 0.073 21.21 5.6.14 0.007 0.093 21.23 5.6.13 0.010 0.043 21.35 5.6.12 0.007 0.083 21.29 5.6.11 0.010 0.053 21.34 5.6.10 0.007 0.087 21.16 5.6.9 0.003 0.070 21.20 5.6.8 0.007 0.047 20.57 5.6.7 0.017 0.063 20.66 5.6.6 0.003 0.080 20.55 5.6.5 0.000 0.083 20.63 5.6.4 0.010 0.080 20.59 5.6.3 0.007 0.073 20.55 5.6.2 0.007 0.037 20.57 5.6.1 0.003 0.043 20.46 5.6.0 0.003 0.047 20.53 5.5.38 0.017 0.067 20.53 5.5.37 0.010 0.087 20.57 5.5.36 0.013 0.077 20.51 5.5.35 0.017 0.073 20.63 5.5.34 0.013 0.073 21.04 5.5.33 0.010 0.080 20.95 5.5.32 0.020 0.077 21.08 5.5.31 0.010 0.060 21.00 5.5.30 0.017 0.063 21.06 5.5.29 0.007 0.067 21.09 5.5.28 0.013 0.070 21.00 5.5.27 0.010 0.050 20.92 5.5.26 0.017 0.077 21.10 5.5.25 0.020 0.063 20.79 5.5.24 0.007 0.077 20.20 5.5.23 0.013 0.070 20.47 5.5.22 0.007 0.080 20.46 5.5.21 0.007 0.073 20.47 5.5.20 0.010 0.067 20.41 5.5.19 0.013 0.050 20.28 5.5.18 0.000 0.063 20.41 5.5.16 0.003 0.040 20.35 5.5.15 0.010 0.033 20.32 5.5.14 0.007 0.037 20.44 5.5.13 0.010 0.040 20.44 5.5.12 0.007 0.037 20.30 5.5.11 0.000 0.043 20.35 5.5.10 0.003 0.050 20.33 5.5.9 0.003 0.060 20.27 5.5.8 0.003 0.033 20.18 5.5.7 0.003 0.040 20.24 5.5.6 0.007 0.033 20.32 5.5.5 0.000 0.067 20.23 5.5.4 0.010 0.037 20.30 5.5.3 0.003 0.080 20.30 5.5.2 0.013 0.027 20.25 5.5.1 0.003 0.050 20.21 5.5.0 0.003 0.040 20.27 5.4.45 0.003 0.077 19.56 5.4.44 0.003 0.053 19.68 5.4.43 0.020 0.067 19.52 5.4.42 0.007 0.080 19.49 5.4.41 0.017 0.050 19.35 5.4.40 0.003 0.070 19.22 5.4.39 0.007 0.073 19.17 5.4.38 0.007 0.073 19.14 5.4.37 0.003 0.073 19.06 5.4.36 0.007 0.053 19.14 5.4.35 0.013 0.070 19.29 5.4.34 0.000 0.043 19.32 5.4.32 0.010 0.033 19.14 5.4.31 0.007 0.037 19.29 5.4.30 0.000 0.040 19.32 5.4.29 0.003 0.037 19.13 5.4.28 0.003 0.037 19.21 5.4.27 0.000 0.050 18.97 5.4.26 0.007 0.037 19.17 5.4.25 0.003 0.037 19.11 5.4.24 0.007 0.047 19.12 5.4.23 0.003 0.067 19.14 5.4.22 0.003 0.033 19.15 5.4.21 0.003 0.067 19.05 5.4.20 0.000 0.037 19.19 5.4.19 0.007 0.060 19.15 5.4.18 0.010 0.037 18.98 5.4.17 0.003 0.040 19.28 5.4.16 0.000 0.063 19.30 5.4.15 0.003 0.067 18.98 5.4.14 0.000 0.037 16.51 5.4.13 0.007 0.033 16.30 5.4.12 0.000 0.043 16.45 5.4.11 0.000 0.040 16.51 5.4.10 0.003 0.033 16.65 5.4.9 0.007 0.033 16.50 5.4.8 0.000 0.040 16.61 5.4.7 0.013 0.027 16.52 5.4.6 0.007 0.030 16.42 5.4.5 0.007 0.033 16.63 5.4.4 0.010 0.030 16.55 5.4.3 0.003 0.037 16.63 5.4.2 0.010 0.030 16.59 5.4.1 0.007 0.033 16.50 5.4.0 0.003 0.037 15.89
preferences:dark mode live preview
37.46 ms | 401 KiB | 5 Q