from __future__ import annotations import json import statistics from collections import Counter, defaultdict from dataclasses import dataclass from pathlib import Path from typing import Any, Iterable from agentic_pd_hybrid.trace import TraceRequest, load_trace @dataclass(frozen=True) class ProfileConfig: trace_path: Path output_path: Path | None = None metrics_path: Path | None = None baseline_metrics_path: Path | None = None candidate_metrics_path: Path | None = None direct_max_uncached_tokens: int = 2048 def write_profile(config: ProfileConfig) -> dict[str, Any]: report = build_profile(config) if config.output_path is not None: config.output_path.parent.mkdir(parents=True, exist_ok=True) with config.output_path.open("w", encoding="utf-8") as handle: json.dump(report, handle, indent=2, sort_keys=True) return report def build_profile(config: ProfileConfig) -> dict[str, Any]: requests = load_trace(config.trace_path) features = _build_trace_features( requests, direct_max_uncached_tokens=config.direct_max_uncached_tokens, ) report: dict[str, Any] = { "trace_path": str(config.trace_path), "direct_max_uncached_tokens": config.direct_max_uncached_tokens, "trace_profile": _trace_profile(requests, features), } if config.metrics_path is not None: metrics = _load_jsonl(config.metrics_path) report["metrics_path"] = str(config.metrics_path) report["metrics_profile"] = _metrics_profile(metrics, features) if ( config.baseline_metrics_path is not None and config.candidate_metrics_path is not None ): baseline = _load_jsonl(config.baseline_metrics_path) candidate = _load_jsonl(config.candidate_metrics_path) report["baseline_metrics_path"] = str(config.baseline_metrics_path) report["candidate_metrics_path"] = str(config.candidate_metrics_path) report["baseline_profile"] = _metrics_profile(baseline, features) report["candidate_profile"] = _metrics_profile(candidate, features) report["paired_comparison"] = _paired_comparison( baseline=baseline, candidate=candidate, features=features, ) return report def print_profile_summary(report: dict[str, Any]) -> None: trace = report["trace_profile"] print( "trace: " f"{trace['request_count']} requests, " f"{trace['session_count']} sessions, " f"{trace['multi_turn_session_count']} multi-turn sessions" ) print( "direct-eligible turns: " f"{trace['direct_eligible_turn2plus_count']}/" f"{trace['turn2plus_count']} " f"({trace['direct_eligible_turn2plus_ratio']:.3f})" ) append_stats = trace.get("append_input_tokens_stats") output_stats = trace.get("output_tokens_stats") if append_stats is not None: print( "append tokens: " f"mean={append_stats['mean']:.1f} " f"p50={append_stats['p50']:.1f} " f"p90={append_stats['p90']:.1f} " f"p99={append_stats['p99']:.1f}" ) if output_stats is not None: print( "output tokens: " f"mean={output_stats['mean']:.1f} " f"p50={output_stats['p50']:.1f} " f"p90={output_stats['p90']:.1f} " f"p99={output_stats['p99']:.1f}" ) comparison = report.get("paired_comparison") if isinstance(comparison, dict): overall = comparison.get("overall", {}) delta = overall.get("latency_delta_s_stats") if delta is not None: print( "candidate - baseline E2E: " f"mean={delta['mean']:.3f}s " f"p50={delta['p50']:.3f}s " f"p90={delta['p90']:.3f}s" ) print( "paired wins/losses: " f"{overall.get('candidate_faster_count', 0)} faster, " f"{overall.get('candidate_slower_count', 0)} slower, " f"{overall.get('paired_count', 0)} paired" ) @dataclass(frozen=True) class _TraceFeature: request_id: str session_id: str turn_id: int input_length: int output_length: int resident_tokens: int append_input_tokens: int | None inter_turn_gap_s: float | None overlap_blocks_with_previous: int | None overlap_ratio_with_previous: float | None direct_eligible: bool turn_class: str append_bin: str input_bin: str output_bin: str resident_bin: str def _build_trace_features( requests: list[TraceRequest], *, direct_max_uncached_tokens: int, ) -> dict[str, _TraceFeature]: ordered_by_session: dict[str, list[TraceRequest]] = defaultdict(list) for request in requests: ordered_by_session[request.session_id].append(request) previous_by_request_id: dict[str, TraceRequest | None] = {} for session_requests in ordered_by_session.values(): ordered = sorted( session_requests, key=lambda request: (request.timestamp_s, request.turn_id, request.chat_id), ) previous: TraceRequest | None = None for request in ordered: previous_by_request_id[request.request_id] = previous previous = request features: dict[str, _TraceFeature] = {} for request in requests: previous = previous_by_request_id.get(request.request_id) append_input_tokens: int | None = None inter_turn_gap_s: float | None = None overlap_blocks: int | None = None overlap_ratio: float | None = None direct_eligible = False if previous is not None: append_input_tokens = request.input_length - ( previous.input_length + previous.output_length ) inter_turn_gap_s = request.timestamp_s - previous.timestamp_s previous_blocks = set(previous.hash_ids) overlap_blocks = sum(1 for block in request.hash_ids if block in previous_blocks) overlap_ratio = ( overlap_blocks / len(request.hash_ids) if request.hash_ids else 0.0 ) direct_eligible = ( append_input_tokens > 0 and append_input_tokens <= direct_max_uncached_tokens and overlap_blocks > 0 ) features[request.request_id] = _TraceFeature( request_id=request.request_id, session_id=request.session_id, turn_id=request.turn_id, input_length=request.input_length, output_length=request.output_length, resident_tokens=request.input_length + request.output_length, append_input_tokens=append_input_tokens, inter_turn_gap_s=inter_turn_gap_s, overlap_blocks_with_previous=overlap_blocks, overlap_ratio_with_previous=overlap_ratio, direct_eligible=direct_eligible, turn_class="turn1" if request.turn_id <= 1 else "turn2plus", append_bin=_token_bin(append_input_tokens), input_bin=_token_bin(request.input_length), output_bin=_token_bin(request.output_length), resident_bin=_token_bin(request.input_length + request.output_length), ) return features def _trace_profile( requests: list[TraceRequest], features: dict[str, _TraceFeature], ) -> dict[str, Any]: session_turns = Counter(request.session_id for request in requests) turn2plus = [feature for feature in features.values() if feature.turn_id > 1] direct_eligible = [feature for feature in turn2plus if feature.direct_eligible] append_values = [ feature.append_input_tokens for feature in turn2plus if feature.append_input_tokens is not None ] positive_append_values = [ value for value in append_values if value is not None and value > 0 ] overlap_ratios = [ feature.overlap_ratio_with_previous for feature in turn2plus if feature.overlap_ratio_with_previous is not None ] gaps = [ feature.inter_turn_gap_s for feature in turn2plus if feature.inter_turn_gap_s is not None ] return { "request_count": len(requests), "session_count": len(session_turns), "multi_turn_session_count": sum(1 for turns in session_turns.values() if turns > 1), "turn2plus_count": len(turn2plus), "direct_eligible_turn2plus_count": len(direct_eligible), "direct_eligible_turn2plus_ratio": ( len(direct_eligible) / len(turn2plus) if turn2plus else 0.0 ), "turn_count_distribution": dict(sorted(Counter(session_turns.values()).items())), "request_type_distribution": dict( sorted(Counter(request.request_type for request in requests).items()) ), "turn_id_distribution": dict( sorted(Counter(request.turn_id for request in requests).items()) ), "append_bin_distribution": dict( sorted(Counter(feature.append_bin for feature in turn2plus).items()) ), "input_bin_distribution": dict( sorted(Counter(feature.input_bin for feature in features.values()).items()) ), "output_bin_distribution": dict( sorted(Counter(feature.output_bin for feature in features.values()).items()) ), "resident_bin_distribution": dict( sorted(Counter(feature.resident_bin for feature in features.values()).items()) ), "input_tokens_stats": _stats( [float(request.input_length) for request in requests] ), "output_tokens_stats": _stats( [float(request.output_length) for request in requests] ), "resident_tokens_stats": _stats( [float(feature.resident_tokens) for feature in features.values()] ), "append_input_tokens_stats": _stats( [float(value) for value in append_values if value is not None] ), "positive_append_input_tokens_stats": _stats( [float(value) for value in positive_append_values] ), "inter_turn_gap_s_stats": _stats([float(value) for value in gaps]), "overlap_ratio_stats": _stats([float(value) for value in overlap_ratios]), "non_positive_append_count": sum( 1 for value in append_values if value is not None and value <= 0 ), } def _metrics_profile( rows: list[dict[str, Any]], features: dict[str, _TraceFeature], ) -> dict[str, Any]: return { "request_count": len(rows), "mechanism_distribution": dict( sorted(Counter(str(row.get("mechanism_name")) for row in rows).items()) ), "execution_mode_distribution": dict( sorted(Counter(str(row.get("execution_mode")) for row in rows).items()) ), "latency_s_stats": _stats(_numeric_values(rows, "latency_s")), "ttft_s_stats": _stats(_numeric_values(rows, "ttft_s")), "tpot_s_stats": _stats(_numeric_values(rows, "tpot_s")), "cached_tokens_stats": _stats(_numeric_values(rows, "cached_tokens")), "actual_kv_transfer_blocks_stats": _stats( _numeric_values(rows, "actual_kv_transfer_blocks") ), "session_reused_count": sum(1 for row in rows if row.get("session_reused")), "session_reset_count": sum(1 for row in rows if row.get("session_reset")), "error_count": sum(1 for row in rows if row.get("error") is not None), "by_turn_class": _group_metrics(rows, features, lambda feature, _row: feature.turn_class), "by_direct_eligible": _group_metrics( rows, features, lambda feature, _row: "eligible" if feature.direct_eligible else "not_eligible", ), "by_append_bin": _group_metrics(rows, features, lambda feature, _row: feature.append_bin), "by_resident_bin": _group_metrics( rows, features, lambda feature, _row: feature.resident_bin, ), "by_execution_mode": _group_metrics( rows, features, lambda _feature, row: str(row.get("execution_mode")), ), } def _paired_comparison( *, baseline: list[dict[str, Any]], candidate: list[dict[str, Any]], features: dict[str, _TraceFeature], ) -> dict[str, Any]: baseline_by_id = { str(row.get("request_id")): row for row in baseline if row.get("latency_s") is not None } candidate_by_id = { str(row.get("request_id")): row for row in candidate if row.get("latency_s") is not None } paired_ids = sorted(set(baseline_by_id) & set(candidate_by_id)) pairs = [ (baseline_by_id[request_id], candidate_by_id[request_id], features.get(request_id)) for request_id in paired_ids ] pairs = [pair for pair in pairs if pair[2] is not None] return { "overall": _delta_summary(pairs), "by_turn_class": _group_deltas( pairs, lambda feature, _base, _cand: feature.turn_class, ), "by_direct_eligible": _group_deltas( pairs, lambda feature, _base, _cand: ( "eligible" if feature.direct_eligible else "not_eligible" ), ), "by_append_bin": _group_deltas( pairs, lambda feature, _base, _cand: feature.append_bin, ), "by_resident_bin": _group_deltas( pairs, lambda feature, _base, _cand: feature.resident_bin, ), "by_candidate_execution_mode": _group_deltas( pairs, lambda _feature, _base, cand: str(cand.get("execution_mode")), ), } def _group_metrics( rows: list[dict[str, Any]], features: dict[str, _TraceFeature], key_fn, ) -> dict[str, Any]: grouped: dict[str, list[dict[str, Any]]] = defaultdict(list) for row in rows: feature = features.get(str(row.get("request_id"))) if feature is None: continue grouped[str(key_fn(feature, row))].append(row) return { key: { "count": len(group_rows), "latency_s_stats": _stats(_numeric_values(group_rows, "latency_s")), "ttft_s_stats": _stats(_numeric_values(group_rows, "ttft_s")), "tpot_s_stats": _stats(_numeric_values(group_rows, "tpot_s")), "session_reused_count": sum( 1 for row in group_rows if row.get("session_reused") ), "error_count": sum(1 for row in group_rows if row.get("error") is not None), } for key, group_rows in sorted(grouped.items()) } def _group_deltas( pairs: list[tuple[dict[str, Any], dict[str, Any], _TraceFeature | None]], key_fn, ) -> dict[str, Any]: grouped: dict[str, list[tuple[dict[str, Any], dict[str, Any], _TraceFeature | None]]] = ( defaultdict(list) ) for base, cand, feature in pairs: if feature is None: continue grouped[str(key_fn(feature, base, cand))].append((base, cand, feature)) return {key: _delta_summary(group_pairs) for key, group_pairs in sorted(grouped.items())} def _delta_summary( pairs: list[tuple[dict[str, Any], dict[str, Any], _TraceFeature | None]], ) -> dict[str, Any]: latency_deltas = [ float(cand["latency_s"]) - float(base["latency_s"]) for base, cand, _feature in pairs if base.get("latency_s") is not None and cand.get("latency_s") is not None ] ttft_deltas = [ float(cand["ttft_s"]) - float(base["ttft_s"]) for base, cand, _feature in pairs if base.get("ttft_s") is not None and cand.get("ttft_s") is not None ] return { "paired_count": len(latency_deltas), "candidate_faster_count": sum(1 for delta in latency_deltas if delta < 0), "candidate_slower_count": sum(1 for delta in latency_deltas if delta > 0), "latency_delta_s_stats": _stats(latency_deltas), "ttft_delta_s_stats": _stats(ttft_deltas), "total_latency_delta_s": sum(latency_deltas), "mean_baseline_latency_s": _mean( [ float(base["latency_s"]) for base, cand, _feature in pairs if base.get("latency_s") is not None and cand.get("latency_s") is not None ] ), "mean_candidate_latency_s": _mean( [ float(cand["latency_s"]) for base, cand, _feature in pairs if base.get("latency_s") is not None and cand.get("latency_s") is not None ] ), } def _load_jsonl(path: Path) -> list[dict[str, Any]]: rows: list[dict[str, Any]] = [] with path.open("r", encoding="utf-8") as handle: for line in handle: if line.strip(): rows.append(json.loads(line)) return rows def _numeric_values(rows: Iterable[dict[str, Any]], key: str) -> list[float]: values: list[float] = [] for row in rows: value = row.get(key) if value is not None: values.append(float(value)) return values def _stats(values: list[float]) -> dict[str, float] | None: clean = [value for value in values if value is not None] if not clean: return None clean.sort() return { "count": float(len(clean)), "mean": statistics.fmean(clean), "p50": _percentile(clean, 0.50), "p90": _percentile(clean, 0.90), "p99": _percentile(clean, 0.99), "min": clean[0], "max": clean[-1], } def _percentile(sorted_values: list[float], percentile: float) -> float: if len(sorted_values) == 1: return sorted_values[0] index = round((len(sorted_values) - 1) * percentile) return sorted_values[index] def _mean(values: list[float]) -> float | None: if not values: return None return statistics.fmean(values) def _token_bin(value: int | None) -> str: if value is None: return "none" if value <= 0: return "<=0" if value <= 512: return "1-512" if value <= 2048: return "513-2048" if value <= 8192: return "2049-8192" if value <= 32768: return "8193-32768" return ">32768"