fix(metrics): read unit cgroup path for process memory split

memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
fix(metrics): read unit cgroup path for process memory split
memory.current was taken from the hierarchy root, so the panel never saw
system.slice/3m-ui.service figures and fell back to raw RSS. Resolve the
path from /proc/self/cgroup and split that unit working set by RSS share.
This commit is contained in:
kazeyukiro committed 2026-09-27 02:28:53 +00:00
1 parent 4cafa139cf
commit 05b4be66d6
2 files changed
+126 -1

No files matched your search

+125
View File
@@ -212,7 +212,132 @@ type cgroupMemory struct {
//
// v1 reports an absurdly large limit when unset (PAGE_COUNTER_MAX), so the
// caller treats a limit above the host's total RAM as "no limit".
// selfCgroupDir returns the absolute sysfs directory for this process' cgroup
// on the unified hierarchy (v2) or the memory controller (v1). Empty when the
// path cannot be resolved — callers then fall back to the legacy root paths.
func selfCgroupDir() string {
data, err := os.ReadFile("/proc/self/cgroup")
if err != nil {
return ""
}
var v2Rel, v1MemRel string
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// v2: "0::/system.slice/3m-ui.service"
if strings.HasPrefix(line, "0::") {
rel := strings.TrimPrefix(line, "0::")
if rel == "" {
rel = "/"
}
v2Rel = rel
continue
}
// v1: "memory:/system.slice/3m-ui.service" or "1:memory:/..."
parts := strings.SplitN(line, ":", 3)
if len(parts) == 3 && (parts[1] == "memory" || strings.Contains(parts[1], "memory")) {
v1MemRel = parts[2]
}
}
if v2Rel != "" {
dir := "/sys/fs/cgroup"
if v2Rel != "/" {
dir = "/sys/fs/cgroup" + v2Rel
}
if st, err := os.Stat(dir); err == nil && st.IsDir() {
return dir
}
}
if v1MemRel != "" {
dir := "/sys/fs/cgroup/memory"
if v1MemRel != "/" {
dir = "/sys/fs/cgroup/memory" + v1MemRel
}
if st, err := os.Stat(dir); err == nil && st.IsDir() {
return dir
}
}
return ""
}
func readCgroupMemory() cgroupMemory {
// System card: prefer the process' own cgroup when it has a real MemoryMax
// (container / unit limit). Otherwise use the hierarchy root so bare-metal
// hosts still show machine-wide pressure, not only the panel unit.
if dir := selfCgroupDir(); dir != "" {
if u, ok := readUintFile(dir + "/memory.current"); ok {
var m cgroupMemory
m.usage = float64(u)
if f, ok := readUintField(dir+"/memory.stat", "inactive_file"); ok {
m.inactiveFile = float64(f)
}
if l, ok := readUintFile(dir + "/memory.max"); ok {
m.limit = float64(l)
}
// Real cap → this cgroup is the accounting domain for the system card.
if m.limit > 0 {
m.ok = true
return m
}
}
if u, ok := readUintFile(dir + "/memory.usage_in_bytes"); ok {
var m cgroupMemory
m.usage = float64(u)
if f, ok := readUintField(dir+"/memory.stat", "total_inactive_file"); ok {
m.inactiveFile = float64(f)
}
if l, ok := readUintFile(dir + "/memory.limit_in_bytes"); ok {
m.limit = float64(l)
}
if m.limit > 0 && m.limit < (1<<62) {
m.ok = true
return m
}
}
}
return readRootCgroupMemory()
}
// readServiceCgroupMemory returns memory.current for this process' unit
// (e.g. system.slice/3m-ui.service). Used to split panel/core so their sum
// matches systemd MemoryCurrent. No root fallback — if the service path is
// unreadable, callers keep RSS.
func readServiceCgroupMemory() cgroupMemory {
dir := selfCgroupDir()
if dir == "" {
return cgroupMemory{}
}
if u, ok := readUintFile(dir + "/memory.current"); ok {
var m cgroupMemory
m.usage = float64(u)
if f, ok := readUintField(dir+"/memory.stat", "inactive_file"); ok {
m.inactiveFile = float64(f)
}
if l, ok := readUintFile(dir + "/memory.max"); ok {
m.limit = float64(l)
}
m.ok = true
return m
}
if u, ok := readUintFile(dir + "/memory.usage_in_bytes"); ok {
var m cgroupMemory
m.usage = float64(u)
if f, ok := readUintField(dir+"/memory.stat", "total_inactive_file"); ok {
m.inactiveFile = float64(f)
}
if l, ok := readUintFile(dir + "/memory.limit_in_bytes"); ok {
m.limit = float64(l)
}
m.ok = true
return m
}
return cgroupMemory{}
}
func readRootCgroupMemory() cgroupMemory {
var m cgroupMemory
if u, ok := readUintFile(cgroupV2MemUsage); ok {
m.usage = float64(u)
+1 -1
View File
@@ -140,7 +140,7 @@ func applyCgroupMemoryShares(procs []*ProcessUsage) {
return
}
total := memoryTotalForPercent()
cg := readCgroupMemory()
cg := readServiceCgroupMemory()
hostTotal := total
info, ok := memoryFromCgroup(cg, hostTotal)
if !ok || info.Used <= 0 {