diff --git a/web-client/src/components/span/span-detail-trace.tsx b/web-client/src/components/span/span-detail-trace.tsx
index 6a313b69..e83b4fa6 100644
--- a/web-client/src/components/span/span-detail-trace.tsx
+++ b/web-client/src/components/span/span-detail-trace.tsx
@@ -1,9 +1,12 @@
-import { For } from "solid-js";
+import { For, JSXElement, Show } from "solid-js";
import { UiSpan } from "~/lib/span/format-spans-for-ui";
+import { Span } from "~/lib/connection/monitor";
import {
computeWaterfallStyle,
computeSlices,
} from "~/lib/span/normalize-spans";
+import { Popover } from "@kobalte/core";
+import { getDetailedTime } from "~/lib/formatters.ts";
export function SpanDetailTrace(props: {
span: UiSpan;
@@ -15,33 +18,127 @@ export function SpanDetailTrace(props: {
};
}) {
return (
-
- {props.span.name} |
-
-
-
-
- {/* Slices is "time slices" as in multiple entry points to a given span */}
-
- {(slice) => (
-
+
+
+ {props.span.name} |
+
+
+
+
+ >
+ {/* Slices is "time slices" as in multiple entry points to a given span */}
+
+ {(slice) => }
+
+
-
- |
+ |
+ {props.span.time.toFixed(2)}ms |
+
+
+
+
+
+ );
+}
+
+function SpanDetailPopOverContent(props: { span: UiSpan }) {
+ const busy = (span: Span) =>
+ span.enters.reduce((acc, enter, i) => {
+ const exit = span.exits[i];
+
+ return acc + (exit.timestamp - enter.timestamp);
+ }, 0);
+
+ return (
+
+
+
+
+ {(busy(props.span.original) / 1e6).toFixed(3)}ms
+
+
+ {(props.span.time - busy(props.span.original) / 1e6).toFixed(3)}ms
+
+
+
+ );
+}
+
+function SpanDetailSlice(props: {
+ slice: {
+ entered: number;
+ exited: number;
+ threadID: number;
+ width: number;
+ marginLeft: number;
+ };
+}) {
+ return (
+
+
+
+
+
+
+ {((props.slice.exited - props.slice.entered) / 1e6).toFixed(3)}
+ ms
+
+ {props.slice.threadID}
+
+ {getDetailedTime(new Date(props.slice.entered / 1e6))}
+
+
+ {getDetailedTime(new Date(props.slice.exited / 1e6))}
+
+
+
+
+
+ );
+}
+
+function ToolTipContent(props: { children: JSXElement }) {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function ToolTipRow(props: { title: string; children?: JSXElement }) {
+ return (
+
+ {props.title} |
+
+ {props.children} |
+
);
}
diff --git a/web-client/src/lib/connection/monitor.ts b/web-client/src/lib/connection/monitor.ts
index 8ff15954..dde12723 100644
--- a/web-client/src/lib/connection/monitor.ts
+++ b/web-client/src/lib/connection/monitor.ts
@@ -15,8 +15,8 @@ export type Span = {
metadataId: bigint;
fields: Field[];
createdAt: number;
- enters: number[];
- exits: number[];
+ enters: { timestamp: number; threadID: number }[];
+ exits: { timestamp: number; threadID: number }[];
closedAt: number;
duration: number;
};
diff --git a/web-client/src/lib/formatters.ts b/web-client/src/lib/formatters.ts
index 9a32d53f..bd3dac5f 100644
--- a/web-client/src/lib/formatters.ts
+++ b/web-client/src/lib/formatters.ts
@@ -36,3 +36,13 @@ export function getTime(date: Date) {
second: "2-digit",
}).format(date);
}
+
+export function getDetailedTime(date: Date) {
+ return Intl.DateTimeFormat("en", {
+ hour: "2-digit",
+ minute: "2-digit",
+ hour12: false,
+ second: "2-digit",
+ fractionalSecondDigits: 3,
+ }).format(date);
+}
diff --git a/web-client/src/lib/span/normalize-spans.ts b/web-client/src/lib/span/normalize-spans.ts
index 3ae7ff52..52c6c413 100644
--- a/web-client/src/lib/span/normalize-spans.ts
+++ b/web-client/src/lib/span/normalize-spans.ts
@@ -54,20 +54,29 @@ export function computeWaterfallStyle(
}
export function computeSlices(span: Span) {
- const allExits = span.exits.reduce((acc, e) => acc + e, 0);
- const allEnters = span.enters.reduce((acc, e) => acc + e, 0);
+ const allExits = span.exits.reduce((acc, e) => acc + e.timestamp, 0);
+ const allEnters = span.enters.reduce((acc, e) => acc + e.timestamp, 0);
- const slices = span.enters.map((enter, i) => {
- const width = scaleToMax([span.exits[i] - enter], allExits - allEnters)[0];
- const offset = scaleNumbers([enter], span.createdAt, span.closedAt)[0];
+ return span.enters.map((entered, i) => {
+ const exited = span.exits[i].timestamp;
+
+ const width = scaleToMax(
+ [exited - entered.timestamp],
+ allExits - allEnters
+ )[0];
+ const offset = scaleNumbers(
+ [entered.timestamp],
+ span.createdAt,
+ span.closedAt
+ )[0];
const marginLeft = offset - (offset * width) / 100;
+
return {
+ entered: entered.timestamp,
+ exited,
+ threadID: entered.threadID,
width,
marginLeft,
};
});
-
- return slices.map(
- (slice) => `width:${slice.width}%;margin-left:${slice.marginLeft}%;`
- );
}
diff --git a/web-client/src/lib/span/update-spans.ts b/web-client/src/lib/span/update-spans.ts
index 97d5fbe5..368e66c2 100644
--- a/web-client/src/lib/span/update-spans.ts
+++ b/web-client/src/lib/span/update-spans.ts
@@ -31,7 +31,10 @@ export function updatedSpans(currentSpans: Span[], spanEvents: SpanEvent[]) {
? convertTimestampToNanoseconds(event.event.enterSpan.at)
: -1;
if (span) {
- span.enters.push(enteredAt);
+ span.enters.push({
+ timestamp: enteredAt,
+ threadID: Number(event.event.enterSpan.threadId),
+ });
}
break;
@@ -43,7 +46,10 @@ export function updatedSpans(currentSpans: Span[], spanEvents: SpanEvent[]) {
? convertTimestampToNanoseconds(event.event.exitSpan.at)
: -1;
if (span) {
- span.exits.push(exitedAt);
+ span.exits.push({
+ timestamp: exitedAt,
+ threadID: Number(event.event.exitSpan.threadId),
+ });
}
break;
}