feat: distinguish full-day traffic rollups
This commit is contained in:
@@ -69,9 +69,9 @@ Workspace 当前提供以下有界 projection:
|
||||
- `accounts`:账号摘要、quota window、当日用量和 provider/group/status 字段。
|
||||
- `status`:channel monitor 摘要。
|
||||
- `sources`:Pricing Monitor 已有的脱敏 source 余额与健康状态。
|
||||
- `traffic.requests`:仅 server6 的最近有界请求样本。
|
||||
- `traffic.requests`:仅 server6 的最新有界请求样本。
|
||||
- `traffic.errors`:server6/server4 的有界逐条错误事件,保留节点、时间、key/account、结构化错误类别和状态链路,不做错误分组或 per-row count。
|
||||
- `traffic.keys`:仅 server6 按 `Asia/Shanghai` 自然日聚合的 key 使用量,payload 同时携带统计日期和时区。
|
||||
- `traffic.keys`:仅 server6 按 `Asia/Shanghai` 自然日完整聚合的 key 使用量;事件样本上限不会截断其 request/token/cost totals,payload 同时携带统计日期和时区。
|
||||
|
||||
投影允许展示 account/key 名称、稳定 ID、model、instance 和运维状态,但不包含 API
|
||||
key 原文、access/refresh token、cookie、密码、数据库凭据或请求/响应正文。ID 始终按
|
||||
@@ -117,10 +117,10 @@ Accounts 只在 canonical 名称 `{family}-quota-{source}` 或
|
||||
provider、URL、display name 或模糊文本推断;source 为 error/stale 时保留名称和状态,
|
||||
但不伪造 CNY 数值。
|
||||
|
||||
Requests 展示 server6 的 key、account、model、token bucket、actual cost、first-token latency、
|
||||
Requests 展示 server6 的最新有界明细:key、account、model、token bucket、actual cost、first-token latency、
|
||||
duration 和 decode throughput。Errors 展示 server6/server4 的逐条 instance、事件时间、status path、key、
|
||||
account、model、phase/type/owner,不显示聚合次数。Key usage、Requests 和对应 account 当天用量只采用
|
||||
server6;Errors 保留双节点。所有 workspace Traffic 都使用服务器声明的 `Asia/Shanghai` 自然日,不依赖客户端本地日期或滚动
|
||||
account、model、phase/type/owner,不显示聚合次数。Key usage 和对应 Traffic Account usage 是 server6
|
||||
完整当天 rollup;Errors 与 Requests 仍是事件样本。所有 workspace Traffic 都使用服务器声明的 `Asia/Shanghai` 自然日,不依赖客户端本地日期或滚动
|
||||
24 小时窗口;每个 Traffic projection 显式携带日期、时区和 `[00:00, 次日 00:00)` 边界。
|
||||
|
||||
## Legacy Direct
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "shusub2"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
description = "Aggregated operations TUI for Sub2API"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
+16
-7
@@ -26,7 +26,7 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
|
||||
APP_NAME = "shusub2"
|
||||
FALLBACK_VERSION = "0.3.2"
|
||||
FALLBACK_VERSION = "0.3.3"
|
||||
DEFAULT_WORKSPACE_URL = "https://price.tailbeb9ad.ts.net/api/ui-data?view=workspace"
|
||||
DEFAULT_WORKSPACE_URL_CONFIG_FILE = "~/.config/shusub2/workspace-url"
|
||||
DEFAULT_API_URL = "http://127.0.0.1:18318/api/tui/accounts"
|
||||
@@ -607,6 +607,8 @@ def fetch_workspace_payload(workspace_url: str, timeout: int) -> dict[str, Any]:
|
||||
raise RuntimeError("Pricing Monitor returned an invalid workspace projection")
|
||||
if workspace_traffic_period(traffic) is None:
|
||||
raise RuntimeError("Pricing Monitor returned an invalid workspace traffic period")
|
||||
if traffic.get("usage_rollups_full_day") is not True:
|
||||
raise RuntimeError("Pricing Monitor returned incomplete workspace traffic usage")
|
||||
channel_monitors = payload["status"].get("channel_monitors")
|
||||
bounded_fields = (
|
||||
("accounts", payload["accounts"].get("accounts"), MAX_WORKSPACE_ACCOUNTS),
|
||||
@@ -697,6 +699,7 @@ def workspace_keys_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"period_kind": period.get("period_kind", ""),
|
||||
"date": period.get("date", ""),
|
||||
"timezone": period.get("timezone", ""),
|
||||
"usage_rollups_full_day": traffic.get("usage_rollups_full_day") is True,
|
||||
"workspace": True,
|
||||
}
|
||||
|
||||
@@ -1983,7 +1986,7 @@ def run_textual(
|
||||
)
|
||||
self.configure_table(
|
||||
self.query_one("#keys", DataTable),
|
||||
("Node", "KEY", "Today", "Tokens", "Req"),
|
||||
("Node", "KEY", "Day total" if not legacy_direct else "Today", "Tokens", "Req"),
|
||||
)
|
||||
self.configure_table(
|
||||
self.query_one("#logs", DataTable),
|
||||
@@ -2251,11 +2254,14 @@ def run_textual(
|
||||
else:
|
||||
summary.append(pricing_summary(self.pricing_payload), style="bright_blue")
|
||||
summary.append(" | ", style="dim")
|
||||
summary.append(f"keys {len(self.key_rows)} ({key_summary.removeprefix('keys ')})", style="yellow")
|
||||
key_scope = "keys day total" if not legacy_direct else "keys"
|
||||
request_scope = "request sample" if not legacy_direct else "requests"
|
||||
error_scope = "error sample" if not legacy_direct else "errors"
|
||||
summary.append(f"{key_scope} {len(self.key_rows)} ({key_summary.removeprefix('keys ')})", style="yellow")
|
||||
summary.append(" | ", style="dim")
|
||||
summary.append(f"requests {len(self.log_rows)}/{logs_total}", style="magenta")
|
||||
summary.append(f"{request_scope} {len(self.log_rows)}/{logs_total}", style="magenta")
|
||||
summary.append(" | ", style="dim")
|
||||
summary.append(f"errors {len(self.error_rows)}/{self.errors_total()}", style="red")
|
||||
summary.append(f"{error_scope} {len(self.error_rows)}/{self.errors_total()}", style="red")
|
||||
self.query_one("#summary", Static).update(summary)
|
||||
|
||||
status_bits = [version_message]
|
||||
@@ -2328,8 +2334,11 @@ def run_textual(
|
||||
if error:
|
||||
detail += f" | {error}"
|
||||
elif table_id == "keys":
|
||||
period_label = usage_period_label(self.keys_payload)
|
||||
if self.keys_payload.get("usage_rollups_full_day") is True:
|
||||
period_label += " full-day"
|
||||
detail = (
|
||||
f"{row['node']} | {row['name']} | {usage_period_label(self.keys_payload)} {format_cost(row['cost'])} | "
|
||||
f"{row['node']} | {row['name']} | {period_label} {format_cost(row['cost'])} | "
|
||||
f"{format_count(row['tokens'])} tokens | {format_count(row['requests'])} req"
|
||||
)
|
||||
elif table_id == "logs":
|
||||
@@ -2398,7 +2407,7 @@ def run_textual(
|
||||
keys_table = self.query_one("#keys", DataTable)
|
||||
keys_table.cursor_type = "row"
|
||||
keys_table.zebra_stripes = True
|
||||
keys_table.add_columns("Node", "Key", "Today", "Tokens", "Req")
|
||||
keys_table.add_columns("Node", "Key", "Day total" if not legacy_direct else "Today", "Tokens", "Req")
|
||||
self.refresh_data(refresh=legacy_direct)
|
||||
self.set_interval(refresh_seconds, self.refresh_data)
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ def workspace_payload_fixture() -> dict[str, object]:
|
||||
"ends_at": local_end.isoformat(),
|
||||
"sample_limit": 1000,
|
||||
"sample_limited": False,
|
||||
"usage_rollups_full_day": True,
|
||||
"partial": False,
|
||||
"instances": [
|
||||
{"instance": "server6", "ok": True, "error_total": 4},
|
||||
@@ -810,6 +811,7 @@ class WorkspaceTests(unittest.TestCase):
|
||||
self.assertEqual(keys["period_kind"], "calendar_day")
|
||||
self.assertEqual(keys["date"], payload["traffic"]["date"])
|
||||
self.assertEqual(keys["timezone"], "Asia/Shanghai")
|
||||
self.assertTrue(keys["usage_rollups_full_day"])
|
||||
self.assertIn(f"today {keys['date']} (Asia/Shanghai)", mod.usage_period_label(keys))
|
||||
self.assertEqual(errors["time_range"], f"today {keys['date']} (Asia/Shanghai)")
|
||||
self.assertEqual({item["_node"] for item in errors["items"]}, {"server6", "server4"})
|
||||
@@ -876,6 +878,12 @@ class WorkspaceTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(RuntimeError, "invalid workspace traffic period"):
|
||||
mod.fetch_workspace_payload("https://workspace.example.test/data", 3)
|
||||
|
||||
incomplete_usage = workspace_payload_fixture()
|
||||
incomplete_usage["traffic"]["usage_rollups_full_day"] = False
|
||||
with mock.patch.object(mod, "fetch_payload", return_value=incomplete_usage):
|
||||
with self.assertRaisesRegex(RuntimeError, "incomplete workspace traffic usage"):
|
||||
mod.fetch_workspace_payload("https://workspace.example.test/data", 3)
|
||||
|
||||
noncanonical_request = workspace_payload_fixture()
|
||||
noncanonical_request["traffic"]["requests"][0]["instance"] = "server4"
|
||||
with mock.patch.object(mod, "fetch_payload", return_value=noncanonical_request):
|
||||
|
||||
Reference in New Issue
Block a user