5a3be3af33b76d69d1fc98ee76b9a37f96e96fc7
max
  Wed Aug 26 00:36:52 2026 -0700
Add opt-in per-phase request timing to the new hgBlat and hgSession pages, shown in a dialog with &measureTiming=1. refs #38157

New reusable perfTimer collector (lib/perfTimer.c) records labeled wall-clock intervals via clock1000() and emits them as JSON; hgBlat and hgSession attach a timing array to their payload when the existing measureTiming cart/CGI var is set. A shared gbShowTimingDialog helper in utils.js renders the server phases plus a client render-time row in a house-style modal, which opens automatically when measureTiming is on. Confirms the position band/locus annotation loop dominates hgSession, and run-BLAT plus locus lookups dominate hgBlat.

diff --git src/lib/perfTimer.c src/lib/perfTimer.c
new file mode 100644
index 00000000000..a14faf381dd
--- /dev/null
+++ src/lib/perfTimer.c
@@ -0,0 +1,73 @@
+/* perfTimer - collect labeled wall-clock timings for a request and emit them as JSON.
+ * See perfTimer.h for how to use it. */
+
+/* Copyright (C) 2026 The Regents of the University of California
+ * See kent/LICENSE or http://genome.ucsc.edu/license/ for licensing information. */
+
+#include "common.h"
+#include "portable.h"
+#include "jsonWrite.h"
+#include "perfTimer.h"
+
+struct perfTimer *perfTimerNew(void)
+/* Return a new perfTimer with the clock started now. */
+{
+struct perfTimer *pt;
+AllocVar(pt);
+pt->startTime = pt->lastTime = clock1000();
+return pt;
+}
+
+void perfTimerStep(struct perfTimer *pt, char *label)
+/* Record the milliseconds elapsed since the previous step under label, and reset the
+ * mark.  No-op if pt is NULL. */
+{
+if (pt == NULL)
+    return;
+long now = clock1000();
+struct perfTimerStep *step;
+AllocVar(step);
+step->label = cloneString(label);
+step->ms = now - pt->lastTime;
+slAddHead(&pt->steps, step);
+pt->lastTime = now;
+}
+
+void perfTimerJson(struct perfTimer *pt, struct jsonWrite *jw, char *name)
+/* Emit "name": [ {"label":..,"ms":..}, ..., {"label":"total","ms":..} ] into jw.
+ * No-op if pt is NULL. */
+{
+if (pt == NULL)
+    return;
+jsonWriteListStart(jw, name);
+struct perfTimerStep *step;
+slReverse(&pt->steps);   /* Restore chronological order. */
+for (step = pt->steps; step != NULL; step = step->next)
+    {
+    jsonWriteObjectStart(jw, NULL);
+    jsonWriteString(jw, "label", step->label);
+    jsonWriteNumber(jw, "ms", step->ms);
+    jsonWriteObjectEnd(jw);
+    }
+jsonWriteObjectStart(jw, NULL);
+jsonWriteString(jw, "label", "total");
+jsonWriteNumber(jw, "ms", clock1000() - pt->startTime);
+jsonWriteObjectEnd(jw);
+jsonWriteListEnd(jw);
+}
+
+void perfTimerFree(struct perfTimer **pPt)
+/* Free a perfTimer and its steps. */
+{
+struct perfTimer *pt = *pPt;
+if (pt == NULL)
+    return;
+struct perfTimerStep *step, *next;
+for (step = pt->steps; step != NULL; step = next)
+    {
+    next = step->next;
+    freeMem(step->label);
+    freeMem(step);
+    }
+freez(pPt);
+}