Skip to content

Commit

Permalink
wip: rough draft calls virtualization
Browse files Browse the repository at this point in the history
  • Loading branch information
johann-crabnebula committed Feb 5, 2024
1 parent 29b1013 commit 6387c2a
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 117 deletions.
3 changes: 2 additions & 1 deletion clients/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"@sentry/browser": "^7.83.0",
"@sentry/vite-plugin": "^2.10.2",
"@solid-primitives/map": "^0.4.8",
"@solidjs/router": "^0.11.0",
"@solidjs/router": "^0.10.5",
"@tanstack/solid-virtual": "^3.0.2",
"clsx": "^2.0.0",
"csp_evaluator": "^1.1.1",
"json-schema-library": "^9.1.2",
Expand Down
30 changes: 23 additions & 7 deletions clients/web/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

262 changes: 153 additions & 109 deletions clients/web/src/components/span/span-list.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSearchParams } from "@solidjs/router";
import { For } from "solid-js";
import { For, Index, createEffect, createMemo } from "solid-js";
import { createStore } from "solid-js/store";
import type { UiSpan } from "~/lib/span/format-spans-for-ui";
import { getColumnDirection } from "~/lib/span/get-column-direction";
Expand All @@ -12,6 +12,7 @@ import {
computeWaterfallStyle,
} from "~/lib/span/normalize-spans";
import clsx from "clsx";
import { createVirtualizer } from "@tanstack/solid-virtual";

export type SortDirection = "asc" | "desc";

Expand All @@ -29,6 +30,7 @@ type Column = {
export function SpanList() {
const callsContext = useCalls();
const spans = callsContext.spans;
let virtualList: HTMLTableElement | undefined;

const [columnSort, setColumnSort] = createStore<ColumnSort>({
name: "initiated",
Expand All @@ -44,23 +46,6 @@ export function SpanList() {

const [, setSearchParams] = useSearchParams();

const filteredSpans = () => {
const filteredSpans: UiSpan[] = [];
spans.forEach((span) => {
if (span.kind) {
filteredSpans.push(span);
}
});
return [...filteredSpans].sort(spanSort);
};

const sortColumn = (name: SortableColumn) => {
setColumnSort({
name,
direction: getColumnDirection(columnSort, name),
});
};

const spanSort = (a: UiSpan, b: UiSpan) => {
const columnName = columnSort.name;
let lhs, rhs;
Expand All @@ -81,98 +66,157 @@ export function SpanList() {
}
};

const computeFilteredSpans = () => {
const filteredSpans: UiSpan[] = [];
spans.forEach((span) => {
if (span.kind) {
filteredSpans.push(span);
}
});

return [...filteredSpans].sort(spanSort);
};

const filteredSpans = createMemo(() => computeFilteredSpans());

const count = createMemo(() => {
return filteredSpans()?.length ?? 0;
});

const sortColumn = (name: SortableColumn) => {
setColumnSort({
name,
direction: getColumnDirection(columnSort, name),
});
virtualizer.measure();
};

const virtualizer = createVirtualizer({
count: 0,
getScrollElement: () => virtualList ?? null,
estimateSize: () => 50,
overscan: 5,
});

createEffect(() => {
const newCount = count();
virtualizer.setOptions({ ...virtualizer.options, count: newCount });
virtualizer.measure();
});

return (
<table class="w-full table-fixed">
<thead>
<tr class="text-left">
<For each={columns}>
{(column) => {
console.log(column);
return (
<th
tabIndex="0"
onKeyDown={(e) => {
if ([" ", "Enter"].includes(e.key) && column.isSortable) {
sortColumn(column.name);
}
}}
onClick={() => column.isSortable && sortColumn(column.name)}
class={`p-1 cursor-pointer hover:bg-[#ffffff09] ${
column.name === "time" || column.name === "initiated"
? "w-2/12" // time and initiated
: column.name === "name"
? "w-3/12" // name
: "w-5/12" // waterfall
}`}
>
<div class="flex uppercase select-none items-center gap-2">
{column.name}
{columnSort.name === column.name && column.isSortable && (
<SortCaret direction={columnSort.direction} />
)}
</div>
</th>
);
}}
</For>
</tr>
</thead>
<tbody>
<For each={filteredSpans()}>
{(span) => {
return (
<tr
onClick={() => setSearchParams({ span: String(span.id) })}
class="even:bg-nearly-invisible cursor-pointer hover:bg-[#ffffff05] even:hover:bg-[#ffffff10]"
>
<td class="p-1 overflow-hidden text-ellipsis" title={span.name}>
{span.name}
</td>
<td
class="p-1 overflow-hidden text-ellipsis"
title={getTime(new Date(span.initiated))}
>
{getTime(new Date(span.initiated))}
</td>
<td
class="p-1 overflow-hidden text-ellipsis"
title={`${span.time.toFixed(2)} ms`}
>
{span.time.toFixed(2)}ms
</td>
<td class="p-1 relative overflow-hidden">
<div class="relative w-[90%]">
<div class="bg-gray-800 w-full absolute rounded-sm h-2" />
<div
class={clsx(
"relative rounded-sm h-2",
computeColorClassName(
span.original.closedAt - span.original.createdAt,
callsContext.durations.durations.average
)
)}
style={computeWaterfallStyle(
span,
callsContext.durations.durations.start,
callsContext.durations.durations.end
)}
<div ref={virtualList} class="overflow-auto max-h-full h-full relative">
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
<table class="w-full table-fixed">
<thead>
<tr class="text-left">
<For each={columns}>
{(column) => {
return (
<th
tabIndex="0"
onKeyDown={(e) => {
if (
[" ", "Enter"].includes(e.key) &&
column.isSortable
) {
sortColumn(column.name);
}
}}
onClick={() =>
column.isSortable && sortColumn(column.name)
}
class={`p-1 cursor-pointer hover:bg-[#ffffff09] ${
column.name === "time" || column.name === "initiated"
? "w-2/12" // time and initiated
: column.name === "name"
? "w-3/12" // name
: "w-5/12" // waterfall
}`}
>
<div class="flex uppercase select-none items-center gap-2">
{column.name}
{columnSort.name === column.name &&
column.isSortable && (
<SortCaret direction={columnSort.direction} />
)}
</div>
</th>
);
}}
</For>
</tr>
</thead>
<tbody>
<For each={virtualizer.getVirtualItems()}>
{(virtualRow, index) => {
const span = () => filteredSpans()[virtualRow.index];
return (
<tr
onClick={() => setSearchParams({ span: String(span().id) })}
class="even:bg-nearly-invisible cursor-pointer hover:bg-[#ffffff05] even:hover:bg-[#ffffff10]"
data-id={virtualRow.index}
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${
virtualRow.start - index() * virtualRow.size
}px)`,
}}
>
<td
class="p-1 overflow-hidden text-ellipsis"
title={span().name}
>
{span().name}
</td>
<td
class="p-1 overflow-hidden text-ellipsis"
title={getTime(new Date(span().initiated))}
>
{getTime(new Date(span().initiated))}
</td>
<td
class="p-1 overflow-hidden text-ellipsis"
title={`${span().time.toFixed(2)} ms`}
>
<For each={computeSlices(span.original)}>
{(slice) => (
<div
class="absolute top-0 left-0 bg-black bg-opacity-10 h-full"
style={slice}
/>
)}
</For>
</div>
</div>
</td>
</tr>
);
}}
</For>
</tbody>
</table>
{span().time.toFixed(2)}ms
</td>
<td class="p-1 relative overflow-hidden">
<div class="relative w-[90%]">
<div class="bg-gray-800 w-full absolute rounded-sm h-2" />
<div
class={clsx(
"relative rounded-sm h-2",
computeColorClassName(
span().original.closedAt -
span().original.createdAt,
callsContext.durations.durations.average
)
)}
style={computeWaterfallStyle(
span(),
callsContext.durations.durations.start,
callsContext.durations.durations.end
)}
>
<For each={computeSlices(span().original)}>
{(slice) => (
<div
class="absolute top-0 left-0 bg-black bg-opacity-10 h-full"
style={slice}
/>
)}
</For>
</div>
</div>
</td>
</tr>
);
}}
</For>
</tbody>
</table>
</div>
</div>
);
}

0 comments on commit 6387c2a

Please sign in to comment.