@tonbo/cli 0.20.0 → 0.22.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 +164 -12
  2. package/package.json +1 -1
package/dist/bin/tonbo.js CHANGED
@@ -710,6 +710,36 @@ var managementOperations = {
710
710
  "idempotencyKey": false,
711
711
  "cli": "tonbo machine activations"
712
712
  },
713
+ "getMachineMetrics": {
714
+ "method": "GET",
715
+ "path": "/machines/{name}/metrics",
716
+ "pathParams": [
717
+ "name"
718
+ ],
719
+ "query": [
720
+ "series",
721
+ "window",
722
+ "granularity",
723
+ "from",
724
+ "to"
725
+ ],
726
+ "idempotencyKey": false,
727
+ "cli": "tonbo machine metrics"
728
+ },
729
+ "getAccountMachineMetrics": {
730
+ "method": "GET",
731
+ "path": "/metrics/machines",
732
+ "pathParams": [],
733
+ "query": [
734
+ "series",
735
+ "window",
736
+ "granularity",
737
+ "from",
738
+ "to"
739
+ ],
740
+ "idempotencyKey": false,
741
+ "cli": "tonbo metrics machines"
742
+ },
713
743
  "listMachineShapes": {
714
744
  "method": "GET",
715
745
  "path": "/machine-shapes",
@@ -2499,6 +2529,14 @@ var ManagementApi = class {
2499
2529
  query: { limit, cursor }
2500
2530
  });
2501
2531
  }
2532
+ /** A managed Machine's metrics per UTC hour or day. */
2533
+ getMachineMetrics(key, name, query = {}) {
2534
+ return this.call("getMachineMetrics", key, { params: { name }, query });
2535
+ }
2536
+ /** The account's Machines' metrics per UTC hour or day, each and summed. */
2537
+ getAccountMachineMetrics(key, query = {}) {
2538
+ return this.call("getAccountMachineMetrics", key, { query });
2539
+ }
2502
2540
  /** The whole answer: the shapes and the default one. */
2503
2541
  listMachineShapes(key) {
2504
2542
  return this.request("listMachineShapes", key, {});
@@ -5330,6 +5368,78 @@ async function metricsVolumesCommand(deps, options = {}) {
5330
5368
  lines.push(`Not read: ${metrics.unavailable_regions.join(", ")}`);
5331
5369
  print(deps, options.json, metrics, lines.join("\n"));
5332
5370
  }
5371
+ function metricsQuery(options) {
5372
+ return {
5373
+ series: options.series,
5374
+ window: options.window,
5375
+ granularity: options.granularity,
5376
+ from: options.from,
5377
+ to: options.to
5378
+ };
5379
+ }
5380
+ var MACHINE_COLUMNS = {
5381
+ awake_seconds: ["AWAKE", (value) => `${Math.round(value / 60)} min`],
5382
+ wakes: ["WAKES", String],
5383
+ refused_wakes: ["REFUSED", String],
5384
+ wake_ms_p50: ["WAKE P50", (value) => `${(value / 1e3).toFixed(1)} s`],
5385
+ wake_ms_max: ["WAKE MAX", (value) => `${(value / 1e3).toFixed(1)} s`],
5386
+ cpu_cores_avg: ["CPU", (value) => `${value} cores`],
5387
+ memory_mib_avg: ["MEMORY", (value) => bytes(value * 1024 * 1024)],
5388
+ network_rx_bytes: ["NET IN", (value) => bytes(value)],
5389
+ network_tx_bytes: ["NET OUT", (value) => bytes(value)],
5390
+ internet_rx_bytes: ["INTERNET IN", (value) => bytes(value)],
5391
+ internet_tx_bytes: ["INTERNET OUT", (value) => bytes(value)]
5392
+ };
5393
+ function machineMetricsTable(granularity, points) {
5394
+ const series = Object.keys(MACHINE_COLUMNS).filter(
5395
+ (name) => points.some((point) => name in point)
5396
+ );
5397
+ return table(
5398
+ [
5399
+ granularity === "hour" ? "HOUR (UTC)" : "DAY (UTC)",
5400
+ ...series.map((name) => MACHINE_COLUMNS[name][0])
5401
+ ],
5402
+ points.map((point) => [
5403
+ granularity === "hour" ? point.start.slice(0, 16).replace("T", " ") : point.start.slice(0, 10),
5404
+ ...series.map((name) => {
5405
+ const value = point[name];
5406
+ if (value === null || value === void 0)
5407
+ return Date.parse(point.start) > Date.now() ? "" : "-";
5408
+ return MACHINE_COLUMNS[name][1](value);
5409
+ })
5410
+ ])
5411
+ );
5412
+ }
5413
+ async function metricsMachinesCommand(deps, options = {}) {
5414
+ const metrics = await deps.api.getAccountMachineMetrics(
5415
+ await deps.auth.apiKey(),
5416
+ metricsQuery(options)
5417
+ );
5418
+ const lines = [
5419
+ `The account's Machines from ${metrics.from} to ${metrics.to} (UTC)`,
5420
+ machineMetricsTable(metrics.granularity, metrics.total.points)
5421
+ ];
5422
+ if (metrics.resources.length > 0) {
5423
+ const sum = (points, name) => points.reduce((total, point) => total + (point[name] ?? 0), 0);
5424
+ const series = Object.keys(MACHINE_COLUMNS).filter(
5425
+ (name) => metrics.total.points.some((point) => name in point)
5426
+ );
5427
+ lines.push(
5428
+ "",
5429
+ table(
5430
+ ["MACHINE", "REGION", ...series.map((name) => MACHINE_COLUMNS[name][0])],
5431
+ metrics.resources.map((resource) => [
5432
+ resource.name,
5433
+ resource.region,
5434
+ ...series.map((name) => MACHINE_COLUMNS[name][1](sum(resource.points, name)))
5435
+ ])
5436
+ )
5437
+ );
5438
+ }
5439
+ if (metrics.unavailable_regions.length > 0)
5440
+ lines.push(`Not read: ${metrics.unavailable_regions.join(", ")}`);
5441
+ print(deps, options.json, metrics, lines.join("\n"));
5442
+ }
5333
5443
  async function streamActivityCommand(deps, name, options = {}) {
5334
5444
  const activity = await deps.api.getStreamActivity(
5335
5445
  await deps.auth.apiKey(),
@@ -5487,18 +5597,6 @@ function volumeFields(volume) {
5487
5597
  ["Size", `${volume.size_gib} GiB`],
5488
5598
  ["Live bytes", `${bytes(volume.live_bytes)} (billed)`],
5489
5599
  ["Last meter report", volume.last_commit_at ?? "none yet"],
5490
- [
5491
- "Restored to",
5492
- volume.restored_to ? `${volume.restored_to.committed_at} (generation ${volume.restored_to.data_generation})` : "none"
5493
- ],
5494
- [
5495
- "Backed up to",
5496
- volume.backed_up_to ? volume.backed_up_to.committed_at ? `${volume.backed_up_to.committed_at} (generation ${volume.backed_up_to.data_generation}; ${volume.backed_up_to.dirty_bytes} bytes pending copy)` : `empty snapshot (generation ${volume.backed_up_to.data_generation}; ${volume.backed_up_to.dirty_bytes} bytes pending copy)` : "unknown"
5497
- ],
5498
- [
5499
- "Cold GETs",
5500
- volume.cold ? `${volume.cold.demand_gets} demand, ${volume.cold.hydrate_gets} hydration` : "none"
5501
- ],
5502
5600
  ["Durability", VOLUMES_PREVIEW ? VOLUME_PREVIEW_TEXT : VOLUME_DURABILITY_LINK],
5503
5601
  ["Created", volume.created_at]
5504
5602
  ]);
@@ -6390,6 +6488,24 @@ async function machineActivationsCommand(deps, name, options = {}) {
6390
6488
  );
6391
6489
  });
6392
6490
  }
6491
+ async function machineMetricsCommand(deps, name, options = {}) {
6492
+ await machineCommand(async () => {
6493
+ const metrics = await deps.api.getMachineMetrics(
6494
+ await deps.auth.apiKey(),
6495
+ name,
6496
+ metricsQuery(options)
6497
+ );
6498
+ print(
6499
+ deps,
6500
+ options.json,
6501
+ metrics,
6502
+ [
6503
+ `Machine ${metrics.name} in ${metrics.region} from ${metrics.from} to ${metrics.to} (UTC)`,
6504
+ machineMetricsTable(metrics.granularity, metrics.points)
6505
+ ].join("\n")
6506
+ );
6507
+ });
6508
+ }
6393
6509
  function appliesText(name, change) {
6394
6510
  const lines = [];
6395
6511
  if (change.ports !== void 0 || change.sleep_after_seconds !== void 0)
@@ -7247,6 +7363,18 @@ var COMMAND_HELP = {
7247
7363
  arguments: NAME("Machine"),
7248
7364
  defaults: { "--limit": LIMIT_DEFAULT }
7249
7365
  },
7366
+ "tonbo machine metrics": {
7367
+ about: [
7368
+ "Prints the Machine's metrics, one row per UTC hour or day: how long it was awake, its wakes and refused wakes and how long they took (the median and the longest), its average CPU (in cores) and memory while awake, the bytes on its network interface, Tonbo's traffic included, and the bytes it exchanged with the internet. A dash is a step it was never awake in; the current step is still growing.",
7369
+ "Give `--window`, or `--from` and `--to` with `--granularity` (at most 32 days hourly, 92 days daily). `--series` prints only the series it names. It carries no money: `tonbo usage show` says what it cost."
7370
+ ],
7371
+ examples: [
7372
+ "tonbo machine metrics web --window 7d",
7373
+ "tonbo machine metrics web --series cpu_cores_avg,memory_mib_avg"
7374
+ ],
7375
+ arguments: NAME("Machine"),
7376
+ defaults: { "--window": "24h" }
7377
+ },
7250
7378
  "tonbo machine shapes": {
7251
7379
  about: [
7252
7380
  "Lists the shapes a Machine may have, each with its vCPUs, memory, scratch disk, whether it is offered and can suspend, and its list price for an hour at full use, and marks the default."
@@ -7340,6 +7468,14 @@ var COMMAND_HELP = {
7340
7468
  examples: ["tonbo usage series --days 7 --compare"],
7341
7469
  defaults: { "--days": "30" }
7342
7470
  },
7471
+ "tonbo metrics machines": {
7472
+ about: [
7473
+ "Prints the account's Machines per UTC hour or day, summed over every region: how long they were awake, their wakes and refused wakes, and the bytes they exchanged with the internet. Then each Machine's totals over the window, most first by the first series printed. `tonbo machine metrics` prints one Machine's CPU, memory and network too.",
7474
+ "Give `--window`, or `--from` and `--to` with `--granularity` (at most 32 days hourly, 92 days daily)."
7475
+ ],
7476
+ examples: ["tonbo metrics machines --window 30d"],
7477
+ defaults: { "--window": "7d" }
7478
+ },
7343
7479
  "tonbo metrics volumes": {
7344
7480
  about: [
7345
7481
  "Prints what the account's Volumes stored, one line per UTC day of the window: the average bytes their files held that day, compressed, summed over every region, which is what they are billed for. Today, and a day not fully rated yet, is marked `so far`. Then the change between the window's first and last complete days.",
@@ -9253,6 +9389,15 @@ function createProgram(makeDependencies = createDependencies) {
9253
9389
  ).action(
9254
9390
  async (name, options) => machineActivationsCommand(dependencies(), name, options)
9255
9391
  );
9392
+ operationOptions(
9393
+ machine.command("metrics <name>").description("show a Machine's awake time, wakes, CPU, memory and network over time").option("--window <window>", "24h or 7d hourly, 30d or 90d daily; 24h when omitted").option(
9394
+ "--series <series>",
9395
+ "the series to print, comma-separated; every series when omitted"
9396
+ ).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"),
9397
+ "getMachineMetrics"
9398
+ ).action(
9399
+ async (name, options) => machineMetricsCommand(dependencies(), name, options)
9400
+ );
9256
9401
  operationOptions(
9257
9402
  machine.command("shapes").description("list the shapes a Machine may have"),
9258
9403
  "listMachineShapes"
@@ -9320,6 +9465,13 @@ function createProgram(makeDependencies = createDependencies) {
9320
9465
  ).action(
9321
9466
  async (options) => metricsVolumesCommand(dependencies(), options)
9322
9467
  );
9468
+ operationOptions(
9469
+ metrics.command("machines").description("show the account's Machines' awake time, wakes and internet bytes over time").option("--window <window>", "24h or 7d hourly, 30d or 90d daily; 7d when omitted").option(
9470
+ "--series <series>",
9471
+ "the series to print, comma-separated; every series when omitted"
9472
+ ).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"),
9473
+ "getAccountMachineMetrics"
9474
+ ).action(async (options) => metricsMachinesCommand(dependencies(), options));
9323
9475
  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));
9324
9476
  ssh.command("config").description(
9325
9477
  "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.20.0",
3
+ "version": "0.22.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": {