@@ -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
7273const 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
109192const meta : Meta < typeof TimelineChart > = {
110193 title : 'Components/TimelineChart' ,
0 commit comments