fix: show request timing and nested usage
This commit is contained in:
+31
-19
@@ -310,6 +310,13 @@ def format_ms(value: Any) -> str:
|
||||
return "-" if number <= 0 else f"{number}ms"
|
||||
|
||||
|
||||
def format_duration_ms_as_seconds(value: Any) -> str:
|
||||
number = as_float(value)
|
||||
if number <= 0:
|
||||
return "-"
|
||||
return format_elapsed_seconds(number / 1000.0)
|
||||
|
||||
|
||||
def format_bytes(value: Any) -> str:
|
||||
number = as_int(value)
|
||||
if number <= 0:
|
||||
@@ -657,6 +664,7 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li
|
||||
continue
|
||||
if needle and not request_match_score(entry, needle):
|
||||
continue
|
||||
usage = entry.get("usage") if isinstance(entry.get("usage"), dict) else {}
|
||||
rows.append(
|
||||
{
|
||||
"seq": as_int(entry.get("seq")),
|
||||
@@ -675,11 +683,11 @@ def normalize_request_rows(payload: dict[str, Any], filter_text: str = "") -> li
|
||||
"inspected": bool(entry.get("inspected")),
|
||||
"status_code": entry.get("status_code"),
|
||||
"upstream_status_code": entry.get("upstream_status_code"),
|
||||
"reasoning_tokens": entry.get("reasoning_tokens"),
|
||||
"input_tokens": entry.get("input_tokens"),
|
||||
"output_tokens": entry.get("output_tokens"),
|
||||
"total_tokens": entry.get("total_tokens"),
|
||||
"cached_tokens": entry.get("cached_tokens"),
|
||||
"reasoning_tokens": entry.get("reasoning_tokens", usage.get("reasoning_tokens")),
|
||||
"input_tokens": entry.get("input_tokens", usage.get("input_tokens")),
|
||||
"output_tokens": entry.get("output_tokens", usage.get("output_tokens")),
|
||||
"total_tokens": entry.get("total_tokens", usage.get("total_tokens")),
|
||||
"cached_tokens": entry.get("cached_tokens", usage.get("cached_tokens")),
|
||||
"upstream_attempt_count": entry.get("upstream_attempt_count"),
|
||||
"request_body_bytes": entry.get("request_body_bytes"),
|
||||
"response_bytes_received": entry.get("response_bytes_received"),
|
||||
@@ -707,18 +715,18 @@ def render_request_detail(row: dict[str, Any]) -> str:
|
||||
f"seq {row['seq']}",
|
||||
f"id {primary_request_id(row)}",
|
||||
f"thread {row.get('thread_id') or '-'}",
|
||||
f"started {short_time(row['started_at'])}",
|
||||
f"{row['method']} {row['path']}",
|
||||
f"profile {row.get('profile_name') or '-'}",
|
||||
f"status {row['status_code'] or '-'}",
|
||||
f"upstream {row['upstream_status_code'] or '-'}",
|
||||
f"attempts {row['upstream_attempt_count'] or 0}",
|
||||
f"first {format_ms(row['first_response_delay_ms'])}",
|
||||
f"duration {format_ms(row['duration_ms'])}",
|
||||
f"first {format_duration_ms_as_seconds(row['first_response_delay_ms'])}",
|
||||
f"duration {format_duration_ms_as_seconds(row['duration_ms'])}",
|
||||
f"request {format_bytes(row['request_body_bytes'])}",
|
||||
f"response {format_bytes(row['response_bytes_received'])}",
|
||||
f"chunks {row['stream_chunk_count'] or 0}",
|
||||
f"life {request_lifecycle_label(row)}",
|
||||
f"started {short_time(row['started_at'])}",
|
||||
f"finished {short_time(row['finished_at'])}",
|
||||
f"updated {request_updated_elapsed(row)}",
|
||||
]
|
||||
@@ -1144,7 +1152,7 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
||||
requests = self.query_one("#requests_table", DataTable)
|
||||
requests.cursor_type = "row"
|
||||
requests.zebra_stripes = True
|
||||
requests.add_columns("Seq", "ID", "Thread", "Status", "Path", "Model", "Reasoning", "Usage", "Resp", "Chunks", "First", "Duration", "Updated", "Note")
|
||||
requests.add_columns("Seq", "ID", "Thread", "Started", "Status", "Path", "Model", "Reasoning", "Usage", "Resp", "Chunks", "First", "Duration", "Updated", "Note")
|
||||
|
||||
logs = self.query_one("#logs_table", DataTable)
|
||||
logs.cursor_type = "row"
|
||||
@@ -1219,22 +1227,25 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
||||
table = self.query_one("#requests_table", DataTable)
|
||||
if table.cursor_row is None or table.cursor_row < 0:
|
||||
return None
|
||||
key = str(table.get_row_at(table.cursor_row).key.value)
|
||||
return self.request_by_key.get(key)
|
||||
if table.cursor_row >= len(self.requests):
|
||||
return None
|
||||
return self.requests[table.cursor_row]
|
||||
|
||||
def _selected_profile_row(self) -> dict[str, Any] | None:
|
||||
table = self.query_one("#profiles_table", DataTable)
|
||||
if table.cursor_row is None or table.cursor_row < 0:
|
||||
return None
|
||||
key = str(table.get_row_at(table.cursor_row).key.value)
|
||||
return self.profile_by_key.get(key)
|
||||
if table.cursor_row >= len(self.profiles):
|
||||
return None
|
||||
return self.profiles[table.cursor_row]
|
||||
|
||||
def _selected_log_row(self) -> dict[str, Any] | None:
|
||||
table = self.query_one("#logs_table", DataTable)
|
||||
if table.cursor_row is None or table.cursor_row < 0:
|
||||
return None
|
||||
key = str(table.get_row_at(table.cursor_row).key.value)
|
||||
return self.log_by_key.get(key)
|
||||
if table.cursor_row >= len(self.logs):
|
||||
return None
|
||||
return self.logs[table.cursor_row]
|
||||
|
||||
def _set_status(self, text: str) -> None:
|
||||
self.query_one("#status", Static).update(text)
|
||||
@@ -1320,6 +1331,7 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
||||
str(row["seq"]),
|
||||
short_text(primary_request_id(row), 18) or "-",
|
||||
short_text(row["thread_id"], 18) or "-",
|
||||
short_time(row["started_at"]),
|
||||
f"{status_symbol(row)} {row.get('status_code') or '-'}",
|
||||
short_text(row["path"], 22),
|
||||
short_text(row["model"] or row["requested_model"] or row["forwarded_model"], 16),
|
||||
@@ -1327,8 +1339,8 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
||||
short_text(request_usage_summary(row), 24),
|
||||
format_bytes(row["response_bytes_received"]),
|
||||
request_chunk_progress(row),
|
||||
format_ms(row["first_response_delay_ms"]),
|
||||
format_ms(row["duration_ms"]),
|
||||
format_duration_ms_as_seconds(row["first_response_delay_ms"]),
|
||||
format_duration_ms_as_seconds(row["duration_ms"]),
|
||||
request_updated_elapsed(row),
|
||||
f"{row['upstream_attempt_count'] or 0}x {short_text(row['error'], 18)}",
|
||||
key=key,
|
||||
@@ -1573,9 +1585,9 @@ def print_once(snapshot: dict[str, Any], filter_text: str = "") -> None:
|
||||
for row in snapshot.get("requests") or []:
|
||||
try:
|
||||
print(
|
||||
f"{row['seq']:>6} {short_text(primary_request_id(row), 16):<16} {short_text(row.get('thread_id'), 14):<14} {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(request_usage_summary(row), 26):<26} {request_chunk_progress(row):<14} {format_ms(row['duration_ms']):<8} {request_updated_elapsed(row):<8} "
|
||||
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"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}"
|
||||
)
|
||||
except BrokenPipeError:
|
||||
|
||||
@@ -109,6 +109,34 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
|
||||
}
|
||||
self.assertEqual(mod.request_usage_summary(row), "in 75 | out 40 | cached 25 (25%)")
|
||||
|
||||
def test_request_rows_read_usage_from_nested_usage_object(self) -> None:
|
||||
mod = load_module()
|
||||
payload = {
|
||||
"entries": [
|
||||
{
|
||||
"seq": 1,
|
||||
"request_id": "req_nested",
|
||||
"path": "/responses",
|
||||
"usage": {
|
||||
"input_tokens": 120,
|
||||
"output_tokens": 33,
|
||||
"total_tokens": 153,
|
||||
"cached_tokens": 20,
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
rows = mod.normalize_request_rows(payload)
|
||||
self.assertEqual(rows[0]["input_tokens"], 120)
|
||||
self.assertEqual(rows[0]["output_tokens"], 33)
|
||||
self.assertEqual(rows[0]["total_tokens"], 153)
|
||||
self.assertEqual(rows[0]["cached_tokens"], 20)
|
||||
|
||||
def test_format_duration_ms_as_seconds(self) -> None:
|
||||
mod = load_module()
|
||||
self.assertEqual(mod.format_duration_ms_as_seconds(1532), "1.5s")
|
||||
self.assertEqual(mod.format_duration_ms_as_seconds(120000), "120s")
|
||||
|
||||
def test_profile_editor_payload_round_trip(self) -> None:
|
||||
mod = load_module()
|
||||
row = {
|
||||
|
||||
Reference in New Issue
Block a user