Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add request time ruler. refactor calculations #3356

Merged
merged 1 commit into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading