Skip to content

Commit 35bef0f

Browse files
committed
feat(usage): surface overages, time window, chain/network, and request counts
- 'usage summary' gains overages plus start_time/end_time rows so the numbers carry their window; unix seconds render as RFC-3339 via a new ParsedTime::from_unix, matching what --from/--to accept. - 'usage by-method' gains CHAIN and NETWORK columns (a method name alone is ambiguous on multi-chain accounts) and renders ARCHIVE with the standard boolean cell. - 'usage by-tag' gains the REQUESTS count.
1 parent 5a1a095 commit 35bef0f

6 files changed

Lines changed: 110 additions & 4 deletions

src/commands/usage.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::Serialize;
77

88
use crate::context::Ctx;
99
use crate::errors::CliError;
10-
use crate::output::{new_table, opt_cell, set_header_bold, write_table, Render};
10+
use crate::output::{bool_cell, new_table, opt_cell, set_header_bold, write_table, Render};
1111
use crate::retry::retrying;
1212
use crate::time_arg;
1313

@@ -128,6 +128,16 @@ impl Render for UsageSummaryView {
128128
opt_cell(&data.credits_remaining),
129129
]);
130130
t.add_row(vec![Cell::new("limit"), opt_cell(&data.limit)]);
131+
t.add_row(vec![Cell::new("overages"), opt_cell(&data.overages)]);
132+
// The API reports the window as unix seconds; show RFC-3339 like the
133+
// --from/--to flags accept. Out-of-range values fall back to the raw
134+
// number rather than erroring a read-only command.
135+
let time_cell = |ts: i64| match time_arg::ParsedTime::from_unix(ts) {
136+
Some(t) => Cell::new(t.to_rfc3339()),
137+
None => Cell::new(ts),
138+
};
139+
t.add_row(vec![Cell::new("start_time"), time_cell(data.start_time)]);
140+
t.add_row(vec![Cell::new("end_time"), time_cell(data.end_time)]);
131141
write_table(w, &t)
132142
}
133143
}
@@ -179,12 +189,18 @@ impl Render for UsageByMethodView {
179189
}
180190
};
181191
let mut t = new_table(ctx);
182-
set_header_bold(&mut t, ctx, vec!["METHOD", "CREDITS", "ARCHIVE"]);
192+
set_header_bold(
193+
&mut t,
194+
ctx,
195+
vec!["METHOD", "CHAIN", "NETWORK", "CREDITS", "ARCHIVE"],
196+
);
183197
for m in &data.methods {
184198
t.add_row(vec![
185199
Cell::new(&m.method_name),
200+
opt_cell(&m.chain),
201+
opt_cell(&m.network),
186202
Cell::new(m.credits_used),
187-
opt_cell(&m.archive),
203+
bool_cell(m.archive),
188204
]);
189205
}
190206
write_table(w, &t)
@@ -233,12 +249,13 @@ impl Render for UsageByTagView {
233249
}
234250
};
235251
let mut t = new_table(ctx);
236-
set_header_bold(&mut t, ctx, vec!["TAG_ID", "LABEL", "CREDITS"]);
252+
set_header_bold(&mut t, ctx, vec!["TAG_ID", "LABEL", "CREDITS", "REQUESTS"]);
237253
for tg in &data.tags {
238254
t.add_row(vec![
239255
opt_cell(&tg.tag_id),
240256
Cell::new(&tg.label),
241257
Cell::new(tg.credits_used),
258+
Cell::new(tg.requests),
242259
]);
243260
}
244261
write_table(w, &t)

src/time_arg.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ impl ParsedTime {
2828
self.0.unix_timestamp()
2929
}
3030

31+
/// Builds from unix seconds; `None` if out of `OffsetDateTime` range.
32+
pub fn from_unix(ts: i64) -> Option<Self> {
33+
OffsetDateTime::from_unix_timestamp(ts).ok().map(Self)
34+
}
35+
3136
pub fn to_rfc3339(self) -> String {
3237
self.0
3338
.format(&Rfc3339)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: tests/table_snapshots.rs
3+
expression: out
4+
---
5+
METHOD CHAIN NETWORK CREDITS ARCHIVE
6+
eth_call eth mainnet 900
7+
getBlockHeight solana mainnet 300
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
source: tests/table_snapshots.rs
3+
expression: out
4+
---
5+
TAG_ID LABEL CREDITS REQUESTS
6+
1 prod 1000 420
7+
untagged 200 80
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
source: tests/table_snapshots.rs
3+
expression: out
4+
---
5+
FIELD VALUE
6+
credits_used 1200
7+
credits_remaining 8800
8+
limit 10000
9+
overages 0
10+
start_time 2026-01-01T00:00:00Z
11+
end_time 2026-02-01T00:00:00Z

tests/table_snapshots.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,65 @@ async fn billing_payments_table_includes_status_and_marketplace() {
288288
insta::assert_snapshot!(out);
289289
}
290290

291+
#[tokio::test]
292+
async fn usage_summary_table_shows_overages_and_window() {
293+
let body = serde_json::json!({
294+
"data": {
295+
"credits_used": 1200,
296+
"credits_remaining": 8800,
297+
"limit": 10000,
298+
"overages": 0,
299+
"start_time": 1767225600,
300+
"end_time": 1769904000
301+
},
302+
"error": null
303+
});
304+
let out = table_stdout("/v0/usage/rpc", body, &["usage", "summary"]).await;
305+
insta::assert_snapshot!(out);
306+
}
307+
308+
#[tokio::test]
309+
async fn usage_by_method_table_includes_chain_and_network() {
310+
let body = serde_json::json!({
311+
"data": {
312+
"methods": [
313+
{
314+
"method_name": "eth_call",
315+
"credits_used": 900,
316+
"archive": false,
317+
"network": "mainnet",
318+
"chain": "eth"
319+
},
320+
{
321+
"method_name": "getBlockHeight",
322+
"credits_used": 300,
323+
"archive": null,
324+
"network": "mainnet",
325+
"chain": "solana"
326+
}
327+
]
328+
},
329+
"error": null
330+
});
331+
let out = table_stdout("/v0/usage/rpc/by-method", body, &["usage", "by-method"]).await;
332+
insta::assert_snapshot!(out);
333+
}
334+
335+
#[tokio::test]
336+
async fn usage_by_tag_table_includes_requests() {
337+
let body = serde_json::json!({
338+
"data": {
339+
"tags": [
340+
{ "tag_id": 1, "label": "prod", "credits_used": 1000, "requests": 420 },
341+
{ "tag_id": null, "label": "untagged", "credits_used": 200, "requests": 80 }
342+
]
343+
},
344+
"error": null
345+
});
346+
let out = table_stdout("/v0/usage/rpc/by-tag", body, &["usage", "by-tag"]).await;
347+
insta::assert_snapshot!(out);
348+
}
349+
291350
#[tokio::test]
292351
async fn endpoint_show_minimal_table_omits_security_and_rate_limit_rows() {
293352
let body = serde_json::json!({

0 commit comments

Comments
 (0)