Jump to content

User:ATDT/global.js

From Meta, a Wikimedia project coordination wiki
This is an archived version of this page, as edited by ATDT (talk | contribs) at 21:17, 13 April 2021. It may differ significantly from the current version.

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
$(function () {
  const parsereport = mw.config.get('wgPageParseReport');
  const origin = parsereport.cachereport.origin;
  const walltime = parsereport.limitreport.walltime * 1000;
  const li = document.createElement('li');
  li.innerText = 'Parsed by ' + origin + ' in ' + walltime + 'ms.';
  document.getElementById('footer-info').appendChild(li);
});

/**
 * Perf menu by Krinkle (revision 11 Feb 2021).
 */
var domReady = new Promise(function (resolve) { document.readyState === 'complete' ? setTimeout(resolve) : document.onreadystatechange = setTimeout.bind(null, resolve); });
domReady.then(function () {
	var prevPortlet;
	var perfMenu;
	var nt;
	var ppr;
	var ppr14;
	var pprInt14;
	var nowInt14;

	var browserResp = 'unknown';
	var ttfb = null;
	var respParts = [];
	var cdnResp = 'unknown';
	var beParts = [];
	var beResp = 0;
	var pcParts = [];
	var pcResp = 0;

	function addItem(texts) {
		if (perfMenu) {
			var item = document.createElement('li');
			item.className = 'perf-textonly';
			if (Array.isArray(texts)) {
				texts.forEach(function (text, i) {
					if (text === null) {
						return;
					}
					if (i !== 0) {
						item.append(document.createElement('br'));
					}
					item.append(text);
				});
			} else {
				item.textContent = texts;
			}
			perfMenu.append(item);
		}
	}

	function conf(key) {
		return window.mw && window.mw.config && window.mw.config.get(key);
	}

	function tfmt(ms) {
		// use milliseconds upto 9000 ms, then use seconds
		return ms > 9000 ? (ms / 1000).toFixed(3) + ' s' : Math.round(ms).toLocaleString() + ' ms';
	}

	function percent(total, sub) {
		var ratio = (sub / total) * 100;
		return Math.floor(ratio) + '%';
	}

	// Vector skin
	prevPortlet = document.querySelector('#p-variants.vector-menu');
	if (prevPortlet) {
		prevPortlet.insertAdjacentHTML('afterend', ''
			+ '<style>#p-perf li.perf-textonly {'
			+ '  padding: 0.42em 0.625em;'
			+ '  line-height: 1.4;'
			+ '  font-size: 0.8125em;'
			+ '  white-space: nowrap;'
			+ '}</style>'
			+ '<nav id="p-perf" class="mw-portlet vector-menu vector-menu-dropdown" aria-labelledby="p-perf-label" role="navigation">'
			+ '<input type="checkbox" class="vector-menu-checkbox" aria-labelledby="p-perf-label"><h3 id="p-perf-label"><span>⏱</span></h3>'
			+ '<div class="vector-menu-content"><ul class="vector-menu-content-list"></ul></div></nav>'
		);
	}
	// Monobook skin
	prevPortlet = document.querySelector('#p-tb.portlet');
	if (prevPortlet) {
		prevPortlet.insertAdjacentHTML('afterend', ''
			+ '<div role="navigation" class="portlet" id="p-perf" aria-labelledby="p-perf-label">'
			+ '<h3 id="p-perf-label" dir="ltr" lang="en">⏱ Performance</h3>'
			+ '<div class="pBody"><ul dir="ltr" lang="en"></ul></div></div>'
		);
	}
	// Timeless skin
	prevPortlet = document.querySelector('#p-pagemisc.mw-portlet');
	if (prevPortlet) {
		prevPortlet.insertAdjacentHTML('afterend', ''
			+ '<style>#p-perf ul { color: #555; line-height: 1; font-size: 85%; }</style>'
			+ '<div role="navigation" class="mw-portlet" id="p-perf" aria-labelledby="p-perf-label">'
			+ '<h3 id="p-perf-label" dir="ltr" lang="en">⏱ Performance</h3>'
			+ '<div class="mw-portlet-body"><ul dir="ltr" lang="en"></ul></div></div>'
		);
	}
	// Minerva skin
	prevPortlet = document.querySelector('footer.minerva-footer');
	if (prevPortlet) {
		prevPortlet.insertAdjacentHTML('beforeend', ''
			// Use hlist for font style, but use separate lines
			+ '<style>#p-perf {'
			+ 'margin-top: 1rem;'
			+ 'overflow: visible;'
			+ '}'
			+ '#p-perf li {'
			+ 'display: list-item;'
			+ 'list-style: circle outside;'
			+ 'margin-left: 0.5rem;'
			+ '}</style>'
			+ '<div id="p-perf" class="post-content footer-content">'
			+ '<h2>⏱ Performance</h2>'
			+ '<ul class="hlist"></ul></div>');
	}
	perfMenu = document.querySelector('#p-perf ul');

	try {
		// Navigation Timing API
		nt = performance.getEntriesByType('navigation')[0];
		ttfb = nt.responseStart;

		// Resource Timing API
		if (nt.transferSize === 0) {
			browserResp = 'local cache (no network)';
		} else if (nt.transferSize > 0 && nt.encodedBodySize > 0 && nt.transferSize < nt.encodedBodySize ) {
			browserResp = 'local cache (after HTTP 304)';
		} else {
			browserResp = 'fresh HTTP 200';
		}
		respParts.push('Response time: ' + tfmt(ttfb));

		// Server Timing API
		if (nt.serverTiming[0].name === 'cache') {
			// One of "hit", "hit-front", "miss" or "pass"
			cdnResp = nt.serverTiming[0].description;
		}
		// MediaWiki-specific: config on all HTML responses
		beResp = (cdnResp.includes('hit') || browserResp.includes('cache')) ? 0 : conf('wgBackendResponseTime');
		if (beResp) {
			beParts.push('MediaWiki backend: ' + conf('wgHostname'));
		} else {
			beParts.push('MediaWiki backend: (cache hit)');
			beParts.push('(cached) host: ' + conf('wgHostname'));
			// Only show dedicated entry here if cached (and thus not shown in respParts)
			beParts.push('(cached) duration: ' + tfmt(conf('wgBackendResponseTime')));
		}

		respParts.push(
			'➙ ' + percent(ttfb, ttfb - beResp) + ' 🌐 Internet connection: ' + tfmt(ttfb - beResp),
			Object.assign(document.createElement('small'), { textContent: '   time between browser and CDN' })
		);
		if (beResp) {
			respParts.push('➙ ' + percent(ttfb, beResp) + ' 🌻 MediaWiki backend: ' + tfmt(beResp));
		}

		// MediaWiki-specific: on when action=view, on a page that exists, is local, and has wikitext content.
		ppr = conf('wgPageParseReport');
		if (ppr.cachereport && ppr.limitreport) {
			ppr14 = ppr.cachereport.timestamp;
			// This is the timestamp after parsing is done when it is about to saved.
			// Therefore, below we don't need to account for parse time itself.
			pprInt14 = Number(ppr14);
			// "2020-10-18T23:50:34.799Z" -> 20201018235034
			nowInt14 = Number(new Date(performance.timeOrigin).toISOString().replace(/([-T:]|\..*$)/g, ''));
			// Assume cache reuse, unless same host and under 5 seconds ago.
			pcResp = (ppr.cachereport.origin === conf('wgHostname') && (nowInt14 - pprInt14) < 5) ? (ppr.limitreport.walltime * 1000) : 0;
			if (pcResp) {
				respParts.push('➙➙ ' + percent(ttfb, pcResp) + ' 🧮 MediaWiki parser: ' + tfmt(pcResp));
				respParts.push('➙➙ ' + percent(ttfb, beResp - pcResp) + ' 🖼 MediaWiki skin: ' + tfmt(beResp - pcResp));
				pcParts.push('MediaWiki parser: miss (freshly parsed)');
			} else {
				respParts.push('➙➙ ' + percent(ttfb, pcResp) + ' 🧮 MediaWiki parser: ' + tfmt(pcResp));
				respParts.push('➙➙ ' + percent(ttfb, beResp - pcResp) + ' 🖼 MediaWiki skin: ' + tfmt(beResp - pcResp));
				pcParts.push('MediaWiki parser: (cache hit)');
				pcParts.push('(cached) host: ' + ppr.cachereport.origin);
				// Only show dedicated entry here if cached (and thus not shown in respParts)
				pcParts.push('(cached) duration: ' + tfmt(ppr.limitreport.walltime * 1000));
				// Only show  if cached, otherwise uninteresting
				pcParts.push('(cached) timestamp: ' + ppr14.slice(0, 4) + '-' + ppr14.slice(4, 6) + '-' + ppr14.slice(6, 8) +
					' ' + ppr14.slice(8, 10) + ':' + ppr14.slice(10, 12) + ':' + ppr14.slice(12, 14) + ' (UTC)'
				);
			}
		}
	} catch (e) {
		// Ignored
	}

	addItem('Response: ' + browserResp);
	addItem(respParts);
	addItem('CDN response: ' + cdnResp);
	addItem(beParts);
	addItem(pcParts);
});