How to identify slow widgets on a Service Portal pageIssue <!-- /*NS Branding Styles*/ --> .ns-kb-css-body-editor-container { p { font-size: 12pt; font-family: Lato; color: #000000; } span { font-size: 12pt; font-family: Lato; color: #000000; } h2 { font-size: 24pt; font-family: Lato; color: black; } h3 { font-size: 18pt; font-family: Lato; color: black; } h4 { font-size: 14pt; font-family: Lato; color: black; } a { font-size: 12pt; font-family: Lato; color: #00718F; } a:hover { font-size: 12pt; color: #024F69; } a:target { font-size: 12pt; color: #032D42; } a:visited { font-size: 12pt; color: #00718f; } ul { font-size: 12pt; font-family: Lato; } li { font-size: 12pt; font-family: Lato; } img { display: ; max-width: ; width: ; height: ; } } Identify slow widgets on a Service Portal page and measure their load times. This article provides a script that highlights each widget, displays load times, and generates a report of widgets exceeding a configurable threshold. Release<!-- /*NS Branding Styles*/ --> .ns-kb-css-body-editor-container { p { font-size: 12pt; font-family: Lato; color: #000000; } span { font-size: 12pt; font-family: Lato; color: #000000; } h2 { font-size: 24pt; font-family: Lato; color: black; } h3 { font-size: 18pt; font-family: Lato; color: black; } h4 { font-size: 14pt; font-family: Lato; color: black; } a { font-size: 12pt; font-family: Lato; color: #00718F; } a:hover { font-size: 12pt; color: #024F69; } a:target { font-size: 12pt; color: #032D42; } a:visited { font-size: 12pt; color: #00718f; } ul { font-size: 12pt; font-family: Lato; } li { font-size: 12pt; font-family: Lato; } img { display: ; max-width: ; width: ; height: ; } } All supported releases Resolution<!-- /*NS Branding Styles*/ --> .ns-kb-css-body-editor-container { p { font-size: 12pt; font-family: Lato; color: #000000; } span { font-size: 12pt; font-family: Lato; color: #000000; } h2 { font-size: 24pt; font-family: Lato; color: black; } h3 { font-size: 18pt; font-family: Lato; color: black; } h4 { font-size: 14pt; font-family: Lato; color: black; } a { font-size: 12pt; font-family: Lato; color: #00718F; } a:hover { font-size: 12pt; color: #024F69; } a:target { font-size: 12pt; color: #032D42; } a:visited { font-size: 12pt; color: #00718f; } ul { font-size: 12pt; font-family: Lato; } li { font-size: 12pt; font-family: Lato; } img { display: ; max-width: ; width: ; height: ; } } 1. Copy the following script: (async () => { let threshold = 1000; // only show load times for widgets higher than this value (in milliseconds) var wa = $("div [widget='widget']").css("border", "1px solid red").css("padding-top", "20px").css("position", "relative") let widgetTable = []; let wmk = []; for (var i = 0; i < wa.length; i++) { let widgetEntry = {}; $0 = wa[i] let scope = $($0).scope(); try{ var widget = scope.widget; } catch(e){ console.error(e); continue; } var timing = 0; let elem = $("<div style='position: absolute; top: 1px; left: 1px'><a target='_blank' href='/$sp.do?id=widget_editor&sys_id=" + widget.sys_id + "'> " + widget.name + "</a> </div>"); var printScope = $("<a href='javascript:void(0);'>Print scope </a>").on('click', function() { console.info(scope); }); elem.append(printScope); widgetEntry.name = widget.name; widgetEntry.rectangle = widget.rectangle_id || 'undefined'; widgetEntry.sys_id = widget.sys_id; let id = scope.widget.rectangle_id + "_" + scope.$id // if this is not a nested widget, go ahead and refresh it. if (!scope.$parent || !scope.$parent.widget) { var widget_name = widget.name; var t0 = performance.now(); await scope.server.refresh(); var t1 = performance.now(); timing = "<div style='float:right;color:red;' id='" + id + "'> Load Time: " + parseInt(t1 - t0) + " ms.</div>"; widgetEntry.load_time_ms = parseInt(t1 - t0) || 0; elem.append(timing); } // add a button to refresh manually. var loadTime = $("<button style='border-radius: 50%;'> ⟳ </button>").on('click', function() { var widget_name = scope.widget.name; let id = scope.widget.rectangle_id + "_" + scope.$id var t0 = performance.now(); scope.server.refresh().then(() => { var t1 = performance.now(); timing = "<div style='float:right;color:red;' id='" + id + "'> Load Time: " + parseInt(t1 - t0) + " ms.</div>"; widgetEntry.load_time_ms = parseInt(t1 - t0) || 0; if ($('#' + id)) { $('#' + id).remove(); } elem.append(timing); console.log("Call to " + widget_name + " took " + (t1 - t0) + " ms."); }); }); elem.append(loadTime); $($0).append(elem); widgetTable.push(widgetEntry); } widgetTable.sort((a, b) => (a.load_time_ms > b.load_time_ms) ? 1 : -1); var slow = widgetTable.filter((e, i, w) => { return e.load_time_ms >= threshold; }); let mkdn = `|Name|sys_id|rectangle_id|Load Time Ms| |:---:|:---:|:---:|:---:| ${slow.map((widgetEntry, i, widgetTable) => { return `|${widgetEntry.name}|${widgetEntry.sys_id}|${widgetEntry.rectangle}|${widgetEntry.load_time_ms}|`; }).join("\n")}`; var doCopy = function() { var el = document.createElement('textarea'); el.value = mkdn; el.setAttribute('readonly', ''); el.style = { position: 'absolute', left: '-9999px' }; document.body.appendChild(el); el.select(); document.execCommand('copy'); document.body.removeChild(el); } var elem = `<div style="float: right; z-index: 1; position: relative; left: -50%; /* or right 50% */ text-align: left; padding:10px"> <h2>${(slow.length > 0)?"Slow Widgets:" : "No Slow Widgets Found!"}</h2> <table style="padding:3px; display:${(slow.length > 0)?"table":"none"}"> <tr><td style="padding:3px;" >Name</td><td style="padding:3px;">sys_id</td><td style="padding:3px;">rectangle id</td><td style="padding:3px;">load time ms</td></tr> ${slow.map((widgetEntry, i, widgetTable) => { return `<tr><td style="padding:3px;">${widgetEntry.name}</td><td style="padding:3px;">${widgetEntry.sys_id}</td><td style="padding:3px;">${widgetEntry.rectangle}</td><td style="padding:3px;">${widgetEntry.load_time_ms}</td></tr>`; }).join(" ")} </table> <button onClick=${doCopy()} style="display:${(slow.length > 0)?"table":"none"}">Copy Markdown</button> </div>`; $('body').append(elem); })(); 2. Go to the portal page you want to test. 3. Open the browser JavaScript console: Mac: Command+Option+JWindows: Ctrl+Shift+J 4. Paste the script into the console, and then press Enter. Review the results The script produced the following output: Each widget displays a red outline with its load time in milliseconds.A summary table appears at the bottom of the page showing widgets that meet or exceed the threshold.By default, the threshold is 1000 milliseconds (one second). To change this value, edit the threshold variable at the top of the script. Each widget overlay includes: The widget name as a link to open it in the widget editorA Print scope link to display the widget scope and loaded data in the consoleA refresh button to reload the widget and update the load time To copy the results, select Copy Markdown in the summary table. Additional considerations Embedded widgets: Embedded (nested) widgets do not automatically refresh because they depend on parameters from the parent widget. Use the refresh button on each embedded widget to measure its load time manually. Related Links<!-- /*NS Branding Styles*/ --> .ns-kb-css-body-editor-container { p { font-size: 12pt; font-family: Lato; color: #000000; } span { font-size: 12pt; font-family: Lato; color: #000000; } h2 { font-size: 24pt; font-family: Lato; color: black; } h3 { font-size: 18pt; font-family: Lato; color: black; } h4 { font-size: 14pt; font-family: Lato; color: black; } a { font-size: 12pt; font-family: Lato; color: #00718F; } a:hover { font-size: 12pt; color: #024F69; } a:target { font-size: 12pt; color: #032D42; } a:visited { font-size: 12pt; color: #00718f; } ul { font-size: 12pt; font-family: Lato; } li { font-size: 12pt; font-family: Lato; } img { display: ; max-width: ; width: ; height: ; } }