Skip to content

Commit 3307117

Browse files
committed
experiments: add traffic ↔ goodput toggle to promoted-vs-original cards
Each card can now show either total throughput (proxy.io) or mean per-session goodput (proxy.session.goodput sum/count) — goodput being the axis the evaluator actually promotes on, so you can check quality, not just volume. Both are market-scoped; goodput uses the same rate(sum)/rate(count) pattern the success-rate line already uses.
1 parent 6cf7f5b commit 3307117

1 file changed

Lines changed: 88 additions & 26 deletions

File tree

src/components/ExperimentsOverview.tsx

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)