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/hg/js/hgSession.js src/hg/js/hgSession.js
index cc2942efb23..474beedc234 100644
--- src/hg/js/hgSession.js
+++ src/hg/js/hgSession.js
@@ -1,31 +1,31 @@
// hgSession.js - the experimental client-rendered "My Sessions" page.
//
// An opt-in modern alternative to the classic server-rendered hgSession page, applying hgBlat's
// facelift strategy (#37996): hgSession.c emits the session list and page config as an inline JSON
// global (hgSessionData) into an empty #sessionApp container, and this file builds the UI - a
// save-current-view card, a searchable/sortable DataTable of saved sessions with inline
// Overwrite/Share/Edit/Delete, and an "Advanced" panel for loading and backup.
//
// The inline table actions POST to small JSON endpoints in hgSession.c (hgS_doDeleteJson, etc.) and
// update the table in place. Navigation actions (load a session, load from URL/file, save to file,
// reset) are ordinary form submits/links against the existing hgSession actions.
//
// Styling: shared house-style components in gbModern.css (.gbPill, .gbCard, .gbModal*, .gbTable,
// .gbBanner, .gbSection), session-specific layout in hgSession.css.
-/* global $, hgSessionData, convertTitleTagsToMouseovers, htmlEncode, commify */
+/* global $, hgSessionData, convertTitleTagsToMouseovers, htmlEncode, commify, gbShowTimingDialog */
// Cart action variables (must match the hgs* defines in hgSession.h; hgSessionPrefix is "hgS_").
var SESS_ACT = {
save: 'hgS_doSaveSessionJson',
rename: 'hgS_doRenameSessionJson',
del: 'hgS_doDeleteJson',
share: 'hgS_doShareJson',
gallery: 'hgS_doGalleryJson',
overwrite: 'hgS_doOverwriteJson',
describe: 'hgS_doDescribeJson'
};
var SESS_P = {
oldName: 'hgS_oldSessionName',
newName: 'hgS_newSessionName',
share: 'hgS_newSessionShare',
@@ -594,30 +594,33 @@
return '
';
}
function sessTableHtml(C) {
if (!C.loggedIn) { return ''; }
return '
Your saved sessions
' +
'
';
}
function sessionBuild() {
sessData = hgSessionData;
+ // When the page was loaded with &measureTiming=1 the C side attaches sessData.timing; time the
+ // client-side render too so the dialog shows the full server+client picture.
+ var tBuildStart = (sessData.timing && window.performance) ? performance.now() : 0;
var C = sessData.config;
var app = $('#sessionApp');
var intro = '
' + sessAccountHtml(C) +
(sessAccountHtml(C) ? ' ' : '') +
'A session is a stable link to a Genome Browser view that you can save, load later, share ' +
'or copy into a manuscript. See the ' +
'Sessions User’s Guide and the ' +
'Session Gallery.
';
// The session most recently saved/overwritten (max lastUse), for the one-click "Update now".
var recent = null;
(sessData.sessions || []).forEach(function(s) {
if (!recent || s.lastUseEpoch > recent.lastUseEpoch) { recent = s; }
});
@@ -650,30 +653,51 @@
});
}
// Advanced toggle.
$('#sessAdvHead').on('click', function() {
var body = document.getElementById('sessAdvBody');
var open = body.style.display !== 'none';
body.style.display = open ? 'none' : 'grid';
$(this).find('.caret').html(open ? '▸' : '▾');
});
// Session table.
if (C.loggedIn) { sessBuildTable(); }
if (typeof convertTitleTagsToMouseovers === 'function') { convertTitleTagsToMouseovers(); }
+
+ // Timing report (only when loaded with &measureTiming=1): a pill that opens the shared dialog.
+ if (sessData.timing) {
+ var clientRows = [{ label: 'build page (JS)',
+ ms: Math.round(performance.now() - tBuildStart) }];
+ var pill = document.createElement('button');
+ pill.type = 'button';
+ pill.className = 'gbPill';
+ pill.id = 'sessTimingBtn';
+ pill.innerHTML = '⏱ Timing';
+ pill.title = 'Show where this page spent its time (server and browser)';
+ pill.addEventListener('click', function() {
+ gbShowTimingDialog(sessData.timing, clientRows);
+ });
+ var bar = document.querySelector('#sessionApp .sessToolbar') ||
+ document.getElementById('sessionApp');
+ bar.appendChild(pill);
+ // measureTiming=1 on the URL is an explicit request to see the numbers, so open the dialog
+ // right away; the pill stays for reopening it after Close.
+ gbShowTimingDialog(sessData.timing, clientRows);
+ }
}
// ---- bulk select / delete ------------------------------------------------
function sessToggleSelect() {
if (sessSelectMode) { sessDeleteSelected(); } else { sessSetSelectMode(true); }
}
function sessSetSelectMode(on) {
sessSelectMode = on;
sessDt.column(0).visible(on); // the leading checkbox column
var b = document.getElementById('sessSelectBtn');
if (on) {
b.textContent = 'Delete all selected';
b.classList.add('primary');