Modern QA2026Measuring LLM Metrics — tiles
Log inJoin
56 / 95 · 05 Performance & Chaos Engineering · LLM Performance Metrics← prev⊞ allnext →☰ Read as one page

9.4Measuring LLM Metrics

Non-Streaming Measurement

For non-streaming endpoints, you can only measure total generation time directly. TTFT must be estimated:

# llm_metrics_collector.py
import time
import json
from dataclasses import dataclass

@dataclass
class LLMMetrics:
    ttft_ms: float
    total_generation_ms: float
    tokens_per_second: float
    prompt_tokens: int
    completion_tokens: int
    total_tokens: int
    model: str

def measure_non_streaming(client, prompt: str, model: str = "gpt-5.5") -> LLMMetrics:
    """Measure LLM performance for a non-streaming request."""
    start = time.perf_counter()

    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=256,
        stream=False,
    )

    total_ms = (time.perf_counter() - start) * 1000
    completion_tokens = response.usage.completion_tokens
    tps = completion_tokens / (total_ms / 1000) if total_ms > 0 else 0

    return LLMMetrics(
        ttft_ms=total_ms * 0.15,  # heuristic: ~15% of total time is prefill
        total_generation_ms=total_ms,
        tokens_per_second=tps,
        prompt_tokens=response.usage.prompt_tokens,
        completion_tokens=completion_tokens,
        total_tokens=response.usage.total_tokens,
        model=model,
    )

Streaming Measurement (Accurate TTFT)

For accurate TTFT, you must use the streaming API:

import time

def measure_streaming(client, prompt: str, model: str = "gpt-5.5") -> LLMMetrics:
    """Measure LLM performance for a streaming request with accurate TTFT."""
    start = time.perf_counter()
    ttft = None
    token_count = 0
    full_response = ""

    stream = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
        max_tokens=256,
        stream=True,
        stream_options={"include_usage": True},
    )

    for chunk in stream:
        if chunk.choices and chunk.choices[0].delta.content:
            if ttft is None:
                ttft = (time.perf_counter() - start) * 1000
            full_response += chunk.choices[0].delta.content
            token_count += 1

        # Usage is in the final chunk when stream_options is set
        if hasattr(chunk, 'usage') and chunk.usage:
            prompt_tokens = chunk.usage.prompt_tokens
            completion_tokens = chunk.usage.completion_tokens

    total_ms = (time.perf_counter() - start) * 1000
    generation_time = total_ms - (ttft or 0)
    tps = completion_tokens / (generation_time / 1000) if generation_time > 0 else 0

    return LLMMetrics(
        ttft_ms=ttft or total_ms,
        total_generation_ms=total_ms,
        tokens_per_second=tps,
        prompt_tokens=prompt_tokens,
        completion_tokens=completion_tokens,
        total_tokens=prompt_tokens + completion_tokens,
        model=model,
    )