Skip to content

Commit 06d3a37

Browse files
authored
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.
1 parent 85de8fc commit 06d3a37

File tree

13 files changed

+1282
-343
lines changed

13 files changed

+1282
-343
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2025 Apple Inc. and the Containerization project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
/// Statistics for a container.
18+
public struct ContainerStatistics: Sendable {
19+
public var id: String
20+
public var process: ProcessStatistics
21+
public var memory: MemoryStatistics
22+
public var cpu: CPUStatistics
23+
public var blockIO: BlockIOStatistics
24+
public var networks: [NetworkStatistics]
25+
26+
public init(
27+
id: String,
28+
process: ProcessStatistics,
29+
memory: MemoryStatistics,
30+
cpu: CPUStatistics,
31+
blockIO: BlockIOStatistics,
32+
networks: [NetworkStatistics]
33+
) {
34+
self.id = id
35+
self.process = process
36+
self.memory = memory
37+
self.cpu = cpu
38+
self.blockIO = blockIO
39+
self.networks = networks
40+
}
41+
42+
/// Process statistics for a container.
43+
public struct ProcessStatistics: Sendable {
44+
public var current: UInt64
45+
public var limit: UInt64
46+
47+
public init(current: UInt64, limit: UInt64) {
48+
self.current = current
49+
self.limit = limit
50+
}
51+
}
52+
53+
/// Memory statistics for a container.
54+
public struct MemoryStatistics: Sendable {
55+
public var usageBytes: UInt64
56+
public var limitBytes: UInt64
57+
public var swapUsageBytes: UInt64
58+
public var swapLimitBytes: UInt64
59+
public var cacheBytes: UInt64
60+
public var kernelStackBytes: UInt64
61+
public var slabBytes: UInt64
62+
public var pageFaults: UInt64
63+
public var majorPageFaults: UInt64
64+
65+
public init(
66+
usageBytes: UInt64,
67+
limitBytes: UInt64,
68+
swapUsageBytes: UInt64,
69+
swapLimitBytes: UInt64,
70+
cacheBytes: UInt64,
71+
kernelStackBytes: UInt64,
72+
slabBytes: UInt64,
73+
pageFaults: UInt64,
74+
majorPageFaults: UInt64
75+
) {
76+
self.usageBytes = usageBytes
77+
self.limitBytes = limitBytes
78+
self.swapUsageBytes = swapUsageBytes
79+
self.swapLimitBytes = swapLimitBytes
80+
self.cacheBytes = cacheBytes
81+
self.kernelStackBytes = kernelStackBytes
82+
self.slabBytes = slabBytes
83+
self.pageFaults = pageFaults
84+
self.majorPageFaults = majorPageFaults
85+
}
86+
}
87+
88+
/// CPU statistics for a container.
89+
public struct CPUStatistics: Sendable {
90+
public var usageUsec: UInt64
91+
public var userUsec: UInt64
92+
public var systemUsec: UInt64
93+
public var throttlingPeriods: UInt64
94+
public var throttledPeriods: UInt64
95+
public var throttledTimeUsec: UInt64
96+
97+
public init(
98+
usageUsec: UInt64,
99+
userUsec: UInt64,
100+
systemUsec: UInt64,
101+
throttlingPeriods: UInt64,
102+
throttledPeriods: UInt64,
103+
throttledTimeUsec: UInt64
104+
) {
105+
self.usageUsec = usageUsec
106+
self.userUsec = userUsec
107+
self.systemUsec = systemUsec
108+
self.throttlingPeriods = throttlingPeriods
109+
self.throttledPeriods = throttledPeriods
110+
self.throttledTimeUsec = throttledTimeUsec
111+
}
112+
}
113+
114+
/// Block I/O statistics for a container.
115+
public struct BlockIOStatistics: Sendable {
116+
public var devices: [BlockIODevice]
117+
118+
public init(devices: [BlockIODevice]) {
119+
self.devices = devices
120+
}
121+
}
122+
123+
/// Block I/O statistics for a specific device.
124+
public struct BlockIODevice: Sendable {
125+
public var major: UInt64
126+
public var minor: UInt64
127+
public var readBytes: UInt64
128+
public var writeBytes: UInt64
129+
public var readOperations: UInt64
130+
public var writeOperations: UInt64
131+
132+
public init(
133+
major: UInt64,
134+
minor: UInt64,
135+
readBytes: UInt64,
136+
writeBytes: UInt64,
137+
readOperations: UInt64,
138+
writeOperations: UInt64
139+
) {
140+
self.major = major
141+
self.minor = minor
142+
self.readBytes = readBytes
143+
self.writeBytes = writeBytes
144+
self.readOperations = readOperations
145+
self.writeOperations = writeOperations
146+
}
147+
}
148+
149+
/// Statistics for a network interface.
150+
public struct NetworkStatistics: Sendable {
151+
public var interface: String
152+
public var receivedPackets: UInt64
153+
public var transmittedPackets: UInt64
154+
public var receivedBytes: UInt64
155+
public var transmittedBytes: UInt64
156+
public var receivedErrors: UInt64
157+
public var transmittedErrors: UInt64
158+
159+
public init(
160+
interface: String,
161+
receivedPackets: UInt64,
162+
transmittedPackets: UInt64,
163+
receivedBytes: UInt64,
164+
transmittedBytes: UInt64,
165+
receivedErrors: UInt64,
166+
transmittedErrors: UInt64
167+
) {
168+
self.interface = interface
169+
self.receivedPackets = receivedPackets
170+
self.transmittedPackets = transmittedPackets
171+
self.receivedBytes = receivedBytes
172+
self.transmittedBytes = transmittedBytes
173+
self.receivedErrors = receivedErrors
174+
self.transmittedErrors = transmittedErrors
175+
}
176+
}
177+
}

Sources/Containerization/InterfaceStatistics.swift

Lines changed: 0 additions & 44 deletions
This file was deleted.

Sources/Containerization/LinuxContainer.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,24 @@ extension LinuxContainer {
821821
return try await state.process.closeStdin()
822822
}
823823

824+
/// Get statistics for the container.
825+
public func statistics() async throws -> ContainerStatistics {
826+
let state = try self.state.withLock { try $0.startedState("statistics") }
827+
828+
let stats = try await state.vm.withAgent { agent in
829+
let allStats = try await agent.containerStatistics(containerIDs: [self.id])
830+
guard let containerStats = allStats.first else {
831+
throw ContainerizationError(
832+
.notFound,
833+
message: "statistics for container \(self.id) not found"
834+
)
835+
}
836+
return containerStats
837+
}
838+
839+
return stats
840+
}
841+
824842
/// Relay a unix socket from in the container to the host, or from the host
825843
/// to inside the container.
826844
public func relayUnixSocket(socket: UnixSocketConfiguration) async throws {
@@ -865,11 +883,12 @@ extension LinuxContainer {
865883
extension VirtualMachineInstance {
866884
/// Scoped access to an agent instance to ensure the resources are always freed (mostly close(2)'ing
867885
/// the vsock fd)
868-
fileprivate func withAgent(fn: @Sendable (VirtualMachineAgent) async throws -> Void) async throws {
886+
fileprivate func withAgent<T>(fn: @Sendable (VirtualMachineAgent) async throws -> T) async throws -> T {
869887
let agent = try await self.dialAgent()
870888
do {
871-
try await fn(agent)
889+
let result = try await fn(agent)
872890
try await agent.close()
891+
return result
873892
} catch {
874893
try? await agent.close()
875894
throw error

0 commit comments

Comments
 (0)