diff --git a/sysfs/meminfo_numa.go b/sysfs/meminfo_numa.go new file mode 100644 index 00000000..14d0ba87 --- /dev/null +++ b/sysfs/meminfo_numa.go @@ -0,0 +1,190 @@ +// Copyright 2023 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package sysfs + +import ( + "bufio" + "bytes" + "fmt" + "io" + "path/filepath" + "strconv" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// Meminfo represents memory statistics for NUMA node. +type Meminfo struct { + MemTotal uint64 + MemFree uint64 + MemUsed uint64 + SwapCached uint64 + Active uint64 + Inactive uint64 + ActiveAnon uint64 + InactiveAnon uint64 + ActiveFile uint64 + InactiveFile uint64 + Unevictable uint64 + Mlocked uint64 + Dirty uint64 + Writeback uint64 + FilePages uint64 + Mapped uint64 + AnonPages uint64 + Shmem uint64 + KernelStack uint64 + PageTables uint64 + NFS_Unstable uint64 + Bounce uint64 + WritebackTmp uint64 + KReclaimable uint64 + Slab uint64 + SReclaimable uint64 + SUnreclaim uint64 + AnonHugePages uint64 + ShmemHugePages uint64 + ShmemPmdMapped uint64 + FileHugePages uint64 + FilePmdMapped uint64 + HugePages_Total uint64 + HugePages_Free uint64 + HugePages_Surp uint64 +} + +func (fs FS) MeminfoNUMA() (map[int]Meminfo, error) { + m := make(map[int]Meminfo) + nodes, err := filepath.Glob(fs.sys.Path(nodePattern)) + if err != nil { + return nil, err + } + + for _, node := range nodes { + nodeNumbers := nodeNumberRegexp.FindStringSubmatch(node) + if len(nodeNumbers) != 2 { + continue + } + nodeNumber, err := strconv.Atoi(nodeNumbers[1]) + if err != nil { + return nil, err + } + b, err := util.ReadFileNoStat(filepath.Join(node, "meminfo")) + if err != nil { + return nil, err + } + meminfo, err := parseMeminfo(bytes.NewReader(b)) + if err != nil { + return nil, err + } + m[nodeNumber] = meminfo + } + return m, nil +} + +func parseMeminfo(r io.Reader) (Meminfo, error) { + var m Meminfo + s := bufio.NewScanner(r) + for s.Scan() { + // Each line has at least a name and value; we ignore the unit. + // A line example: "Node 0 MemTotal: 395936028 kB" + fields := strings.Fields(s.Text()) + if len(fields) < 4 { + return Meminfo{}, fmt.Errorf("malformed meminfo line: %q", s.Text()) + } + + v, err := strconv.ParseUint(fields[3], 0, 64) + if err != nil { + return Meminfo{}, err + } + + switch fields[2] { + case "MemTotal:": + m.MemTotal = v + case "MemFree:": + m.MemFree = v + case "MemUsed:": + m.MemUsed = v + case "SwapCached:": + m.SwapCached = v + case "Active:": + m.Active = v + case "Inactive:": + m.Inactive = v + case "Active(anon):": + m.ActiveAnon = v + case "Inactive(anon):": + m.InactiveAnon = v + case "Active(file):": + m.ActiveFile = v + case "Inactive(file):": + m.InactiveFile = v + case "Unevictable:": + m.Unevictable = v + case "Mlocked:": + m.Mlocked = v + case "Dirty:": + m.Dirty = v + case "Writeback:": + m.Writeback = v + case "FilePages:": + m.FilePages = v + case "Mapped:": + m.Mapped = v + case "AnonPages:": + m.AnonPages = v + case "Shmem:": + m.Shmem = v + case "KernelStack:": + m.KernelStack = v + case "PageTables:": + m.PageTables = v + case "NFS_Unstable:": + m.NFS_Unstable = v + case "Bounce:": + m.Bounce = v + case "WritebackTmp:": + m.WritebackTmp = v + case "KReclaimable:": + m.KReclaimable = v + case "Slab:": + m.Slab = v + case "SReclaimable:": + m.SReclaimable = v + case "SUnreclaim:": + m.SUnreclaim = v + case "AnonHugePages:": + m.AnonHugePages = v + case "ShmemHugePages:": + m.ShmemHugePages = v + case "ShmemPmdMapped:": + m.ShmemPmdMapped = v + case "FileHugePages:": + m.FileHugePages = v + case "FilePmdMapped:": + m.FilePmdMapped = v + case "HugePages_Total:": + m.HugePages_Total = v + case "HugePages_Free:": + m.HugePages_Free = v + case "HugePages_Surp:": + m.HugePages_Surp = v + } + } + + return m, nil +} diff --git a/sysfs/meminfo_numa_test.go b/sysfs/meminfo_numa_test.go new file mode 100644 index 00000000..ba49711c --- /dev/null +++ b/sysfs/meminfo_numa_test.go @@ -0,0 +1,41 @@ +// Copyright 2023 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package sysfs + +import ( + "testing" +) + +func TestMeminfoNUMA(t *testing.T) { + fs, err := NewFS(sysTestFixtures) + if err != nil { + t.Fatal(err) + } + + meminfo, err := fs.MeminfoNUMA() + if err != nil { + t.Fatal(err) + } + + if want, got := uint64(133), meminfo[1].HugePages_Total; want != got { + t.Errorf("want meminfo stat HugePages_Total value %d, got %d", want, got) + } + + if want, got := uint64(134), meminfo[1].HugePages_Free; want != got { + t.Errorf("want meminfo stat HugePages_Free value %d, got %d", want, got) + } +} diff --git a/sysfs/numa.go b/sysfs/numa.go new file mode 100644 index 00000000..e4b57d0d --- /dev/null +++ b/sysfs/numa.go @@ -0,0 +1,24 @@ +// Copyright 2023 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux +// +build linux + +package sysfs + +import "regexp" + +var ( + nodePattern = "devices/system/node/node[0-9]*" + nodeNumberRegexp = regexp.MustCompile(`.*devices/system/node/node([0-9]*)`) +) diff --git a/sysfs/vmstat_numa.go b/sysfs/vmstat_numa.go index 8a6d0959..ae4124f3 100644 --- a/sysfs/vmstat_numa.go +++ b/sysfs/vmstat_numa.go @@ -21,18 +21,12 @@ import ( "bytes" "fmt" "path/filepath" - "regexp" "strconv" "strings" "github.com/prometheus/procfs/internal/util" ) -var ( - nodePattern = "devices/system/node/node[0-9]*" - nodeNumberRegexp = regexp.MustCompile(`.*devices/system/node/node([0-9]*)`) -) - type VMStat struct { NrFreePages uint64 NrZoneInactiveAnon uint64 diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index a965b110..1585bfd7 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -12727,6 +12727,45 @@ Mode: 775 Directory: fixtures/sys/devices/system/node/node1 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/devices/system/node/node1/meminfo +Lines: 35 +Node 1 MemTotal: 101 kB +Node 1 MemFree: 102 kB +Node 1 MemUsed: 103 kB +Node 1 SwapCached: 104 kB +Node 1 Active: 105 kB +Node 1 Inactive: 106 kB +Node 1 Active(anon): 107 kB +Node 1 Inactive(anon): 108 kB +Node 1 Active(file): 109 kB +Node 1 Inactive(file): 110 kB +Node 1 Unevictable: 111 kB +Node 1 Mlocked: 112 kB +Node 1 Dirty: 113 kB +Node 1 Writeback: 114 kB +Node 1 FilePages: 115 kB +Node 1 Mapped: 116 kB +Node 1 AnonPages: 117 kB +Node 1 Shmem: 118 kB +Node 1 KernelStack: 119 kB +Node 1 PageTables: 120 kB +Node 1 NFS_Unstable: 121 kB +Node 1 Bounce: 122 kB +Node 1 WritebackTmp: 123 kB +Node 1 KReclaimable: 124 kB +Node 1 Slab: 125 kB +Node 1 SReclaimable: 126 kB +Node 1 SUnreclaim: 127 kB +Node 1 AnonHugePages: 128 kB +Node 1 ShmemHugePages: 129 kB +Node 1 ShmemPmdMapped: 130 kB +Node 1 FileHugePages: 131 kB +Node 1 FilePmdMapped: 132 kB +Node 1 HugePages_Total: 133 +Node 1 HugePages_Free: 134 +Node 1 HugePages_Surp: 135EOF +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/sys/devices/system/node/node1/vmstat Lines: 6 nr_free_pages 1 @@ -12740,6 +12779,45 @@ Mode: 644 Directory: fixtures/sys/devices/system/node/node2 Mode: 755 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/sys/devices/system/node/node2/meminfo +Lines: 35 +Node 2 MemTotal: 201 kB +Node 2 MemFree: 202 kB +Node 2 MemUsed: 203 kB +Node 2 SwapCached: 204 kB +Node 2 Active: 205 kB +Node 2 Inactive: 206 kB +Node 2 Active(anon): 207 kB +Node 2 Inactive(anon): 208 kB +Node 2 Active(file): 209 kB +Node 2 Inactive(file): 210 kB +Node 2 Unevictable: 211 kB +Node 2 Mlocked: 212 kB +Node 2 Dirty: 213 kB +Node 2 Writeback: 214 kB +Node 2 FilePages: 215 kB +Node 2 Mapped: 216 kB +Node 2 AnonPages: 217 kB +Node 2 Shmem: 218 kB +Node 2 KernelStack: 219 kB +Node 2 PageTables: 220 kB +Node 2 NFS_Unstable: 221 kB +Node 2 Bounce: 222 kB +Node 2 WritebackTmp: 223 kB +Node 2 KReclaimable: 224 kB +Node 2 Slab: 225 kB +Node 2 SReclaimable: 226 kB +Node 2 SUnreclaim: 227 kB +Node 2 AnonHugePages: 228 kB +Node 2 ShmemHugePages: 229 kB +Node 2 ShmemPmdMapped: 230 kB +Node 2 FileHugePages: 231 kB +Node 2 FilePmdMapped: 232 kB +Node 2 HugePages_Total: 233 +Node 2 HugePages_Free: 234 +Node 2 HugePages_Surp: 235EOF +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/sys/devices/system/node/node2/vmstat Lines: 6 nr_free_pages 7