@tonbo/cli 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/tonbo.js +131 -4
  2. package/package.json +1 -1
package/dist/bin/tonbo.js CHANGED
@@ -796,6 +796,20 @@ var managementOperations = {
796
796
  "idempotencyKey": false,
797
797
  "cli": "tonbo metrics streams"
798
798
  },
799
+ "getAccountDatabaseAndStreamMetrics": {
800
+ "method": "GET",
801
+ "path": "/metrics/databases-and-streams",
802
+ "pathParams": [],
803
+ "query": [
804
+ "series",
805
+ "window",
806
+ "granularity",
807
+ "from",
808
+ "to"
809
+ ],
810
+ "idempotencyKey": false,
811
+ "cli": "tonbo metrics databases-and-streams"
812
+ },
799
813
  "listMachineShapes": {
800
814
  "method": "GET",
801
815
  "path": "/machine-shapes",
@@ -1062,6 +1076,22 @@ var managementOperations = {
1062
1076
  "idempotencyKey": false,
1063
1077
  "cli": "tonbo volume delete"
1064
1078
  },
1079
+ "getVolumeNameMetrics": {
1080
+ "method": "GET",
1081
+ "path": "/volumes/{name}/metrics",
1082
+ "pathParams": [
1083
+ "name"
1084
+ ],
1085
+ "query": [
1086
+ "series",
1087
+ "window",
1088
+ "granularity",
1089
+ "from",
1090
+ "to"
1091
+ ],
1092
+ "idempotencyKey": false,
1093
+ "cli": "tonbo volume metrics"
1094
+ },
1065
1095
  "listStreams": {
1066
1096
  "method": "GET",
1067
1097
  "path": "/streams",
@@ -2445,6 +2475,10 @@ var ManagementApi = class {
2445
2475
  getAccountStreamMetrics(key, query = {}) {
2446
2476
  return this.call("getAccountStreamMetrics", key, { query });
2447
2477
  }
2478
+ /** The account's databases and streams together, with one server latency for both. */
2479
+ getAccountDatabaseAndStreamMetrics(key, query = {}) {
2480
+ return this.call("getAccountDatabaseAndStreamMetrics", key, { query });
2481
+ }
2448
2482
  /** The whole usage page: its rows, `other`, prices and checkpoint. */
2449
2483
  listStreamUsage(key, page, query = {}) {
2450
2484
  return this.page("listStreamUsage", key, page, query);
@@ -2459,6 +2493,10 @@ var ManagementApi = class {
2459
2493
  getVolumeMetrics(key, query = {}) {
2460
2494
  return this.call("getVolumeMetrics", key, { query });
2461
2495
  }
2496
+ /** One Volume's guest I/O and stored bytes per UTC hour or day. */
2497
+ getVolumeNameMetrics(key, name, query = {}) {
2498
+ return this.call("getVolumeNameMetrics", key, { params: { name }, query });
2499
+ }
2462
2500
  /** The account's writes of yesterday and today, hourly, per lane. */
2463
2501
  getUsageToday(key) {
2464
2502
  return this.call("getUsageToday", key);
@@ -5394,7 +5432,7 @@ function printAccountRegistryMetrics(deps, noun, metrics, options) {
5394
5432
  "",
5395
5433
  table(
5396
5434
  [
5397
- noun === "databases" ? "DATABASE" : "STREAM",
5435
+ noun === "databases" ? "DATABASE" : noun === "streams" ? "STREAM" : "NAME",
5398
5436
  "REGION",
5399
5437
  ...sized ? ["STORED"] : [],
5400
5438
  ...counted.map((name) => REGISTRY_COLUMNS[name][0])
@@ -5402,7 +5440,7 @@ function printAccountRegistryMetrics(deps, noun, metrics, options) {
5402
5440
  metrics.resources.map((resource) => {
5403
5441
  const stored = [...resource.points].reverse().find((point) => point.stored_bytes !== null && point.stored_bytes !== void 0);
5404
5442
  return [
5405
- resource.name,
5443
+ resource.kind ? `${resource.name} (${resource.kind})` : resource.name,
5406
5444
  resource.region,
5407
5445
  ...sized ? [stored ? bytes(stored.stored_bytes ?? 0) : "-"] : [],
5408
5446
  ...counted.map(
@@ -5434,6 +5472,13 @@ async function metricsStreamsCommand(deps, options = {}) {
5434
5472
  );
5435
5473
  printAccountRegistryMetrics(deps, "streams", metrics, options);
5436
5474
  }
5475
+ async function metricsDatabasesAndStreamsCommand(deps, options = {}) {
5476
+ const metrics = await deps.api.getAccountDatabaseAndStreamMetrics(
5477
+ await deps.auth.apiKey(),
5478
+ metricsQuery(options)
5479
+ );
5480
+ printAccountRegistryMetrics(deps, "databases and streams", metrics, options);
5481
+ }
5437
5482
  function streamFields(stream) {
5438
5483
  return fields([
5439
5484
  ["Name", stream.name],
@@ -5609,14 +5654,14 @@ async function metricsVolumesCommand(deps, options = {}) {
5609
5654
  ["DAY", "STORED", ""],
5610
5655
  points.map((point) => [
5611
5656
  point.start.slice(0, 10),
5612
- bytes(point.stored_bytes),
5657
+ bytes(point.stored_bytes ?? 0),
5613
5658
  point.partial ? "so far" : ""
5614
5659
  ])
5615
5660
  )
5616
5661
  ];
5617
5662
  const complete = points.filter((point) => !point.partial);
5618
5663
  if (complete.length > 1) {
5619
- const change = complete[complete.length - 1].stored_bytes - complete[0].stored_bytes;
5664
+ const change = (complete[complete.length - 1].stored_bytes ?? 0) - (complete[0].stored_bytes ?? 0);
5620
5665
  lines.push(
5621
5666
  `Change over its complete days: ${change < 0 ? "-" : "+"}${bytes(Math.abs(change))}`
5622
5667
  );
@@ -6009,6 +6054,44 @@ async function resolveVolumeBinding(deps, key, spec) {
6009
6054
  region: volume.region
6010
6055
  };
6011
6056
  }
6057
+ var VOLUME_COLUMNS = {
6058
+ stored_bytes: ["STORED", (value) => bytes(value)],
6059
+ read_bytes: ["READ", (value) => bytes(value)],
6060
+ write_bytes: ["WRITTEN", (value) => bytes(value)],
6061
+ read_ops: ["READ OPS", String],
6062
+ write_ops: ["WRITE OPS", String],
6063
+ trim_ops: ["TRIMS", String]
6064
+ };
6065
+ async function volumeMetricsCommand(deps, name, options = {}) {
6066
+ const metrics = await deps.api.getVolumeNameMetrics(
6067
+ await deps.auth.apiKey(),
6068
+ name,
6069
+ metricsQuery(options)
6070
+ );
6071
+ const series = Object.keys(VOLUME_COLUMNS).filter(
6072
+ (key) => metrics.points.some((point) => key in point)
6073
+ );
6074
+ const hourly = metrics.granularity === "hour";
6075
+ print(
6076
+ deps,
6077
+ options.json,
6078
+ metrics,
6079
+ [
6080
+ `Volume ${metrics.name} in ${metrics.region} from ${metrics.from} to ${metrics.to} (UTC)`,
6081
+ table(
6082
+ [hourly ? "HOUR (UTC)" : "DAY (UTC)", ...series.map((key) => VOLUME_COLUMNS[key][0]), ""],
6083
+ metrics.points.map((point) => [
6084
+ hourly ? point.start.slice(0, 16).replace("T", " ") : point.start.slice(0, 10),
6085
+ ...series.map((key) => {
6086
+ const value = point[key];
6087
+ return value === null || value === void 0 ? "-" : VOLUME_COLUMNS[key][1](value);
6088
+ }),
6089
+ point.partial ? "so far" : ""
6090
+ ])
6091
+ )
6092
+ ].join("\n")
6093
+ );
6094
+ }
6012
6095
 
6013
6096
  // src/machine.ts
6014
6097
  var MACHINE_ERROR_HINTS = {
@@ -7866,6 +7949,18 @@ var COMMAND_HELP = {
7866
7949
  examples: ["tonbo volume show data"],
7867
7950
  arguments: NAME("Volume")
7868
7951
  },
7952
+ "tonbo volume metrics": {
7953
+ about: [
7954
+ "Prints the Volume's metrics, one row per UTC hour or day: the bytes and requests its Machine read, wrote and trimmed, as the Volume served them, and what its files held at the step's last sample, compressed. The I/O updates about every 30 seconds while the Volume is attached; the current step is marked `so far`.",
7955
+ "Give `--window` (24h hourly; 7d, 30d or 90d daily, 7d also hourly with `--granularity hour`), or `--from` and `--to` with `--granularity` (at most 8 days hourly, 92 days daily). `--series` prints only the series it names. It carries no money: `tonbo usage show` says what the Volume cost."
7956
+ ],
7957
+ examples: [
7958
+ "tonbo volume metrics data --window 24h",
7959
+ "tonbo volume metrics data --series read_bytes,write_bytes --window 7d"
7960
+ ],
7961
+ arguments: NAME("Volume"),
7962
+ defaults: { "--window": "30d" }
7963
+ },
7869
7964
  "tonbo volume move": {
7870
7965
  about: [
7871
7966
  "Moves the Volume to the Machine `--to`, taking it from the Machine that has it now in one write: the Volume is unbound there (that Machine's ETag changes and it boots without it) and bound to `--to`, which may be awake and gets it at its next boot. The CLI reads both Machines' ETags first, so a Machine changed since is refused, never overwritten. Without `--path` the Volume keeps the path it has now, or stays a whole disk; a whole disk moves only to a Machine on the same image.",
@@ -7913,6 +8008,16 @@ var COMMAND_HELP = {
7913
8008
  examples: ["tonbo metrics databases --window 30d --series stored_bytes,write_units"],
7914
8009
  defaults: { "--window": "7d" }
7915
8010
  },
8011
+ "tonbo metrics databases-and-streams": {
8012
+ about: [
8013
+ "Prints the account's databases and streams together, per UTC hour or day, summed over every region, with the same series as `tonbo database metrics`: the total's server latency comes from every database's and stream's merged histograms, one figure for both kinds, never an average of percentiles. Server latency is measured at Tonbo's servers, from request to response headers; your network adds to this. Then each one's row over the window, named with its kind.",
8014
+ "Give `--window`, or `--from` and `--to` with `--granularity` (at most 31 days hourly, 92 days daily)."
8015
+ ],
8016
+ examples: [
8017
+ "tonbo metrics databases-and-streams --window 30d --series server_read_latency_p50_ms"
8018
+ ],
8019
+ defaults: { "--window": "7d" }
8020
+ },
7916
8021
  "tonbo metrics streams": {
7917
8022
  about: [
7918
8023
  "Prints the account's streams per UTC hour or day, summed over every region, with the same series as `tonbo stream metrics`: the total's server latency comes from every stream's merged histograms, never an average of percentiles. Server latency is measured at Tonbo's servers, from request to response headers; your network adds to this. Then each stream's row over the window: its stored size at the last measured step and its counts summed.",
@@ -9900,6 +10005,17 @@ function createProgram(makeDependencies = createDependencies) {
9900
10005
  ).action(
9901
10006
  async (name, options) => volumeShowCommand(dependencies(), name, options)
9902
10007
  );
10008
+ operationOptions(
10009
+ volume.command("metrics <name>").description(
10010
+ "show what a Volume's Machine read, wrote and trimmed, and what it held, over time"
10011
+ ).option("--window <window>", "24h hourly, or 7d, 30d or 90d daily; 30d when omitted").option(
10012
+ "--series <series>",
10013
+ "the series to print, comma-separated; every series when omitted"
10014
+ ).option("--granularity <step>", "hour or day: with --from and --to, or with --window 7d").option("--from <time>", "with --to, the window's start, on a UTC hour or day").option("--to <time>", "with --from, the window's end, exclusive"),
10015
+ "getVolumeNameMetrics"
10016
+ ).action(
10017
+ async (name, options) => volumeMetricsCommand(dependencies(), name, options)
10018
+ );
9903
10019
  operationOptions(
9904
10020
  volume.command("move <name>").description(
9905
10021
  "move a Volume to another Machine of its region, taking it from the asleep Machine that has it in the same write"
@@ -9968,6 +10084,17 @@ function createProgram(makeDependencies = createDependencies) {
9968
10084
  ).option("--granularity <step>", "with --from and --to, hour or day").option("--from <time>", "with --to, the window's start, on a UTC hour or day").option("--to <time>", "with --from, the window's end, exclusive"),
9969
10085
  "getAccountStreamMetrics"
9970
10086
  ).action(async (options) => metricsStreamsCommand(dependencies(), options));
10087
+ operationOptions(
10088
+ metrics.command("databases-and-streams").description(
10089
+ "show the account's databases and streams together, with one server latency for both"
10090
+ ).option("--window <window>", "24h or 7d hourly, 30d or 90d daily; 7d when omitted").option(
10091
+ "--series <series>",
10092
+ "the series to print, comma-separated; every series when omitted"
10093
+ ).option("--granularity <step>", "with --from and --to, hour or day").option("--from <time>", "with --to, the window's start, on a UTC hour or day").option("--to <time>", "with --from, the window's end, exclusive"),
10094
+ "getAccountDatabaseAndStreamMetrics"
10095
+ ).action(
10096
+ async (options) => metricsDatabasesAndStreamsCommand(dependencies(), options)
10097
+ );
9971
10098
  const ssh = program.command("ssh").description("open a terminal on a Machine, or run a command there, over SSH").argument("<machine>", "the Machine's name").argument("[command...]", "a command to run instead of a shell, after --").action(async (name, command) => sshCommand(dependencies(), name, command));
9972
10099
  ssh.command("config").description(
9973
10100
  "print the ssh_config block that lets ssh, scp, sftp and Remote-SSH reach every Machine"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonbo/cli",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "The Tonbo CLI. Compute, disks, databases and streams for AI agents: your agent picks what it needs, sets it up, and knows what it costs, from the command line.",
5
5
  "homepage": "https://tonbo.dev",
6
6
  "bugs": {