Jaime Neto | desenvolvimento para web

jun/11

24

Cálculo de tempo decorrido com ZF


Não é muito raro vermos em alguns sites, principalmente no rodapé, a informação do tempo decorrido para exibir uma página. E nos sites em PHP, geralmente é usado a função microtime para calcular esse tempo, chamando-a antes e depois do trecho de código que queremos calcular o tempo de execução, e depois subtraindo o primeiro do último.

Isso é muito útil, principalmente para trabalharmos na melhora da performance do código. Foi daí que me deu a idéia de criar um plugin para Zend Framework para facilitar o uso dessa função, e eis o resultado:

class My_Controller_Plugin_ElapsedTime
    extends Zend_Controller_Plugin_Abstract
{
  /*
   * Constantes que serão usadas para identificar a partir de
   * que parte da execução deseja-se calcular o tempo decorrido.
   * O nome das constantes é baseado no nome das funções que
   * inicializam os valores correspondentes na propriedade
   * $_startTime, logo abaixo
   */
  const EVENT_ROUTE_STARTUP = 1;
  const EVENT_ROUTE_SHUTDOWN = 2;
  const EVENT_DISPATCH_LOOP_STARTUP = 3;
  const EVENT_PRE_DISPATCH = 4;
  const EVENT_POST_DISPATCH = 5;
  const EVENT_DISPATCH_LOOP_SHUTDOWN = 6;
 
  // Guarda o tempo de inicialização de cada parte da execução
  static protected $_startTime = array(
    self::EVENT_ROUTE_STARTUP => null,
    self::EVENT_ROUTE_SHUTDOWN => null,
    self::EVENT_DISPATCH_LOOP_STARTUP => null,
    self::EVENT_PRE_DISPATCH => null,
    self::EVENT_POST_DISPATCH => null,
    self::EVENT_DISPATCH_LOOP_SHUTDOWN => null
  );
 
  // Define o tempo de inicialização antes de as rotas serem
  // inicializadas
  public function routeStartup(
     Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_ROUTE_STARTUP] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização depois de as rotas serem
  // inicializadas
  public function routeShutdown(
     Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_ROUTE_SHUTDOWN] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização antes de iniciar o loop
  // de dispatch
  public function dispatchLoopStartup(
      Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_DISPATCH_LOOP_STARTUP] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização antes do dispatch de
  // uma action
  public function preDispatch(
      Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_PRE_DISPATCH] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização depois do dispatch de
  // uma action
  public function postDispatch(
      Zend_Controller_Request_Abstract $request)
  {
    self::$_startTime[self::EVENT_POST_DISPATCH] =
        $this->microtime();
  }
 
  // Define o tempo de inicialização depois de finalizar o
  // loop de dispatch
  public function dispatchLoopShutdown()
  {
    self::$_startTime[self::EVENT_DISPATCH_LOOP_SHUTDOWN] =
        $this->microtime();
  }
 
  /**
   * Retorna o tempo de inicialização de cada evento do plugin,
   * ou de um evento específico, que pode ser passado como
   * parâmetro baseado no valor das contantes desta classe
   *
   * @param int $event
   * @return mixed Float se receber $event, senão, array
   */
  public function getStartTime($event=null)
  {
    if ($event) {
      if (!isset(self::$_startTime[$event])) {
        throw new Zend_Exception('Invalid value for $event: '
           . $event);
      }
      return self::$_startTime[$event];
    }
    return self::$_startTime;
  }
 
  /**
   * Calcula o tempo decorrido de um trecho e código.
   *
   * @param int $event
   * @return float
   */
  public function getElapsedTime(
     $event=self::EVENT_ROUTE_STARTUP)
  {
    $startTime = $this->getStartTime($event);
 
    // Lança uma excessão Caso o evento não tenha sido
    // inicializado ainda
    if (!$startTime) {
      throw new Zend_Exception('Event has not been '
        . 'initialized yet');
    }
 
    // Pega o microtime atual e substrai dele o tempo
    // inicial para calcular o tempo decorrido
    $endTime = $this->microtime();
    $elapsedTime = $endTime - $startTime;
 
    return $elapsedTime;
  }
 
  /**
   * Pega o microtime em formato float, levando em conta as
   * variações sofridas pela função de acordo com a versão do
   * PHP
   */
  private function _microtime()
  {
    if (version_compare(PHP_VERSION, '5.0.0', '>=')) {
      // O parâmetro já converte o restulado da função para
      // float a partir da versão 5.0.0
      return microtime(true);
    } else {
      // Antes da versão 5.0.0, a conversão para float tinha
      // que ser feita manualmente
      list($usec, $sec) = explode(" ", microtime());
      return ((float)$usec + (float)$sec);
    }
  }
}

Ufa, parece coisa demais pra uma coisa tão simples, não é? Mas tudo isso nos dá a possibilidade de medir o tempo de execução a partir de várias partes diferentes do código, que é uma facilidade que os plugins do ZF nos oferece, então, por que não aproveitar isso pra fazer um plugin mais completo, não é?

Feito o plugin, agora vamos ao View Helper, que vai se encarregar de pegar o valor definido no plugin e subtraí-lo de um novo microtime para exibir na página:

class My_View_Helper_ElapsedTime
     extends Zend_View_Helper_Abstract
{
  /**
   * Exibe o tempo decorrido de um trecho de código
   * baseado nos valores inicializados no plugin
   * 'My_Controller_Plugin_ElapsedTime'
   *
   * @param int $precision
   * @param string|Zend_Locale $locale
   * @return float
   * @throws Zend_Exception
   */
  public function elapsedTime($precision = null,
      $locale = null, $event =
      My_Controller_Plugin_ElapsedTime::EVENT_ROUTE_STARTUP)
  {
    $pluginClass = 'My_Controller_Plugin_ElapsedTime';
 
    // Verifica se o plugin foi registrado
    if (!Zend_Controller_Front::getInstance()
            ->hasPlugin($pluginClass)) {
      throw new Exception("Plugin '{$_pluginClass}' is not '
          . 'registered");
    }
 
    // Primeiro vamos pegar o plugin lá do front controller,
    // que já foi inicializado
    $elapsedTimePlugin = Zend_Controller_Front::getInstance()
         ->getPlugin($pluginClass);
 
    // Agora vamos pegar o valor calculado do tempo decorrido,
    // passando como parâmetro uma daquelas constantes definidas
    // na classe do plugin, pra dizer a partir de que ponto do
    // código onde calcular o tempo de execução. O padrão é de
    // antes da inicialização das rotas.
    $elapsedTime = $elapsedTimePlugin->getElapsedTime($event);
 
    // Retorna o tempo decorrido formatado, levando em conta o
    // número de decimais a ser exibido e a localização, para
    // apresentar os símbolos de separação de milhares e de
    // decimais corretos
    return Zend_Locale_Format::toFloat($elapsedTime, array(
      'precision' => $precision, 'locale' => $locale));
    }
 
}

Agora é só chamar o view helper lá no script pra exibir o tempo? Não, ainda falta uma coisa. É preciso inicializar o plugin primeiro. Basta colocar lá no application.ini uma linhazinha de código, que ele será inicializado automaticamente:

resources.frontController.plugins[] = My_Controller_Plugin_ElapsedTime

Agora sim, vamos ao script phtml chamar nosso view helper e ver ele funcionando:

Essa página foi carregada em <?php echo $this->elapsedTime(2) ?> segundos

Agora vamos experimentar alterar os parâmetros pra ver os tempo decorrido a partir de diferentes trechos do código:

<ul><?php
$events = array(
  1 => 'routeStartup',
  2 => 'routeShutdown',
  3 => 'dispatchLoopStartup',
  4 => 'preDispatch',
  5 => 'postDispatch',
//  6 => 'dispatchLoopShutdown'
);
foreach($events as $eventId => $eventName) {
  echo '<li>' . $eventName . ': '
     . $this->elapsedTime(2, 'pt_BR', $eventId)
     . ' segundos</li>';
}
?></ul>

O último tipo de evento (dispatchLoopShutdown) ficou comentado por uma boa razão. Quando a view é executada, esse ponto do código ainda não foi alcançado, portanto será lançada uma excessão, como foi definido na classe do plugin. Esse caso é usado mais para fins de debug mesmo, e para ver o resultado desse cálculo, é preciso chamar o plugin diretamente do front controller. Um exemplo, é chamá-lo no index.php no fim do arquivo:

$application->bootstrap()->run();
echo Zend_Controller_Front::getInstance()
    ->getPlugin('Case_Controller_Plugin_ElapsedTime')
    ->getElapsedTime(
            Case_Controller_Plugin_ElapsedTime::EVENT_DISPATCH_LOOP_SHUTDOWN
    );

Prontinho. Façam bom proveito!

· · ·

1 comment

  • Ramon · 20 de julho de 2011 às 11:09

    Parabéns muito interessante o plugin, andei muito tempo atrás de um medidor de performance desse tipo xD

    Responder

Leave a Reply

<<

>>

Theme Design by devolux.nh2.me