Selkies
Developer Referenceresource_stats

SystemUsage

Samples (cpu_percent, mem_total_bytes, mem_used_bytes) for the dashboards' gauges.

psutil shows a container every processor and byte of the host, though its cgroup allows it a share of them, and the host's figures count every other tenant too. Where the cgroup the process runs in limits CPU or memory, the sampler reads that cgroup: on the unified hierarchy cpu.stat and memory.current against cpu.max and memory.max, on the legacy one the cpuacct and memory controllers against the CFS quota and the memory limit. Memory in use leaves out the page cache the kernel can drop (the inactive file pages), the way container tools report it, and CPU is the usage counter's growth between two samples over the cores the cgroup may use: its quota, else the processors it is allowed. A limit the cgroup does not set is the host's total. A cgroup that limits nothing is not the environment, and neither is one that cannot be read: the node is, through psutil's system-wide figures.

Attributes

attributeLIMITS
= ('cpu.max', 'memory.max', 'cpu.cfs_quota_us', 'memory.limit_in_bytes')

Functions

constructor__init__(root=CGROUP_ROOT, proc_cgroup=PROC_CGROUP, clock=time.monotonic) -> None
Source Code
def __init__(self, root: str = CGROUP_ROOT, proc_cgroup: str = PROC_CGROUP,
             clock: Callable[[], float] = time.monotonic) -> None:
    self._clock = clock
    self._unified = False
    self._cpu_dir: Optional[str] = None
    self._mem_dir: Optional[str] = None
    self._last: Optional[Tuple[float, float]] = None
    for line in (_read(proc_cgroup) or "").splitlines():
        parts = line.split(":", 2)
        if len(parts) != 3:
            continue
        controllers, path = parts[1], parts[2].lstrip("/")
        if controllers == "":
            own = os.path.join(root, path)
            if os.path.isfile(os.path.join(own, "cpu.stat")):
                self._unified, self._cpu_dir, self._mem_dir = True, own, own
            continue
        names = controllers.split(",")
        # A container that keeps the host's cgroup names sees only its own
        # cgroup at the controller's mount, so that is the fallback.
        for mount in (os.path.join(root, controllers, path), os.path.join(root, controllers)):
            if not os.path.isdir(mount):
                continue
            if ("cpuacct" in names or "cpu" in names) and self._cpu_dir is None:
                self._cpu_dir = mount
            if "memory" in names and self._mem_dir is None:
                self._mem_dir = mount
            break
    if not self._limited():
        self._cpu_dir = self._mem_dir = None
paramrootstr
= CGROUP_ROOT
paramproc_cgroupstr
= PROC_CGROUP
paramclockCallable[[], float]
= time.monotonic

Returns

None
func_limited() -> bool

Whether the cgroup caps CPU or memory below the host.

Source Code
def _limited(self) -> bool:
    """Whether the cgroup caps CPU or memory below the host."""
    host = psutil.virtual_memory().total
    for d in (self._cpu_dir, self._mem_dir):
        for name in self.LIMITS:
            text = (_read(os.path.join(d, name)) or "") if d else ""
            if text.split() and text.split()[0].isdigit():
                if name.startswith("cpu") or int(text.split()[0]) < host:
                    return True
    return False

Returns

bool
func_cpu() -> Optional[Tuple[float, float]]

The cgroup's CPU time in seconds and the cores it may use.

Source Code
def _cpu(self) -> Optional[Tuple[float, float]]:
    """The cgroup's CPU time in seconds and the cores it may use."""
    d = self._cpu_dir
    if d is None:
        return None
    if self._unified:
        usec = _stat(os.path.join(d, "cpu.stat"), "usage_usec")
        if usec is None:
            return None
        quota = (_read(os.path.join(d, "cpu.max")) or "").split()
        cores = int(quota[0]) / int(quota[1]) if len(quota) == 2 and quota[0].isdigit() else _allowed_cores()
        return usec / 1e6, cores
    nsec = _read(os.path.join(d, "cpuacct.usage"))
    if nsec is None or not nsec.isdigit():
        return None
    quota, period = _read(os.path.join(d, "cpu.cfs_quota_us")), _read(os.path.join(d, "cpu.cfs_period_us"))
    limited = quota is not None and period is not None and quota.isdigit() and int(quota) > 0 and int(period) > 0
    return int(nsec) / 1e9, int(quota) / int(period) if limited else _allowed_cores()

Returns

typing.Optional[typing.Tuple[float, float]]
func_memory() -> Optional[Tuple[int, int]]

The cgroup's memory limit, the host's total without one, and its use.

Source Code
def _memory(self) -> Optional[Tuple[int, int]]:
    """The cgroup's memory limit, the host's total without one, and its use."""
    d = self._mem_dir
    if d is None:
        return None
    if self._unified:
        current, limit = _read(os.path.join(d, "memory.current")), _read(os.path.join(d, "memory.max"))
        cache = _stat(os.path.join(d, "memory.stat"), "inactive_file")
    else:
        current, limit = _read(os.path.join(d, "memory.usage_in_bytes")), _read(os.path.join(d, "memory.limit_in_bytes"))
        cache = _stat(os.path.join(d, "memory.stat"), "total_inactive_file")
    if current is None or not current.isdigit():
        return None
    host = psutil.virtual_memory().total
    total = int(limit) if limit is not None and limit.isdigit() and int(limit) < host else host
    return total, max(0, int(current) - (cache or 0))

Returns

typing.Optional[typing.Tuple[int, int]]
funcsample() -> Tuple[float, int, int]
Source Code
def sample(self) -> Tuple[float, int, int]:
    cpu, mem, now = self._cpu(), self._memory(), self._clock()
    if cpu is None:
        percent = psutil.cpu_percent()
    else:
        used, cores = cpu
        percent = 0.0
        if self._last is not None and now > self._last[1]:
            percent = min(100.0, max(0.0, (used - self._last[0]) / (now - self._last[1]) / max(cores, 1e-9) * 100))
        self._last = (used, now)
    if mem is None:
        vm = psutil.virtual_memory()
        mem = (vm.total, vm.used)
    return round(percent, 1), mem[0], mem[1]

Returns

typing.Tuple[float, int, int]

On this page

Edit on GitHub