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