Page MenuHomePhabricator

Examine critical rendering path (DevTools Timeline analysis)
Closed, ResolvedPublic

Description

I did this analysis to better understand how T142129: Source code eval should be async in mw.loader.work will change things (and to prevent certain regressions).

Critical path examination

Gathered with the DevTools in Chrome canary 52 on a MacBook Pro, running MediaWiki-Vagrant.

  • CPU throttle: 2x slowdown.
  • Network throttle: 100ms 750kb/s ("Regular 3G")
  • Repeat view (local storage warmed up)

Simplified for brevity.

300ms:

  • Receive Response (Main_Page)
    • Send Request (Priority Highest; load.php?modules=... styles)
    • Send Request (Priority Low; load.php?modules=startup)
    • Send Request (Priority Highest; load.php?modules=site.styles)
    • Evaluate Script. documentElement.className
    • Evaluate Script. RLQ.push(config, state, implement, load top)
  • HTML: Parse
    • Send Request (poweredby_mediawiki)
    • Receive Response (load.php?modules=site.styles)

600ms:

    • Receive Response (load.php?modules=... styles)
      • Evaluate Script. RLQ.push(load bottom, more config)
  • HTML: Parse
    • Paint: Calculate styles
      • Send Request (mediawiki-vagrant.png logo)
    • Paint: Layout
  • Mark: DOMContentLoaded

This shows (as we know) that stylesheets block execution of inline scripts. The bottom inline script doesn't execute until the stylesheet is fully received and parsed.

See also:

This also shows (as we know) that the discovery of the logo resource is blocked on the arrival of the main stylesheet. While first paint usually waits for the main stylesheet, the logo will have only just started loading in the background at that time, and as such tends to show up later. See T100999.

800ms:

  • Mark: firstPaint
  • Finish Loading (poweredby_mediawiki)
  • Finish Loading (mediawiki-vagrant.png logo)

1000ms:

  • Finish Loading (load.php?modules=startup)
    • Evaluate Script. isCompatible, getElementsByTagName('head'), appendChild
      • Send Request (load.php?modules=jquery+mediawiki)

1300ms:

  • Finish Loading (load.php?modules=jquery+mediawiki)
    • Evaluate Script.
      • (83ms) define jQuery, Sizzle and Major GC
      • (0.5ms) define mw.now
      • (0.3ms) define $j via mw.log.deprecate
  • Event: load (load.php?modules=jquery+mediawiki)
    • Evaluate Script onreadystatechange/startUp from load.php?modules=startup
      • (3ms) mw.loader.register
      • (0.3ms) mw.config.set
      • (120ms) run RLQ functions from HTML
        • (0.1ms) mw.config.set from top
        • (2ms) mw.loader.state from top
        • (56ms) mw.loader.implement user.tokens and user.options
          • (52ms) execute/runScript/using/work (most store.init and globalEval)
        • (62ms) mw.loader.load top queue
        • (21ms) mw.loader.load bottom queue (also pushed into RLQ already)

The slow mw.loader.implement statement is confusing at first because it's merely implementing two tiny private modules that have almost no execution time.

The reason for the slow down is that all modules must wait for legacy modules, which technically must include embedded private modules like these as well. So this is the first point where something requires the legacy modules, which we end up loading and evaluating from cache at this point. These legacy modules are removed in https://gerrit.wikimedia.org/r/282505. The general change to make cache-eval asynchronous would mitigate this as well by deferred it to later (T142129; https://gerrit.wikimedia.org/r/304158).

1500ms:

  • Event: load; Function Call (jQuery.ready event fires)
    • jquery.js (internal listeners)
    • (19ms) mediawiki.js listener (code that'll emit mwLoadEnd)
    • mediawiki.page.startup listener (for mw.hook#fire)
    • (15ms) vector.js listener
  • Mark: Load

For more details about mwLoadEnd and vector.js overhead, see https://gerrit.wikimedia.org/r/306871 and https://gerrit.wikimedia.org/r/306869 .

1800ms

  • Timer Fired; Function Call (addEmbeddedCSS)
    • newStyleTag
    • (62ms) runScript

This timer marks the end of the CSS buffer for all top and bottom modules. Any modules with scripts but no styles will have executed already in the load() around 1300ms. Execution for modules with styles happens here.

A few notable scripts:

(62ms) runScript

  • (3ms) mediawiki.action.view.postEdit (cookie.get, cookie.set, setTimeout)
  • (2ms) ext.uls.common (mw.loader.using)
  • (17ms) ext.uls.webfonts (mw.webfonts.setup, $(body).css, Recalculate Style Forced reflow)
  • (5ms) ext.uls.interface (jQuery/querySelectorAll, Message#parser, jQuery#text, Store#get)
  • (27ms) mediawiki.page.ready
    • $.fn.makeCollapsible
    • $.fn.checkboxShiftClick
    • (25ms) $.fn.updateTooltipAccessKeys (Message#parser)

2900ms

  • Timer Fired; Function Call (addEmbeddedCSS)
    • newStyleTag
    • (25ms) runScript

This timer marks the end of the first (and usually only) CSS buffer for modules lazy-loaded via mw.loader.using() by one of the previously executed scripts.

A few notable scripts:

(25ms) runScript

  • (7ms) ext.visualEditor.desktopArticleTarget.init (cookie.get, DOM writes)
  • (14ms) mediawiki.searchSuggest ($.fn.suggestions, $.fn.css, Recalculate Style Forced reflow, Minor GC)

2990ms
Timer Fired; Function Call ($.fn.webfonts, WebFonts#parse, $.fn.css, Recalculate Style Forced reflow, $.fn.is, $.fn.find)


Relevant open tasks and patches:

Event Timeline

Krinkle renamed this task from Examine critical rendering path to Examine critical rendering path (DevTools Timeline analysis).Aug 27 2016, 1:22 AM
Krinkle closed this task as Resolved.
Krinkle created this task.

It's been a while since I did an in-depth analysis using DevTools Timeline capture in Chrome. The tools have also improved a lot and made things substantially quicker to digest and understand.

One thing in particular that helped a lot is in Timeline, blocks representing JavaScript execution are not only labeled with the function name, but also coloured based on where the source code came from.

This made it easy to find where a stack of jquery/mediawiki.js ends and identify the nodes where individual module execution starts. (globalEval/VM has its own color, too).

Screen Shot 2016-08-26 at 18.37.52.png (738×1 px, 204 KB)

In the image above, blue is the jquery+mediawiki base module request. Any code underneath an orange block is a core or extension module executing. The orange blocks are execution from localStorage eval.

Clicking on an orange block then leads to the module in question:

Screen Shot 2016-08-26 at 18.38.07.png (288×1 px, 77 KB)