Skip to content

Commit

Permalink
tracer: Virtualize the event view list
Browse files Browse the repository at this point in the history
To keep the UI responsive as the number of events grows.
  • Loading branch information
oleavr committed Oct 15, 2024
1 parent e0d05cc commit 70daf4a
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 24 deletions.
1 change: 1 addition & 0 deletions apps/tracer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"react-resplit": "^1.3.2-alpha.0",
"react-stay-at-bottom": "^1.1.1",
"react-use-websocket": "^4.8.1",
"react-viewport-list": "^7.1.2",
"use-debounce": "^10.0.3"
},
"devDependencies": {
Expand Down
67 changes: 43 additions & 24 deletions apps/tracer/src/EventView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Card } from "@blueprintjs/core";
import Ansi from "@curvenote/ansi-to-react";
import { ReactElement, useCallback, useEffect, useRef, useState } from "react";
import { useStayAtBottom } from "react-stay-at-bottom";
import { ViewportList } from "react-viewport-list";

export interface EventViewProps {
events: Event[];
Expand Down Expand Up @@ -31,15 +32,28 @@ export default function EventView({
}: EventViewProps) {
const containerRef = useRef<HTMLDivElement>(null);
const selectedRef = useRef<HTMLDivElement>(null);
const [items, setItems] = useState<(EventItem | ThreadIdMarkerItem)[]>([]);
const [selectedCallerSymbol, setSelectedCallerSymbol] = useState<string | null>("");
const [selectedBacktraceSymbols, setSelectedBacktraceSymbols] = useState<string[] | null>(null);
let lastTid: number | null = null;

useStayAtBottom(containerRef, {
initialStay: true,
autoStay: true
});

useEffect(() => {
let lastTid: number | null = null;
setItems(events.reduce((result, event, i) => {
const [_targetId, _timestamp, threadId, _depth, _caller, _backtrace, _message, style] = event;
if (threadId !== lastTid) {
result.push([i, threadId, style]);
lastTid = threadId;
}
result.push([i, event]);
return result;
}, [] as (EventItem | ThreadIdMarkerItem)[]));
}, [events]);

useEffect(() => {
const item = selectedRef.current;
if (item === null) {
Expand Down Expand Up @@ -137,34 +151,38 @@ export default function EventView({

return (
<div ref={containerRef} className="event-view">
{
events.reduce((result, [targetId, timestamp, threadId, depth, _caller, _backtrace, message, style], i) => {
let timestampStr = timestamp.toString();
const timestampPaddingNeeded = Math.max(6 - timestampStr.length, 0);
for (let i = 0; i !== timestampPaddingNeeded; i++) {
timestampStr = NON_BLOCKING_SPACE + timestampStr;
}

const colorClass = "ansi-" + style.join("-");

if (threadId !== lastTid) {
result.push(
<div key={i + "-heading"} className={"event-heading " + colorClass}>
<ViewportList items={items}>
{(item) => {
if (item.length === 3) {
const [index, threadId, style] = item;
const colorClass = "ansi-" + style.join("-");
return (
<div key={`{index}-heading`} className={"event-heading " + colorClass}>
/* TID 0x{threadId.toString(16)} */
</div>
);
lastTid = threadId;
}

const isSelected = i === selectedIndex;
const [index, event] = item;
const [targetId, timestamp, _threadId, depth, _caller, _backtrace, message, style] = event;

const isSelected = index === selectedIndex;
const eventClasses = ["event-item"];
if (isSelected) {
eventClasses.push("event-selected");
}

result.push(
let timestampStr = timestamp.toString();
const timestampPaddingNeeded = Math.max(6 - timestampStr.length, 0);
for (let i = 0; i !== timestampPaddingNeeded; i++) {
timestampStr = NON_BLOCKING_SPACE + timestampStr;
}

const colorClass = "ansi-" + style.join("-");

return (
<div
key={i}
key={index}
ref={isSelected ? selectedRef : undefined}
className={eventClasses.join(" ")}
>
Expand All @@ -175,18 +193,19 @@ export default function EventView({
className={"event-message " + colorClass}
minimal={true}
alignText="left"
onClick={() => onActivate(targetId, i)}
onClick={() => onActivate(targetId, index)}
>
<Ansi>{message}</Ansi>
</Button>
</div>
{isSelected ? selectedEventDetails : null}
</div>
);

return result;
}, [] as JSX.Element[])
}
}}
</ViewportList>
</div>
);
}
}

type EventItem = [index: number, event: Event];
type ThreadIdMarkerItem = [index: number, threadId: number, style: string[]];

0 comments on commit 70daf4a

Please sign in to comment.