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 ( + <> + + {props.children}
+ + ); +} + +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; }