@@ -592,6 +592,42 @@ function trackCountryKey(track: string, country: string): string {
592592 return `${ track } |${ country } ` ;
593593}
594594
595+ // The two things a card can plot per track: total throughput (proxy.io bytes/s)
596+ // or mean per-session goodput (proxy.session.goodput sum/count) — goodput being
597+ // the metric the evaluator actually promotes on, so "quality" not just "volume".
598+ type TrafficMetric = "traffic" | "goodput" ;
599+
600+ // avgGoodputByTrack turns the goodput sum + count series into mean per-session
601+ // goodput (bytes/sec) per track: rate(sum)/rate(count) at each step (time
602+ // cancels), the counter analog of the median the evaluator gates on.
603+ function avgGoodputByTrack (
604+ sumSeries : Array < { key : string ; points : Array < { ts : number ; value : number } > } > ,
605+ countSeries : Array < { key : string ; points : Array < { ts : number ; value : number } > } > ,
606+ ) : Map < string , Array < { ts : number ; value : number } > > {
607+ const index = ( series : typeof sumSeries ) => {
608+ const m = new Map < string , Map < number , number > > ( ) ;
609+ for ( const s of series ) {
610+ const ts = m . get ( s . key ) ?? new Map < number , number > ( ) ;
611+ for ( const p of s . points ) ts . set ( p . ts , p . value ) ;
612+ m . set ( s . key , ts ) ;
613+ }
614+ return m ;
615+ } ;
616+ const sums = index ( sumSeries ) , counts = index ( countSeries ) ;
617+ const out = new Map < string , Array < { ts : number ; value : number } > > ( ) ;
618+ for ( const track of new Set ( [ ...sums . keys ( ) , ...counts . keys ( ) ] ) ) {
619+ const st = sums . get ( track ) , ct = counts . get ( track ) ;
620+ const pts : Array < { ts : number ; value : number } > = [ ] ;
621+ for ( const ts of new Set ( [ ...( st ?. keys ( ) ?? [ ] ) , ...( ct ?. keys ( ) ?? [ ] ) ] ) ) {
622+ const c = ct ?. get ( ts ) ?? 0 ;
623+ pts . push ( { ts, value : c > 0 ? ( st ?. get ( ts ) ?? 0 ) / c : 0 } ) ;
624+ }
625+ pts . sort ( ( a , b ) => a . ts - b . ts ) ;
626+ out . set ( track , pts ) ;
627+ }
628+ return out ;
629+ }
630+
595631// PromotionTrafficCard is one promotion's traffic chart: the promoted track's
596632// line vs the original (control), both scoped to the experiment's target market,
597633// over the shared window. A vertical marker at the promotion time (when it falls
@@ -627,7 +663,7 @@ function PromotionTrafficCard({ point, seriesByTrackCountry, startMs, endMs, log
627663 < span style = { { color : CONTROL_COLOR } } > { point . originalTrackName } </ span >
628664 </ div >
629665 { ! hasData ? (
630- < div style = { { ...mono , fontSize : "0.58rem" , color : "var(--text-muted)" , padding : "2.5rem 0" , textAlign : "center" } } > No { point . targetCountry } traffic in this window.</ div >
666+ < div style = { { ...mono , fontSize : "0.58rem" , color : "var(--text-muted)" , padding : "2.5rem 0" , textAlign : "center" } } > No { point . targetCountry } data in this window.</ div >
631667 ) : (
632668 < ResponsiveContainer width = "100%" height = { 170 } >
633669 < LineChart data = { rows } margin = { { top : 10 , right : 10 , bottom : 4 , left : 4 } } >
@@ -663,6 +699,7 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
663699 const { isAuthenticated } = useAuth ( ) ;
664700 const [ hours , setHours ] = useState ( 168 ) ;
665701 const [ logScale , setLogScale ] = useState ( true ) ;
702+ const [ metric , setMetric ] = useState < TrafficMetric > ( "traffic" ) ;
666703 const [ country , setCountry ] = useState ( "" ) ;
667704 const [ protocol , setProtocol ] = useState ( "" ) ;
668705 const [ provider , setProvider ] = useState ( "" ) ;
@@ -687,11 +724,10 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
687724 const startMs = data ?. windowStart ? Date . parse ( data . windowStart ) : 0 ;
688725 const endMs = data ?. windowEnd ? Date . parse ( data . windowEnd ) : 0 ;
689726
690- // Traffic is scoped to each promotion's target market: a control is often a
691- // multi-market incumbent, so its total traffic dwarfs a single-market
692- // challenger — only the target-market slice is a fair comparison. One
693- // proxy.io query per distinct market (filtered to that market, grouped by
694- // proxy.track), keyed by (track, market) for the cards to slice.
727+ // Everything is scoped to each promotion's target market: a control is often a
728+ // multi-market incumbent, so its total dwarfs a single-market challenger — only
729+ // the target-market slice is a fair comparison. Queries run per distinct market
730+ // (filtered to that market), keyed by (track, market) for the cards to slice.
695731 const byMarket = useMemo ( ( ) => {
696732 const m = new Map < string , string [ ] > ( ) ;
697733 for ( const p of promotions ) {
@@ -727,27 +763,48 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
727763 setTrafficError ( null ) ;
728764 const windowSec = ( endMs - startMs ) / 1000 ;
729765 const stepSeconds = Math . max ( 900 , Math . round ( windowSec / 168 ) ) ; // ~a point/hour over a week
766+ const countryFilter = ( market : string ) => ( { key : "geo.country.iso_code" , dataType : "string" , op : "=" , value : market } ) ;
730767 try {
731- const results = await Promise . all (
732- [ ...byMarket . entries ( ) ] . map ( ( [ market , names ] ) =>
733- fetchSigNozMetrics ( buildExperimentTrackQuery ( {
734- metricName : "proxy.io" , trackNames : names , trackKey : "proxy.track" ,
735- timeAggregation : "rate" , spaceAggregation : "sum" , startMs, endMs, stepSeconds,
736- extraFilters : [
737- { key : "network.io.direction" , dataType : "string" , op : "=" , value : "transmit" } ,
738- { key : "geo.country.iso_code" , dataType : "string" , op : "=" , value : market } ,
739- ] ,
740- } ) ) . then ( ( resp ) => ( { market, series : extractTrackSeries ( resp ) } ) ) ,
741- ) ,
742- ) ;
743- if ( cancelled ) return ;
744768 const m = new Map < string , Array < { ts : number ; value : number } > > ( ) ;
745- for ( const { market, series } of results ) {
746- for ( const s of series ) m . set ( trackCountryKey ( s . key , market ) , s . points ) ;
769+ if ( metric === "traffic" ) {
770+ // Total throughput: proxy.io (transmit) per market, tagged proxy.track.
771+ const results = await Promise . all (
772+ [ ...byMarket . entries ( ) ] . map ( ( [ market , names ] ) =>
773+ fetchSigNozMetrics ( buildExperimentTrackQuery ( {
774+ metricName : "proxy.io" , trackNames : names , trackKey : "proxy.track" ,
775+ timeAggregation : "rate" , spaceAggregation : "sum" , startMs, endMs, stepSeconds,
776+ extraFilters : [ { key : "network.io.direction" , dataType : "string" , op : "=" , value : "transmit" } , countryFilter ( market ) ] ,
777+ } ) ) . then ( ( resp ) => ( { market, series : extractTrackSeries ( resp ) } ) ) ,
778+ ) ,
779+ ) ;
780+ if ( cancelled ) return ;
781+ for ( const { market, series } of results ) {
782+ for ( const s of series ) m . set ( trackCountryKey ( s . key , market ) , s . points ) ;
783+ }
784+ } else {
785+ // Mean per-session goodput: sum/count per market, tagged track.
786+ const results = await Promise . all (
787+ [ ...byMarket . entries ( ) ] . map ( async ( [ market , names ] ) => {
788+ const mk = ( metricName : string ) => buildExperimentTrackQuery ( {
789+ metricName, trackNames : names , trackKey : "track" ,
790+ timeAggregation : "rate" , spaceAggregation : "sum" , startMs, endMs, stepSeconds,
791+ extraFilters : [ countryFilter ( market ) ] ,
792+ } ) ;
793+ const [ sumResp , countResp ] = await Promise . all ( [
794+ fetchSigNozMetrics ( mk ( "proxy.session.goodput.sum" ) ) ,
795+ fetchSigNozMetrics ( mk ( "proxy.session.goodput.count" ) ) ,
796+ ] ) ;
797+ return { market, avg : avgGoodputByTrack ( extractTrackSeries ( sumResp ) , extractTrackSeries ( countResp ) ) } ;
798+ } ) ,
799+ ) ;
800+ if ( cancelled ) return ;
801+ for ( const { market, avg } of results ) {
802+ for ( const [ track , points ] of avg ) m . set ( trackCountryKey ( track , market ) , points ) ;
803+ }
747804 }
748805 setSeriesByTrackCountry ( m ) ;
749806 } catch ( err ) {
750- if ( ! cancelled ) setTrafficError ( err instanceof Error ? err . message : "Failed to load traffic " ) ;
807+ if ( ! cancelled ) setTrafficError ( err instanceof Error ? err . message : "Failed to load metrics " ) ;
751808 } finally {
752809 if ( ! cancelled ) setTrafficLoading ( false ) ;
753810 }
@@ -756,7 +813,7 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
756813 return ( ) => { cancelled = true ; } ;
757814 // byMarket is captured but keyed on the stable marketsKey string.
758815 // eslint-disable-next-line react-hooks/exhaustive-deps
759- } , [ marketsKey , startMs , endMs , isAuthenticated ] ) ;
816+ } , [ marketsKey , startMs , endMs , isAuthenticated , metric ] ) ;
760817
761818 const hasFilters = Boolean ( country || protocol || provider ) ;
762819 const windowLabel = COMPARISON_WINDOWS . find ( ( w ) => w . hours === hours ) ?. label ?? `${ hours } h` ;
@@ -765,13 +822,18 @@ function PromotedTraffic({ enabled }: { enabled: boolean }) {
765822 < div style = { card } >
766823 < div style = { { display : "flex" , justifyContent : "space-between" , alignItems : "flex-start" , gap : "1rem" , flexWrap : "wrap" , marginBottom : "0.6rem" } } >
767824 < div >
768- < div style = { { ...sectionLabel , marginBottom : "0.15rem" } } > Promoted vs original — traffic over time</ div >
825+ < div style = { { ...sectionLabel , marginBottom : "0.15rem" } } > Promoted vs original — { metric === "goodput" ? "goodput over time" : " traffic over time" } </ div >
769826 < div style = { { ...mono , fontSize : "0.55rem" , color : "var(--text-muted)" } } >
770- One card per promotion over the last { windowLabel } , scoped to the experiment's target market: the < span style = { { color : CHALLENGER_COLOR } } > promoted</ span > track's traffic vs the < span style = { { color : CONTROL_COLOR } } > original</ span > (control). If the promotion is working, the promoted line climbs while the control's share of that market falls.
827+ One card per promotion over the last { windowLabel } , scoped to the experiment's target market: the < span style = { { color : CHALLENGER_COLOR } } > promoted</ span > track vs the < span style = { { color : CONTROL_COLOR } } > original</ span > (control). { metric === "goodput"
828+ ? "Goodput (mean bytes/sec per session) is the metric the evaluator promotes on — this shows whether the promoted track still wins on quality."
829+ : "Traffic is total throughput; if the promotion is working the promoted line climbs while the control's share of that market falls." }
771830 </ div >
772831 </ div >
773832 < div style = { { display : "flex" , gap : "0.3rem" , alignItems : "center" , flexWrap : "wrap" } } >
774- < button type = "button" onClick = { ( ) => setLogScale ( ( v ) => ! v ) } style = { chip ( false ) } title = "Toggle log / linear traffic axis" >
833+ < button type = "button" onClick = { ( ) => setMetric ( "traffic" ) } style = { chip ( metric === "traffic" ) } aria-pressed = { metric === "traffic" } > traffic</ button >
834+ < button type = "button" onClick = { ( ) => setMetric ( "goodput" ) } style = { chip ( metric === "goodput" ) } aria-pressed = { metric === "goodput" } > goodput</ button >
835+ < span style = { { width : 1 , height : "1rem" , background : "#ffffff14" , margin : "0 0.15rem" } } />
836+ < button type = "button" onClick = { ( ) => setLogScale ( ( v ) => ! v ) } style = { chip ( false ) } title = "Toggle log / linear axis" >
775837 { logScale ? "log" : "linear" }
776838 </ button >
777839 < span style = { { width : 1 , height : "1rem" , background : "#ffffff14" , margin : "0 0.15rem" } } />
0 commit comments