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/hgSession/hgSession.c src/hg/hgSession/hgSession.c index 683bfe1ad64..56c7a75a81d 100644 --- src/hg/hgSession/hgSession.c +++ src/hg/hgSession/hgSession.c @@ -30,32 +30,35 @@ #include "customTrack.h" #include "customFactory.h" #include "udc.h" #include "hgSession.h" #include "hgConfig.h" #include "sessionThumbnail.h" #include "filePath.h" #include "obscure.h" #include "trashDir.h" #include "hubConnect.h" #include "trackHub.h" #include "errCatch.h" #include "sessionData.h" #include "jsonParse.h" #include "jsonWrite.h" +#include "perfTimer.h" char *database = NULL; +struct perfTimer *hgSessionTiming = NULL; /* Non-NULL when &measureTiming is set; times the page + * and is emitted as hgSessionData.timing for the JS. */ void usage() /* Explain usage and exit. */ { errAbort( "hgSession - Interface with wiki login and do session saving/loading.\n" "usage:\n" " hgSession <various CGI settings>\n" ); } /* Global variables. */ struct cart *cart; char *excludeVars[] = {"Submit", "submit", hgsSessionDataDbSuffix, NULL}; @@ -2152,49 +2155,51 @@ jsonWriteString(jw, "changePasswordUrl", wikiLinkChangePasswordUrl(cartSessionId(cart))); } jsonWriteString(jw, "signupUrl", wikiLinkUserSignupUrl(cartSessionId(cart))); } jsonWriteStringf(jw, "classicUrl", "hgSession?sessionNewPage=0&%s=%s", cartSessionVarName(), cartSessionId(cart)); jsonWriteString(jw, "helpUrl", "../goldenPath/help/hgSessionHelp.html"); jsonWriteString(jw, "galleryUrl", "../goldenPath/help/sessions.html"); jsonWriteStringf(jw, "publicSessionsUrl", "../cgi-bin/hgPublicSessions?%s", cartSidUrlString(cart)); /* Reset-to-defaults link, same as showCartLinks(). */ char returnAddress[512]; safef(returnAddress, sizeof(returnAddress), "%s?%s", hgSessionName(), cartSidUrlString(cart)); jsonWriteStringf(jw, "resetUrl", "../cgi-bin/cartReset?%s&destination=%s", cartSidUrlString(cart), cgiEncodeFull(returnAddress)); jsonWriteObjectEnd(jw); // config +perfTimerStep(hgSessionTiming, "page header + config"); jsonWriteListStart(jw, "sessions"); if (loggedIn) { struct sqlConnection *conn = hConnectCentral(); if (sqlTableExists(conn, namedSessionTable)) { char *encUserName = cgiEncodeFull(userName); boolean gotSettings = (sqlFieldIndex(conn, namedSessionTable, "settings") >= 0); char query[512]; if (gotSettings) sqlSafef(query, sizeof(query), "SELECT sessionName, shared, firstUse, useCount, contents, settings, lastUse FROM %s " "WHERE userName = '%s' ORDER BY sessionName;", namedSessionTable, encUserName); else sqlSafef(query, sizeof(query), "SELECT sessionName, shared, firstUse, useCount, contents, lastUse FROM %s " "WHERE userName = '%s' ORDER BY sessionName;", namedSessionTable, encUserName); struct sqlResult *sr = sqlGetResult(conn, query); + perfTimerStep(hgSessionTiming, "load sessions from MySQL"); char **row; /* Cache one connection per assembly db so the per-session band/locus lookups don't * re-open a connection for every row when many sessions share an assembly. */ struct hash *dbConnCache = hashNew(0); while ((row = sqlNextRow(sr)) != NULL) { char *encSessionName = row[0]; char *sessionName = cgiDecodeClone(encSessionName); int shared = atoi(row[1]); char *firstUse = cloneString(row[2]); struct tm firstUseTm; ZeroVar(&firstUseTm); strptime(firstUse, "%Y-%m-%d %T", &firstUseTm); long epoch = (long)mktime(&firstUseTm); /* created = date only for display; createdFull = date+minute for the hover. */ @@ -2301,71 +2306,78 @@ jsonWriteString(jw, "description", description); jsonWriteString(jw, "shareUrl", dyUrl->string); jsonWriteObjectEnd(jw); dyStringFree(&dyUrl); freez(&band); freez(&locus); freez(&firstUse); freez(&dateOnly); freez(&createdFull); freez(&lastUse); freez(&lastUseDate); freez(&sessionName); } sqlFreeResult(&sr); + perfTimerStep(hgSessionTiming, "annotate positions (band + locus) + build JSON"); /* Release the cached per-assembly connections. */ struct hashEl *hel, *helList = hashElListHash(dbConnCache); for (hel = helList; hel != NULL; hel = hel->next) { struct sqlConnection *dbConn = hel->val; hFreeConn(&dbConn); } hashElFreeList(&helList); hashFree(&dbConnCache); } hDisconnectCentral(&conn); } jsonWriteListEnd(jw); // sessions } void doMainPageNew(char *userName, char *message) /* Render the experimental client-rendered Sessions page: framework header (gold "My Sessions" * band), the experimental banner, an empty #sessionApp container, and the hgSessionData JSON that * hgSession.js reads to build the UI. */ { +if (isNotEmpty(cartOptionalString(cart, "measureTiming"))) + hgSessionTiming = perfTimerNew(); /* times the page; emitted as hgSessionData.timing */ cspWriteResponseHeader(); puts("Content-Type:text/html\n"); cartWebStart(cart, NULL, "My Sessions"); jsInit(); jsIncludeDataTablesLibs(); webIncludeResourceFile("gbModern.css"); webIncludeResourceFile("hgSession.css"); jsIncludeFile("hgSession.js", NULL); printSessionNewPageBanner(TRUE); if (isNotEmpty(message)) printf("<div class='gbBanner'>%s</div>\n", message); printf("<div id='sessionApp' class='gbApp'></div>\n"); struct jsonWrite *jw = jsonWriteNew(); jsonWriteObjectStart(jw, NULL); sessionDataToJson(userName, jw); +/* When &measureTiming is set, hand the per-phase timings to hgSession.js (it shows them in a + * dialog). Emitted at the top level as hgSessionData.timing. */ +perfTimerJson(hgSessionTiming, jw, "timing"); jsonWriteObjectEnd(jw); jsInlineF("var hgSessionData = %s;\n", jw->dy->string); jsonWriteFree(&jw); +perfTimerFree(&hgSessionTiming); cartWebEnd(); } /* ---- JSON action endpoints for the experimental page's inline table actions ---- */ static void saveSessionJsonOk(struct sqlConnection *conn, char *extraFields) /* Emit {"success": true[, <extraFields>]} and disconnect. extraFields (may be NULL) is inserted * verbatim after "success": true, e.g. ", \"shared\": 2". */ { puts("Content-Type:application/json\n"); printf("{\"success\": true%s}\n", extraFields ? extraFields : ""); hDisconnectCentral(&conn); }