Skip to content

Commit 39e062f

Browse files
authored
storybook: improve trace timeline data (#2577)
## Summary Updates the sample rows data in TimelineChart.stories to better reproduce #2312
1 parent 727d327 commit 39e062f

2 files changed

Lines changed: 116 additions & 28 deletions

File tree

‎.changeset/shy-dodos-push.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/app": patch
3+
---
4+
5+
storybook: Updates the sample rows data in TimelineChart.stories

‎packages/app/src/components/TimelineChart/TimelineChart.stories.tsx‎

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function makeRow(
3737
<div className="d-flex align-items-center" style={{ height: 24 }}>
3838
{Array.from({ length: level }).map((_, idx) => (
3939
<div
40+
// eslint-disable-next-line @eslint-react/no-array-index-key
4041
key={idx}
4142
style={{
4243
borderLeft: '1px solid var(--color-border)',
@@ -71,40 +72,122 @@ function makeRow(
7172

7273
const services = [
7374
'api-gateway',
74-
'auth-service',
75-
'user-service',
75+
'checkout-service',
76+
'inventory-service',
77+
'payment-service',
7678
'postgres',
77-
'dashboard-svc',
7879
'redis',
79-
'metrics-svc',
80+
'kafka',
8081
'clickhouse',
8182
];
82-
const ops = [
83-
'GET',
84-
'POST',
85-
'SELECT',
86-
'INSERT',
87-
'SET',
88-
'validateToken',
89-
'aggregate',
90-
'query',
91-
];
83+
// Long-running operations produce multi-second spans; the short ops produce
84+
// sub-100ms database writes. This mix of durations exercises how the waterfall
85+
// renders both very long and very short spans on the same timeline.
86+
const longOps = ['SELECT', 'aggregate', 'query', 'validateToken'];
87+
const shortOps = ['INSERT', 'UPDATE', 'DELETE', 'SET', 'GET'];
9288

93-
const sampleRows = Array.from({ length: 1000 }, (_, i) => {
94-
const level = i === 0 ? 0 : (i * 7) % 4;
95-
const childCount = level < 2 && i % 3 === 0 ? ((i * 3) % 5) + 1 : 0;
96-
const start = i * 3;
97-
const duration = 10 + ((i * 13) % 90);
98-
return makeRow(
99-
`s${i}`,
100-
services[i % services.length],
101-
`${ops[i % ops.length]} /op-${i}`,
102-
start,
103-
duration,
104-
level,
105-
childCount,
89+
// Small deterministic PRNG (mulberry32) so the story renders identically on
90+
// every load instead of depending on Math.random().
91+
function mulberry32(seed: number) {
92+
return () => {
93+
seed |= 0;
94+
seed = (seed + 0x6d2b79f5) | 0;
95+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
96+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
97+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
98+
};
99+
}
100+
101+
const TRACE_DURATION_MS = 8000;
102+
103+
// Builds a realistic sample trace (~414 spans across ~8s) that mixes a wide
104+
// range of span durations so the story exercises the full breadth of the
105+
// waterfall's rendering, including its minimum-width behavior and zooming.
106+
//
107+
//
108+
// The generated trace mixes:
109+
// - a root span covering the whole trace,
110+
// - a few long spans (up to ~2.4s),
111+
// - a spread of mid-range children (tens to hundreds of ms, up to ~1.2s), and
112+
// - many very short (14-29ms) database writes that fall below the min-width
113+
// floor.
114+
const sampleRows = (() => {
115+
const rand = mulberry32(2312);
116+
const rows: ReturnType<typeof makeRow>[] = [];
117+
118+
// Root span covering the whole ~8s trace.
119+
rows.push(
120+
makeRow(
121+
'root',
122+
services[0],
123+
'GET /api/checkout',
124+
0,
125+
TRACE_DURATION_MS * 0.985,
126+
0,
127+
8,
128+
),
106129
);
107-
});
130+
131+
const groupCount = 8;
132+
const groupWindow = TRACE_DURATION_MS / groupCount;
133+
134+
for (let g = 0; g < groupCount; g++) {
135+
const service = services[1 + (g % (services.length - 1))];
136+
const groupStart = g * groupWindow + rand() * (groupWindow * 0.1);
137+
138+
// One long-running span per group. Group 1 gets an explicit ~2.4s span so
139+
// there is a clear "hundreds of times longer" reference bar.
140+
const longDuration = g === 1 ? 2400 : 300 + rand() * (groupWindow * 1.4);
141+
const shortCount = 40 + Math.floor(rand() * 12); // ~40-51 tiny children
142+
143+
rows.push(
144+
makeRow(
145+
`svc-${g}`,
146+
service,
147+
`${longOps[g % longOps.length]} batch-${g}`,
148+
groupStart,
149+
longDuration,
150+
1,
151+
shortCount,
152+
),
153+
);
154+
155+
// Children scattered inside the long span. Each picks a duration from a
156+
// weighted set of ranges so the waterfall covers a good spread of widths:
157+
// mostly tiny sub-floor DB writes, plus a healthy sprinkling of mid-range
158+
// (100s of ms) and occasional near-second spans.
159+
for (let s = 0; s < shortCount; s++) {
160+
const roll = rand();
161+
let childDuration: number;
162+
if (roll < 0.5) {
163+
childDuration = 14 + Math.floor(rand() * 16); // 14-29ms (below floor)
164+
} else if (roll < 0.72) {
165+
childDuration = 30 + Math.floor(rand() * 60); // 30-89ms
166+
} else if (roll < 0.92) {
167+
childDuration = 100 + Math.floor(rand() * 400); // 100-499ms
168+
} else {
169+
childDuration = 500 + Math.floor(rand() * 700); // 500-1199ms
170+
}
171+
// Keep the child within its parent span.
172+
childDuration = Math.min(childDuration, Math.floor(longDuration));
173+
const start =
174+
groupStart + rand() * Math.max(longDuration - childDuration, 1);
175+
rows.push(
176+
makeRow(
177+
`svc-${g}-w${s}`,
178+
services[4 + (s % 4)], // postgres / redis / kafka / clickhouse
179+
`${shortOps[s % shortOps.length]} row-${s}`,
180+
start,
181+
childDuration,
182+
2,
183+
0,
184+
),
185+
);
186+
}
187+
}
188+
189+
return rows;
190+
})();
108191

109192
const meta: Meta<typeof TimelineChart> = {
110193
title: 'Components/TimelineChart',

0 commit comments

Comments
 (0)