7007b23dcc178ac0d6a64642ecd5a1060c6f6a40 braney Thu Aug 27 13:47:54 2026 -0700 hgTracks: measure how long the track image takes to reach the reader, refs #38109 We cannot choose a png compression level without knowing the reader's connection speed. Our own logs will not tell us: apache stops timing once the kernel has the bytes, so its duration field stays near two milliseconds whether the image is 20 KB or 500 KB. The reader's browser does know. It keeps a timing record for every image it loads, holding the bytes taken off the wire and the time waited. This reads that record for the track image and reports the numbers on the query string of DOT.gif, a 43 byte image that already sits in htdocs/images. No process starts, nothing touches the cart, and the client address, the time and the numbers all land in the same access log we already archive. New hg.conf setting pngTimingSampleRate. It is the N in one page load in N. Zero, or the setting left out, turns the whole thing off, which is the default. The report carries three numbers. ts is the bytes off the wire, d is the whole fetch, and x is only the time the bytes were arriving. x is the denominator for throughput, d is what the reader actually waited. diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js index 57c021b4b6b..9dda73c521b 100644 --- src/hg/js/hgTracks.js +++ src/hg/js/hgTracks.js @@ -7903,32 +7903,81 @@ myDataList.insertBefore(newListEl, customTracksItem.parentNode.nextSibling); else myDataList.insertBefore(newListEl, myDataList.firstElementChild); newLink.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); myVariants.showDialog(); }); } } if (typeof showMouseovers !== 'undefined' && showMouseovers) { convertTitleTagsToMouseovers(); } + if (typeof pngTimingSampleRate !== 'undefined' && pngTimingSampleRate > 0) { + reportPngTiming(pngTimingSampleRate); + } + }); +function reportPngTiming(sampleRate) { + /* Report how long the track image took to reach this reader, on one page + * load in sampleRate. The browser keeps a timing record for every image it + * loads, holding the bytes it took off the wire and the time it waited. We + * cannot get that from our own logs: apache stops timing once the kernel has + * the bytes. Bytes divided by time gives the reader's throughput, which is + * what decides whether a lower png compression level helps them or hurts + * them. The two numbers ride on the query string of a 43 byte image, so the + * apache log line is the whole record and no process has to start. */ + if (Math.random() * sampleRate >= 1) + return; + if (!window.performance || !window.performance.getEntriesByType) + return; + + var sendTiming = function () { + var entries = window.performance.getEntriesByType("resource"); + for (var i = 0; i < entries.length; i++) { + var entry = entries[i]; + // the track image is ../trash/hgt/hgt_<host>_<user>_<hex>.png. The + // guidelines and the side label images are named differently. + if (entry.name.indexOf("/hgt/hgt_") < 0) + continue; + if (entry.name.indexOf(".png") < 0) + continue; + var bytes = entry.transferSize; + var download = entry.responseEnd - entry.responseStart; + // an image answered from the browser cache has no transfer to time + if (!bytes || !download) + return; + // duration covers the whole fetch, download only the bytes arriving + new Image().src = "../images/DOT.gif?hgtPng=1" + + "&ts=" + Math.round(bytes) + + "&d=" + Math.round(entry.duration) + + "&x=" + Math.round(download); + return; + } + }; + + // the timing record only exists once the image has finished loading + if (document.readyState === "complete") + sendTiming(); + else + window.addEventListener("load", sendTiming); +} + function hgtWarnTiming(maxSeconds) { /* show a dialog box if the page load time was slower than x seconds. Has buttons to hide or never show this again. */ var loadTime = window.performance.timing.domContentLoadedEventStart-window.performance.timing.navigationStart; /// in msecs var loadSeconds = loadTime/1000; if (loadSeconds < maxSeconds) return; var skipNotification = localStorage.getItem("hgTracks.hideSpeedNotification"); dumpCart(loadSeconds, skipNotification); if (skipNotification) return; msg = "This page took "+loadSeconds+" seconds to load, more than "+maxSeconds+" seconds. We strive to keep "+ "the UCSC Genome Browser quick and responsive. See our "+