Skip to content

Commit

Permalink
feat: add request time ruler. refactor calculations (#3356)
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman authored Nov 7, 2024
1 parent b31c96f commit f2c318c
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 53 deletions.
8 changes: 4 additions & 4 deletions frontend/console/src/features/traces/TraceDetailItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ export const TraceDetailItem: React.FC<TraceDetailItemProps> = ({

return (
<li key={event.id.toString()} className={listItemClass} onClick={() => handleEventClick(event.id)}>
<span className='flex items-center w-1/2 text-sm font-medium'>
<span className='mr-2'>{icon}</span>
<span className='mr-2'>{action}</span>
{eventName}
<span className='flex items-center w-1/2 text-sm gap-x-2 font-medium'>
<span>{icon}</span>
<span>{action}</span>
<span>{eventName}</span>
</span>

<div className='relative w-2/3 h-4 flex-grow'>
Expand Down
25 changes: 12 additions & 13 deletions frontend/console/src/features/traces/TraceDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type React from 'react'
import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import type { TraceEvent } from '../../api/timeline/use-request-trace-events'
import type { Event } from '../../protos/xyz/block/ftl/v1/console/console_pb'
import { durationToMillis } from '../../utils'
import { TraceDetailItem } from './TraceDetailItem'
import { TraceRulerItem } from './TraceRulerItem'
import { requestStartTime, totalDurationForRequest } from './traces.utils'

interface TraceDetailsProps {
requestKey: string
Expand All @@ -14,15 +16,8 @@ interface TraceDetailsProps {
export const TraceDetails: React.FC<TraceDetailsProps> = ({ events, selectedEventId, requestKey }) => {
const navigate = useNavigate()

const traceEvents = events.map((event) => event.entry.value as TraceEvent)
const requestStartTime = Math.min(...traceEvents.map((event) => event.timeStamp?.toDate().getTime() ?? 0))
const requestEndTime = Math.max(
...traceEvents.map((event) => {
const eventDuration = event.duration ? durationToMillis(event.duration) : 0
return (event.timeStamp?.toDate().getTime() ?? 0) + eventDuration
}),
)
const totalEventDuration = requestEndTime - requestStartTime
const startTime = useMemo(() => requestStartTime(events), [events])
const totalEventDuration = useMemo(() => totalDurationForRequest(events), [events])

const handleEventClick = (eventId: bigint) => {
navigate(`/traces/${requestKey}?event_id=${eventId}`)
Expand All @@ -35,11 +30,15 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({ events, selectedEven
Total Duration: <span className='font-bold text-indigo-600 dark:text-indigo-400'>{totalEventDuration} ms</span>
</h2>
<p className='text-sm text-gray-600 dark:text-gray-300'>
Start Time: <span className='text-gray-800 dark:text-gray-100'>{new Date(requestStartTime).toLocaleString()}</span>
Start Time: <span className='text-gray-800 dark:text-gray-100'>{new Date(startTime).toLocaleString()}</span>
</p>
</div>

<ul className='space-y-2'>
<ul>
<div className='mb-1'>
<TraceRulerItem duration={totalEventDuration} />
</div>

{events.map((event, index) => {
const traceEvent = event.entry.value as TraceEvent
const eventDurationMs = (traceEvent.duration?.nanos ?? 0) / 1000000
Expand All @@ -51,7 +50,7 @@ export const TraceDetails: React.FC<TraceDetailsProps> = ({ events, selectedEven
traceEvent={traceEvent}
eventDurationMs={eventDurationMs}
requestDurationMs={totalEventDuration}
requestStartTime={requestStartTime}
requestStartTime={startTime}
selectedEventId={selectedEventId}
handleEventClick={handleEventClick}
/>
Expand Down
15 changes: 4 additions & 11 deletions frontend/console/src/features/traces/TraceGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '../../protos/xyz/block/ftl/v1/console/console_pb'
import { classNames, durationToMillis } from '../../utils'
import { eventBackgroundColor } from '../timeline/timeline.utils'
import { eventBarLeftOffsetPercentage } from './traces.utils'
import { eventBarLeftOffsetPercentage, requestStartTime, totalDurationForRequest } from './traces.utils'

const EventBlock = ({
event,
Expand Down Expand Up @@ -87,22 +87,15 @@ export const TraceGraph = ({ requestKey, selectedEventId }: { requestKey?: strin
return
}

const traceEvents = events.map((event) => event.entry.value as TraceEvent)
const requestStartTime = Math.min(...traceEvents.map((event) => event.timeStamp?.toDate().getTime() ?? 0))
const requestEndTime = Math.max(
...traceEvents.map((event) => {
const eventDuration = event.duration ? durationToMillis(event.duration) : 0
return (event.timeStamp?.toDate().getTime() ?? 0) + eventDuration
}),
)
const totalEventDuration = requestEndTime - requestStartTime
const startTime = requestStartTime(events)
const totalEventDuration = totalDurationForRequest(events)

return (
<div className='flex flex-col'>
{events.map((c, index) => (
<div key={index} className='flex hover:bg-indigo-500/60 hover:dark:bg-indigo-500/10 rounded-sm'>
<div className='w-full relative'>
<EventBlock event={c} isSelected={c.id === selectedEventId} requestStartTime={requestStartTime} requestDuration={totalEventDuration} />
<EventBlock event={c} isSelected={c.id === selectedEventId} requestStartTime={startTime} requestDuration={totalEventDuration} />
</div>
</div>
))}
Expand Down
17 changes: 5 additions & 12 deletions frontend/console/src/features/traces/TraceGraphHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,20 @@
import { Activity03Icon } from 'hugeicons-react'
import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { type TraceEvent, useRequestTraceEvents } from '../../api/timeline/use-request-trace-events'
import { durationToMillis } from '../../utils'
import { useRequestTraceEvents } from '../../api/timeline/use-request-trace-events'
import { totalDurationForRequest } from './traces.utils'

export const TraceGraphHeader = ({ requestKey, eventId }: { requestKey?: string; eventId: bigint }) => {
const navigate = useNavigate()
const requestEvents = useRequestTraceEvents(requestKey)
const events = requestEvents.data?.reverse() ?? []

const totalEventDuration = useMemo(() => totalDurationForRequest(events), [events])

if (events.length === 0) {
return null
}

const traceEvents = events.map((event) => event.entry.value as TraceEvent)
const requestStartTime = Math.min(...traceEvents.map((event) => event.timeStamp?.toDate().getTime() ?? 0))
const requestEndTime = Math.max(
...traceEvents.map((event) => {
const eventDuration = event.duration ? durationToMillis(event.duration) : 0
return (event.timeStamp?.toDate().getTime() ?? 0) + eventDuration
}),
)
const totalEventDuration = requestEndTime - requestStartTime

return (
<div className='flex items-center justify-between'>
<span className='text-xs font-mono'>
Expand Down
18 changes: 18 additions & 0 deletions frontend/console/src/features/traces/TraceGraphRuler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const TraceGraphRuler = ({ duration }: { duration: number }) => {
const tickInterval = duration / 4
const ticks = Array.from({ length: 5 }, (_, i) => ({
value: Math.round(i * tickInterval),
position: `${(i * 100) / 4}%`,
}))

return (
<div className='relative border-b border-gray-200 dark:border-gray-600 w-full h-6'>
{ticks.map((tick, index) => (
<div key={index} className='absolute bottom-0 transform -translate-x-1/2' style={{ left: tick.position }}>
<span className='absolute bottom-2 text-xs font-roboto-mono text-gray-500 dark:text-gray-400 -translate-x-1/2 whitespace-nowrap'>{tick.value}ms</span>
<span className='block h-2 w-[1px] bg-gray-200 dark:bg-gray-600' />
</div>
))}
</div>
)
}
13 changes: 13 additions & 0 deletions frontend/console/src/features/traces/TraceRulerItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { TraceGraphRuler } from './TraceGraphRuler'

export const TraceRulerItem = ({ duration }: { duration: number }) => {
return (
<li key='trace-ruler-item' className='flex items-center justify-between px-2'>
<span className='flex items-center w-1/2 text-sm gap-x-2 font-medium' />
<div className='relative w-2/3 h-full flex-grow'>
<TraceGraphRuler duration={duration} />
</div>
<span className='text-xs font-medium ml-4 w-20 text-right' />
</li>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { CodeBlock } from '../../../components/CodeBlock'
import type { AsyncExecuteEvent, Event } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { formatDuration } from '../../../utils/date.utils'
import { DeploymentCard } from '../../deployments/DeploymentCard'
import { asyncEventTypeString } from '../../timeline/timeline.utils'
import { refString } from '../../verbs/verb.utils'

export const TraceDetailsAsyncCall = ({ event }: { event: Event }) => {
const asyncCall = event.entry.value as AsyncExecuteEvent

return (
<>
<span className='text-xl font-semibold'>Async Call Details</span>
Expand All @@ -21,14 +23,17 @@ export const TraceDetailsAsyncCall = ({ event }: { event: Event }) => {
<DeploymentCard className='mt-4' deploymentKey={asyncCall.deploymentKey} />

<ul className='pt-4 space-y-2'>
<li>
<AttributeBadge name='event_type' value={asyncEventTypeString(asyncCall.asyncEventType)} />
</li>
<li>
<AttributeBadge name='duration' value={formatDuration(asyncCall.duration)} />
</li>
{asyncCall.requestKey && (
<li>
<AttributeBadge name='request' value={asyncCall.requestKey} />
</li>
)}
<li>
<AttributeBadge name='duration' value={formatDuration(asyncCall.duration)} />
</li>
{asyncCall.verbRef && (
<li>
<AttributeBadge name='destination' value={refString(asyncCall.verbRef)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ export const TraceDetailsPubsubPublish = ({ event }: { event: Event }) => {
const pubsubPublish = event.entry.value as PubSubPublishEvent
return (
<>
<span className='text-xl font-semibold'>Pubsub Publish Details</span>
<span className='text-xl font-semibold'>PubSub Publish Details</span>

{pubsubPublish.request && (
<>
<h3 className='pt-4'>Request</h3>
<CodeBlock code={pubsubPublish.request} language='json' />
</>
)}
{pubsubPublish.error && (
<>
<h3 className='pt-4'>Error</h3>
Expand All @@ -21,22 +27,20 @@ export const TraceDetailsPubsubPublish = ({ event }: { event: Event }) => {
<DeploymentCard className='mt-4' deploymentKey={pubsubPublish.deploymentKey} />

<ul className='pt-4 space-y-2'>
{pubsubPublish.requestKey && (
<li>
<AttributeBadge name='request' value={pubsubPublish.requestKey} />
</li>
)}
<li>
<AttributeBadge name='topic' value={pubsubPublish.topic} />
</li>
<li>
<AttributeBadge name='duration' value={formatDuration(pubsubPublish.duration)} />
</li>
{pubsubPublish.verbRef && (
{pubsubPublish.requestKey && (
<li>
<AttributeBadge name='destination' value={refString(pubsubPublish.verbRef)} />
<AttributeBadge name='request' value={pubsubPublish.requestKey} />
</li>
)}
{pubsubPublish.verbRef && (
<li>
<AttributeBadge name='source' value={refString(pubsubPublish.verbRef)} />
<AttributeBadge name='verb_ref' value={refString(pubsubPublish.verbRef)} />
</li>
)}
</ul>
Expand Down
19 changes: 18 additions & 1 deletion frontend/console/src/features/traces/traces.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { TraceEvent } from '../../api/timeline/use-request-trace-events'
import type { Event } from '../../protos/xyz/block/ftl/v1/console/console_pb'
import { compareTimestamps } from '../../utils'
import { compareTimestamps, durationToMillis } from '../../utils'

export const eventBarLeftOffsetPercentage = (event: Event, requestStartTime: number, requestDurationMs: number) => {
if (!event.timeStamp) {
Expand Down Expand Up @@ -37,3 +38,19 @@ export const groupEventsByRequestKey = (events: Event[]): Record<string, Event[]
return acc
}, {})
}

export const requestStartTime = (events: Event[]): number => {
const traceEvents = events.map((event) => event.entry.value as TraceEvent)
return Math.min(...traceEvents.map((event) => event.timeStamp?.toDate().getTime() ?? 0))
}

export const totalDurationForRequest = (events: Event[]): number => {
const traceEvents = events.map((event) => event.entry.value as TraceEvent)
const requestEndTime = Math.max(
...traceEvents.map((event) => {
const eventDuration = event.duration ? durationToMillis(event.duration) : 0
return (event.timeStamp?.toDate().getTime() ?? 0) + eventDuration
}),
)
return requestEndTime - requestStartTime(events)
}

0 comments on commit f2c318c

Please sign in to comment.