Skip to content

Instantly share code, notes, and snippets.

@fprochazka
Created November 8, 2014 23:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fprochazka/fdba54944e285015f220 to your computer and use it in GitHub Desktop.
Save fprochazka/fdba54944e285015f220 to your computer and use it in GitHub Desktop.
NewRelicProfilingListener
<?php
namespace NewRelic;
use Kdyby;
use Nette;
use Tracy\Debugger;
/**
* @method setAppname($name, $license = NULL, $xmit = FALSE)
* @method noticeError($message, $exception = NULL)
* @method nameTransaction($name)
* @method endOfTransaction()
* @method endTransaction($ignore = FALSE)
* @method startTransaction($appname, $license = NULL)
* @method ignoreTransaction()
* @method ignoreApdex()
* @method backgroundJob($flag)
* @method captureParams($enable)
* @method addCustomParameter($key, $value)
* @method addCustomTracer($callback)
* @method getBrowserTimingHeader($flag = TRUE)
* @method getBrowserTimingFooter($flag = TRUE)
* @method disableAutorum()
* @method setUserAttributes($user, $account, $product)
*/
class Client extends Nette\Object
{
/**
* @param string $name
* @param string $value
*/
public function customMetric($name, $value)
{
$this->__call(__FUNCTION__, ['Custom/' . $name, $value]);
}
public function customTimeMetric($name, &$second, &$first)
{
if (empty($second) || empty($first)) {
return;
}
$this->customMetric($name, round(abs($second - $first) * 1000, 0));
}
public function __call($name, $args)
{
$function = 'newrelic_' . self::underscore($name);
if (!extension_loaded('newrelic')) {
return FALSE;
}
if (!function_exists($function)) {
return parent::__call($name, $args);
}
return call_user_func_array($function, $args);
}
/**
* camelCaseAction name -> under_score
* @param string
* @return string
*/
private static function underscore($s)
{
$s = preg_replace('#(.)(?=[A-Z])#', '$1_', $s);
$s = strtolower($s);
$s = rawurlencode($s);
return $s;
}
}
<?php
namespace NewRelic;
use Kdyby;
use Nette;
use Nette\Application\Application;
use Nette\Application\Request;
use Tracy\Debugger;
use Nette\Utils\Strings;
/**
* Instead of misleading `www/index.php` requests, show presenter:action breakdown.
*/
class NewRelicProfilingListener extends Nette\Object implements Kdyby\Events\Subscriber
{
const APP_METRIC_PREFIX = 'Rohlik';
/**
* @var \Nette\DI\Container
*/
private $container;
/**
* @var Client
*/
private $client;
public function __construct(Nette\DI\Container $container)
{
$this->container = $container;
$this->client = new Client;
}
public function getSubscribedEvents()
{
return array(
'Nette\\Application\\Application::onStartup',
'Nette\\Application\\Application::onShutdown',
'Nette\\Application\\Application::onRequest',
'Nette\\Application\\Application::onResponse',
);
}
public function onStartup(Application $app)
{
$_ENV['APP_STARTUP_TIME_FLOAT'] = microtime(TRUE);
}
public function onRequest(Application $app, Request $request)
{
if (!empty($request->parameters['exception']) && $request->parameters['exception'] instanceof \Exception) {
return;
}
$_ENV['APP_REQUEST_TIME_FLOAT'] = microtime(TRUE);
if (PHP_SAPI === 'cli') {
$this->client->setAppname('rohlik.cz/Cron');
$this->client->nameTransaction('$ ' . basename($_SERVER['argv'][0]) . ' ' . implode(' ', array_slice($_SERVER['argv'], 1)));
$this->client->backgroundJob(TRUE);
} else {
$module = explode(':', trim($request->getPresenterName(), ':'))[0];
$this->client->setAppname('rohlik.cz/' . ($module === 'Nette' ? 'Front' : $module));
if ($module === 'Cron') {
$this->client->backgroundJob(TRUE);
}
$params = $request->getParameters();
$this->client->nameTransaction($request->getPresenterName() . (isset($params['action']) ? ':' . $params['action'] : '') . (isset($params['do']) ? '?signal=' . preg_replace('~[0-9]+~', '*', $params['do']) : ''));
}
$this->client->customTimeMetric('Nette/RequestTime', $_ENV['APP_REQUEST_TIME_FLOAT'], $_ENV['APP_STARTUP_TIME_FLOAT']);
$this->client->customTimeMetric('Nette/CompilationTime', $_ENV['COMPILATION_TIME_FLOAT'], $_ENV['REQUEST_TIME_FLOAT']);
$this->client->customTimeMetric('Nette/StartupTime', $_ENV['APP_STARTUP_TIME_FLOAT'], $_ENV['COMPILATION_TIME_FLOAT']);
}
public function onResponse(Application $app, Nette\Application\IResponse $response)
{
$_ENV['APP_RESPONSE_TIME_FLOAT'] = microtime(TRUE);
$this->client->customTimeMetric('Nette/ResponseTime', $_ENV['APP_RESPONSE_TIME_FLOAT'], $_ENV['APP_REQUEST_TIME_FLOAT']);
if (($presenter = $app->getPresenter()) && $presenter instanceof Nette\Application\UI\Presenter) {
$module = explode(':', trim($presenter->getName(), ':'))[0];
$module = $module === 'Api' ? 'Api:V1' : $module;
// $this->client->customTimeMetric('Presenter/' . $module . '/Run', $_ENV['APP_PRESENTER_LEAVE'], $_ENV['APP_PRESENTER_ENTER']);
$this->client->customTimeMetric('Presenter/' . $module . '/Shutdown', $_ENV['APP_PRESENTER_LEAVE'], $_ENV['APP_PRESENTER_SEND_RESPONSE']);
$this->client->customTimeMetric('Presenter/' . $module . '/InitGlobals', $_ENV['APP_PRESENTER_REQUIREMENTS_BEGIN'], $_ENV['APP_PRESENTER_BEFORE_INIT']);
$this->client->customTimeMetric('Presenter/' . $module . '/Startup', $_ENV['APP_PRESENTER_STARTUP_END'], $_ENV['APP_PRESENTER_REQUIREMENTS_BEGIN']);
$this->client->customTimeMetric('Presenter/' . $module . '/Action', $_ENV['APP_PRESENTER_ACTION_END'], $_ENV['APP_PRESENTER_ACTION_BEGIN']);
$this->client->customTimeMetric('Presenter/' . $module . '/Render', $_ENV['APP_PRESENTER_RENDER_END'], $_ENV['APP_PRESENTER_RENDER_BEGIN']);
$this->client->customTimeMetric('Presenter/' . $module . '/BeforeRender', $_ENV['APP_PRESENTER_RENDER_BEGIN'], $_ENV['APP_PRESENTER_ACTION_END']);
$this->client->customTimeMetric('Presenter/' . $module . '/ProcessSignal', $_ENV['APP_PRESENTER_SIGNAL_END'], $_ENV['APP_PRESENTER_SIGNAL_BEGIN']);
$this->client->customTimeMetric('Presenter/' . $module . '/AfterRender', $_ENV['APP_PRESENTER_AFTER_RENDER_END'], $_ENV['APP_PRESENTER_RENDER_END']);
$this->client->customTimeMetric('Presenter/' . $module . '/SendTemplate', $_ENV['APP_PRESENTER_SEND_TEMPLATE_END'], $_ENV['APP_PRESENTER_SEND_TEMPLATE_BEGIN']);
}
$user = $this->container->getService('user');
/** @var UserContext $user */
if ($user->isLoggedIn()) {
$this->client->addCustomParameter('user', $user->getId());
}
}
public function onShutdown(Application $app)
{
$_ENV['APP_SHUTDOWN_TIME_FLOAT'] = microtime(TRUE);
$this->client->customTimeMetric('Nette/ResponseSendingTime', $_ENV['APP_SHUTDOWN_TIME_FLOAT'], $_ENV['APP_RESPONSE_TIME_FLOAT']);
}
}
<?php
namespace NewRelic;
use Kdyby;
use Nette;
use Nette\Application;
use Nette\ComponentModel\IContainer;
trait PresenterProfiler
{
private $methodCalls = array(
'loadState' => 0,
'saveGlobalState' => 0,
);
/**
* $_ENV['APP_PRESENTER_LEAVE'] - $_ENV['APP_PRESENTER_ENTER'] = run()
* $_ENV['APP_PRESENTER_LEAVE'] - $_ENV['APP_PRESENTER_SEND_RESPONSE'] = shutdown()
*/
public function run(Application\Request $request)
{
$_ENV['APP_PRESENTER_ENTER'] = microtime(TRUE);
try {
return parent::run($request);
} finally {
$_ENV['APP_PRESENTER_LEAVE'] = microtime(TRUE);
}
}
/**
* At the end of setParent, the globals init is called
*/
public function setParent(IContainer $parent = NULL, $name = NULL)
{
parent::setParent($parent, $name);
$_ENV['APP_PRESENTER_BEFORE_INIT'] = microtime(TRUE);
return $this;
}
/**
* First load state is called right before checkRequirements()
*
* $_ENV['APP_PRESENTER_REQUIREMENTS_BEGIN'] - $_ENV['APP_PRESENTER_BEFORE_INIT'] = initGlobalParameters()
*/
public function loadState(array $params)
{
$this->methodCalls['loadState'] += 1;
parent::loadState($params);
if (count($this->methodCalls['loadState']) === 1) {
$_ENV['APP_PRESENTER_REQUIREMENTS_BEGIN'] = microtime(TRUE);
}
}
/**
* action is after startup
*
* $_ENV['APP_PRESENTER_STARTUP_END'] - $_ENV['APP_PRESENTER_REQUIREMENTS_BEGIN'] = checkRequirements() + startup()
* $_ENV['APP_PRESENTER_ACTION_END'] - $_ENV['APP_PRESENTER_ACTION_BEGIN'] = action<default>()
* $_ENV['APP_PRESENTER_RENDER_END'] - $_ENV['APP_PRESENTER_RENDER_BEGIN'] = render<default>()
* $_ENV['APP_PRESENTER_RENDER_BEGIN'] - $_ENV['APP_PRESENTER_ACTION_END'] = beforeRender()
*/
protected function tryCall($method, array $params)
{
if ($isAction = Nette\Utils\Strings::startsWith($method, 'action')) {
$_ENV['APP_PRESENTER_STARTUP_END'] = microtime(TRUE);
$_ENV['APP_PRESENTER_ACTION_BEGIN'] = microtime(TRUE);
} elseif ($isRender = Nette\Utils\Strings::startsWith($method, 'render')) {
$_ENV['APP_PRESENTER_RENDER_BEGIN'] = microtime(TRUE);
}
try {
return parent::tryCall($method, $params);
} finally {
if ($isAction) {
$_ENV['APP_PRESENTER_ACTION_END'] = microtime(TRUE);
} elseif (!empty($isRender)) {
$_ENV['APP_PRESENTER_RENDER_END'] = microtime(TRUE);
}
}
}
/**
* $_ENV['APP_PRESENTER_SIGNAL_END'] - $_ENV['APP_PRESENTER_SIGNAL_BEGIN'] = processSignal()
*/
public function processSignal()
{
$_ENV['APP_PRESENTER_SIGNAL_BEGIN'] = microtime(TRUE);
parent::processSignal();
$_ENV['APP_PRESENTER_SIGNAL_END'] = microtime(TRUE);
}
/**
* $_ENV['APP_PRESENTER_AFTER_RENDER_END'] - $_ENV['APP_PRESENTER_RENDER_END'] = afterRender()
*/
protected function saveGlobalState()
{
$this->methodCalls['saveGlobalState'] += 1;
if (count($this->methodCalls['saveGlobalState']) === 1) {
$_ENV['APP_PRESENTER_AFTER_RENDER_END'] = microtime(TRUE);
}
parent::saveGlobalState();
}
/**
* $_ENV['APP_PRESENTER_SEND_TEMPLATE_END'] - $_ENV['APP_PRESENTER_SEND_TEMPLATE_BEGIN'] = sendTemplate()
*/
public function sendTemplate()
{
$_ENV['APP_PRESENTER_SEND_TEMPLATE_BEGIN'] = microtime(TRUE);
try {
parent::sendTemplate();
} finally {
$_ENV['APP_PRESENTER_SEND_TEMPLATE_END'] = microtime(TRUE);
}
}
public function sendResponse(Application\IResponse $response)
{
$_ENV['APP_PRESENTER_SEND_RESPONSE'] = microtime(TRUE);
parent::sendResponse($response);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment