Skip to content

Commit b23793f

Browse files
feat: add api to query devbox usage (#7296)
1 parent 764b445 commit b23793f

File tree

6 files changed

+219
-4
lines changed

6 files changed

+219
-4
lines changed

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 112
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-b493cadf2e3b9658163a6bcf8f51e088dda169f12d68469c4441d17e889f5556.yml
3-
openapi_spec_hash: b27ec3822d88d10efa268f1681fddff3
4-
config_hash: 6c26299fd9ef01fb4713612a9a2ad17c
1+
configured_endpoints: 113
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-37187e72e61b850045924e6cb40207159ab794114add75a2bf36c87915326306.yml
3+
openapi_spec_hash: 33510c114bef0847855cdaf8f55c0774
4+
config_hash: 9f86425631c30497276e58b744dd3654

api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ from runloop_api_client.types import (
126126
DevboxExecutionDetailView,
127127
DevboxKillExecutionRequest,
128128
DevboxListView,
129+
DevboxResourceUsageView,
129130
DevboxSendStdInRequest,
130131
DevboxSendStdInResult,
131132
DevboxSnapshotListView,
@@ -158,6 +159,7 @@ Methods:
158159
- <code title="post /v1/devboxes/{id}/read_file_contents">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">read_file_contents</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_read_file_contents_params.py">params</a>) -> str</code>
159160
- <code title="post /v1/devboxes/{id}/remove_tunnel">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">remove_tunnel</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_remove_tunnel_params.py">params</a>) -> object</code>
160161
- <code title="post /v1/devboxes/{id}/resume">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">resume</a>(id) -> <a href="./src/runloop_api_client/types/devbox_view.py">DevboxView</a></code>
162+
- <code title="get /v1/devboxes/{id}/usage">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">retrieve_resource_usage</a>(id) -> <a href="./src/runloop_api_client/types/devbox_resource_usage_view.py">DevboxResourceUsageView</a></code>
161163
- <code title="post /v1/devboxes/{id}/shutdown">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">shutdown</a>(id) -> <a href="./src/runloop_api_client/types/devbox_view.py">DevboxView</a></code>
162164
- <code title="post /v1/devboxes/{id}/snapshot_disk">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">snapshot_disk</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_snapshot_disk_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devbox_snapshot_view.py">DevboxSnapshotView</a></code>
163165
- <code title="post /v1/devboxes/{id}/snapshot_disk_async">client.devboxes.<a href="./src/runloop_api_client/resources/devboxes/devboxes.py">snapshot_disk_async</a>(id, \*\*<a href="src/runloop_api_client/types/devbox_snapshot_disk_async_params.py">params</a>) -> <a href="./src/runloop_api_client/types/devbox_snapshot_view.py">DevboxSnapshotView</a></code>

src/runloop_api_client/resources/devboxes/devboxes.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
from ...types.shared_params.mount import Mount
106106
from ...types.devbox_snapshot_view import DevboxSnapshotView
107107
from ...types.shared.launch_parameters import LaunchParameters as SharedLaunchParameters
108+
from ...types.devbox_resource_usage_view import DevboxResourceUsageView
108109
from ...types.devbox_execution_detail_view import DevboxExecutionDetailView
109110
from ...types.devbox_create_ssh_key_response import DevboxCreateSSHKeyResponse
110111
from ...types.shared_params.launch_parameters import LaunchParameters
@@ -1364,6 +1365,43 @@ def resume(
13641365
cast_to=DevboxView,
13651366
)
13661367

1368+
def retrieve_resource_usage(
1369+
self,
1370+
id: str,
1371+
*,
1372+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
1373+
# The extra values given here take precedence over values defined on the client or passed to this method.
1374+
extra_headers: Headers | None = None,
1375+
extra_query: Query | None = None,
1376+
extra_body: Body | None = None,
1377+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
1378+
) -> DevboxResourceUsageView:
1379+
"""Get resource usage metrics for a specific Devbox.
1380+
1381+
Returns CPU, memory, and disk
1382+
consumption calculated from the Devbox's lifecycle, excluding any suspended
1383+
periods for CPU and memory. Disk usage includes the full elapsed time since
1384+
storage is consumed even when suspended.
1385+
1386+
Args:
1387+
extra_headers: Send extra headers
1388+
1389+
extra_query: Add additional query parameters to the request
1390+
1391+
extra_body: Add additional JSON properties to the request
1392+
1393+
timeout: Override the client-level default timeout for this request, in seconds
1394+
"""
1395+
if not id:
1396+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
1397+
return self._get(
1398+
f"/v1/devboxes/{id}/usage",
1399+
options=make_request_options(
1400+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
1401+
),
1402+
cast_to=DevboxResourceUsageView,
1403+
)
1404+
13671405
def shutdown(
13681406
self,
13691407
id: str,
@@ -2980,6 +3018,43 @@ async def resume(
29803018
cast_to=DevboxView,
29813019
)
29823020

3021+
async def retrieve_resource_usage(
3022+
self,
3023+
id: str,
3024+
*,
3025+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
3026+
# The extra values given here take precedence over values defined on the client or passed to this method.
3027+
extra_headers: Headers | None = None,
3028+
extra_query: Query | None = None,
3029+
extra_body: Body | None = None,
3030+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
3031+
) -> DevboxResourceUsageView:
3032+
"""Get resource usage metrics for a specific Devbox.
3033+
3034+
Returns CPU, memory, and disk
3035+
consumption calculated from the Devbox's lifecycle, excluding any suspended
3036+
periods for CPU and memory. Disk usage includes the full elapsed time since
3037+
storage is consumed even when suspended.
3038+
3039+
Args:
3040+
extra_headers: Send extra headers
3041+
3042+
extra_query: Add additional query parameters to the request
3043+
3044+
extra_body: Add additional JSON properties to the request
3045+
3046+
timeout: Override the client-level default timeout for this request, in seconds
3047+
"""
3048+
if not id:
3049+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
3050+
return await self._get(
3051+
f"/v1/devboxes/{id}/usage",
3052+
options=make_request_options(
3053+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
3054+
),
3055+
cast_to=DevboxResourceUsageView,
3056+
)
3057+
29833058
async def shutdown(
29843059
self,
29853060
id: str,
@@ -3434,6 +3509,9 @@ def __init__(self, devboxes: DevboxesResource) -> None:
34343509
self.resume = to_raw_response_wrapper(
34353510
devboxes.resume,
34363511
)
3512+
self.retrieve_resource_usage = to_raw_response_wrapper(
3513+
devboxes.retrieve_resource_usage,
3514+
)
34373515
self.shutdown = to_raw_response_wrapper(
34383516
devboxes.shutdown,
34393517
)
@@ -3539,6 +3617,9 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None:
35393617
self.resume = async_to_raw_response_wrapper(
35403618
devboxes.resume,
35413619
)
3620+
self.retrieve_resource_usage = async_to_raw_response_wrapper(
3621+
devboxes.retrieve_resource_usage,
3622+
)
35423623
self.shutdown = async_to_raw_response_wrapper(
35433624
devboxes.shutdown,
35443625
)
@@ -3644,6 +3725,9 @@ def __init__(self, devboxes: DevboxesResource) -> None:
36443725
self.resume = to_streamed_response_wrapper(
36453726
devboxes.resume,
36463727
)
3728+
self.retrieve_resource_usage = to_streamed_response_wrapper(
3729+
devboxes.retrieve_resource_usage,
3730+
)
36473731
self.shutdown = to_streamed_response_wrapper(
36483732
devboxes.shutdown,
36493733
)
@@ -3749,6 +3833,9 @@ def __init__(self, devboxes: AsyncDevboxesResource) -> None:
37493833
self.resume = async_to_streamed_response_wrapper(
37503834
devboxes.resume,
37513835
)
3836+
self.retrieve_resource_usage = async_to_streamed_response_wrapper(
3837+
devboxes.retrieve_resource_usage,
3838+
)
37523839
self.shutdown = async_to_streamed_response_wrapper(
37533840
devboxes.shutdown,
37543841
)

src/runloop_api_client/types/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
from .benchmark_start_run_params import BenchmarkStartRunParams as BenchmarkStartRunParams
8585
from .blueprint_build_parameters import BlueprintBuildParameters as BlueprintBuildParameters
8686
from .devbox_execute_sync_params import DevboxExecuteSyncParams as DevboxExecuteSyncParams
87+
from .devbox_resource_usage_view import DevboxResourceUsageView as DevboxResourceUsageView
8788
from .gateway_config_list_params import GatewayConfigListParams as GatewayConfigListParams
8889
from .input_context_update_param import InputContextUpdateParam as InputContextUpdateParam
8990
from .network_policy_list_params import NetworkPolicyListParams as NetworkPolicyListParams
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
from typing import Optional
4+
5+
from .._models import BaseModel
6+
7+
__all__ = ["DevboxResourceUsageView"]
8+
9+
10+
class DevboxResourceUsageView(BaseModel):
11+
id: str
12+
"""The devbox ID."""
13+
14+
disk_gb_seconds: int
15+
"""Disk usage in GB-seconds (total_elapsed_seconds multiplied by disk size in GB).
16+
17+
Disk is billed for elapsed time since storage is consumed even when suspended.
18+
"""
19+
20+
memory_gb_seconds: int
21+
"""Memory usage in GB-seconds (total_active_seconds multiplied by memory in GB)."""
22+
23+
start_time_ms: int
24+
"""The devbox creation time in milliseconds since epoch."""
25+
26+
status: str
27+
"""The current status of the devbox."""
28+
29+
total_active_seconds: int
30+
"""
31+
Total time in seconds the devbox was actively running (excludes time spent
32+
suspended).
33+
"""
34+
35+
total_elapsed_seconds: int
36+
"""
37+
Total elapsed time in seconds from devbox creation to now (or end time if
38+
terminated). Includes all time regardless of devbox state.
39+
"""
40+
41+
vcpu_seconds: int
42+
"""
43+
vCPU usage in vCPU-seconds (total_active_seconds multiplied by the number of
44+
vCPUs).
45+
"""
46+
47+
end_time_ms: Optional[int] = None
48+
"""The devbox end time in milliseconds since epoch, or null if still running."""

tests/api_resources/test_devboxes.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
TunnelView,
1818
DevboxTunnelView,
1919
DevboxSnapshotView,
20+
DevboxResourceUsageView,
2021
DevboxExecutionDetailView,
2122
DevboxCreateSSHKeyResponse,
2223
DevboxAsyncExecutionDetailView,
@@ -845,6 +846,44 @@ def test_path_params_resume(self, client: Runloop) -> None:
845846
"",
846847
)
847848

849+
@parametrize
850+
def test_method_retrieve_resource_usage(self, client: Runloop) -> None:
851+
devbox = client.devboxes.retrieve_resource_usage(
852+
"id",
853+
)
854+
assert_matches_type(DevboxResourceUsageView, devbox, path=["response"])
855+
856+
@parametrize
857+
def test_raw_response_retrieve_resource_usage(self, client: Runloop) -> None:
858+
response = client.devboxes.with_raw_response.retrieve_resource_usage(
859+
"id",
860+
)
861+
862+
assert response.is_closed is True
863+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
864+
devbox = response.parse()
865+
assert_matches_type(DevboxResourceUsageView, devbox, path=["response"])
866+
867+
@parametrize
868+
def test_streaming_response_retrieve_resource_usage(self, client: Runloop) -> None:
869+
with client.devboxes.with_streaming_response.retrieve_resource_usage(
870+
"id",
871+
) as response:
872+
assert not response.is_closed
873+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
874+
875+
devbox = response.parse()
876+
assert_matches_type(DevboxResourceUsageView, devbox, path=["response"])
877+
878+
assert cast(Any, response.is_closed) is True
879+
880+
@parametrize
881+
def test_path_params_retrieve_resource_usage(self, client: Runloop) -> None:
882+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
883+
client.devboxes.with_raw_response.retrieve_resource_usage(
884+
"",
885+
)
886+
848887
@parametrize
849888
def test_method_shutdown(self, client: Runloop) -> None:
850889
devbox = client.devboxes.shutdown(
@@ -2457,6 +2496,44 @@ async def test_path_params_resume(self, async_client: AsyncRunloop) -> None:
24572496
"",
24582497
)
24592498

2499+
@parametrize
2500+
async def test_method_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None:
2501+
devbox = await async_client.devboxes.retrieve_resource_usage(
2502+
"id",
2503+
)
2504+
assert_matches_type(DevboxResourceUsageView, devbox, path=["response"])
2505+
2506+
@parametrize
2507+
async def test_raw_response_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None:
2508+
response = await async_client.devboxes.with_raw_response.retrieve_resource_usage(
2509+
"id",
2510+
)
2511+
2512+
assert response.is_closed is True
2513+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
2514+
devbox = await response.parse()
2515+
assert_matches_type(DevboxResourceUsageView, devbox, path=["response"])
2516+
2517+
@parametrize
2518+
async def test_streaming_response_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None:
2519+
async with async_client.devboxes.with_streaming_response.retrieve_resource_usage(
2520+
"id",
2521+
) as response:
2522+
assert not response.is_closed
2523+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
2524+
2525+
devbox = await response.parse()
2526+
assert_matches_type(DevboxResourceUsageView, devbox, path=["response"])
2527+
2528+
assert cast(Any, response.is_closed) is True
2529+
2530+
@parametrize
2531+
async def test_path_params_retrieve_resource_usage(self, async_client: AsyncRunloop) -> None:
2532+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
2533+
await async_client.devboxes.with_raw_response.retrieve_resource_usage(
2534+
"",
2535+
)
2536+
24602537
@parametrize
24612538
async def test_method_shutdown(self, async_client: AsyncRunloop) -> None:
24622539
devbox = await async_client.devboxes.shutdown(

0 commit comments

Comments
 (0)