f01c7dc7ea48d00510033d7c9448b20bd9c68e19
braney
  Mon Aug 31 13:44:24 2026 -0700
hgTracks: keep the fast png deliveries in the timing sample, refs #38109

Two fixes from a review of the timing beacon.

The first threw away a real measurement.  A download whose responseEnd and
responseStart fall in the same clock tick was treated the same as an image that
never crossed the wire.  Firefox rounds resource timing to a millisecond, so a
small track image on a fast link lands there.  Dropping those samples would
leave only the slow connections in the record, which is the wrong population to
pick a compression level from.  The beacon now sends them and reports x=0.
Take d as the denominator when x is zero.

The second was the beacon image itself.  An Image with no reference to it can be
collected before the request goes out.  It now lives in a variable that outlives
the function.

diff --git src/hg/js/hgTracks.js src/hg/js/hgTracks.js
index 9dda73c521b..4b20c81b4b0 100644
--- src/hg/js/hgTracks.js
+++ src/hg/js/hgTracks.js
@@ -7909,61 +7909,72 @@
                 myVariants.showDialog();
             });
         }
     }
 
     if (typeof showMouseovers !== 'undefined' && showMouseovers) {
         convertTitleTagsToMouseovers();
     }
 
     if (typeof pngTimingSampleRate !== 'undefined' && pngTimingSampleRate > 0) {
         reportPngTiming(pngTimingSampleRate);
     }
 
 });
 
+// hold the beacon image in a variable that outlives reportPngTiming.  An
+// Image with no reference to it can be collected before the request goes out.
+var pngTimingBeacon;
+
 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)
+            // no bytes means the browser never took the image off the wire, or
+            // it does not report the size.  Either way there is nothing to time.
+            if (!bytes)
                 return;
+            // a download of zero is a real delivery that finished inside one
+            // clock tick, not a missing measurement.  Firefox rounds resource
+            // timing to a millisecond, so a small image on a fast link lands
+            // there.  Report it and let the reader of the log fall back to d.
+            // Dropping it would leave only the slow connections in the sample.
             // duration covers the whole fetch, download only the bytes arriving
-            new Image().src = "../images/DOT.gif?hgtPng=1" +
+            pngTimingBeacon = new Image();
+            pngTimingBeacon.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) {