Files
ali-trace-tools/trace_analyzer/models.py
Gahow Wang 825062f554 feat(analyzer): surface TTFT/TPOT as features and CDF figures
The gateway records three epoch-ms stamps per request (admitted, forwarded to
backend, backend first token). They reached *-raw.jsonl but stopped there:
features.csv had only total latency, and none of the figures covered
time-to-first-token.

Derive ttft_ms, ttft_e2e_ms and tpot_ms in one helper shared by the streaming
path (preparation.py) and the in-memory path (features.py) so the two cannot
drift, and add figures 14 (TTFT, backend vs end-to-end) and 15 (TPOT). The two
TTFT series are plotted together because their offset is the gateway admission
delay, which is then readable straight off the chart.

Rows missing a backend stamp and single-token responses are recorded as 0 and
filtered at plot time; left in, they would stack at the origin and pull every
percentile down. Figure 15 is a single panel: TPOT is near-deterministic
(p50 21.9, p90 23.0), so a p90 zoom renders a flat line and a vertical edge.

Verified by recomputing ttft_ms straight from the original SLS rows for all
2190 requests of a sample: 0 mismatches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 06:41:51 +00:00

98 lines
2.2 KiB
Python

from dataclasses import dataclass, field
@dataclass
class MessageEvent:
role: str
content_type: str
text_len: int
has_cache_control: bool = False
item_count: int = 0
@dataclass
class ToolSpec:
name: str
tool_type: str
@dataclass
class UsageStats:
input_tokens: int = 0
output_tokens: int = 0
total_tokens: int = 0
reasoning_tokens: int = 0
cached_tokens: int = 0
@dataclass
class RequestMeta:
provider: str
line_number: int
request_id: str
session_id: str
request_model: str
time: str
status_code: str
status_name: str
request_ready_time_ms: int
request_end_time_ms: int
total_cost_time_ms: int
backend_first_request_time_ms: int = 0
backend_first_response_time_ms: int = 0
@dataclass
class TraceRecord:
meta: RequestMeta
canonical_prompt: str = ""
messages: list[MessageEvent] = field(default_factory=list)
role_sequence: list[str] = field(default_factory=list)
declared_tools: list[ToolSpec] = field(default_factory=list)
usage: UsageStats = field(default_factory=UsageStats)
raw_messages: list[dict] = field(default_factory=list)
@dataclass
class TraceFeatures:
request_id: str
session_id: str
model: str
status_code: str
time: str
message_count: int
conversation_depth: int
declared_tool_count: int
assistant_msg_count: int
tool_msg_count: int
user_msg_count: int
system_msg_count: int
assistant_to_tool_count: int
tool_to_assistant_count: int
tool_to_tool_count: int
assistant_to_user_count: int
user_to_assistant_count: int
max_consecutive_tool_msgs: int
avg_tool_burst_len: float
has_tool_loop: int
input_tokens: int
output_tokens: int
total_tokens: int
reasoning_tokens: int
cached_tokens: int
cache_hit_ratio: float
uncached_prompt_tokens: int
output_input_ratio: float
latency_ms: int
ms_per_input_token: float
ms_per_output_token: float
long_context: int
high_cache: int
tool_burst_alert: int
tool_loop_alert: int
slow_request: int = 0
ttft_ms: int = 0
ttft_e2e_ms: int = 0
tpot_ms: float = 0.0
pattern_labels: list[str] = field(default_factory=list)