mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 20:43:02 +02:00
Merge pull request #1327 from kernelkit/container-limits
Container limits
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
"""Operational data provider for infix-containers YANG model.
|
||||
|
||||
Collects container status, network info, resource limits from cgroups,
|
||||
and runtime statistics via podman commands.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
from .common import LOG
|
||||
from .host import HOST
|
||||
|
||||
@@ -46,6 +54,165 @@ def network(ps, inspect):
|
||||
return net
|
||||
|
||||
|
||||
def parse_size_kib(size_str):
|
||||
"""Parse size string like '1.5MB' or '512kB' to KiB (kibibytes)."""
|
||||
if not size_str:
|
||||
return 0
|
||||
|
||||
size_str = size_str.strip().upper()
|
||||
|
||||
# Extract numeric part and unit
|
||||
match = re.match(r'([0-9.]+)\s*([KMGT]?I?B)?', size_str)
|
||||
if not match:
|
||||
return 0
|
||||
|
||||
value = float(match.group(1))
|
||||
unit = match.group(2) if match.group(2) else 'B'
|
||||
|
||||
# Convert to KiB (kibibytes)
|
||||
multipliers = {
|
||||
'B': 1/1024,
|
||||
'KB': 1000/1024, 'KIB': 1,
|
||||
'MB': (1000**2)/1024, 'MIB': 1024,
|
||||
'GB': (1000**3)/1024, 'GIB': 1024**2,
|
||||
'TB': (1000**4)/1024, 'TIB': 1024**3,
|
||||
}
|
||||
|
||||
return int(value * multipliers.get(unit, 1))
|
||||
|
||||
|
||||
def parse_cgroup_memory(mem_str):
|
||||
"""Parse cgroup memory.max value (bytes) to KiB."""
|
||||
if not mem_str or mem_str == "max":
|
||||
return 0
|
||||
try:
|
||||
mem_bytes = int(mem_str)
|
||||
return mem_bytes // 1024
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
|
||||
def parse_cgroup_cpu(cpu_str):
|
||||
"""Parse cgroup cpu.max value to millicores."""
|
||||
if not cpu_str:
|
||||
return 0
|
||||
parts = cpu_str.split()
|
||||
if len(parts) != 2 or parts[0] == "max":
|
||||
return 0
|
||||
try:
|
||||
quota = int(parts[0])
|
||||
period = int(parts[1])
|
||||
# Convert to millicores: (quota/period) * 1000
|
||||
return (quota * 1000) // period
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
|
||||
def read_cgroup_limits(inspect):
|
||||
"""Read resource limits from cgroup files for a container."""
|
||||
if not inspect or not isinstance(inspect, dict):
|
||||
return None
|
||||
|
||||
cgroup_path = inspect.get("State", {}).get("CgroupPath")
|
||||
if not cgroup_path:
|
||||
return None
|
||||
|
||||
cgroup_base = f"/sys/fs/cgroup{cgroup_path}"
|
||||
mem_val = 0
|
||||
cpu_val = 0
|
||||
|
||||
try:
|
||||
# Read memory limit (in bytes, convert to KiB)
|
||||
mem_max_path = os.path.join(cgroup_base, "memory.max")
|
||||
if os.path.exists(mem_max_path):
|
||||
with open(mem_max_path, 'r') as f:
|
||||
mem_str = f.read().strip()
|
||||
mem_val = parse_cgroup_memory(mem_str)
|
||||
|
||||
# Read CPU limit (quota and period in microseconds, convert to millicores)
|
||||
cpu_max_path = os.path.join(cgroup_base, "cpu.max")
|
||||
if os.path.exists(cpu_max_path):
|
||||
with open(cpu_max_path, 'r') as f:
|
||||
cpu_str = f.read().strip()
|
||||
cpu_val = parse_cgroup_cpu(cpu_str)
|
||||
except Exception as e:
|
||||
LOG.error(f"failed reading cgroup limits: {e}")
|
||||
return None
|
||||
|
||||
if mem_val > 0 or cpu_val > 0:
|
||||
result = {}
|
||||
if mem_val > 0:
|
||||
result["memory"] = f"{mem_val}"
|
||||
if cpu_val > 0:
|
||||
result["cpu"] = cpu_val
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def resource_stats(name):
|
||||
"""Get resource usage stats for a running container using podman stats."""
|
||||
cmd = ['podman', 'stats', '--no-stream', '--format', 'json', '--no-reset', name]
|
||||
try:
|
||||
stats = HOST.run_json(cmd, default=[])
|
||||
if not stats or len(stats) == 0:
|
||||
return None
|
||||
|
||||
stat = stats[0]
|
||||
rusage = {}
|
||||
|
||||
# Memory usage - parse used memory, convert to KiB
|
||||
# Encode as string for uint64 compatibility
|
||||
mem_usage_str = stat.get("mem_usage", "")
|
||||
if "/" in mem_usage_str:
|
||||
mem_used_str = mem_usage_str.split("/")[0].strip()
|
||||
mem_used_kib = parse_size_kib(mem_used_str)
|
||||
rusage["memory"] = f"{mem_used_kib}"
|
||||
|
||||
# CPU percentage - format as decimal64 with 2 fractional digits
|
||||
cpu_perc = stat.get("cpu_percent", "0%").rstrip("%")
|
||||
try:
|
||||
rusage["cpu"] = "{:.2f}".format(float(cpu_perc))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
block_io = stat.get("block_io", "0B / 0B")
|
||||
if "/" in block_io:
|
||||
block_read_str, block_write_str = block_io.split("/")
|
||||
block_read_kib = parse_size_kib(block_read_str.strip())
|
||||
block_write_kib = parse_size_kib(block_write_str.strip())
|
||||
|
||||
rusage["block-io"] = {}
|
||||
if block_read_kib > 0:
|
||||
rusage["block-io"]["read"] = f"{block_read_kib}"
|
||||
if block_write_kib > 0:
|
||||
rusage["block-io"]["write"] = f"{block_write_kib}"
|
||||
|
||||
net_io = stat.get("net_io", "0B / 0B")
|
||||
if "/" in net_io:
|
||||
net_rx_str, net_tx_str = net_io.split("/")
|
||||
net_rx_kib = parse_size_kib(net_rx_str.strip())
|
||||
net_tx_kib = parse_size_kib(net_tx_str.strip())
|
||||
|
||||
rusage["net-io"] = {}
|
||||
if net_rx_kib > 0:
|
||||
rusage["net-io"]["received"] = f"{net_rx_kib}"
|
||||
if net_tx_kib > 0:
|
||||
rusage["net-io"]["sent"] = f"{net_tx_kib}"
|
||||
|
||||
pids = stat.get("pids", "0")
|
||||
try:
|
||||
rusage["pids"] = int(pids)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return rusage if rusage else None
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(f"failed getting stats for {name}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def container(ps):
|
||||
out = {
|
||||
"name": ps["Names"][0],
|
||||
@@ -70,6 +237,15 @@ def container(ps):
|
||||
if net:
|
||||
out["network"] = net
|
||||
|
||||
limits = read_cgroup_limits(inspect)
|
||||
if limits:
|
||||
out["resource-limit"] = limits
|
||||
|
||||
if out["running"]:
|
||||
rusage = resource_stats(out["name"])
|
||||
if rusage:
|
||||
out["resource-usage"] = rusage
|
||||
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user