diff --git a/packages/js-sdk/src/api/schema.gen.ts b/packages/js-sdk/src/api/schema.gen.ts index 3cc9a39e3..52bcee721 100644 --- a/packages/js-sdk/src/api/schema.gen.ts +++ b/packages/js-sdk/src/api/schema.gen.ts @@ -98,6 +98,27 @@ export interface paths { }; }; }; + "/sandboxes/{sandboxID}/metrics": { + /** @description Get sandbox metrics */ + get: { + parameters: { + path: { + sandboxID: components["parameters"]["sandboxID"]; + }; + }; + responses: { + /** @description Successfully returned the sandbox metrics */ + 200: { + content: { + "application/json": components["schemas"]["SandboxMetric"][]; + }; + }; + 401: components["responses"]["401"]; + 404: components["responses"]["404"]; + 500: components["responses"]["500"]; + }; + }; + }; "/sandboxes/{sandboxID}/refreshes": { /** @description Refresh the sandbox extending its time to live */ post: { @@ -407,6 +428,29 @@ export interface components { SandboxMetadata: { [key: string]: string; }; + /** @description Metric entry with timestamp and line */ + SandboxMetric: { + /** + * Format: float + * @description CPU usage percentage + */ + cpuPct: number; + /** + * Format: int64 + * @description Total memory in MiB + */ + memMiBTotal: number; + /** + * Format: int64 + * @description Memory used in MiB + */ + memMiBUsed: number; + /** + * Format: date-time + * @description Timestamp of the log entry + */ + timestamp: string; + }; Team: { /** @description API key for the team */ apiKey: string; diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 37edbcae6..21243d943 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -10,6 +10,7 @@ import { createRpcLogger } from '../logs' import { Commands, Pty } from './commands' import { Filesystem } from './filesystem' import { SandboxApi } from './sandboxApi' +import { components } from '../api/schema.gen' /** * Options for creating a new Sandbox. @@ -278,6 +279,23 @@ export class Sandbox extends SandboxApi { return true } + /** + * Get the metrics of the sandbox. + * + * @param timeoutMs timeout in **milliseconds**. + * @param opts connection options. + * + * @returns metrics of the sandbox. + */ + async getMetrics( + opts?: Pick + ): Promise { + return await Sandbox.getMetrics(this.sandboxId, { + ...this.connectionConfig, + ...opts, + }) + } + /** * Set the timeout of the sandbox. * After the timeout expires the sandbox will be automatically killed. diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 1cf997f4d..6a12aee7a 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -114,6 +114,44 @@ export class SandboxApi { ) } + /** + * Get the metrics of the sandbox. + * + * @param sandboxId sandbox ID. + * @param timeoutMs timeout in **milliseconds**. + * @param opts connection options. + * + * @returns metrics of the sandbox. + */ + static async getMetrics( + sandboxId: string, + opts?: SandboxApiOpts + ): Promise { + const config = new ConnectionConfig(opts) + const client = new ApiClient(config) + + const res = await client.api.GET('/sandboxes/{sandboxID}/metrics', { + params: { + path: { + sandboxID: sandboxId, + }, + }, + signal: config.getSignal(opts?.requestTimeoutMs), + }) + + const err = handleApiError(res) + if (err) { + throw err + } + + return ( + res.data?.map((metric: components['schemas']['SandboxMetric']) => ({ + ...metric, + timestamp: new Date(metric.timestamp).toISOString(), + })) ?? [] + ) + } + /** * Set the timeout of the specified sandbox. * After the timeout expires the sandbox will be automatically killed. diff --git a/packages/js-sdk/tests/sandbox/metrics.test.ts b/packages/js-sdk/tests/sandbox/metrics.test.ts new file mode 100644 index 000000000..9aea7e81e --- /dev/null +++ b/packages/js-sdk/tests/sandbox/metrics.test.ts @@ -0,0 +1,12 @@ +import { assert } from 'vitest' + +import { isDebug, sandboxTest, wait } from '../setup.js' + +sandboxTest('get sandbox metrics', async ({ sandbox }) => { + const metrics = await sandbox.getMetrics() + + assert.isAtLeast(metrics.length, 1) + assert.isAtLeast(metrics[0]?.cpuPct, 0) + assert.isAtLeast(metrics[0]?.memMiBTotal, 0) + assert.isAtLeast(metrics[0]?.memMiBUsed, 0) +}) diff --git a/spec/openapi.yml b/spec/openapi.yml index 17a1db854..4cd90ab6d 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -335,6 +335,31 @@ components: - ready - error + SandboxMetric: + description: Metric entry with timestamp and line + required: + - timestamp + - cpuPct + - memMiBUsed + - memMiBTotal + properties: + timestamp: + type: string + format: date-time + description: Timestamp of the log entry + cpuPct: + type: number + format: float + description: CPU usage percentage + memMiBUsed: + type: integer + format: int64 + description: Memory used in MiB + memMiBTotal: + type: integer + format: int64 + description: Total memory in MiB + Error: required: - code @@ -516,6 +541,32 @@ paths: $ref: "#/components/responses/404" "500": $ref: "#/components/responses/500" + + /sandboxes/{sandboxID}/metrics: + get: + description: Get sandbox metrics + tags: [sandboxes] + security: + - ApiKeyAuth: [] + parameters: + - $ref: "#/components/parameters/sandboxID" + responses: + "200": + description: Successfully returned the sandbox metrics + content: + application/json: + schema: + type: array + items: + type: object + $ref: "#/components/schemas/SandboxMetric" + "404": + $ref: "#/components/responses/404" + "401": + $ref: "#/components/responses/401" + "500": + $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/refreshes: post: