From 06d3a37f8d3bfca97e8ebc2d37c78cc826e63297 Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Wed, 8 Oct 2025 07:09:10 -0700 Subject: [PATCH] Add support for container statistics (#318) Closes #138 This rounds up cgroup and network stats (via netlink) and exposes them via an agent rpc and on LinuxContainer. While this is not an entirely accurate view into the full resources being used by the container given the virtualized nature (doesn't account for vcpu, device etc overhead) it should give an accurate overview of the workload resource usage in the guest. This change besides the stated goal also removes the interfaceStatistics rpc and just moves these network stats into the new containerStatistics one. Related to that, it also renames the InterfaceStatistics struct to NetworkStatistics and moves this into ContainerStatistics as a nested struct. --- .../ContainerStatistics.swift | 177 ++++ .../InterfaceStatistics.swift | 44 - Sources/Containerization/LinuxContainer.swift | 23 +- .../SandboxContext/SandboxContext.grpc.swift | 200 ++-- .../SandboxContext/SandboxContext.pb.swift | 905 ++++++++++++++---- .../SandboxContext/SandboxContext.proto | 84 +- .../VirtualMachineAgent.swift | 8 +- Sources/Containerization/Vminitd.swift | 70 +- Sources/Integration/Suite.swift | 1 + Sources/Integration/VMTests.swift | 43 + vminitd/Sources/vminitd/Cgroup2Manager.swift | 3 + .../Sources/vminitd/ManagedContainer.swift | 4 + vminitd/Sources/vminitd/Server+GRPC.swift | 141 ++- 13 files changed, 1321 insertions(+), 382 deletions(-) create mode 100644 Sources/Containerization/ContainerStatistics.swift delete mode 100644 Sources/Containerization/InterfaceStatistics.swift diff --git a/Sources/Containerization/ContainerStatistics.swift b/Sources/Containerization/ContainerStatistics.swift new file mode 100644 index 00000000..c3cb9cdf --- /dev/null +++ b/Sources/Containerization/ContainerStatistics.swift @@ -0,0 +1,177 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +/// Statistics for a container. +public struct ContainerStatistics: Sendable { + public var id: String + public var process: ProcessStatistics + public var memory: MemoryStatistics + public var cpu: CPUStatistics + public var blockIO: BlockIOStatistics + public var networks: [NetworkStatistics] + + public init( + id: String, + process: ProcessStatistics, + memory: MemoryStatistics, + cpu: CPUStatistics, + blockIO: BlockIOStatistics, + networks: [NetworkStatistics] + ) { + self.id = id + self.process = process + self.memory = memory + self.cpu = cpu + self.blockIO = blockIO + self.networks = networks + } + + /// Process statistics for a container. + public struct ProcessStatistics: Sendable { + public var current: UInt64 + public var limit: UInt64 + + public init(current: UInt64, limit: UInt64) { + self.current = current + self.limit = limit + } + } + + /// Memory statistics for a container. + public struct MemoryStatistics: Sendable { + public var usageBytes: UInt64 + public var limitBytes: UInt64 + public var swapUsageBytes: UInt64 + public var swapLimitBytes: UInt64 + public var cacheBytes: UInt64 + public var kernelStackBytes: UInt64 + public var slabBytes: UInt64 + public var pageFaults: UInt64 + public var majorPageFaults: UInt64 + + public init( + usageBytes: UInt64, + limitBytes: UInt64, + swapUsageBytes: UInt64, + swapLimitBytes: UInt64, + cacheBytes: UInt64, + kernelStackBytes: UInt64, + slabBytes: UInt64, + pageFaults: UInt64, + majorPageFaults: UInt64 + ) { + self.usageBytes = usageBytes + self.limitBytes = limitBytes + self.swapUsageBytes = swapUsageBytes + self.swapLimitBytes = swapLimitBytes + self.cacheBytes = cacheBytes + self.kernelStackBytes = kernelStackBytes + self.slabBytes = slabBytes + self.pageFaults = pageFaults + self.majorPageFaults = majorPageFaults + } + } + + /// CPU statistics for a container. + public struct CPUStatistics: Sendable { + public var usageUsec: UInt64 + public var userUsec: UInt64 + public var systemUsec: UInt64 + public var throttlingPeriods: UInt64 + public var throttledPeriods: UInt64 + public var throttledTimeUsec: UInt64 + + public init( + usageUsec: UInt64, + userUsec: UInt64, + systemUsec: UInt64, + throttlingPeriods: UInt64, + throttledPeriods: UInt64, + throttledTimeUsec: UInt64 + ) { + self.usageUsec = usageUsec + self.userUsec = userUsec + self.systemUsec = systemUsec + self.throttlingPeriods = throttlingPeriods + self.throttledPeriods = throttledPeriods + self.throttledTimeUsec = throttledTimeUsec + } + } + + /// Block I/O statistics for a container. + public struct BlockIOStatistics: Sendable { + public var devices: [BlockIODevice] + + public init(devices: [BlockIODevice]) { + self.devices = devices + } + } + + /// Block I/O statistics for a specific device. + public struct BlockIODevice: Sendable { + public var major: UInt64 + public var minor: UInt64 + public var readBytes: UInt64 + public var writeBytes: UInt64 + public var readOperations: UInt64 + public var writeOperations: UInt64 + + public init( + major: UInt64, + minor: UInt64, + readBytes: UInt64, + writeBytes: UInt64, + readOperations: UInt64, + writeOperations: UInt64 + ) { + self.major = major + self.minor = minor + self.readBytes = readBytes + self.writeBytes = writeBytes + self.readOperations = readOperations + self.writeOperations = writeOperations + } + } + + /// Statistics for a network interface. + public struct NetworkStatistics: Sendable { + public var interface: String + public var receivedPackets: UInt64 + public var transmittedPackets: UInt64 + public var receivedBytes: UInt64 + public var transmittedBytes: UInt64 + public var receivedErrors: UInt64 + public var transmittedErrors: UInt64 + + public init( + interface: String, + receivedPackets: UInt64, + transmittedPackets: UInt64, + receivedBytes: UInt64, + transmittedBytes: UInt64, + receivedErrors: UInt64, + transmittedErrors: UInt64 + ) { + self.interface = interface + self.receivedPackets = receivedPackets + self.transmittedPackets = transmittedPackets + self.receivedBytes = receivedBytes + self.transmittedBytes = transmittedBytes + self.receivedErrors = receivedErrors + self.transmittedErrors = transmittedErrors + } + } +} diff --git a/Sources/Containerization/InterfaceStatistics.swift b/Sources/Containerization/InterfaceStatistics.swift deleted file mode 100644 index 6a1e190b..00000000 --- a/Sources/Containerization/InterfaceStatistics.swift +++ /dev/null @@ -1,44 +0,0 @@ -//===----------------------------------------------------------------------===// -// Copyright © 2025 Apple Inc. and the Containerization project authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -//===----------------------------------------------------------------------===// - -/// Statistics for a network interface. -public struct InterfaceStatistics { - public var name: String - public var receivedPackets: UInt64? - public var transmittedPackets: UInt64? - public var receivedBytes: UInt64? - public var transmittedBytes: UInt64? - public var receivedErrors: UInt64? - public var transmittedErrors: UInt64? - - public init( - name: String, - receivedPackets: UInt64?, - transmittedPackets: UInt64?, - receivedBytes: UInt64?, - transmittedBytes: UInt64?, - receivedErrors: UInt64?, - transmittedErrors: UInt64? - ) { - self.name = name - self.receivedPackets = receivedPackets - self.transmittedPackets = transmittedPackets - self.receivedBytes = receivedBytes - self.transmittedBytes = transmittedBytes - self.receivedErrors = receivedErrors - self.transmittedErrors = transmittedErrors - } -} diff --git a/Sources/Containerization/LinuxContainer.swift b/Sources/Containerization/LinuxContainer.swift index cc5e1fef..0cd18944 100644 --- a/Sources/Containerization/LinuxContainer.swift +++ b/Sources/Containerization/LinuxContainer.swift @@ -821,6 +821,24 @@ extension LinuxContainer { return try await state.process.closeStdin() } + /// Get statistics for the container. + public func statistics() async throws -> ContainerStatistics { + let state = try self.state.withLock { try $0.startedState("statistics") } + + let stats = try await state.vm.withAgent { agent in + let allStats = try await agent.containerStatistics(containerIDs: [self.id]) + guard let containerStats = allStats.first else { + throw ContainerizationError( + .notFound, + message: "statistics for container \(self.id) not found" + ) + } + return containerStats + } + + return stats + } + /// Relay a unix socket from in the container to the host, or from the host /// to inside the container. public func relayUnixSocket(socket: UnixSocketConfiguration) async throws { @@ -865,11 +883,12 @@ extension LinuxContainer { extension VirtualMachineInstance { /// Scoped access to an agent instance to ensure the resources are always freed (mostly close(2)'ing /// the vsock fd) - fileprivate func withAgent(fn: @Sendable (VirtualMachineAgent) async throws -> Void) async throws { + fileprivate func withAgent(fn: @Sendable (VirtualMachineAgent) async throws -> T) async throws -> T { let agent = try await self.dialAgent() do { - try await fn(agent) + let result = try await fn(agent) try await agent.close() + return result } catch { try? await agent.close() throw error diff --git a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift index 938bcaa6..ed59bfe2 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.grpc.swift @@ -114,6 +114,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtoc callOptions: CallOptions? ) -> UnaryCall + func containerStatistics( + _ request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, + callOptions: CallOptions? + ) -> UnaryCall + func proxyVsock( _ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, callOptions: CallOptions? @@ -154,11 +159,6 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtoc callOptions: CallOptions? ) -> UnaryCall - func interfaceStatistics( - _ request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, - callOptions: CallOptions? - ) -> UnaryCall - func sync( _ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, callOptions: CallOptions? @@ -464,6 +464,24 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtocol { ) } + /// Get statistics for containers. + /// + /// - Parameters: + /// - request: Request to send to ContainerStatistics. + /// - callOptions: Call options. + /// - Returns: A `UnaryCall` with futures for the metadata, status and response. + public func containerStatistics( + _ request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, + callOptions: CallOptions? = nil + ) -> UnaryCall { + return self.makeUnaryCall( + path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.containerStatistics.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeContainerStatisticsInterceptors() ?? [] + ) + } + /// Proxy a vsock port to a unix domain socket in the guest, or vice versa. /// /// - Parameters: @@ -608,24 +626,6 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextClientProtocol { ) } - /// Get statistics about an interface. - /// - /// - Parameters: - /// - request: Request to send to InterfaceStatistics. - /// - callOptions: Call options. - /// - Returns: A `UnaryCall` with futures for the metadata, status and response. - public func interfaceStatistics( - _ request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, - callOptions: CallOptions? = nil - ) -> UnaryCall { - return self.makeUnaryCall( - path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.interfaceStatistics.path, - request: request, - callOptions: callOptions ?? self.defaultCallOptions, - interceptors: self.interceptors?.makeInterfaceStatisticsInterceptors() ?? [] - ) - } - /// Perform the sync syscall. /// /// - Parameters: @@ -806,6 +806,11 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientP callOptions: CallOptions? ) -> GRPCAsyncUnaryCall + func makeContainerStatisticsCall( + _ request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, + callOptions: CallOptions? + ) -> GRPCAsyncUnaryCall + func makeProxyVsockCall( _ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, callOptions: CallOptions? @@ -846,11 +851,6 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientP callOptions: CallOptions? ) -> GRPCAsyncUnaryCall - func makeInterfaceStatisticsCall( - _ request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, - callOptions: CallOptions? - ) -> GRPCAsyncUnaryCall - func makeSyncCall( _ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, callOptions: CallOptions? @@ -1064,6 +1064,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco ) } + public func makeContainerStatisticsCall( + _ request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, + callOptions: CallOptions? = nil + ) -> GRPCAsyncUnaryCall { + return self.makeAsyncUnaryCall( + path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.containerStatistics.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeContainerStatisticsInterceptors() ?? [] + ) + } + public func makeProxyVsockCall( _ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, callOptions: CallOptions? = nil @@ -1160,18 +1172,6 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco ) } - public func makeInterfaceStatisticsCall( - _ request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, - callOptions: CallOptions? = nil - ) -> GRPCAsyncUnaryCall { - return self.makeAsyncUnaryCall( - path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.interfaceStatistics.path, - request: request, - callOptions: callOptions ?? self.defaultCallOptions, - interceptors: self.interceptors?.makeInterfaceStatisticsInterceptors() ?? [] - ) - } - public func makeSyncCall( _ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, callOptions: CallOptions? = nil @@ -1391,6 +1391,18 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco ) } + public func containerStatistics( + _ request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, + callOptions: CallOptions? = nil + ) async throws -> Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse { + return try await self.performAsyncUnaryCall( + path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.containerStatistics.path, + request: request, + callOptions: callOptions ?? self.defaultCallOptions, + interceptors: self.interceptors?.makeContainerStatisticsInterceptors() ?? [] + ) + } + public func proxyVsock( _ request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, callOptions: CallOptions? = nil @@ -1487,18 +1499,6 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncClientProtoco ) } - public func interfaceStatistics( - _ request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, - callOptions: CallOptions? = nil - ) async throws -> Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse { - return try await self.performAsyncUnaryCall( - path: Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.interfaceStatistics.path, - request: request, - callOptions: callOptions ?? self.defaultCallOptions, - interceptors: self.interceptors?.makeInterfaceStatisticsInterceptors() ?? [] - ) - } - public func sync( _ request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, callOptions: CallOptions? = nil @@ -1591,6 +1591,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientInterc /// - Returns: Interceptors to use when invoking 'closeProcessStdin'. func makeCloseProcessStdinInterceptors() -> [ClientInterceptor] + /// - Returns: Interceptors to use when invoking 'containerStatistics'. + func makeContainerStatisticsInterceptors() -> [ClientInterceptor] + /// - Returns: Interceptors to use when invoking 'proxyVsock'. func makeProxyVsockInterceptors() -> [ClientInterceptor] @@ -1615,9 +1618,6 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextClientInterc /// - Returns: Interceptors to use when invoking 'configureHosts'. func makeConfigureHostsInterceptors() -> [ClientInterceptor] - /// - Returns: Interceptors to use when invoking 'interfaceStatistics'. - func makeInterfaceStatisticsInterceptors() -> [ClientInterceptor] - /// - Returns: Interceptors to use when invoking 'sync'. func makeSyncInterceptors() -> [ClientInterceptor] @@ -1646,6 +1646,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata { Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.waitProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.resizeProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.closeProcessStdin, + Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.containerStatistics, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.proxyVsock, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.stopVsockProxy, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.ipLinkSet, @@ -1654,7 +1655,6 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata { Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.ipRouteAddDefault, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureDns, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.configureHosts, - Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.interfaceStatistics, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.sync, Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata.Methods.kill, ] @@ -1757,6 +1757,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata { type: GRPCCallType.unary ) + public static let containerStatistics = GRPCMethodDescriptor( + name: "ContainerStatistics", + path: "/com.apple.containerization.sandbox.v3.SandboxContext/ContainerStatistics", + type: GRPCCallType.unary + ) + public static let proxyVsock = GRPCMethodDescriptor( name: "ProxyVsock", path: "/com.apple.containerization.sandbox.v3.SandboxContext/ProxyVsock", @@ -1805,12 +1811,6 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextClientMetadata { type: GRPCCallType.unary ) - public static let interfaceStatistics = GRPCMethodDescriptor( - name: "InterfaceStatistics", - path: "/com.apple.containerization.sandbox.v3.SandboxContext/InterfaceStatistics", - type: GRPCCallType.unary - ) - public static let sync = GRPCMethodDescriptor( name: "Sync", path: "/com.apple.containerization.sandbox.v3.SandboxContext/Sync", @@ -1880,6 +1880,9 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider: Ca /// Close IO for a given process. func closeProcessStdin(request: Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinRequest, context: StatusOnlyCallContext) -> EventLoopFuture + /// Get statistics for containers. + func containerStatistics(request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, context: StatusOnlyCallContext) -> EventLoopFuture + /// Proxy a vsock port to a unix domain socket in the guest, or vice versa. func proxyVsock(request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, context: StatusOnlyCallContext) -> EventLoopFuture @@ -1904,9 +1907,6 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider: Ca /// Configure /etc/hosts. func configureHosts(request: Com_Apple_Containerization_Sandbox_V3_ConfigureHostsRequest, context: StatusOnlyCallContext) -> EventLoopFuture - /// Get statistics about an interface. - func interfaceStatistics(request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, context: StatusOnlyCallContext) -> EventLoopFuture - /// Perform the sync syscall. func sync(request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, context: StatusOnlyCallContext) -> EventLoopFuture @@ -2070,6 +2070,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider { userFunction: self.closeProcessStdin(request:context:) ) + case "ContainerStatistics": + return UnaryServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeContainerStatisticsInterceptors() ?? [], + userFunction: self.containerStatistics(request:context:) + ) + case "ProxyVsock": return UnaryServerHandler( context: context, @@ -2142,15 +2151,6 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextProvider { userFunction: self.configureHosts(request:context:) ) - case "InterfaceStatistics": - return UnaryServerHandler( - context: context, - requestDeserializer: ProtobufDeserializer(), - responseSerializer: ProtobufSerializer(), - interceptors: self.interceptors?.makeInterfaceStatisticsInterceptors() ?? [], - userFunction: self.interfaceStatistics(request:context:) - ) - case "Sync": return UnaryServerHandler( context: context, @@ -2280,6 +2280,12 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvide context: GRPCAsyncServerCallContext ) async throws -> Com_Apple_Containerization_Sandbox_V3_CloseProcessStdinResponse + /// Get statistics for containers. + func containerStatistics( + request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, + context: GRPCAsyncServerCallContext + ) async throws -> Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse + /// Proxy a vsock port to a unix domain socket in the guest, or vice versa. func proxyVsock( request: Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest, @@ -2328,12 +2334,6 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvide context: GRPCAsyncServerCallContext ) async throws -> Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse - /// Get statistics about an interface. - func interfaceStatistics( - request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, - context: GRPCAsyncServerCallContext - ) async throws -> Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse - /// Perform the sync syscall. func sync( request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, @@ -2510,6 +2510,15 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvider { wrapping: { try await self.closeProcessStdin(request: $0, context: $1) } ) + case "ContainerStatistics": + return GRPCAsyncServerHandler( + context: context, + requestDeserializer: ProtobufDeserializer(), + responseSerializer: ProtobufSerializer(), + interceptors: self.interceptors?.makeContainerStatisticsInterceptors() ?? [], + wrapping: { try await self.containerStatistics(request: $0, context: $1) } + ) + case "ProxyVsock": return GRPCAsyncServerHandler( context: context, @@ -2582,15 +2591,6 @@ extension Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvider { wrapping: { try await self.configureHosts(request: $0, context: $1) } ) - case "InterfaceStatistics": - return GRPCAsyncServerHandler( - context: context, - requestDeserializer: ProtobufDeserializer(), - responseSerializer: ProtobufSerializer(), - interceptors: self.interceptors?.makeInterfaceStatisticsInterceptors() ?? [], - wrapping: { try await self.interfaceStatistics(request: $0, context: $1) } - ) - case "Sync": return GRPCAsyncServerHandler( context: context, @@ -2681,6 +2681,10 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextServerInterc /// Defaults to calling `self.makeInterceptors()`. func makeCloseProcessStdinInterceptors() -> [ServerInterceptor] + /// - Returns: Interceptors to use when handling 'containerStatistics'. + /// Defaults to calling `self.makeInterceptors()`. + func makeContainerStatisticsInterceptors() -> [ServerInterceptor] + /// - Returns: Interceptors to use when handling 'proxyVsock'. /// Defaults to calling `self.makeInterceptors()`. func makeProxyVsockInterceptors() -> [ServerInterceptor] @@ -2713,10 +2717,6 @@ public protocol Com_Apple_Containerization_Sandbox_V3_SandboxContextServerInterc /// Defaults to calling `self.makeInterceptors()`. func makeConfigureHostsInterceptors() -> [ServerInterceptor] - /// - Returns: Interceptors to use when handling 'interfaceStatistics'. - /// Defaults to calling `self.makeInterceptors()`. - func makeInterfaceStatisticsInterceptors() -> [ServerInterceptor] - /// - Returns: Interceptors to use when handling 'sync'. /// Defaults to calling `self.makeInterceptors()`. func makeSyncInterceptors() -> [ServerInterceptor] @@ -2747,6 +2747,7 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata { Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.waitProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.resizeProcess, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.closeProcessStdin, + Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.containerStatistics, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.proxyVsock, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.stopVsockProxy, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.ipLinkSet, @@ -2755,7 +2756,6 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata { Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.ipRouteAddDefault, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.configureDns, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.configureHosts, - Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.interfaceStatistics, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.sync, Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata.Methods.kill, ] @@ -2858,6 +2858,12 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata { type: GRPCCallType.unary ) + public static let containerStatistics = GRPCMethodDescriptor( + name: "ContainerStatistics", + path: "/com.apple.containerization.sandbox.v3.SandboxContext/ContainerStatistics", + type: GRPCCallType.unary + ) + public static let proxyVsock = GRPCMethodDescriptor( name: "ProxyVsock", path: "/com.apple.containerization.sandbox.v3.SandboxContext/ProxyVsock", @@ -2906,12 +2912,6 @@ public enum Com_Apple_Containerization_Sandbox_V3_SandboxContextServerMetadata { type: GRPCCallType.unary ) - public static let interfaceStatistics = GRPCMethodDescriptor( - name: "InterfaceStatistics", - path: "/com.apple.containerization.sandbox.v3.SandboxContext/InterfaceStatistics", - type: GRPCCallType.unary - ) - public static let sync = GRPCMethodDescriptor( name: "Sync", path: "/com.apple.containerization.sandbox.v3.SandboxContext/Sync", diff --git a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift index 7645a9c1..f4b63152 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.pb.swift +++ b/Sources/Containerization/SandboxContext/SandboxContext.pb.swift @@ -954,89 +954,6 @@ public struct Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse: Send public init() {} } -public struct Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - public var interface: String = String() - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -public struct Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - public var receivedPackets: UInt64 { - get {return _receivedPackets ?? 0} - set {_receivedPackets = newValue} - } - /// Returns true if `receivedPackets` has been explicitly set. - public var hasReceivedPackets: Bool {return self._receivedPackets != nil} - /// Clears the value of `receivedPackets`. Subsequent reads from it will return its default value. - public mutating func clearReceivedPackets() {self._receivedPackets = nil} - - public var transmittedPackets: UInt64 { - get {return _transmittedPackets ?? 0} - set {_transmittedPackets = newValue} - } - /// Returns true if `transmittedPackets` has been explicitly set. - public var hasTransmittedPackets: Bool {return self._transmittedPackets != nil} - /// Clears the value of `transmittedPackets`. Subsequent reads from it will return its default value. - public mutating func clearTransmittedPackets() {self._transmittedPackets = nil} - - public var receivedBytes: UInt64 { - get {return _receivedBytes ?? 0} - set {_receivedBytes = newValue} - } - /// Returns true if `receivedBytes` has been explicitly set. - public var hasReceivedBytes: Bool {return self._receivedBytes != nil} - /// Clears the value of `receivedBytes`. Subsequent reads from it will return its default value. - public mutating func clearReceivedBytes() {self._receivedBytes = nil} - - public var transmittedBytes: UInt64 { - get {return _transmittedBytes ?? 0} - set {_transmittedBytes = newValue} - } - /// Returns true if `transmittedBytes` has been explicitly set. - public var hasTransmittedBytes: Bool {return self._transmittedBytes != nil} - /// Clears the value of `transmittedBytes`. Subsequent reads from it will return its default value. - public mutating func clearTransmittedBytes() {self._transmittedBytes = nil} - - public var receivedErrors: UInt64 { - get {return _receivedErrors ?? 0} - set {_receivedErrors = newValue} - } - /// Returns true if `receivedErrors` has been explicitly set. - public var hasReceivedErrors: Bool {return self._receivedErrors != nil} - /// Clears the value of `receivedErrors`. Subsequent reads from it will return its default value. - public mutating func clearReceivedErrors() {self._receivedErrors = nil} - - public var transmittedErrors: UInt64 { - get {return _transmittedErrors ?? 0} - set {_transmittedErrors = newValue} - } - /// Returns true if `transmittedErrors` has been explicitly set. - public var hasTransmittedErrors: Bool {return self._transmittedErrors != nil} - /// Clears the value of `transmittedErrors`. Subsequent reads from it will return its default value. - public mutating func clearTransmittedErrors() {self._transmittedErrors = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _receivedPackets: UInt64? = nil - fileprivate var _transmittedPackets: UInt64? = nil - fileprivate var _receivedBytes: UInt64? = nil - fileprivate var _transmittedBytes: UInt64? = nil - fileprivate var _receivedErrors: UInt64? = nil - fileprivate var _transmittedErrors: UInt64? = nil -} - public struct Com_Apple_Containerization_Sandbox_V3_SyncRequest: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -1083,6 +1000,212 @@ public struct Com_Apple_Containerization_Sandbox_V3_KillResponse: Sendable { public init() {} } +public struct Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Empty = all containers + public var containerIds: [String] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var containers: [Com_Apple_Containerization_Sandbox_V3_ContainerStats] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_ContainerStats: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var containerID: String { + get {return _storage._containerID} + set {_uniqueStorage()._containerID = newValue} + } + + public var process: Com_Apple_Containerization_Sandbox_V3_ProcessStats { + get {return _storage._process ?? Com_Apple_Containerization_Sandbox_V3_ProcessStats()} + set {_uniqueStorage()._process = newValue} + } + /// Returns true if `process` has been explicitly set. + public var hasProcess: Bool {return _storage._process != nil} + /// Clears the value of `process`. Subsequent reads from it will return its default value. + public mutating func clearProcess() {_uniqueStorage()._process = nil} + + public var memory: Com_Apple_Containerization_Sandbox_V3_MemoryStats { + get {return _storage._memory ?? Com_Apple_Containerization_Sandbox_V3_MemoryStats()} + set {_uniqueStorage()._memory = newValue} + } + /// Returns true if `memory` has been explicitly set. + public var hasMemory: Bool {return _storage._memory != nil} + /// Clears the value of `memory`. Subsequent reads from it will return its default value. + public mutating func clearMemory() {_uniqueStorage()._memory = nil} + + public var cpu: Com_Apple_Containerization_Sandbox_V3_CPUStats { + get {return _storage._cpu ?? Com_Apple_Containerization_Sandbox_V3_CPUStats()} + set {_uniqueStorage()._cpu = newValue} + } + /// Returns true if `cpu` has been explicitly set. + public var hasCpu: Bool {return _storage._cpu != nil} + /// Clears the value of `cpu`. Subsequent reads from it will return its default value. + public mutating func clearCpu() {_uniqueStorage()._cpu = nil} + + public var blockIo: Com_Apple_Containerization_Sandbox_V3_BlockIOStats { + get {return _storage._blockIo ?? Com_Apple_Containerization_Sandbox_V3_BlockIOStats()} + set {_uniqueStorage()._blockIo = newValue} + } + /// Returns true if `blockIo` has been explicitly set. + public var hasBlockIo: Bool {return _storage._blockIo != nil} + /// Clears the value of `blockIo`. Subsequent reads from it will return its default value. + public mutating func clearBlockIo() {_uniqueStorage()._blockIo = nil} + + public var networks: [Com_Apple_Containerization_Sandbox_V3_NetworkStats] { + get {return _storage._networks} + set {_uniqueStorage()._networks = newValue} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +public struct Com_Apple_Containerization_Sandbox_V3_ProcessStats: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var current: UInt64 = 0 + + /// 0 or max value = unlimited + public var limit: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_MemoryStats: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var usageBytes: UInt64 = 0 + + public var limitBytes: UInt64 = 0 + + public var swapUsageBytes: UInt64 = 0 + + public var swapLimitBytes: UInt64 = 0 + + public var cacheBytes: UInt64 = 0 + + public var kernelStackBytes: UInt64 = 0 + + public var slabBytes: UInt64 = 0 + + public var pageFaults: UInt64 = 0 + + public var majorPageFaults: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_CPUStats: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var usageUsec: UInt64 = 0 + + public var userUsec: UInt64 = 0 + + public var systemUsec: UInt64 = 0 + + public var throttlingPeriods: UInt64 = 0 + + public var throttledPeriods: UInt64 = 0 + + public var throttledTimeUsec: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_BlockIOStats: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var devices: [Com_Apple_Containerization_Sandbox_V3_BlockIOEntry] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_BlockIOEntry: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var major: UInt64 = 0 + + public var minor: UInt64 = 0 + + public var readBytes: UInt64 = 0 + + public var writeBytes: UInt64 = 0 + + public var readOperations: UInt64 = 0 + + public var writeOperations: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Com_Apple_Containerization_Sandbox_V3_NetworkStats: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var interface: String = String() + + public var receivedPackets: UInt64 = 0 + + public var transmittedPackets: UInt64 = 0 + + public var receivedBytes: UInt64 = 0 + + public var transmittedBytes: UInt64 = 0 + + public var receivedErrors: UInt64 = 0 + + public var transmittedErrors: UInt64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "com.apple.containerization.sandbox.v3" @@ -2864,104 +2987,6 @@ extension Com_Apple_Containerization_Sandbox_V3_ConfigureHostsResponse: SwiftPro } } -extension Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".InterfaceStatisticsRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interface"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.interface) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if !self.interface.isEmpty { - try visitor.visitSingularStringField(value: self.interface, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, rhs: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest) -> Bool { - if lhs.interface != rhs.interface {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".InterfaceStatisticsResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "receivedPackets"), - 2: .same(proto: "transmittedPackets"), - 3: .same(proto: "receivedBytes"), - 4: .same(proto: "transmittedBytes"), - 5: .same(proto: "receivedErrors"), - 6: .same(proto: "transmittedErrors"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularUInt64Field(value: &self._receivedPackets) }() - case 2: try { try decoder.decodeSingularUInt64Field(value: &self._transmittedPackets) }() - case 3: try { try decoder.decodeSingularUInt64Field(value: &self._receivedBytes) }() - case 4: try { try decoder.decodeSingularUInt64Field(value: &self._transmittedBytes) }() - case 5: try { try decoder.decodeSingularUInt64Field(value: &self._receivedErrors) }() - case 6: try { try decoder.decodeSingularUInt64Field(value: &self._transmittedErrors) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._receivedPackets { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1) - } }() - try { if let v = self._transmittedPackets { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 2) - } }() - try { if let v = self._receivedBytes { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 3) - } }() - try { if let v = self._transmittedBytes { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 4) - } }() - try { if let v = self._receivedErrors { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 5) - } }() - try { if let v = self._transmittedErrors { - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 6) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse, rhs: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse) -> Bool { - if lhs._receivedPackets != rhs._receivedPackets {return false} - if lhs._transmittedPackets != rhs._transmittedPackets {return false} - if lhs._receivedBytes != rhs._receivedBytes {return false} - if lhs._transmittedBytes != rhs._transmittedBytes {return false} - if lhs._receivedErrors != rhs._receivedErrors {return false} - if lhs._transmittedErrors != rhs._transmittedErrors {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - extension Com_Apple_Containerization_Sandbox_V3_SyncRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".SyncRequest" public static let _protobuf_nameMap = SwiftProtobuf._NameMap() @@ -3069,3 +3094,521 @@ extension Com_Apple_Containerization_Sandbox_V3_KillResponse: SwiftProtobuf.Mess return true } } + +extension Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ContainerStatisticsRequest" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "container_ids"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedStringField(value: &self.containerIds) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.containerIds.isEmpty { + try visitor.visitRepeatedStringField(value: self.containerIds, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, rhs: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest) -> Bool { + if lhs.containerIds != rhs.containerIds {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ContainerStatisticsResponse" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "containers"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.containers) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.containers.isEmpty { + try visitor.visitRepeatedMessageField(value: self.containers, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse, rhs: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse) -> Bool { + if lhs.containers != rhs.containers {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_ContainerStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ContainerStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "container_id"), + 2: .same(proto: "process"), + 3: .same(proto: "memory"), + 4: .same(proto: "cpu"), + 5: .standard(proto: "block_io"), + 6: .same(proto: "networks"), + ] + + fileprivate class _StorageClass { + var _containerID: String = String() + var _process: Com_Apple_Containerization_Sandbox_V3_ProcessStats? = nil + var _memory: Com_Apple_Containerization_Sandbox_V3_MemoryStats? = nil + var _cpu: Com_Apple_Containerization_Sandbox_V3_CPUStats? = nil + var _blockIo: Com_Apple_Containerization_Sandbox_V3_BlockIOStats? = nil + var _networks: [Com_Apple_Containerization_Sandbox_V3_NetworkStats] = [] + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _containerID = source._containerID + _process = source._process + _memory = source._memory + _cpu = source._cpu + _blockIo = source._blockIo + _networks = source._networks + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &_storage._containerID) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._process) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._memory) }() + case 4: try { try decoder.decodeSingularMessageField(value: &_storage._cpu) }() + case 5: try { try decoder.decodeSingularMessageField(value: &_storage._blockIo) }() + case 6: try { try decoder.decodeRepeatedMessageField(value: &_storage._networks) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !_storage._containerID.isEmpty { + try visitor.visitSingularStringField(value: _storage._containerID, fieldNumber: 1) + } + try { if let v = _storage._process { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._memory { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = _storage._cpu { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = _storage._blockIo { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + if !_storage._networks.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._networks, fieldNumber: 6) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ContainerStats, rhs: Com_Apple_Containerization_Sandbox_V3_ContainerStats) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._containerID != rhs_storage._containerID {return false} + if _storage._process != rhs_storage._process {return false} + if _storage._memory != rhs_storage._memory {return false} + if _storage._cpu != rhs_storage._cpu {return false} + if _storage._blockIo != rhs_storage._blockIo {return false} + if _storage._networks != rhs_storage._networks {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_ProcessStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ProcessStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "current"), + 2: .same(proto: "limit"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.current) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.limit) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.current != 0 { + try visitor.visitSingularUInt64Field(value: self.current, fieldNumber: 1) + } + if self.limit != 0 { + try visitor.visitSingularUInt64Field(value: self.limit, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_ProcessStats, rhs: Com_Apple_Containerization_Sandbox_V3_ProcessStats) -> Bool { + if lhs.current != rhs.current {return false} + if lhs.limit != rhs.limit {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_MemoryStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".MemoryStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "usage_bytes"), + 2: .standard(proto: "limit_bytes"), + 3: .standard(proto: "swap_usage_bytes"), + 4: .standard(proto: "swap_limit_bytes"), + 5: .standard(proto: "cache_bytes"), + 6: .standard(proto: "kernel_stack_bytes"), + 7: .standard(proto: "slab_bytes"), + 8: .standard(proto: "page_faults"), + 9: .standard(proto: "major_page_faults"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.usageBytes) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.limitBytes) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.swapUsageBytes) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self.swapLimitBytes) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self.cacheBytes) }() + case 6: try { try decoder.decodeSingularUInt64Field(value: &self.kernelStackBytes) }() + case 7: try { try decoder.decodeSingularUInt64Field(value: &self.slabBytes) }() + case 8: try { try decoder.decodeSingularUInt64Field(value: &self.pageFaults) }() + case 9: try { try decoder.decodeSingularUInt64Field(value: &self.majorPageFaults) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.usageBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.usageBytes, fieldNumber: 1) + } + if self.limitBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.limitBytes, fieldNumber: 2) + } + if self.swapUsageBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.swapUsageBytes, fieldNumber: 3) + } + if self.swapLimitBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.swapLimitBytes, fieldNumber: 4) + } + if self.cacheBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.cacheBytes, fieldNumber: 5) + } + if self.kernelStackBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.kernelStackBytes, fieldNumber: 6) + } + if self.slabBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.slabBytes, fieldNumber: 7) + } + if self.pageFaults != 0 { + try visitor.visitSingularUInt64Field(value: self.pageFaults, fieldNumber: 8) + } + if self.majorPageFaults != 0 { + try visitor.visitSingularUInt64Field(value: self.majorPageFaults, fieldNumber: 9) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_MemoryStats, rhs: Com_Apple_Containerization_Sandbox_V3_MemoryStats) -> Bool { + if lhs.usageBytes != rhs.usageBytes {return false} + if lhs.limitBytes != rhs.limitBytes {return false} + if lhs.swapUsageBytes != rhs.swapUsageBytes {return false} + if lhs.swapLimitBytes != rhs.swapLimitBytes {return false} + if lhs.cacheBytes != rhs.cacheBytes {return false} + if lhs.kernelStackBytes != rhs.kernelStackBytes {return false} + if lhs.slabBytes != rhs.slabBytes {return false} + if lhs.pageFaults != rhs.pageFaults {return false} + if lhs.majorPageFaults != rhs.majorPageFaults {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_CPUStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".CPUStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "usage_usec"), + 2: .standard(proto: "user_usec"), + 3: .standard(proto: "system_usec"), + 4: .standard(proto: "throttling_periods"), + 5: .standard(proto: "throttled_periods"), + 6: .standard(proto: "throttled_time_usec"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.usageUsec) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.userUsec) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.systemUsec) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self.throttlingPeriods) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self.throttledPeriods) }() + case 6: try { try decoder.decodeSingularUInt64Field(value: &self.throttledTimeUsec) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.usageUsec != 0 { + try visitor.visitSingularUInt64Field(value: self.usageUsec, fieldNumber: 1) + } + if self.userUsec != 0 { + try visitor.visitSingularUInt64Field(value: self.userUsec, fieldNumber: 2) + } + if self.systemUsec != 0 { + try visitor.visitSingularUInt64Field(value: self.systemUsec, fieldNumber: 3) + } + if self.throttlingPeriods != 0 { + try visitor.visitSingularUInt64Field(value: self.throttlingPeriods, fieldNumber: 4) + } + if self.throttledPeriods != 0 { + try visitor.visitSingularUInt64Field(value: self.throttledPeriods, fieldNumber: 5) + } + if self.throttledTimeUsec != 0 { + try visitor.visitSingularUInt64Field(value: self.throttledTimeUsec, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_CPUStats, rhs: Com_Apple_Containerization_Sandbox_V3_CPUStats) -> Bool { + if lhs.usageUsec != rhs.usageUsec {return false} + if lhs.userUsec != rhs.userUsec {return false} + if lhs.systemUsec != rhs.systemUsec {return false} + if lhs.throttlingPeriods != rhs.throttlingPeriods {return false} + if lhs.throttledPeriods != rhs.throttledPeriods {return false} + if lhs.throttledTimeUsec != rhs.throttledTimeUsec {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_BlockIOStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BlockIOStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "devices"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.devices) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.devices.isEmpty { + try visitor.visitRepeatedMessageField(value: self.devices, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_BlockIOStats, rhs: Com_Apple_Containerization_Sandbox_V3_BlockIOStats) -> Bool { + if lhs.devices != rhs.devices {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_BlockIOEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BlockIOEntry" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "major"), + 2: .same(proto: "minor"), + 3: .standard(proto: "read_bytes"), + 4: .standard(proto: "write_bytes"), + 5: .standard(proto: "read_operations"), + 6: .standard(proto: "write_operations"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.major) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.minor) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.readBytes) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self.writeBytes) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self.readOperations) }() + case 6: try { try decoder.decodeSingularUInt64Field(value: &self.writeOperations) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.major != 0 { + try visitor.visitSingularUInt64Field(value: self.major, fieldNumber: 1) + } + if self.minor != 0 { + try visitor.visitSingularUInt64Field(value: self.minor, fieldNumber: 2) + } + if self.readBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.readBytes, fieldNumber: 3) + } + if self.writeBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.writeBytes, fieldNumber: 4) + } + if self.readOperations != 0 { + try visitor.visitSingularUInt64Field(value: self.readOperations, fieldNumber: 5) + } + if self.writeOperations != 0 { + try visitor.visitSingularUInt64Field(value: self.writeOperations, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_BlockIOEntry, rhs: Com_Apple_Containerization_Sandbox_V3_BlockIOEntry) -> Bool { + if lhs.major != rhs.major {return false} + if lhs.minor != rhs.minor {return false} + if lhs.readBytes != rhs.readBytes {return false} + if lhs.writeBytes != rhs.writeBytes {return false} + if lhs.readOperations != rhs.readOperations {return false} + if lhs.writeOperations != rhs.writeOperations {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Com_Apple_Containerization_Sandbox_V3_NetworkStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NetworkStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "interface"), + 2: .same(proto: "receivedPackets"), + 3: .same(proto: "transmittedPackets"), + 4: .same(proto: "receivedBytes"), + 5: .same(proto: "transmittedBytes"), + 6: .same(proto: "receivedErrors"), + 7: .same(proto: "transmittedErrors"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.interface) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.receivedPackets) }() + case 3: try { try decoder.decodeSingularUInt64Field(value: &self.transmittedPackets) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self.receivedBytes) }() + case 5: try { try decoder.decodeSingularUInt64Field(value: &self.transmittedBytes) }() + case 6: try { try decoder.decodeSingularUInt64Field(value: &self.receivedErrors) }() + case 7: try { try decoder.decodeSingularUInt64Field(value: &self.transmittedErrors) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.interface.isEmpty { + try visitor.visitSingularStringField(value: self.interface, fieldNumber: 1) + } + if self.receivedPackets != 0 { + try visitor.visitSingularUInt64Field(value: self.receivedPackets, fieldNumber: 2) + } + if self.transmittedPackets != 0 { + try visitor.visitSingularUInt64Field(value: self.transmittedPackets, fieldNumber: 3) + } + if self.receivedBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.receivedBytes, fieldNumber: 4) + } + if self.transmittedBytes != 0 { + try visitor.visitSingularUInt64Field(value: self.transmittedBytes, fieldNumber: 5) + } + if self.receivedErrors != 0 { + try visitor.visitSingularUInt64Field(value: self.receivedErrors, fieldNumber: 6) + } + if self.transmittedErrors != 0 { + try visitor.visitSingularUInt64Field(value: self.transmittedErrors, fieldNumber: 7) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Com_Apple_Containerization_Sandbox_V3_NetworkStats, rhs: Com_Apple_Containerization_Sandbox_V3_NetworkStats) -> Bool { + if lhs.interface != rhs.interface {return false} + if lhs.receivedPackets != rhs.receivedPackets {return false} + if lhs.transmittedPackets != rhs.transmittedPackets {return false} + if lhs.receivedBytes != rhs.receivedBytes {return false} + if lhs.transmittedBytes != rhs.transmittedBytes {return false} + if lhs.receivedErrors != rhs.receivedErrors {return false} + if lhs.transmittedErrors != rhs.transmittedErrors {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/Containerization/SandboxContext/SandboxContext.proto b/Sources/Containerization/SandboxContext/SandboxContext.proto index 91b93bdc..ea3b90ed 100644 --- a/Sources/Containerization/SandboxContext/SandboxContext.proto +++ b/Sources/Containerization/SandboxContext/SandboxContext.proto @@ -41,6 +41,9 @@ service SandboxContext { // Close IO for a given process. rpc CloseProcessStdin(CloseProcessStdinRequest) returns (CloseProcessStdinResponse); + // Get statistics for containers. + rpc ContainerStatistics(ContainerStatisticsRequest) returns (ContainerStatisticsResponse); + // Proxy a vsock port to a unix domain socket in the guest, or vice versa. rpc ProxyVsock(ProxyVsockRequest) returns (ProxyVsockResponse); // Stop a vsock proxy to a unix domain socket. @@ -58,8 +61,6 @@ service SandboxContext { rpc ConfigureDns(ConfigureDnsRequest) returns (ConfigureDnsResponse); // Configure /etc/hosts. rpc ConfigureHosts(ConfigureHostsRequest) returns (ConfigureHostsResponse); - // Get statistics about an interface. - rpc InterfaceStatistics(InterfaceStatisticsRequest) returns (InterfaceStatisticsResponse); // Perform the sync syscall. rpc Sync(SyncRequest) returns (SyncResponse); @@ -276,19 +277,6 @@ message ConfigureHostsRequest { message ConfigureHostsResponse {} -message InterfaceStatisticsRequest { - string interface = 1; -} - -message InterfaceStatisticsResponse { - optional uint64 receivedPackets = 1; - optional uint64 transmittedPackets = 2; - optional uint64 receivedBytes = 3; - optional uint64 transmittedBytes = 4; - optional uint64 receivedErrors = 5; - optional uint64 transmittedErrors = 6; -} - message SyncRequest {} message SyncResponse {} @@ -298,3 +286,69 @@ message KillRequest { } message KillResponse { int32 result = 1; } + +message ContainerStatisticsRequest { + repeated string container_ids = 1; // Empty = all containers +} + +message ContainerStatisticsResponse { + repeated ContainerStats containers = 1; +} + +message ContainerStats { + string container_id = 1; + ProcessStats process = 2; + MemoryStats memory = 3; + CPUStats cpu = 4; + BlockIOStats block_io = 5; + repeated NetworkStats networks = 6; +} + +message ProcessStats { + uint64 current = 1; + uint64 limit = 2; // 0 or max value = unlimited +} + +message MemoryStats { + uint64 usage_bytes = 1; + uint64 limit_bytes = 2; + uint64 swap_usage_bytes = 3; + uint64 swap_limit_bytes = 4; + uint64 cache_bytes = 5; + uint64 kernel_stack_bytes = 6; + uint64 slab_bytes = 7; + uint64 page_faults = 8; + uint64 major_page_faults = 9; +} + +message CPUStats { + uint64 usage_usec = 1; + uint64 user_usec = 2; + uint64 system_usec = 3; + uint64 throttling_periods = 4; + uint64 throttled_periods = 5; + uint64 throttled_time_usec = 6; +} + +message BlockIOStats { + repeated BlockIOEntry devices = 1; +} + +message BlockIOEntry { + uint64 major = 1; + uint64 minor = 2; + uint64 read_bytes = 3; + uint64 write_bytes = 4; + uint64 read_operations = 5; + uint64 write_operations = 6; +} + +message NetworkStats { + string interface = 1; + uint64 receivedPackets = 2; + uint64 transmittedPackets = 3; + uint64 receivedBytes = 4; + uint64 transmittedBytes = 5; + uint64 receivedErrors = 6; + uint64 transmittedErrors = 7; +} diff --git a/Sources/Containerization/VirtualMachineAgent.swift b/Sources/Containerization/VirtualMachineAgent.swift index b8fec02e..024f1a52 100644 --- a/Sources/Containerization/VirtualMachineAgent.swift +++ b/Sources/Containerization/VirtualMachineAgent.swift @@ -68,7 +68,9 @@ public protocol VirtualMachineAgent: Sendable { func routeAddDefault(name: String, gateway: String) async throws func configureDNS(config: DNS, location: String) async throws func configureHosts(config: Hosts, location: String) async throws - func interfaceStatistics(name: String) async throws -> InterfaceStatistics + + // Container statistics + func containerStatistics(containerIDs: [String]) async throws -> [ContainerStatistics] } extension VirtualMachineAgent { @@ -84,7 +86,7 @@ extension VirtualMachineAgent { throw ContainerizationError(.unsupported, message: "writeFile") } - public func interfaceStatistics(name: String) async throws -> InterfaceStatistics { - throw ContainerizationError(.unsupported, message: "interfaceStatistics") + public func containerStatistics(containerIDs: [String]) async throws -> [ContainerStatistics] { + throw ContainerizationError(.unsupported, message: "containerStatistics") } } diff --git a/Sources/Containerization/Vminitd.swift b/Sources/Containerization/Vminitd.swift index 3db62944..f1d1f05b 100644 --- a/Sources/Containerization/Vminitd.swift +++ b/Sources/Containerization/Vminitd.swift @@ -89,21 +89,65 @@ extension Vminitd: VirtualMachineAgent { }) } - /// Get statistics about an interface. - public func interfaceStatistics(name: String) async throws -> InterfaceStatistics { - let stats = try await client.interfaceStatistics( + /// Get statistics for containers. If `containerIDs` is empty returns stats for all containers + /// in the guest. + public func containerStatistics(containerIDs: [String]) async throws -> [ContainerStatistics] { + let response = try await client.containerStatistics( .with { - $0.interface = name + $0.containerIds = containerIDs }) - return InterfaceStatistics( - name: name, - receivedPackets: stats.hasReceivedPackets ? stats.receivedPackets : nil, - transmittedPackets: stats.hasTransmittedPackets ? stats.transmittedPackets : nil, - receivedBytes: stats.hasReceivedBytes ? stats.receivedBytes : nil, - transmittedBytes: stats.hasTransmittedBytes ? stats.transmittedBytes : nil, - receivedErrors: stats.hasReceivedErrors ? stats.receivedErrors : nil, - transmittedErrors: stats.hasTransmittedErrors ? stats.transmittedErrors : nil - ) + + return response.containers.map { protoStats in + ContainerStatistics( + id: protoStats.containerID, + process: .init( + current: protoStats.process.current, + limit: protoStats.process.limit + ), + memory: .init( + usageBytes: protoStats.memory.usageBytes, + limitBytes: protoStats.memory.limitBytes, + swapUsageBytes: protoStats.memory.swapUsageBytes, + swapLimitBytes: protoStats.memory.swapLimitBytes, + cacheBytes: protoStats.memory.cacheBytes, + kernelStackBytes: protoStats.memory.kernelStackBytes, + slabBytes: protoStats.memory.slabBytes, + pageFaults: protoStats.memory.pageFaults, + majorPageFaults: protoStats.memory.majorPageFaults + ), + cpu: .init( + usageUsec: protoStats.cpu.usageUsec, + userUsec: protoStats.cpu.userUsec, + systemUsec: protoStats.cpu.systemUsec, + throttlingPeriods: protoStats.cpu.throttlingPeriods, + throttledPeriods: protoStats.cpu.throttledPeriods, + throttledTimeUsec: protoStats.cpu.throttledTimeUsec + ), + blockIO: .init( + devices: protoStats.blockIo.devices.map { device in + .init( + major: device.major, + minor: device.minor, + readBytes: device.readBytes, + writeBytes: device.writeBytes, + readOperations: device.readOperations, + writeOperations: device.writeOperations + ) + } + ), + networks: protoStats.networks.map { network in + ContainerStatistics.NetworkStatistics( + interface: network.interface, + receivedPackets: network.receivedPackets, + transmittedPackets: network.transmittedPackets, + receivedBytes: network.receivedBytes, + transmittedBytes: network.transmittedBytes, + receivedErrors: network.receivedErrors, + transmittedErrors: network.transmittedErrors + ) + } + ) + } } /// Mount a filesystem in the sandbox's environment. diff --git a/Sources/Integration/Suite.swift b/Sources/Integration/Suite.swift index 158dd50d..feb86c6f 100644 --- a/Sources/Integration/Suite.swift +++ b/Sources/Integration/Suite.swift @@ -216,6 +216,7 @@ struct IntegrationSuite: AsyncParsableCommand { "container manager": testContainerManagerCreate, "container reuse": testContainerReuse, "container /dev/console": testContainerDevConsole, + "container statistics": testContainerStatistics, ] var passed = 0 diff --git a/Sources/Integration/VMTests.swift b/Sources/Integration/VMTests.swift index 0b6d9e1f..8c5c06ee 100644 --- a/Sources/Integration/VMTests.swift +++ b/Sources/Integration/VMTests.swift @@ -206,6 +206,49 @@ extension IntegrationSuite { } } + func testContainerStatistics() async throws { + let id = "test-container-statistics" + + let bs = try await bootstrap() + let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in + config.process.arguments = ["sleep", "infinity"] + } + + do { + try await container.create() + try await container.start() + + let stats = try await container.statistics() + + guard stats.id == id else { + throw IntegrationError.assert(msg: "stats container ID '\(stats.id)' != '\(id)'") + } + + guard stats.process.current > 0 else { + throw IntegrationError.assert(msg: "process count should be > 0, got \(stats.process.current)") + } + + guard stats.memory.usageBytes > 0 else { + throw IntegrationError.assert(msg: "memory usage should be > 0, got \(stats.memory.usageBytes)") + } + + guard stats.cpu.usageUsec > 0 else { + throw IntegrationError.assert(msg: "CPU usage should be > 0, got \(stats.cpu.usageUsec)") + } + + print("Container statistics:") + print(" Processes: \(stats.process.current)") + print(" Memory: \(stats.memory.usageBytes) bytes") + print(" CPU: \(stats.cpu.usageUsec) usec") + print(" Networks: \(stats.networks.count) interfaces") + + try await container.stop() + } catch { + try? await container.stop() + throw error + } + } + func testContainerStopIdempotency() async throws { let id = "test-container-stop-idempotency" diff --git a/vminitd/Sources/vminitd/Cgroup2Manager.swift b/vminitd/Sources/vminitd/Cgroup2Manager.swift index 1e5c8593..7a2f514b 100644 --- a/vminitd/Sources/vminitd/Cgroup2Manager.swift +++ b/vminitd/Sources/vminitd/Cgroup2Manager.swift @@ -223,6 +223,9 @@ struct Cgroup2Manager: Sendable { private func parseSingleValue(_ content: String?) -> UInt64? { guard let content = content, !content.isEmpty else { return nil } + if content == "max" { + return UInt64.max + } return UInt64(content) } diff --git a/vminitd/Sources/vminitd/ManagedContainer.swift b/vminitd/Sources/vminitd/ManagedContainer.swift index 24a392b6..b541a865 100644 --- a/vminitd/Sources/vminitd/ManagedContainer.swift +++ b/vminitd/Sources/vminitd/ManagedContainer.swift @@ -159,6 +159,10 @@ extension ManagedContainer { try self.cgroupManager.delete(force: true) } + func stats() throws -> Cgroup2Stats { + try self.cgroupManager.stats() + } + func getExecOrInit(execID: String) throws -> ManagedProcess { if execID == self.id { return self.initProcess diff --git a/vminitd/Sources/vminitd/Server+GRPC.swift b/vminitd/Sources/vminitd/Server+GRPC.swift index 8eeb16b9..56b54e60 100644 --- a/vminitd/Sources/vminitd/Server+GRPC.swift +++ b/vminitd/Sources/vminitd/Server+GRPC.swift @@ -907,44 +907,68 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid return .init() } - func interfaceStatistics( - request: Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsRequest, + func containerStatistics( + request: Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsRequest, context: GRPC.GRPCAsyncServerCallContext - ) async throws -> Com_Apple_Containerization_Sandbox_V3_InterfaceStatisticsResponse { + ) async throws -> Com_Apple_Containerization_Sandbox_V3_ContainerStatisticsResponse { log.debug( - "interfaceStatistics", + "containerStatistics", metadata: [ - "name": "\(request.interface)" + "container_ids": "\(request.containerIds)" ]) do { - let socket = try DefaultNetlinkSocket() - let session = NetlinkSession(socket: socket, log: log) - let responses = try session.linkGet(interface: request.interface, includeStats: true) - guard responses.count == 1 else { - throw ContainerizationError( - .internalError, - message: "linkGet returned invalid number of interfaces: \(responses.count)" - ) + // Get all network interfaces (skip loopback) + let interfaces = try getNetworkInterfaces() + + // Get containers to query + let containerIDs: [String] + if request.containerIds.isEmpty { + containerIDs = await Array(state.containers.keys) + } else { + containerIDs = request.containerIds } - let stats = try responses[0].getStatistics() - return .with { - if let stats { - $0.receivedPackets = stats.rxPackets - $0.transmittedPackets = stats.txPackets - $0.receivedBytes = stats.rxBytes - $0.transmittedBytes = stats.txBytes - $0.receivedErrors = stats.rxErrors - $0.transmittedErrors = stats.txErrors + + var containerStats: [Com_Apple_Containerization_Sandbox_V3_ContainerStats] = [] + + for containerID in containerIDs { + let container = try await state.get(container: containerID) + let cgStats = try await container.stats() + + // Get network stats for all interfaces + let socket = try DefaultNetlinkSocket() + let session = NetlinkSession(socket: socket, log: log) + var networkStats: [Com_Apple_Containerization_Sandbox_V3_NetworkStats] = [] + + for interface in interfaces { + let responses = try session.linkGet(interface: interface, includeStats: true) + if responses.count == 1, let stats = try responses[0].getStatistics() { + networkStats.append( + .with { + $0.interface = interface + $0.receivedPackets = stats.rxPackets + $0.transmittedPackets = stats.txPackets + $0.receivedBytes = stats.rxBytes + $0.transmittedBytes = stats.txBytes + $0.receivedErrors = stats.rxErrors + $0.transmittedErrors = stats.txErrors + }) + } } + + containerStats.append(mapStatsToProto(containerID: containerID, cgStats: cgStats, networkStats: networkStats)) + } + + return .with { + $0.containers = containerStats } } catch { log.error( - "interfaceStatistics", + "containerStatistics", metadata: [ "error": "\(error)" ]) - throw GRPCStatus(code: .internalError, message: "interfaceStatistics: \(error)") + throw GRPCStatus(code: .internalError, message: "containerStatistics: \(error)") } } @@ -958,6 +982,75 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContextAsyncProvid return error } + // NOTE: This is just crummy. It works because today the assumption is + // every NIC in the root net namespace is for the container(s), but if we + // ever supported individual containers having their own NICs/IPs then this + // logic needs to change. We only create ethernet devices today too, so that's + // what this filters for as well. + private func getNetworkInterfaces() throws -> [String] { + let netPath = URL(filePath: "/sys/class/net") + let interfaces = try FileManager.default.contentsOfDirectory( + at: netPath, + includingPropertiesForKeys: nil + ) + return + interfaces + .map { $0.lastPathComponent } + .filter { $0.hasPrefix("eth") } + } + + private func mapStatsToProto( + containerID: String, + cgStats: Cgroup2Stats, + networkStats: [Com_Apple_Containerization_Sandbox_V3_NetworkStats] + ) -> Com_Apple_Containerization_Sandbox_V3_ContainerStats { + .with { + $0.containerID = containerID + + $0.process = .with { + $0.current = cgStats.pids?.current ?? 0 + $0.limit = cgStats.pids?.max ?? 0 + } + + $0.memory = .with { + $0.usageBytes = cgStats.memory?.usage ?? 0 + $0.limitBytes = cgStats.memory?.usageLimit ?? 0 + $0.swapUsageBytes = cgStats.memory?.swapUsage ?? 0 + $0.swapLimitBytes = cgStats.memory?.swapLimit ?? 0 + $0.cacheBytes = cgStats.memory?.file ?? 0 + $0.kernelStackBytes = cgStats.memory?.kernelStack ?? 0 + $0.slabBytes = cgStats.memory?.slab ?? 0 + $0.pageFaults = cgStats.memory?.pgfault ?? 0 + $0.majorPageFaults = cgStats.memory?.pgmajfault ?? 0 + } + + $0.cpu = .with { + $0.usageUsec = cgStats.cpu?.usageUsec ?? 0 + $0.userUsec = cgStats.cpu?.userUsec ?? 0 + $0.systemUsec = cgStats.cpu?.systemUsec ?? 0 + $0.throttlingPeriods = cgStats.cpu?.nrPeriods ?? 0 + $0.throttledPeriods = cgStats.cpu?.nrThrottled ?? 0 + $0.throttledTimeUsec = cgStats.cpu?.throttledUsec ?? 0 + } + + $0.blockIo = .with { + $0.devices = + cgStats.io?.entries.map { entry in + .with { + $0.major = entry.major + $0.minor = entry.minor + $0.readBytes = entry.rbytes + $0.writeBytes = entry.wbytes + $0.readOperations = entry.rios + $0.writeOperations = entry.wios + } + } ?? [] + } + + $0.networks = networkStats + } + } + func sync( request: Com_Apple_Containerization_Sandbox_V3_SyncRequest, context: GRPC.GRPCAsyncServerCallContext