Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/olive-hounds-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---

feat: show inline span durations in trace timeline
1 change: 1 addition & 0 deletions packages/app/src/components/DBTraceWaterfallChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,7 @@ export function DBTraceWaterfallChartContainer({
minWidthPerc: 1,
isError,
markers,
showDuration: type !== SourceKind.Log,
},
],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TimelineSpanEventMarker,
type TTimelineSpanEventMarker,
} from './TimelineSpanEventMarker';
import { renderMs } from './utils';

export type TTimelineEvent = {
id: string;
Expand All @@ -17,6 +18,7 @@ export type TTimelineEvent = {
minWidthPerc?: number;
isError?: boolean;
markers?: TTimelineSpanEventMarker[];
showDuration?: boolean;
};

type TimelineChartRowProps = {
Expand Down Expand Up @@ -57,6 +59,12 @@ export const TimelineChartRowEvents = memo(function ({
const percMarginLeft =
scale * (((e.start - lastEventEnd) / maxVal) * 100);

const durationMs = e.end - e.start;
const barCenter = (e.start + e.end) / 2;
const timelineMidpoint = maxVal / 2;
// Duration on left when majority of bar is past halfway, otherwise on right
const durationOnRight = barCenter <= timelineMidpoint;

return (
<Tooltip
key={e.id}
Expand All @@ -72,32 +80,60 @@ export const TimelineChartRowEvents = memo(function ({
}}
>
<div
onMouseEnter={() => onEventHover?.(e.id)}
className="d-flex align-items-center h-100 cursor-pointer text-truncate hover-opacity"
style={{
userSelect: 'none',
position: 'relative',
minWidth: `${percWidth.toFixed(6)}%`,
width: `${percWidth.toFixed(6)}%`,
marginLeft: `${percMarginLeft.toFixed(6)}%`,
position: 'relative',
borderRadius: 2,
fontSize: height * 0.5,
color: e.color,
backgroundColor: e.backgroundColor,
// overflow: 'visible',
}}
>
<div style={{ margin: 'auto' }} className="px-2">
{e.body}
<div
onMouseEnter={() => onEventHover?.(e.id)}
className="d-flex align-items-center h-100 cursor-pointer text-truncate hover-opacity"
style={{
userSelect: 'none',
width: '100%',
position: 'relative',
borderRadius: 2,
fontSize: height * 0.5,
color: e.color,
backgroundColor: e.backgroundColor,
}}
>
<div style={{ margin: 'auto' }} className="px-2">
{e.body}
</div>
{e.markers?.map((marker, idx) => (
<TimelineSpanEventMarker
key={`${e.id}-marker-${idx}`}
marker={marker}
eventStart={e.start}
eventEnd={e.end}
height={height}
/>
))}
</div>
{e.markers?.map((marker, idx) => (
<TimelineSpanEventMarker
key={`${e.id}-marker-${idx}`}
marker={marker}
eventStart={e.start}
eventEnd={e.end}
height={height}
/>
))}
{!!e.showDuration && (
<span
style={{
position: 'absolute',
top: 0,
height: '100%',
display: 'flex',
alignItems: 'center',
fontSize: height * 0.5,
color: 'var(--color-text)',
whiteSpace: 'nowrap',
pointerEvents: 'none',
...(durationOnRight
? { left: '100%', marginLeft: 4 }
: { right: '100%', marginRight: 4 }),
}}
>
{renderMs(durationMs)}
</span>
)}
</div>
</Tooltip>
);
Expand Down
24 changes: 20 additions & 4 deletions packages/app/src/components/TimelineChart/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { calculateInterval, renderMs } from '../utils';

describe('renderMs', () => {
it('returns "0ms" for 0', () => {
expect(renderMs(0)).toBe('0ms');
});

it('formats sub-second values as ms', () => {
expect(renderMs(500)).toBe('500ms');
expect(renderMs(999)).toBe('999ms');
Expand All @@ -25,6 +21,26 @@ describe('renderMs', () => {
expect(renderMs(1500)).toBe('1.500s');
expect(renderMs(1234.567)).toBe('1.235s');
});

it('returns "0µs" for 0', () => {
expect(renderMs(0)).toBe('0µs');
});

it('formats sub-millisecond values as µs', () => {
expect(renderMs(0.001)).toBe('1µs');
expect(renderMs(0.5)).toBe('500µs');
expect(renderMs(0.999)).toBe('999µs');
});

it('rounds sub-millisecond values to nearest µs', () => {
expect(renderMs(0.0005)).toBe('1µs');
expect(renderMs(0.9994)).toBe('999µs');
});

it('falls through to ms when µs rounds to 1000', () => {
// 0.9995ms rounds to 1000µs, so it should render as 1ms instead
expect(renderMs(0.9995)).toBe('1ms');
});
});

describe('calculateInterval', () => {
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/components/TimelineChart/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
export function renderMs(ms: number) {
if (ms < 1) {
const µsRounded = Math.round(ms * 1000);

if (µsRounded !== 1000) {
return `${µsRounded}µs`;
}
}

if (ms < 1000) {
return `${Math.round(ms)}ms`;
}
Expand Down
Loading