Unofficial Parser Timer for MODX Revolution

We have been wondering about how we can speed MODX up to load.

Jason Coward, the MODX's Co-founder & Chief Architect, has written a snippet (called Executioner) to catch the execution time of snippet or chunk element, by wrapping them into the snippet's call. More information about it is on http://jasoncoward.com/technology/2010/10/the-executioner-snippet.html.

There is also another paid plugin (called debugParser) by Bezumkin, from Russian community, that tries to calculate the speed by showing the log on front-end. While most of its documentation is in Russian, this is a good plugin you can try.

But still, they are not as close as I expect to track the execution time, because they are actually wrapper add-ons, using MODX's API, which take their own additional time to initiate.

So, I ended up by trying another way around to directly hack the MODX's core to get what I wanted.

This method is not recommended, unless you know what you are doing. Thus, this was why I titled this post as "Unofficial".

There are 2 (two) files that need to be hacked:

  1. core/model/modx/modparser.class.php
  2. core/model/modx/modx.class.php

core/model/modx/modparser.class.php

Hack #1

class modParser {
    /**
     * A reference to the modX instance
     * @var modX $modx
     */
    public $modx= null;

    // ... more properties on here ...

    /**
     * If the parser is currently removing all unprocessed tags
     * @var bool $_removingUnprocessed
     */
    protected $_removingUnprocessed = false;

    /**
     * hack #1 starts
     * @var array timer 
     */
    protected $timer = array();
    protected $timerTotal = 0;
    /**
     * hack #1 ends
     */
    
    /**
     * @param xPDO $modx A reference to the modX|xPDO instance
     */
    function __construct(xPDO &$modx) {
        $this->modx =& $modx;
    }

Hack #2

In processTag() method, around line 450, add this hacks:

    // ... more codes on here ...

    if ($cacheable && $token !== '+') {
        $elementOutput= $this->loadFromCache($outerTag);
    }
    /**
     * hack #2 starts
     */
    if ($this->modx->getOption('parser_timer', null, false)) {
        $startTime = rtrim(sprintf('%.20F', microtime(true)), '0');
    }
    /**
     * hack #2 ends
     */
    if ($elementOutput === null) {
    
    // ... more codes on here ...
    

Hack #3

Still in processTag() method, around line 523, add this hacks:

    // ... more codes on here ...

    $this->_processingTag = false;

    /**
     * hack #3 starts
     */
    if ($this->modx->getOption('parser_timer', null, false)) {
        $endTime = microtime(true);
        $timer['time'] = rtrim(sprintf('%.20F', $endTime - $startTime), '0');
        $timer['tagName'] = $tagName;
        $timer['outerTag'] = $outerTag;
        $timer['innerTag'] = $innerTag;
        $timer['elementOutput'] = $elementOutput;
        $this->timer[] = $timer;
        $this->timerTotal += $timer['time'];
    }
    /**
     * hack #3 ends
     */
    
    return $elementOutput;
    
    // ... more codes on here ...    

Hack #4

Now, below the processTag() method, add this new additional method below. It is for getting the result and sort the timers from longest to quickest.

    /**
     * hack #4
     * @return type
     */
    public function getTimer() {
        $output = array();
        if ($this->modx->getOption('parser_timer', null, false)) {
            $total = array();
            foreach ($this->timer as $v) {
                $total[] = $v['time'];
            }
            array_multisort($total, SORT_DESC, SORT_NUMERIC, $this->timer);
            $output = array(
                'time' => $this->timerTotal,
                'tags' => $this->timer
            );
        }
        return $output;
    }

core/model/modx/modx.class.php

Now in the main MODX's class, we only need 1 hack that will get the output.

Search for a method called "_postProcess()", modify it as follow

    /**
     * Executed after the response is sent and execution is completed.
     *
     * @access protected
     */
    public function _postProcess() {
        if ($this->resourceGenerated && $this->getOption('cache_resource', null, true)) {
            if (is_object($this->resource) && $this->resource instanceof modResource && $this->resource->get('id') && $this->resource->get('cacheable')) {
                $this->invokeEvent('OnBeforeSaveWebPageCache');
                $this->cacheManager->generateResource($this->resource);
            }
        }
        /**
         * hack starts
         */
        if ($this->getOption('parser_timer', null, false) && $this->getParser() && is_callable(array($this->parser, 'getTimer'))) {
            try {
                $parserTimer = $this->parser->getTimer();
                $this->cacheManager->writeFile(
                        MODX_CORE_PATH . "cache/logs/parser_timer/{$this->resource->get('id')}.parser.log"
                        , print_r($parserTimer, 1)
                        , 'w');
            } catch (Exception $ex) {
                $this->log(modX::LOG_LEVEL_ERROR, __METHOD__ . ' ' . $ex->getMessage());
            }
        }
        /**
         * hack ends
         */
        $this->invokeEvent('OnWebPageComplete');
    }

If you notice, the additional hack will write the log into a file, under the core/cache/logs/ folder, and overwrite it on each initiation.

System Settings

If you aware, I use "parser_timer" setting to allow this hack runs.

So, you need to add a new setting on System Settings.

Parser Timer Setting

You can leave any other fields empty.

Output

Now, if you visit front-end, you will get new files under core/cache/logs/ being generated, with more raw and helpful information in it.

Parser Timer Files

Hopefuly this can help other MODX developers on debugging somehow.

Enjoy!



Comments

blog comments powered by Disqus