diff --git a/changes/3707.feature.md b/changes/3707.feature.md new file mode 100644 index 00000000000..e73a65d64d0 --- /dev/null +++ b/changes/3707.feature.md @@ -0,0 +1 @@ +Add sum of resource slots field to scaling group schema diff --git a/docs/manager/graphql-reference/schema.graphql b/docs/manager/graphql-reference/schema.graphql index 74d56366fcd..47864e36f12 100644 --- a/docs/manager/graphql-reference/schema.graphql +++ b/docs/manager/graphql-reference/schema.graphql @@ -1182,6 +1182,11 @@ type ScalingGroup { """ status: String = "ALIVE" ): JSONString + + """ + Added in 25.3.1. The sum of occupied slots across compute sessions that occupying agent's resources. Only includes sessions owned by the user. + """ + own_session_occupied_resource_slots: JSONString } type StorageVolume implements Item { diff --git a/docs/manager/rest-reference/openapi.json b/docs/manager/rest-reference/openapi.json index 8c2999a60db..9eba68e8eb0 100644 --- a/docs/manager/rest-reference/openapi.json +++ b/docs/manager/rest-reference/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "Backend.AI Manager API", "description": "Backend.AI Manager REST API specification", - "version": "25.2.0", + "version": "25.3.0", "contact": { "name": "Lablup Inc.", "url": "https://docs.backend.ai", diff --git a/src/ai/backend/manager/models/scaling_group.py b/src/ai/backend/manager/models/scaling_group.py index 92ad180b99f..c231d0a5798 100644 --- a/src/ai/backend/manager/models/scaling_group.py +++ b/src/ai/backend/manager/models/scaling_group.py @@ -439,6 +439,15 @@ class ScalingGroup(graphene.ObjectType): ), ) + # TODO: Replace this field with a generic resource slot query API + own_session_occupied_resource_slots = graphene.Field( + graphene.JSONString, + description=( + "Added in 25.3.1. The sum of occupied slots across compute sessions that occupying agent's resources. " + "Only includes sessions owned by the user." + ), + ) + async def resolve_agent_count_by_status( self, info: graphene.ResolveInfo, status: str = AgentStatus.ALIVE.name ) -> int: @@ -479,6 +488,34 @@ async def resolve_agent_total_resource_slots_by_status( "available_slots": total_available_slots.to_json(), } + # TODO: Replace this field with a generic resource slot query API + async def resolve_own_session_occupied_resource_slots( + self, info: graphene.ResolveInfo + ) -> Mapping[str, Any]: + from .agent import AgentRow + from .kernel import AGENT_RESOURCE_OCCUPYING_KERNEL_STATUSES, KernelRow + + graph_ctx: GraphQueryContext = info.context + user = graph_ctx.user + async with graph_ctx.db.begin_readonly_session() as db_session: + query = ( + sa.select(KernelRow) + .join(KernelRow.agent_row) + .where( + sa.and_( + KernelRow.status.in_(AGENT_RESOURCE_OCCUPYING_KERNEL_STATUSES), + KernelRow.user_uuid == user["uuid"], + AgentRow.scaling_group == self.name, + ) + ) + ) + result = await db_session.scalars(query) + kernel_rows = cast(list[KernelRow], result.all()) + occupied_slots = ResourceSlot() + for kernel in kernel_rows: + occupied_slots += kernel.occupied_slots + return occupied_slots.to_json() + @classmethod def from_row( cls,