mirror of
https://github.com/apple/container.git
synced 2026-09-26 01:25:52 +00:00
Vminitd: Add cgroup stats support (#306)
This is not wired up to anything right now, but it will be used for implementing stats gathering on the host via an rpc in the future. I've left off the rpc portion as this is already a decent amount to review.
This commit is contained in:
@@ -197,6 +197,354 @@ struct Cgroup2Manager: Sendable {
|
||||
}
|
||||
try FileManager.default.removeItem(at: self.path)
|
||||
}
|
||||
|
||||
func stats() throws -> Cgroup2Stats {
|
||||
let pidsStats = try self.readPidsStats()
|
||||
let memoryStats = try self.readMemoryStats()
|
||||
let cpuStats = try self.readCPUStats()
|
||||
let ioStats = try self.readIOStats()
|
||||
|
||||
return Cgroup2Stats(
|
||||
pids: pidsStats,
|
||||
memory: memoryStats,
|
||||
cpu: cpuStats,
|
||||
io: ioStats
|
||||
)
|
||||
}
|
||||
|
||||
private func readFileContent(fileName: String) throws -> String? {
|
||||
let filePath = self.path.appending(path: fileName)
|
||||
guard FileManager.default.fileExists(atPath: filePath.path) else {
|
||||
return nil
|
||||
}
|
||||
return try String(contentsOf: filePath, encoding: .utf8)
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
private func parseSingleValue(_ content: String?) -> UInt64? {
|
||||
guard let content = content, !content.isEmpty else { return nil }
|
||||
return UInt64(content)
|
||||
}
|
||||
|
||||
private func parseKeyValuePairs(_ content: String?) -> [String: UInt64] {
|
||||
guard let content = content else { return [:] }
|
||||
var result: [String: UInt64] = [:]
|
||||
|
||||
for line in content.components(separatedBy: .newlines) {
|
||||
let parts = line.components(separatedBy: .whitespaces)
|
||||
if parts.count == 2, let value = UInt64(parts[1]) {
|
||||
result[parts[0]] = value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func readPidsStats() throws -> PidsStats? {
|
||||
guard let currentContent = try readFileContent(fileName: "pids.current"),
|
||||
let current = parseSingleValue(currentContent)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let maxContent = try readFileContent(fileName: "pids.max")
|
||||
let max = parseSingleValue(maxContent)
|
||||
|
||||
return PidsStats(current: current, max: max)
|
||||
}
|
||||
|
||||
private func readMemoryStats() throws -> MemoryStats? {
|
||||
guard let usageContent = try readFileContent(fileName: "memory.current"),
|
||||
let usage = parseSingleValue(usageContent)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let usageLimit = parseSingleValue(try readFileContent(fileName: "memory.max"))
|
||||
let swapUsage = parseSingleValue(try readFileContent(fileName: "memory.swap.current"))
|
||||
let swapLimit = parseSingleValue(try readFileContent(fileName: "memory.swap.max"))
|
||||
|
||||
let statContent = try readFileContent(fileName: "memory.stat")
|
||||
let statValues = parseKeyValuePairs(statContent)
|
||||
|
||||
return MemoryStats(
|
||||
usage: usage,
|
||||
usageLimit: usageLimit,
|
||||
swapUsage: swapUsage,
|
||||
swapLimit: swapLimit,
|
||||
anon: statValues["anon"] ?? 0,
|
||||
file: statValues["file"] ?? 0,
|
||||
kernelStack: statValues["kernel_stack"] ?? 0,
|
||||
slab: statValues["slab"] ?? 0,
|
||||
sock: statValues["sock"] ?? 0,
|
||||
shmem: statValues["shmem"] ?? 0,
|
||||
fileMapped: statValues["file_mapped"] ?? 0,
|
||||
fileDirty: statValues["file_dirty"] ?? 0,
|
||||
fileWriteback: statValues["file_writeback"] ?? 0,
|
||||
pgfault: statValues["pgfault"] ?? 0,
|
||||
pgmajfault: statValues["pgmajfault"] ?? 0,
|
||||
workingsetRefault: statValues["workingset_refault"] ?? 0,
|
||||
workingsetActivate: statValues["workingset_activate"] ?? 0,
|
||||
workingsetNodereclaim: statValues["workingset_nodereclaim"] ?? 0,
|
||||
inactiveAnon: statValues["inactive_anon"] ?? 0,
|
||||
activeAnon: statValues["active_anon"] ?? 0,
|
||||
inactiveFile: statValues["inactive_file"] ?? 0,
|
||||
activeFile: statValues["active_file"] ?? 0
|
||||
)
|
||||
}
|
||||
|
||||
private func readCPUStats() throws -> CPUStats? {
|
||||
let statContent = try readFileContent(fileName: "cpu.stat")
|
||||
let statValues = parseKeyValuePairs(statContent)
|
||||
|
||||
guard !statValues.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return CPUStats(
|
||||
usageUsec: statValues["usage_usec"] ?? 0,
|
||||
userUsec: statValues["user_usec"] ?? 0,
|
||||
systemUsec: statValues["system_usec"] ?? 0,
|
||||
nrPeriods: statValues["nr_periods"] ?? 0,
|
||||
nrThrottled: statValues["nr_throttled"] ?? 0,
|
||||
throttledUsec: statValues["throttled_usec"] ?? 0
|
||||
)
|
||||
}
|
||||
|
||||
private func readIOStats() throws -> IOStats? {
|
||||
guard let statContent = try readFileContent(fileName: "io.stat") else {
|
||||
return IOStats(entries: [])
|
||||
}
|
||||
|
||||
var entries: [IOEntry] = []
|
||||
|
||||
for line in statContent.components(separatedBy: .newlines) {
|
||||
guard !line.isEmpty else { continue }
|
||||
|
||||
let parts = line.components(separatedBy: .whitespaces)
|
||||
guard parts.count >= 2 else { continue }
|
||||
|
||||
let deviceParts = parts[0].components(separatedBy: ":")
|
||||
guard deviceParts.count == 2,
|
||||
let major = UInt64(deviceParts[0]),
|
||||
let minor = UInt64(deviceParts[1])
|
||||
else {
|
||||
continue
|
||||
}
|
||||
|
||||
var rbytes: UInt64 = 0
|
||||
var wbytes: UInt64 = 0
|
||||
var rios: UInt64 = 0
|
||||
var wios: UInt64 = 0
|
||||
var dbytes: UInt64 = 0
|
||||
var dios: UInt64 = 0
|
||||
|
||||
for i in 1..<parts.count {
|
||||
let keyValue = parts[i].components(separatedBy: "=")
|
||||
guard keyValue.count == 2, let value = UInt64(keyValue[1]) else { continue }
|
||||
|
||||
switch keyValue[0] {
|
||||
case "rbytes":
|
||||
rbytes = value
|
||||
case "wbytes":
|
||||
wbytes = value
|
||||
case "rios":
|
||||
rios = value
|
||||
case "wios":
|
||||
wios = value
|
||||
case "dbytes":
|
||||
dbytes = value
|
||||
case "dios":
|
||||
dios = value
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
entries.append(
|
||||
IOEntry(
|
||||
major: major,
|
||||
minor: minor,
|
||||
rbytes: rbytes,
|
||||
wbytes: wbytes,
|
||||
rios: rios,
|
||||
wios: wios,
|
||||
dbytes: dbytes,
|
||||
dios: dios
|
||||
))
|
||||
}
|
||||
|
||||
return IOStats(entries: entries)
|
||||
}
|
||||
}
|
||||
|
||||
struct Cgroup2Stats: Sendable {
|
||||
var pids: PidsStats?
|
||||
var memory: MemoryStats?
|
||||
var cpu: CPUStats?
|
||||
var io: IOStats?
|
||||
|
||||
init(
|
||||
pids: PidsStats? = nil,
|
||||
memory: MemoryStats? = nil,
|
||||
cpu: CPUStats? = nil,
|
||||
io: IOStats? = nil
|
||||
) {
|
||||
self.pids = pids
|
||||
self.memory = memory
|
||||
self.cpu = cpu
|
||||
self.io = io
|
||||
}
|
||||
}
|
||||
|
||||
struct PidsStats: Sendable {
|
||||
var current: UInt64
|
||||
var max: UInt64?
|
||||
|
||||
init(current: UInt64, max: UInt64? = nil) {
|
||||
self.current = current
|
||||
self.max = max
|
||||
}
|
||||
}
|
||||
|
||||
struct MemoryStats: Sendable {
|
||||
var usage: UInt64
|
||||
var usageLimit: UInt64?
|
||||
var swapUsage: UInt64?
|
||||
var swapLimit: UInt64?
|
||||
|
||||
var anon: UInt64
|
||||
var file: UInt64
|
||||
var kernelStack: UInt64
|
||||
var slab: UInt64
|
||||
var sock: UInt64
|
||||
var shmem: UInt64
|
||||
var fileMapped: UInt64
|
||||
var fileDirty: UInt64
|
||||
var fileWriteback: UInt64
|
||||
|
||||
var pgfault: UInt64
|
||||
var pgmajfault: UInt64
|
||||
|
||||
var workingsetRefault: UInt64
|
||||
var workingsetActivate: UInt64
|
||||
var workingsetNodereclaim: UInt64
|
||||
|
||||
var inactiveAnon: UInt64
|
||||
var activeAnon: UInt64
|
||||
var inactiveFile: UInt64
|
||||
var activeFile: UInt64
|
||||
|
||||
init(
|
||||
usage: UInt64,
|
||||
usageLimit: UInt64? = nil,
|
||||
swapUsage: UInt64? = nil,
|
||||
swapLimit: UInt64? = nil,
|
||||
anon: UInt64 = 0,
|
||||
file: UInt64 = 0,
|
||||
kernelStack: UInt64 = 0,
|
||||
slab: UInt64 = 0,
|
||||
sock: UInt64 = 0,
|
||||
shmem: UInt64 = 0,
|
||||
fileMapped: UInt64 = 0,
|
||||
fileDirty: UInt64 = 0,
|
||||
fileWriteback: UInt64 = 0,
|
||||
pgfault: UInt64 = 0,
|
||||
pgmajfault: UInt64 = 0,
|
||||
workingsetRefault: UInt64 = 0,
|
||||
workingsetActivate: UInt64 = 0,
|
||||
workingsetNodereclaim: UInt64 = 0,
|
||||
inactiveAnon: UInt64 = 0,
|
||||
activeAnon: UInt64 = 0,
|
||||
inactiveFile: UInt64 = 0,
|
||||
activeFile: UInt64 = 0
|
||||
) {
|
||||
self.usage = usage
|
||||
self.usageLimit = usageLimit
|
||||
self.swapUsage = swapUsage
|
||||
self.swapLimit = swapLimit
|
||||
self.anon = anon
|
||||
self.file = file
|
||||
self.kernelStack = kernelStack
|
||||
self.slab = slab
|
||||
self.sock = sock
|
||||
self.shmem = shmem
|
||||
self.fileMapped = fileMapped
|
||||
self.fileDirty = fileDirty
|
||||
self.fileWriteback = fileWriteback
|
||||
self.pgfault = pgfault
|
||||
self.pgmajfault = pgmajfault
|
||||
self.workingsetRefault = workingsetRefault
|
||||
self.workingsetActivate = workingsetActivate
|
||||
self.workingsetNodereclaim = workingsetNodereclaim
|
||||
self.inactiveAnon = inactiveAnon
|
||||
self.activeAnon = activeAnon
|
||||
self.inactiveFile = inactiveFile
|
||||
self.activeFile = activeFile
|
||||
}
|
||||
}
|
||||
|
||||
struct CPUStats: Sendable {
|
||||
var usageUsec: UInt64
|
||||
var userUsec: UInt64
|
||||
var systemUsec: UInt64
|
||||
var nrPeriods: UInt64
|
||||
var nrThrottled: UInt64
|
||||
var throttledUsec: UInt64
|
||||
|
||||
init(
|
||||
usageUsec: UInt64 = 0,
|
||||
userUsec: UInt64 = 0,
|
||||
systemUsec: UInt64 = 0,
|
||||
nrPeriods: UInt64 = 0,
|
||||
nrThrottled: UInt64 = 0,
|
||||
throttledUsec: UInt64 = 0
|
||||
) {
|
||||
self.usageUsec = usageUsec
|
||||
self.userUsec = userUsec
|
||||
self.systemUsec = systemUsec
|
||||
self.nrPeriods = nrPeriods
|
||||
self.nrThrottled = nrThrottled
|
||||
self.throttledUsec = throttledUsec
|
||||
}
|
||||
}
|
||||
|
||||
struct IOStats: Sendable {
|
||||
var entries: [IOEntry]
|
||||
|
||||
init(entries: [IOEntry] = []) {
|
||||
self.entries = entries
|
||||
}
|
||||
}
|
||||
|
||||
struct IOEntry: Sendable {
|
||||
var major: UInt64
|
||||
var minor: UInt64
|
||||
var rbytes: UInt64
|
||||
var wbytes: UInt64
|
||||
var rios: UInt64
|
||||
var wios: UInt64
|
||||
var dbytes: UInt64
|
||||
var dios: UInt64
|
||||
|
||||
init(
|
||||
major: UInt64,
|
||||
minor: UInt64,
|
||||
rbytes: UInt64 = 0,
|
||||
wbytes: UInt64 = 0,
|
||||
rios: UInt64 = 0,
|
||||
wios: UInt64 = 0,
|
||||
dbytes: UInt64 = 0,
|
||||
dios: UInt64 = 0
|
||||
) {
|
||||
self.major = major
|
||||
self.minor = minor
|
||||
self.rbytes = rbytes
|
||||
self.wbytes = wbytes
|
||||
self.rios = rios
|
||||
self.wios = wios
|
||||
self.dbytes = dbytes
|
||||
self.dios = dios
|
||||
}
|
||||
}
|
||||
|
||||
extension Cgroup2Manager {
|
||||
|
||||
Reference in New Issue
Block a user