Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Sources/Containerization/ContainerStatistics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ public struct ContainerStatistics: Sendable {
public var transmittedBytes: UInt64
public var receivedErrors: UInt64
public var transmittedErrors: UInt64
/// Packets the device dropped (e.g. full rx ring or tx queue).
public var receivedDropped: UInt64
public var transmittedDropped: UInt64

public init(
interface: String,
Expand All @@ -190,7 +193,9 @@ public struct ContainerStatistics: Sendable {
receivedBytes: UInt64,
transmittedBytes: UInt64,
receivedErrors: UInt64,
transmittedErrors: UInt64
transmittedErrors: UInt64,
receivedDropped: UInt64,
transmittedDropped: UInt64
) {
self.interface = interface
self.receivedPackets = receivedPackets
Expand All @@ -199,6 +204,8 @@ public struct ContainerStatistics: Sendable {
self.transmittedBytes = transmittedBytes
self.receivedErrors = receivedErrors
self.transmittedErrors = transmittedErrors
self.receivedDropped = receivedDropped
self.transmittedDropped = transmittedDropped
}
}

Expand Down
17 changes: 16 additions & 1 deletion Sources/Containerization/SandboxContext/SandboxContext.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,11 @@ public nonisolated struct Com_Apple_Containerization_Sandbox_V3_NetworkStats: Se

public var transmittedErrors: UInt64 = 0

/// Packets the device dropped (e.g. full rx ring or tx queue), from rtnl_link_stats64.
public var receivedDropped: UInt64 = 0

public var transmittedDropped: UInt64 = 0

public var unknownFields = SwiftProtobuf.UnknownStorage()

public init() {}
Expand Down Expand Up @@ -4670,7 +4675,7 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_BlockIOEntry: SwiftP

nonisolated 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(bytecode: "\0\u{1}interface\0\u{1}receivedPackets\0\u{1}transmittedPackets\0\u{1}receivedBytes\0\u{1}transmittedBytes\0\u{1}receivedErrors\0\u{1}transmittedErrors\0")
public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}interface\0\u{1}receivedPackets\0\u{1}transmittedPackets\0\u{1}receivedBytes\0\u{1}transmittedBytes\0\u{1}receivedErrors\0\u{1}transmittedErrors\0\u{1}receivedDropped\0\u{1}transmittedDropped\0")

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
Expand All @@ -4685,6 +4690,8 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_NetworkStats: SwiftP
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) }()
case 8: try { try decoder.decodeSingularUInt64Field(value: &self.receivedDropped) }()
case 9: try { try decoder.decodeSingularUInt64Field(value: &self.transmittedDropped) }()
default: break
}
}
Expand Down Expand Up @@ -4712,6 +4719,12 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_NetworkStats: SwiftP
if self.transmittedErrors != 0 {
try visitor.visitSingularUInt64Field(value: self.transmittedErrors, fieldNumber: 7)
}
if self.receivedDropped != 0 {
try visitor.visitSingularUInt64Field(value: self.receivedDropped, fieldNumber: 8)
}
if self.transmittedDropped != 0 {
try visitor.visitSingularUInt64Field(value: self.transmittedDropped, fieldNumber: 9)
}
try unknownFields.traverse(visitor: &visitor)
}

Expand All @@ -4723,6 +4736,8 @@ nonisolated extension Com_Apple_Containerization_Sandbox_V3_NetworkStats: SwiftP
if lhs.transmittedBytes != rhs.transmittedBytes {return false}
if lhs.receivedErrors != rhs.receivedErrors {return false}
if lhs.transmittedErrors != rhs.transmittedErrors {return false}
if lhs.receivedDropped != rhs.receivedDropped {return false}
if lhs.transmittedDropped != rhs.transmittedDropped {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down
3 changes: 3 additions & 0 deletions Sources/Containerization/SandboxContext/SandboxContext.proto
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,9 @@ message NetworkStats {
uint64 transmittedBytes = 5;
uint64 receivedErrors = 6;
uint64 transmittedErrors = 7;
// Packets the device dropped (e.g. full rx ring or tx queue), from rtnl_link_stats64.
uint64 receivedDropped = 8;
uint64 transmittedDropped = 9;
}

// Memory event counters from cgroup2's memory.events file.
Expand Down
4 changes: 3 additions & 1 deletion Sources/Containerization/Vminitd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ extension Vminitd: VirtualMachineAgent {
receivedBytes: network.receivedBytes,
transmittedBytes: network.transmittedBytes,
receivedErrors: network.receivedErrors,
transmittedErrors: network.transmittedErrors
transmittedErrors: network.transmittedErrors,
receivedDropped: network.receivedDropped,
transmittedDropped: network.transmittedDropped
)
} : nil,
memoryEvents: categories.contains(.memoryEvents) && protoStats.hasMemoryEvents
Expand Down
65 changes: 65 additions & 0 deletions Sources/Integration/ContainerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5146,6 +5146,71 @@ extension IntegrationSuite {
}
}

@available(macOS 26.0, *)
func testContainerNetworkInterfaceStatistics() async throws {
let id = "test-container-network-interface-statistics"
let bs = try await bootstrap(id)

let network = try VmnetNetwork()
var manager = try ContainerManager(vmm: bs.vmm, network: network)
defer {
try? manager.delete(id)
}

let container = try await manager.create(
id,
image: bs.image,
rootfs: bs.rootfs
) { config in
config.process.arguments = ["sleep", "infinity"]
config.bootLog = bs.bootLog
}

do {
try await container.create()
try await container.start()

func eth0(_ stats: ContainerStatistics) throws -> ContainerStatistics.NetworkStatistics {
guard let eth0 = stats.networks?.first(where: { $0.interface == "eth0" }) else {
throw IntegrationError.assert(msg: "expected eth0 in network statistics, got \(stats.networks?.map(\.interface) ?? [])")
}
return eth0
}

let before = try eth0(try await container.statistics(categories: .network))

// Generate some traffic across the interface. The gateway may not answer
// ping, so the exit code is ignored; the packets still go out eth0.
let exec = try await container.exec("ping-gateway") { config in
config.arguments = ["sh", "-c", "ping -c 3 -W 1 $(ip route | awk '/default/ {print $3; exit}'); true"]
}
try await exec.start()
_ = try await exec.wait()
try await exec.delete()

let after = try eth0(try await container.statistics(categories: .network))

guard after.transmittedPackets > before.transmittedPackets else {
throw IntegrationError.assert(msg: "expected eth0 transmittedPackets to grow, got \(before.transmittedPackets) -> \(after.transmittedPackets)")
}

// Drop counters are cumulative, so they must never go backwards.
guard after.receivedDropped >= before.receivedDropped, after.transmittedDropped >= before.transmittedDropped else {
throw IntegrationError.assert(
msg:
"eth0 dropped counters decreased: rx \(before.receivedDropped)->\(after.receivedDropped) tx \(before.transmittedDropped)->\(after.transmittedDropped)"
)
}

try await container.kill(.kill)
try await container.wait()
try await container.stop()
} catch {
try? await container.stop()
throw error
}
}

@available(macOS 26.0, *)
func testNetworkingEnabledIPv6() async throws {
let id = "test-networking-enabled-ipv6"
Expand Down
1 change: 1 addition & 0 deletions Sources/Integration/Suite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ struct IntegrationSuite: AsyncParsableCommand {
Test("container interface custom MTU", testInterfaceMTU),
Test("container networking disabled", testNetworkingDisabled),
Test("container networking enabled", testNetworkingEnabled),
Test("container network interface statistics", testContainerNetworkInterfaceStatistics),
Test("container networking enabled ipv6", testNetworkingEnabledIPv6),
Test("container IPv6 address", testIPv6AddressAdd),
Test("container IPv6 default route", testIPv6DefaultRoute),
Expand Down
2 changes: 2 additions & 0 deletions vminitd/Sources/VminitdCore/Server+GRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1713,6 +1713,8 @@ extension Initd: Com_Apple_Containerization_Sandbox_V3_SandboxContext.SimpleServ
$0.transmittedBytes = stats.txBytes
$0.receivedErrors = stats.rxErrors
$0.transmittedErrors = stats.txErrors
$0.receivedDropped = stats.rxDropped
$0.transmittedDropped = stats.txDropped
})
}
}
Expand Down
Loading