Skip to content

Commit

Permalink
Add metrics methods to SDKs (#543)
Browse files Browse the repository at this point in the history
### Description

Consolidate the branches from #523,
#524 &
#525

This relates to
[E2B-1311](https://linear.app/e2b/issue/E2B-1311/add-metrics-method-to-sandbox-to-sdks-that-returns-captured-metrics)

### Test
`[js-sdk] pnpm test metrics`
`[python-sdk]` poetry run pytest -n 4 --verbose -x -k "test_metrics"`

### Usage 

#### js-sdk
```js
import { Sandbox } from '@e2b/code-interpreter'

const sbx = await Sandbox.create()
console.log('Sandbox created', sbx.sandboxId)

const metrics = await sbx.getMetrics() 
console.log('Sandbox metrics:', metrics)

/*
[
  {
    cpuCount: 2,
    cpuPct: 50.05,
    memTotalMiB: 484,
    memUsedMiB: 37,
    timestamp: '2025-01-23T23:44:12.222Z'
  },
  {
    cpuCount: 2,
    cpuPct: 4.5,
    memTotalMiB: 484,
    memUsedMiB: 37,
    timestamp: '2025-01-23T23:44:13.220Z'
  }
]
*/
```

#### python-sdk
```py
from e2b_code_interpreter import Sandbox

sbx = Sandbox()
print('Sandbox created', sbx.sandbox_id)

metrics = sbx.get_metrics() 
print('Sandbox metrics', metrics) 


# [
#     SandboxMetrics(timestamp=datetime.datetime(2025, 1, 23, 23, 58, 42, 84050, tzinfo=tzutc()), cpu_pct=50.07, cpu_count=2, mem_used_mib=37, mem_total_mib=484), 
#     SandboxMetrics(timestamp=datetime.datetime(2025, 1, 23, 23, 58, 44, 84845, tzinfo=tzutc()), cpu_pct=4.75, cpu_count=2, mem_used_mib=38, mem_total_mib=484),
# ]
```

#### cli
```sh
jonas@mac cli % pnpm build
jonas@mac cli % E2B_DOMAIN=goulash.dev ./dist/index.js sbx sp
jonas@mac cli % E2B_DOMAIN=goulash.dev ./dist/index.js sbx metrics {sandbox_id}

Metrics for sandbox {sandbox_id}

[2025-01-23 00:58:58.829Z]  { cpuCount: 2, cpuPct: 50.21, logger: '', memTotalMiB: 484, memUsedMiB: 38, timestamp: '2025-01-23T00:58:58.829638869Z' }
[2025-01-23 00:59:03.814Z]  { cpuCount: 2, cpuPct: 5.16, logger: '', memTotalMiB: 484, memUsedMiB: 37, timestamp: '2025-01-23T00:59:03.814028031Z' }
[2025-01-23 00:59:08.815Z]  { cpuCount: 2, cpuPct: 1.6, logger: '', memTotalMiB: 484, memUsedMiB: 37, timestamp: '2025-01-23T00:59:08.815933749Z' }
```
  • Loading branch information
0div authored Jan 24, 2025
2 parents 0be28d9 + d920f54 commit 4a1f253
Show file tree
Hide file tree
Showing 40 changed files with 1,305 additions and 105 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ generate-js:
cd packages/js-sdk && pnpm generate-envd-api
cd spec/envd && buf generate --template buf-js.gen.yaml

# `brew install protobuf` beforehand
generate-python:
$(MAKE) -C packages/connect-python build
cd packages/python-sdk && make generate-api
Expand Down
50 changes: 50 additions & 0 deletions apps/web/src/app/(docs)/docs/sandbox/list/page.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Link from 'next/link'

# List running sandboxes

You can list all running sandboxes using the `Sandbox.list()` method.
Expand Down Expand Up @@ -58,3 +60,51 @@ Running sandbox started at: 2024-10-15 21:13:07.311861+00:00
Running sandbox template id: 3e4rngfa34txe0gxc1zf
```
</CodeGroup>


## Filtering sandboxes
<Note>
This feature is in a private beta.
</Note>

You can filter sandboxes by specifying <Link href="/docs/sandbox/metadata">Metadata</Link> key value pairs.
Specifying multiple key value pairs will return sandboxes that match all of them.

This can be useful when you have a large number of sandboxes and want to find only specific ones. The filtering is performed on the server.

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpreter'

// Create sandbox with metadata.
const sandbox = await Sandbox.create({
metadata: {
env: 'dev', // $HighlightLine
app: 'my-app', // $HighlightLine
userId: '123', // $HighlightLine
},
})

// List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`.
const runningSandboxes = await Sandbox.list({
filters: { userId: '123', env: 'dev' } // $HighlightLine
})
```
```python
from e2b_code_interpreter import Sandbox

# Create sandbox with metadata.
sandbox = Sandbox(
metadata={
"env": "dev", # $HighlightLine
"app": "my-app", # $HighlightLine
"user_id": "123", # $HighlightLine
},
)

# List running sandboxes that has `userId` key with value `123` and `env` key with value `dev`.
running_sandboxes = Sandbox.list(filters={
"userId": "123", "env": "dev" # $HighlightLine
})
```
</CodeGroup>
5 changes: 5 additions & 0 deletions apps/web/src/app/(docs)/docs/sandbox/metadata/page.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Link from 'next/link'

# Sandbox metadata

Metadata is a way to attach arbitrary key-value pairs for a sandbox.
Expand Down Expand Up @@ -47,3 +49,6 @@ running_sandboxes = Sandbox.list()
print(running_sandboxes[0].metadata)
```
</CodeGroup>

## Filtering sandboxes by metadata
You can also filter sandboxes by metadata, you can find more about it <Link href="/docs/sandbox/list#filtering-sandboxes">here</Link>.
11 changes: 1 addition & 10 deletions apps/web/src/components/Pricing/SwitchToProButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,9 @@ import Spinner from '@/components/Spinner'

import { TierActiveTag } from './TierActiveTag'
import { getBillingUrl } from '@/app/(dashboard)/dashboard/utils'
import { toast } from '@/components/ui/use-toast'

function createCheckout(domain: string, tierID: string, teamID: string) {
if (domain !== 'e2b.dev') {
console.error('Managing billing is allowed only at e2b.dev.')
toast({
title: 'Error',
description: 'Managing billing is allowed only at e2b.dev.',
})
}

return fetch(getBillingUrl(domain, '/checkouts'), {
return fetch(getBillingUrl('e2b.dev', '/checkouts'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
41 changes: 27 additions & 14 deletions apps/web/src/utils/useUser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ export type Team = {
apiKeys: string[]
}

interface APIKey { api_key: string; }
interface UserTeam {
id: string;
name: string;
is_default: boolean;
tier: string;
email: string;
team_api_keys: { api_key: string; }[];
teams: {
tier: string;
email: string;
team_api_keys: { api_key: string; }[];
id: string;
name: string;
}
}

export type E2BUser = (User & {
Expand Down Expand Up @@ -110,20 +113,30 @@ export const CustomUserContextProvider = (props) => {
if (!session) return
if (!session.user.id) return

// @ts-ignore
const { data: userTeams, teamsError } = await supabase
const { data: userTeams, error: teamsError } = await supabase
.from('users_teams')
.select('teams (id, name, is_default, tier, email, team_api_keys (api_key))')
.select('is_default, teams (id, name, tier, email, team_api_keys (api_key))')
.eq('user_id', session?.user.id) // Due to RLS, we could also safely just fetch all, but let's be explicit for sure

if (teamsError) Sentry.captureException(teamsError)
// TODO: Adjust when user can be part of multiple teams
// @ts-ignore
const teams = userTeams?.map(userTeam => userTeam.teams).map((team: UserTeam) => ({
...team,
apiKeys: team.team_api_keys.map(apiKey => apiKey.api_key)
} as Team))

if (userTeams === undefined || userTeams === null) {
console.log('No user teams found')
Sentry.captureEvent({ message: 'No user teams found' })
return
}

const typedUserTeams = userTeams as unknown as UserTeam[]
const teams: Team[] = typedUserTeams.map((userTeam: UserTeam): Team => {
return {
id: userTeam.teams.id,
name: userTeam.teams.name,
tier: userTeam.teams.tier,
is_default: userTeam.is_default,
email: userTeam.teams.email,
apiKeys: userTeam.teams.team_api_keys.map((apiKey: APIKey) => apiKey.api_key),
}
})
const defaultTeam = teams?.find(team => team.is_default)

if (!defaultTeam) {
Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/commands/sandbox/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as commander from 'commander'

import { connectCommand } from './connect'
import { listCommand } from './list'
import { killCommand } from './kill'
import { spawnCommand } from './spawn'
import { listCommand } from './list'
import { logsCommand } from './logs'
import { metricsCommand } from './metrics'
import { spawnCommand } from './spawn'

export const sandboxCommand = new commander.Command('sandbox')
.description('work with sandboxes')
Expand All @@ -14,3 +15,4 @@ export const sandboxCommand = new commander.Command('sandbox')
.addCommand(killCommand)
.addCommand(spawnCommand)
.addCommand(logsCommand)
.addCommand(metricsCommand)
8 changes: 4 additions & 4 deletions packages/cli/src/commands/sandbox/logs.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import * as chalk from 'chalk'
import * as commander from 'commander'
import * as e2b from 'e2b'
import * as util from 'util'
import * as chalk from 'chalk'

import { client, connectionConfig } from 'src/api'
import { asBold, asTimestamp, withUnderline } from 'src/utils/format'
import { listSandboxes } from './list'
import { wait } from 'src/utils/wait'
import { handleE2BRequestError } from '../../utils/errors'
import { listSandboxes } from './list'

const maxRuntime = 24 * 60 * 60 * 1000 // 24 hours in milliseconds

function getShortID(sandboxID: string) {
return sandboxID.split('-')[0]
}

function waitForSandboxEnd(sandboxID: string) {
export function waitForSandboxEnd(sandboxID: string) {
let isRunning = true

async function monitor() {
Expand Down Expand Up @@ -87,7 +87,7 @@ enum LogFormat {
PRETTY = 'pretty',
}

function cleanLogger(logger?: string) {
export function cleanLogger(logger?: string) {
if (!logger) {
return ''
}
Expand Down
Loading

0 comments on commit 4a1f253

Please sign in to comment.