trace performance fix
Build and Publish / Metadata (push) Successful in 9s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 24s

This commit is contained in:
developer committed 2026-09-24 19:04:00 +00:00
1 parent 6dc2785f71
commit 4f6e6ba35d
1 file changed
+36 -26
+36 -26
View File
@@ -1,4 +1,4 @@
import { useMemo, useState, useRef, useEffect } from "react"; import { useMemo, useState, useRef, useEffect, useDeferredValue } from "react";
import { useVirtualizer } from "@tanstack/react-virtual"; import { useVirtualizer } from "@tanstack/react-virtual";
import { import {
Sparkles, Sparkles,
@@ -54,6 +54,8 @@ export type ProcessedTrace = {
endpoint: string; endpoint: string;
}; };
const EMPTY_TRACES: TraceData[] = [];
// Flattened item for virtualized tree rendering // Flattened item for virtualized tree rendering
interface FlattenedSpanItem { interface FlattenedSpanItem {
span: ProcessedSpan; span: ProcessedSpan;
@@ -96,14 +98,24 @@ const flattenSpanTree = (
collapsedSet: Set<string>, collapsedSet: Set<string>,
traceMinTime: number, traceMinTime: number,
traceTotalRange: number, traceTotalRange: number,
depth = 0, initialDepth = 0,
): FlattenedSpanItem[] => { ): FlattenedSpanItem[] => {
const result: FlattenedSpanItem[] = []; const result: FlattenedSpanItem[] = [];
const pending: Array<{ span: ProcessedSpan; depth: number }> = [];
for (const span of nodes) { for (let index = nodes.length - 1; index >= 0; index -= 1) {
const hasChildren = Boolean(span.children && span.children.length > 0); pending.push({ span: nodes[index], depth: initialDepth });
}
// Avoid recursively spreading descendant arrays: deep parent/child chains
// otherwise copy the same nodes once per ancestor.
while (pending.length > 0) {
const current = pending.pop();
if (!current) break;
const { span, depth } = current;
const children = span.children ?? [];
const hasChildren = children.length > 0;
const isCollapsed = collapsedSet.has(span.spanId); const isCollapsed = collapsedSet.has(span.spanId);
const offsetPercent = const offsetPercent =
traceTotalRange === 0 ? 0 : ((span.startTime - traceMinTime) / traceTotalRange) * 100; traceTotalRange === 0 ? 0 : ((span.startTime - traceMinTime) / traceTotalRange) * 100;
const widthPercent = const widthPercent =
@@ -118,16 +130,10 @@ const flattenSpanTree = (
widthPercent: Math.max(0.5, Math.min(widthPercent, 100 - offsetPercent)), widthPercent: Math.max(0.5, Math.min(widthPercent, 100 - offsetPercent)),
}); });
if (hasChildren && !isCollapsed && span.children) { if (hasChildren && !isCollapsed) {
result.push( for (let index = children.length - 1; index >= 0; index -= 1) {
...flattenSpanTree( pending.push({ span: children[index], depth: depth + 1 });
span.children, }
collapsedSet,
traceMinTime,
traceTotalRange,
depth + 1,
),
);
} }
} }
@@ -539,6 +545,7 @@ const VirtualizedSpanWaterfall = ({
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [errorsOnly, setErrorsOnly] = useState(false); const [errorsOnly, setErrorsOnly] = useState(false);
const [minDurationMsFilter, setMinDurationMsFilter] = useState<number>(0); const [minDurationMsFilter, setMinDurationMsFilter] = useState<number>(0);
const deferredSearchQuery = useDeferredValue(searchQuery);
const parentRef = useRef<HTMLDivElement>(null); const parentRef = useRef<HTMLDivElement>(null);
// Build tree // Build tree
@@ -563,8 +570,8 @@ const VirtualizedSpanWaterfall = ({
const durMs = item.span.duration / 1_000_000; const durMs = item.span.duration / 1_000_000;
if (durMs < minDurationMsFilter) return false; if (durMs < minDurationMsFilter) return false;
} }
if (searchQuery.trim()) { if (deferredSearchQuery.trim()) {
const query = searchQuery.toLowerCase().trim(); const query = deferredSearchQuery.toLowerCase().trim();
const matchesName = item.span.name.toLowerCase().includes(query); const matchesName = item.span.name.toLowerCase().includes(query);
const matchesService = item.span.service.toLowerCase().includes(query); const matchesService = item.span.service.toLowerCase().includes(query);
const matchesAttrs = Object.values(item.span.formattedAttributes).some((v) => const matchesAttrs = Object.values(item.span.formattedAttributes).some((v) =>
@@ -574,7 +581,7 @@ const VirtualizedSpanWaterfall = ({
} }
return true; return true;
}); });
}, [flattenedItems, filterService, errorsOnly, minDurationMsFilter, searchQuery]); }, [flattenedItems, filterService, errorsOnly, minDurationMsFilter, deferredSearchQuery]);
// Virtualizer // Virtualizer
@@ -1683,10 +1690,12 @@ export const processTraces = (
), ),
); );
const uniqueSpans = spanAccumulator.filter( const seenSpanIds = new Set<string>();
(span, index, arr) => const uniqueSpans = spanAccumulator.filter((span) => {
arr.findIndex((candidate) => candidate.spanId === span.spanId) === index, if (seenSpanIds.has(span.spanId)) return false;
); seenSpanIds.add(span.spanId);
return true;
});
const traceGroups = uniqueSpans.reduce<Record<string, ProcessedSpan[]>>((acc, span) => { const traceGroups = uniqueSpans.reduce<Record<string, ProcessedSpan[]>>((acc, span) => {
if (!acc[span.traceId]) acc[span.traceId] = []; if (!acc[span.traceId]) acc[span.traceId] = [];
@@ -1739,7 +1748,7 @@ export interface TraceVisualizationProps {
} }
export const TraceVisualization = ({ traces }: TraceVisualizationProps) => { export const TraceVisualization = ({ traces }: TraceVisualizationProps) => {
const safeTraces = traces ?? []; const safeTraces = traces ?? EMPTY_TRACES;
const { const {
logCountBySpanId, logCountBySpanId,
activeTraceId, activeTraceId,
@@ -1756,6 +1765,7 @@ export const TraceVisualization = ({ traces }: TraceVisualizationProps) => {
"recent" | "duration-desc" | "duration-asc" | "errors" | "spans" "recent" | "duration-desc" | "duration-asc" | "errors" | "spans"
>("recent"); >("recent");
const [groupByEndpoint, setGroupByEndpoint] = useState(false); const [groupByEndpoint, setGroupByEndpoint] = useState(false);
const deferredTraceSearchQuery = useDeferredValue(traceSearchQuery);
const orderedTraces: ProcessedTrace[] = useMemo( const orderedTraces: ProcessedTrace[] = useMemo(
() => processTraces(safeTraces, logCountBySpanId), () => processTraces(safeTraces, logCountBySpanId),
@@ -1774,8 +1784,8 @@ export const TraceVisualization = ({ traces }: TraceVisualizationProps) => {
if (traceFilter === "errors" && !t.hasError) return false; if (traceFilter === "errors" && !t.hasError) return false;
if (traceFilter === "slow" && t.duration / 1_000_000 < 200) return false; if (traceFilter === "slow" && t.duration / 1_000_000 < 200) return false;
if (traceServiceFilter !== "all" && !t.services.includes(traceServiceFilter)) return false; if (traceServiceFilter !== "all" && !t.services.includes(traceServiceFilter)) return false;
if (traceSearchQuery.trim()) { if (deferredTraceSearchQuery.trim()) {
const q = traceSearchQuery.toLowerCase().trim(); const q = deferredTraceSearchQuery.toLowerCase().trim();
const matchName = t.name.toLowerCase().includes(q); const matchName = t.name.toLowerCase().includes(q);
const matchId = t.id.toLowerCase().includes(q); const matchId = t.id.toLowerCase().includes(q);
const matchService = t.services.some((s) => s.toLowerCase().includes(q)); const matchService = t.services.some((s) => s.toLowerCase().includes(q));
@@ -1791,7 +1801,7 @@ export const TraceVisualization = ({ traces }: TraceVisualizationProps) => {
if (traceSortBy === "spans") return b.spans.length - a.spans.length; if (traceSortBy === "spans") return b.spans.length - a.spans.length;
return b.startTime - a.startTime; // recent return b.startTime - a.startTime; // recent
}); });
}, [orderedTraces, traceFilter, traceServiceFilter, traceSearchQuery, traceSortBy]); }, [orderedTraces, traceFilter, traceServiceFilter, deferredTraceSearchQuery, traceSortBy]);
const groupCounts = useMemo(() => { const groupCounts = useMemo(() => {
const counts: Record<string, number> = {}; const counts: Record<string, number> = {};