Skip to content

Commit 78fb46a

Browse files
committed
Add max,min and abs(max) as selectable aggregation to maxmem diff summary and resource diff summary
1 parent 21c47aa commit 78fb46a

2 files changed

Lines changed: 159 additions & 20 deletions

File tree

comparisons/maxmem_summary_viewer.html

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
.grid {
114114
display: grid;
115115
gap: 12px;
116-
grid-template-columns: repeat(4, minmax(160px, 1fr));
116+
grid-template-columns: repeat(5, minmax(160px, 1fr));
117117
}
118118

119119
label {
@@ -488,6 +488,14 @@ <h1>Max Memory Summary Viewer</h1>
488488
<option value="adiff-asc">PR - baseline ascending</option>
489489
</select>
490490
</div>
491+
<div>
492+
<label for="aggregationSelect">Aggregation</label>
493+
<select id="aggregationSelect">
494+
<option value="max" selected>max</option>
495+
<option value="min">min</option>
496+
<option value="abs-max">abs(max)</option>
497+
</select>
498+
</div>
491499
<label class="checkbox" for="hideZeroRows">
492500
<input id="hideZeroRows" type="checkbox">
493501
Hide workflows with only zero values
@@ -553,13 +561,20 @@ <h1>Max Memory Summary Viewer</h1>
553561
{ key: "pdiff", label: "100 * (PR - baseline) / baseline", unit: "%" }
554562
];
555563

564+
const AGGREGATIONS = [
565+
{ key: "max", label: "max" },
566+
{ key: "min", label: "min" },
567+
{ key: "abs-max", label: "abs(max)" }
568+
];
569+
556570
const state = {
557571
payload: null,
558572
workflows: [],
559573
steps: [],
560574
search: "",
561575
quantityKey: "max memory",
562576
sortKey: "workflow-asc",
577+
aggregationKey: "max",
563578
expandedWorkflows: new Set(),
564579
detailSortKey: "step",
565580
detailSortDir: "asc"
@@ -569,6 +584,7 @@ <h1>Max Memory Summary Viewer</h1>
569584
workflowSearch: document.getElementById("workflowSearch"),
570585
quantitySelect: document.getElementById("quantitySelect"),
571586
sortSelect: document.getElementById("sortSelect"),
587+
aggregationSelect: document.getElementById("aggregationSelect"),
572588
hideZeroRows: document.getElementById("hideZeroRows"),
573589
loadedCount: document.getElementById("loadedCount"),
574590
visibleCount: document.getElementById("visibleCount"),
@@ -613,6 +629,20 @@ <h1>Max Memory Summary Viewer</h1>
613629
return `${quantityKey} ${legendKey}`;
614630
}
615631

632+
function currentAggregation() {
633+
return AGGREGATIONS.find((aggregation) => aggregation.key === state.aggregationKey) || AGGREGATIONS[0];
634+
}
635+
636+
function aggregatedNumericValue(value) {
637+
if (typeof value !== "number" || Number.isNaN(value)) {
638+
return value;
639+
}
640+
if (state.aggregationKey === "abs-max") {
641+
return Math.abs(value);
642+
}
643+
return value;
644+
}
645+
616646
function formatValue(value, quantity, legend) {
617647
if (value === undefined || value === null || Number.isNaN(value)) {
618648
return "-";
@@ -690,7 +720,7 @@ <h1>Max Memory Summary Viewer</h1>
690720
case "pdiff-asc": {
691721
const legendKey = state.sortKey.split("-")[0];
692722
const sortField = fieldName(currentQuantity().key, legendKey);
693-
return compareMaybeNumbers(maxStepValue(left, sortField).value, maxStepValue(right, sortField).value)
723+
return compareMaybeNumbers(aggregateStepValue(left, sortField).value, aggregateStepValue(right, sortField).value)
694724
|| workflowNumber(left.name) - workflowNumber(right.name)
695725
|| left.name.localeCompare(right.name);
696726
}
@@ -700,7 +730,7 @@ <h1>Max Memory Summary Viewer</h1>
700730
case "pdiff-desc": {
701731
const legendKey = state.sortKey.split("-")[0];
702732
const sortField = fieldName(currentQuantity().key, legendKey);
703-
return compareMaybeNumbers(maxStepValue(right, sortField).value, maxStepValue(left, sortField).value)
733+
return compareMaybeNumbers(aggregateStepValue(right, sortField).value, aggregateStepValue(left, sortField).value)
704734
|| workflowNumber(left.name) - workflowNumber(right.name)
705735
|| left.name.localeCompare(right.name);
706736
}
@@ -731,7 +761,7 @@ <h1>Max Memory Summary Viewer</h1>
731761
renderTable();
732762
}
733763

734-
function maxStepValue(row, selectedField) {
764+
function aggregateStepValue(row, selectedField) {
735765
let bestValue = null;
736766
let bestStepData = null;
737767

@@ -741,7 +771,30 @@ <h1>Max Memory Summary Viewer</h1>
741771
if (typeof value !== "number" || Number.isNaN(value)) {
742772
continue;
743773
}
744-
if (bestValue === null || value > bestValue) {
774+
775+
if (bestValue === null) {
776+
bestValue = value;
777+
bestStepData = stepData;
778+
continue;
779+
}
780+
781+
if (state.aggregationKey === "min") {
782+
if (value < bestValue) {
783+
bestValue = value;
784+
bestStepData = stepData;
785+
}
786+
continue;
787+
}
788+
789+
if (state.aggregationKey === "abs-max") {
790+
if (Math.abs(value) > Math.abs(bestValue)) {
791+
bestValue = Math.abs(value);
792+
bestStepData = stepData;
793+
}
794+
continue;
795+
}
796+
797+
if (value > bestValue) {
745798
bestValue = value;
746799
bestStepData = stepData;
747800
}
@@ -821,10 +874,10 @@ <h1>Max Memory Summary Viewer</h1>
821874
workflow: workflowName,
822875
step,
823876
stepOrder: stepNumber(step),
824-
baseline: stepData[fieldName(quantity.key, "base")],
825-
pullRequest: stepData[fieldName(quantity.key, "pr")],
826-
absoluteDiff: stepData[fieldName(quantity.key, "adiff")],
827-
percentDiff: stepData[fieldName(quantity.key, "pdiff")]
877+
baseline: aggregatedNumericValue(stepData[fieldName(quantity.key, "base")]),
878+
pullRequest: aggregatedNumericValue(stepData[fieldName(quantity.key, "pr")]),
879+
absoluteDiff: aggregatedNumericValue(stepData[fieldName(quantity.key, "adiff")]),
880+
percentDiff: aggregatedNumericValue(stepData[fieldName(quantity.key, "pdiff")])
828881
};
829882
});
830883

@@ -880,6 +933,7 @@ <h1>Max Memory Summary Viewer</h1>
880933

881934
function buildDetailTable(workflowName, steps) {
882935
const quantity = currentQuantity();
936+
const aggregation = currentAggregation();
883937
const detailRows = getDetailRows(workflowName, steps);
884938
const percentLegend = LEGENDS.find((legend) => legend.key === "pdiff");
885939
const numberLegend = { key: "base", unit: quantity.unit };
@@ -892,10 +946,10 @@ <h1>Max Memory Summary Viewer</h1>
892946
<tr>
893947
<th class="sortable" data-sort-key="workflow">Workflow${detailSortIndicator("workflow")}</th>
894948
<th class="sortable" data-sort-key="step">Step${detailSortIndicator("step")}</th>
895-
<th class="sortable th-num" data-sort-key="baseline">Baseline${detailSortIndicator("baseline")}</th>
896-
<th class="sortable th-num" data-sort-key="pullRequest">Pull Request${detailSortIndicator("pullRequest")}</th>
897-
<th class="sortable th-num" data-sort-key="absoluteDiff">PR - Baseline${detailSortIndicator("absoluteDiff")}</th>
898-
<th class="sortable th-num" data-sort-key="percentDiff">Percent Diff${detailSortIndicator("percentDiff")}</th>
949+
<th class="sortable th-num" data-sort-key="baseline">Baseline (${aggregation.label})${detailSortIndicator("baseline")}</th>
950+
<th class="sortable th-num" data-sort-key="pullRequest">Pull Request (${aggregation.label})${detailSortIndicator("pullRequest")}</th>
951+
<th class="sortable th-num" data-sort-key="absoluteDiff">PR - Baseline (${aggregation.label})${detailSortIndicator("absoluteDiff")}</th>
952+
<th class="sortable th-num" data-sort-key="percentDiff">Percent Diff (${aggregation.label})${detailSortIndicator("percentDiff")}</th>
899953
</tr>
900954
</thead>
901955
<tbody>
@@ -934,6 +988,7 @@ <h1>Max Memory Summary Viewer</h1>
934988
const rows = getWorkflowRows();
935989
const peak = peakSignal(rows, selectedField);
936990
const adiffLegend = LEGENDS.find((legend) => legend.key === "adiff") || LEGENDS[0];
991+
const aggregation = currentAggregation();
937992

938993
el.loadedCount.textContent = state.workflows.length.toLocaleString();
939994
el.visibleCount.textContent = rows.length.toLocaleString();
@@ -957,7 +1012,7 @@ <h1>Max Memory Summary Viewer</h1>
9571012
const legendHead = document.createElement("th");
9581013
legendHead.className = "sortable th-num";
9591014
legendHead.setAttribute("data-sort-key", legend.key);
960-
legendHead.textContent = `${legend.label} (max)${mainSortIndicator(legend.key)}`;
1015+
legendHead.textContent = `${legend.label} (${aggregation.label})${mainSortIndicator(legend.key)}`;
9611016
el.tableHeadRow.appendChild(legendHead);
9621017
}
9631018

@@ -985,7 +1040,7 @@ <h1>Max Memory Summary Viewer</h1>
9851040

9861041
for (const legend of LEGENDS) {
9871042
const legendField = fieldName(quantity.key, legend.key);
988-
const maxValue = maxStepValue(row, legendField);
1043+
const maxValue = aggregateStepValue(row, legendField);
9891044
const maxValueCell = document.createElement("td");
9901045
maxValueCell.className = cellClass(quantity, legend, maxValue.stepData, maxValue.value);
9911046
maxValueCell.textContent = formatValue(maxValue.value, quantity, legend);
@@ -1014,7 +1069,7 @@ <h1>Max Memory Summary Viewer</h1>
10141069
});
10151070
attachDetailSortHandlers();
10161071

1017-
setStatus(`Showing ${rows.length.toLocaleString()} workflows for ${quantity.label}, sorted by ${mainSortColumn() === "workflow" ? "workflow" : mainSortColumn() + " max"}.`, false);
1072+
setStatus(`Showing ${rows.length.toLocaleString()} workflows for ${quantity.label}, sorted by ${mainSortColumn() === "workflow" ? "workflow" : mainSortColumn() + " " + aggregation.label}.`, false);
10181073
}
10191074

10201075
function populateSelectors() {
@@ -1023,6 +1078,10 @@ <h1>Max Memory Summary Viewer</h1>
10231078
.join("");
10241079
el.quantitySelect.value = state.quantityKey;
10251080
el.sortSelect.value = state.sortKey;
1081+
el.aggregationSelect.innerHTML = AGGREGATIONS
1082+
.map((aggregation) => `<option value="${escapeHtml(aggregation.key)}">${escapeHtml(aggregation.label)}</option>`)
1083+
.join("");
1084+
el.aggregationSelect.value = state.aggregationKey;
10261085
}
10271086

10281087
function attachEvents() {
@@ -1041,6 +1100,11 @@ <h1>Max Memory Summary Viewer</h1>
10411100
renderTable();
10421101
});
10431102

1103+
el.aggregationSelect.addEventListener("change", () => {
1104+
state.aggregationKey = el.aggregationSelect.value;
1105+
renderTable();
1106+
});
1107+
10441108
el.hideZeroRows.addEventListener("change", () => {
10451109
renderTable();
10461110
});

comparisons/resources_diff_viewer.html

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171

7272
.controls-grid {
7373
display: grid;
74-
grid-template-columns: 1fr 220px;
74+
grid-template-columns: 1fr 220px 200px;
7575
gap: 12px;
7676
}
7777

@@ -257,6 +257,14 @@ <h1>FastTimerService Resources Difference</h1>
257257
<option value="dealloc">Dealloc Memory</option>
258258
</select>
259259
</div>
260+
<div>
261+
<label for="aggregationSelect">Aggregation</label>
262+
<select id="aggregationSelect">
263+
<option value="max" selected>max</option>
264+
<option value="min">min</option>
265+
<option value="abs-max">abs(max)</option>
266+
</select>
267+
</div>
260268
</div>
261269
</div>
262270

@@ -362,17 +370,25 @@ <h1>FastTimerService Resources Difference</h1>
362370
{ key: "events", label: "Events", metricKey: "events", valueType: "value", decimals: 0 }
363371
];
364372

373+
const AGGREGATION_OPTIONS = [
374+
{ key: "max", label: "max" },
375+
{ key: "min", label: "min" },
376+
{ key: "abs-max", label: "abs(max)" }
377+
];
378+
365379
const state = {
366380
payload: null,
367381
filter: "",
368382
sortKey: "diff-desc",
369383
selectedMetricKey: "cpu-frac",
384+
selectedAggregationKey: "max",
370385
expandedModules: new Set()
371386
};
372387

373388
const el = {
374389
filterInput: document.getElementById("filterInput"),
375390
valueSelect: document.getElementById("valueSelect"),
391+
aggregationSelect: document.getElementById("aggregationSelect"),
376392
legendBar: document.getElementById("legendBar"),
377393
totalsTitle: document.getElementById("totalsTitle"),
378394
totalBody: document.getElementById("totalBody"),
@@ -393,10 +409,58 @@ <h1>FastTimerService Resources Difference</h1>
393409
return SELECTABLE_METRICS.find((option) => option.key === key) || SELECTABLE_METRICS[0];
394410
}
395411

412+
function getAggregationOption(key) {
413+
return AGGREGATION_OPTIONS.find((option) => option.key === key) || AGGREGATION_OPTIONS[0];
414+
}
415+
416+
function valueFromCandidates(candidates) {
417+
const values = candidates
418+
.map((candidate) => Number(candidate))
419+
.filter((value) => Number.isFinite(value));
420+
if (!values.length) return NaN;
421+
422+
switch (state.selectedAggregationKey) {
423+
case "min":
424+
return Math.min(...values);
425+
case "abs-max": {
426+
const absMax = Math.max(...values.map((value) => Math.abs(value)));
427+
return Number.isFinite(absMax) ? absMax : NaN;
428+
}
429+
case "max":
430+
default:
431+
return Math.max(...values);
432+
}
433+
}
434+
435+
function metricFieldCandidates(metric, baseKey) {
436+
if (!metric || typeof metric !== "object") return [];
437+
438+
const candidates = [];
439+
const direct = metric[baseKey];
440+
if (Array.isArray(direct)) {
441+
candidates.push(...direct);
442+
} else if (direct && typeof direct === "object") {
443+
candidates.push(direct.max, direct.min, direct.abs_max, direct.absMax);
444+
} else {
445+
candidates.push(direct);
446+
}
447+
448+
candidates.push(
449+
metric[`${baseKey}_max`],
450+
metric[`${baseKey}_min`],
451+
metric[`${baseKey}_abs_max`],
452+
metric[`${baseKey}_absMax`]
453+
);
454+
455+
return candidates;
456+
}
457+
396458
function metricValue(metric, option, variant) {
397459
if (!metric) return NaN;
398-
if (option.valueType === "percent") return metric[`frac_${variant}`];
399-
return metric[variant];
460+
const baseKey = option.valueType === "percent" ? `frac_${variant}` : variant;
461+
const aggregated = valueFromCandidates(metricFieldCandidates(metric, baseKey));
462+
if (!Number.isNaN(aggregated)) return aggregated;
463+
return Number(metric[baseKey]);
400464
}
401465

402466
function formatMetricValue(metric, option, variant) {
@@ -447,6 +511,13 @@ <h1>FastTimerService Resources Difference</h1>
447511
renderModulesTable();
448512
}
449513

514+
function setSelectedAggregation(aggregationKey) {
515+
state.selectedAggregationKey = aggregationKey;
516+
el.aggregationSelect.value = aggregationKey;
517+
renderTotals();
518+
renderModulesTable();
519+
}
520+
450521
function selectedMetricValue(module, variant, option) {
451522
const selectedOption = option || getMetricOption(state.selectedMetricKey);
452523
return asNumber(metricValue(module[selectedOption.metricKey], selectedOption, variant));
@@ -528,9 +599,10 @@ <h1>FastTimerService Resources Difference</h1>
528599
const total = state.payload.total;
529600
const expanded = state.expandedModules.has("__total__");
530601
const selectedOption = getMetricOption(state.selectedMetricKey);
602+
const aggregationOption = getAggregationOption(state.selectedAggregationKey);
531603
const selectedMetric = total[selectedOption.metricKey];
532604
el.totalBody.innerHTML = "";
533-
el.totalsTitle.textContent = `Totals (${selectedOption.label})`;
605+
el.totalsTitle.textContent = `Totals (${selectedOption.label}, ${aggregationOption.label})`;
534606
const row = document.createElement("tr");
535607
row.innerHTML = `
536608
<td><button class="expand-btn" data-key="__total__">${expanded ? "▼" : "▶"}</button></td>
@@ -649,6 +721,9 @@ <h1>FastTimerService Resources Difference</h1>
649721
el.valueSelect.addEventListener("change", () => {
650722
setSelectedMetric(el.valueSelect.value);
651723
});
724+
el.aggregationSelect.addEventListener("change", () => {
725+
setSelectedAggregation(el.aggregationSelect.value);
726+
});
652727
}
653728

654729
function loadFromObject(data, sourceLabel) {

0 commit comments

Comments
 (0)