feat: show request payload size

This commit is contained in:
2026-07-01 09:50:06 +08:00
parent eaaf957854
commit 58f3caeea5
2 changed files with 49 additions and 7 deletions
+14 -6
View File
@@ -346,9 +346,13 @@ def format_duration_ms_as_seconds(value: Any) -> str:
def format_bytes(value: Any) -> str: def format_bytes(value: Any) -> str:
number = as_int(value) if value is None or str(value).strip() == "":
if number <= 0:
return "-" return "-"
number = as_int(value)
if number < 0:
return "-"
if number == 0:
return "0B"
if number >= 1_000_000: if number >= 1_000_000:
return f"{number / 1_000_000:.1f}MB".rstrip("0").rstrip(".") return f"{number / 1_000_000:.1f}MB".rstrip("0").rstrip(".")
if number >= 1_000: if number >= 1_000:
@@ -478,7 +482,8 @@ def request_row_cells(row: dict[str, Any], text_limits: dict[str, int] | None =
short_text(row["model"] or row["requested_model"] or row["forwarded_model"], limits.get("model", 16)), short_text(row["model"] or row["requested_model"] or row["forwarded_model"], limits.get("model", 16)),
row["reasoning_tokens"] if row["reasoning_tokens"] is not None else "-", row["reasoning_tokens"] if row["reasoning_tokens"] is not None else "-",
short_text(request_usage_summary(row), limits.get("usage", 24)), short_text(request_usage_summary(row), limits.get("usage", 24)),
format_bytes(row["response_bytes_received"]), format_bytes(row.get("request_body_bytes")),
format_bytes(row.get("response_bytes_received")),
request_chunk_progress(row), request_chunk_progress(row),
format_duration_ms_as_seconds(row["first_response_delay_ms"]), format_duration_ms_as_seconds(row["first_response_delay_ms"]),
format_duration_ms_as_seconds(row["duration_ms"]), format_duration_ms_as_seconds(row["duration_ms"]),
@@ -781,8 +786,8 @@ def render_request_detail(row: dict[str, Any]) -> str:
f"attempts {row['upstream_attempt_count'] or 0}", f"attempts {row['upstream_attempt_count'] or 0}",
f"first {format_duration_ms_as_seconds(row['first_response_delay_ms'])}", f"first {format_duration_ms_as_seconds(row['first_response_delay_ms'])}",
f"duration {format_duration_ms_as_seconds(row['duration_ms'])}", f"duration {format_duration_ms_as_seconds(row['duration_ms'])}",
f"request {format_bytes(row['request_body_bytes'])}", f"request {format_bytes(row.get('request_body_bytes'))}",
f"response {format_bytes(row['response_bytes_received'])}", f"response {format_bytes(row.get('response_bytes_received'))}",
f"chunks {row['stream_chunk_count'] or 0}", f"chunks {row['stream_chunk_count'] or 0}",
f"life {request_lifecycle_label(row)}", f"life {request_lifecycle_label(row)}",
f"finished {short_time(row['finished_at'])}", f"finished {short_time(row['finished_at'])}",
@@ -1217,6 +1222,7 @@ def run_textual(
"model": 16, "model": 16,
"reasoning": 10, "reasoning": 10,
"usage": 36, "usage": 36,
"req": 10,
"resp": 10, "resp": 10,
"chunks": 14, "chunks": 14,
"first": 8, "first": 8,
@@ -1234,6 +1240,7 @@ def run_textual(
"model": 24, "model": 24,
"reasoning": 10, "reasoning": 10,
"usage": 72, "usage": 72,
"req": 12,
"resp": 12, "resp": 12,
"chunks": 16, "chunks": 16,
"first": 8, "first": 8,
@@ -1273,6 +1280,7 @@ def run_textual(
"model": requests.add_column("Model", width=width_profile["model"], key="model"), "model": requests.add_column("Model", width=width_profile["model"], key="model"),
"reasoning": requests.add_column("Reasoning", width=width_profile["reasoning"], key="reasoning"), "reasoning": requests.add_column("Reasoning", width=width_profile["reasoning"], key="reasoning"),
"usage": requests.add_column("Usage", width=self.request_usage_width, key="usage"), "usage": requests.add_column("Usage", width=self.request_usage_width, key="usage"),
"req": requests.add_column("Req", width=width_profile["req"], key="req"),
"resp": requests.add_column("Resp", width=width_profile["resp"], key="resp"), "resp": requests.add_column("Resp", width=width_profile["resp"], key="resp"),
"chunks": requests.add_column("Chunks", width=width_profile["chunks"], key="chunks"), "chunks": requests.add_column("Chunks", width=width_profile["chunks"], key="chunks"),
"first": requests.add_column("First", width=width_profile["first"], key="first"), "first": requests.add_column("First", width=width_profile["first"], key="first"),
@@ -1785,7 +1793,7 @@ def print_once(snapshot: dict[str, Any], filter_text: str = "") -> None:
print( print(
f"{row['seq']:>6} {short_text(primary_request_id(row), 16):<16} {short_text(row.get('thread_id'), 14):<14} {short_time(row.get('started_at')):<19} {status_symbol(row)} {row.get('status_code') or '-':<4} " f"{row['seq']:>6} {short_text(primary_request_id(row), 16):<16} {short_text(row.get('thread_id'), 14):<14} {short_time(row.get('started_at')):<19} {status_symbol(row)} {row.get('status_code') or '-':<4} "
f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} " f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} "
f"{short_text(request_usage_summary(row), 26):<26} {request_chunk_progress(row):<14} {format_duration_ms_as_seconds(row['duration_ms']):<8} {request_updated_elapsed(row):<8} " f"{short_text(request_usage_summary(row), 26):<26} {format_bytes(row.get('request_body_bytes')):<8} {request_chunk_progress(row):<14} {format_duration_ms_as_seconds(row['duration_ms']):<8} {request_updated_elapsed(row):<8} "
f"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}" f"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}"
) )
except BrokenPipeError: except BrokenPipeError:
+35 -1
View File
@@ -126,6 +126,7 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
"input_tokens": 1200, "input_tokens": 1200,
"output_tokens": 340, "output_tokens": 340,
"cached_tokens": 128, "cached_tokens": 128,
"request_body_bytes": 0,
"response_bytes_received": 363800, "response_bytes_received": 363800,
"response_stream": True, "response_stream": True,
"stream_chunk_count": 587, "stream_chunk_count": 587,
@@ -137,11 +138,44 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
} }
compact = mod.request_row_cells(row, {"id": 18, "thread": 18, "path": 22, "model": 16, "usage": 24, "note": 22}) compact = mod.request_row_cells(row, {"id": 18, "thread": 18, "path": 22, "model": 16, "usage": 24, "note": 22})
wide = mod.request_row_cells(row, {"id": 36, "thread": 36, "path": 40, "model": 24, "usage": 72, "note": 48}) wide = mod.request_row_cells(row, {"id": 36, "thread": 36, "path": 40, "model": 24, "usage": 72, "note": 48})
self.assertEqual(compact[9], "0B")
self.assertEqual(wide[9], "0B")
self.assertLess(len(compact[1]), len(wide[1])) self.assertLess(len(compact[1]), len(wide[1]))
self.assertLess(len(compact[2]), len(wide[2])) self.assertLess(len(compact[2]), len(wide[2]))
self.assertLess(len(compact[5]), len(wide[5])) self.assertLess(len(compact[5]), len(wide[5]))
self.assertLess(len(compact[8]), len(wide[8])) self.assertLess(len(compact[8]), len(wide[8]))
self.assertLess(len(compact[14]), len(wide[14])) self.assertLess(len(compact[15]), len(wide[15]))
def test_request_row_cells_show_request_bytes(self) -> None:
mod = load_module()
row = {
"seq": 1,
"request_id": "req_1",
"response_id": "resp_1",
"thread_id": "thread_1",
"started_at": "2026-06-30T12:00:00Z",
"status_code": 200,
"path": "/responses",
"model": "gpt-5",
"requested_model": "",
"forwarded_model": "",
"reasoning_tokens": 7,
"input_tokens": 20,
"output_tokens": 10,
"cached_tokens": 5,
"request_body_bytes": 1536,
"response_bytes_received": 4096,
"response_stream": True,
"stream_chunk_count": 4,
"first_response_delay_ms": 1200,
"duration_ms": 3400,
"upstream_attempt_count": 1,
"error": "",
"finished_at": "2026-06-30T12:00:03Z",
}
cells = mod.request_row_cells(row)
self.assertEqual(cells[9], "1.5KB")
self.assertEqual(cells[10], "4.1KB")
def test_request_rows_read_usage_from_nested_usage_object(self) -> None: def test_request_rows_read_usage_from_nested_usage_object(self) -> None:
mod = load_module() mod = load_module()