Skip to content

Commit

Permalink
fix(meminfo): account for optional unit field so values are accurate
Browse files Browse the repository at this point in the history
Addresses: #565

Previously, this function was ignoring the optional unit field, leading
to incorrect results on systems that do report the field. This uses the
humanize lib used elsewhere within the Prometheus ecosystem to normalize
the value to bytes, including when a unit is provided.

To try and maintain existing behavior, the fixed/unit-scaled values are
stored as new struct fields.

Signed-off-by: TJ Hoplock <t.hoplock@gmail.com>
  • Loading branch information
tjhop committed Sep 23, 2023
1 parent c05b611 commit 765860f
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 52 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/prometheus/procfs
go 1.19

require (
github.com/dustin/go-humanize v1.0.1
github.com/google/go-cmp v0.5.9
golang.org/x/sync v0.3.0
golang.org/x/sys v0.11.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
Expand Down
221 changes: 169 additions & 52 deletions meminfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"strconv"
"strings"

"github.com/dustin/go-humanize"

"github.com/prometheus/procfs/internal/util"
)

Expand Down Expand Up @@ -140,6 +142,59 @@ type Meminfo struct {
DirectMap4k *uint64
DirectMap2M *uint64
DirectMap1G *uint64

// The struct fields below are the byte-normalized counterparts to the
// existing struct fields. Values are normalized using the optional
// unit field in the meminfo line.
// Same as the counterpart fields without the `Bytes` suffix
MemTotalBytes *uint64
MemFreeBytes *uint64
MemAvailableBytes *uint64
BuffersBytes *uint64
CachedBytes *uint64
SwapCachedBytes *uint64
ActiveBytes *uint64
InactiveBytes *uint64
ActiveAnonBytes *uint64
InactiveAnonBytes *uint64
ActiveFileBytes *uint64
InactiveFileBytes *uint64
UnevictableBytes *uint64
MlockedBytes *uint64
SwapTotalBytes *uint64
SwapFreeBytes *uint64
DirtyBytes *uint64
WritebackBytes *uint64
AnonPagesBytes *uint64
MappedBytes *uint64
ShmemBytes *uint64
SlabBytes *uint64
SReclaimableBytes *uint64
SUnreclaimBytes *uint64
KernelStackBytes *uint64
PageTablesBytes *uint64
NFSUnstableBytes *uint64
BounceBytes *uint64
WritebackTmpBytes *uint64
CommitLimitBytes *uint64
CommittedASBytes *uint64
VmallocTotalBytes *uint64
VmallocUsedBytes *uint64
VmallocChunkBytes *uint64
HardwareCorruptedBytes *uint64
AnonHugePagesBytes *uint64
ShmemHugePagesBytes *uint64
ShmemPmdMappedBytes *uint64
CmaTotalBytes *uint64
CmaFreeBytes *uint64
HugePagesTotalBytes *uint64
HugePagesFreeBytes *uint64
HugePagesRsvdBytes *uint64
HugePagesSurpBytes *uint64
HugepagesizeBytes *uint64
DirectMap4kBytes *uint64
DirectMap2MBytes *uint64
DirectMap1GBytes *uint64
}

// Meminfo returns an information about current kernel/system memory statistics.
Expand All @@ -162,114 +217,176 @@ 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.
fields := strings.Fields(s.Text())
if len(fields) < 2 {
return nil, fmt.Errorf("%w: Malformed line %q", ErrFileParse, s.Text())
}
var val, valBytes uint64

v, err := strconv.ParseUint(fields[1], 0, 64)
if err != nil {
return nil, err
}

switch len(fields) {
case 2:
// No unit present, use the parsed the value as bytes directly.
val = v
valBytes = v
case 3:
// Unit present in optional 3rd field, convert and normalize to bytes.
bytes, err := humanize.ParseBytes(fmt.Sprintf("%s %s", fields[1], fields[2]))
if err != nil {
return nil, err
}
val = v
valBytes = bytes
default:
return nil, fmt.Errorf("%w: Malformed line %q", ErrFileParse, s.Text())
}

switch fields[0] {
case "MemTotal:":
m.MemTotal = &v
m.MemTotal = &val
m.MemTotalBytes = &valBytes
case "MemFree:":
m.MemFree = &v
m.MemFree = &val
m.MemFreeBytes = &valBytes
case "MemAvailable:":
m.MemAvailable = &v
m.MemAvailable = &val
m.MemAvailableBytes = &valBytes
case "Buffers:":
m.Buffers = &v
m.Buffers = &val
m.BuffersBytes = &valBytes
case "Cached:":
m.Cached = &v
m.Cached = &val
m.CachedBytes = &valBytes
case "SwapCached:":
m.SwapCached = &v
m.SwapCached = &val
m.SwapCachedBytes = &valBytes
case "Active:":
m.Active = &v
m.Active = &val
m.ActiveBytes = &valBytes
case "Inactive:":
m.Inactive = &v
m.Inactive = &val
m.InactiveBytes = &valBytes
case "Active(anon):":
m.ActiveAnon = &v
m.ActiveAnon = &val
m.ActiveAnonBytes = &valBytes
case "Inactive(anon):":
m.InactiveAnon = &v
m.InactiveAnon = &val
m.InactiveAnonBytes = &valBytes
case "Active(file):":
m.ActiveFile = &v
m.ActiveFile = &val
m.ActiveFileBytes = &valBytes
case "Inactive(file):":
m.InactiveFile = &v
m.InactiveFile = &val
m.InactiveFileBytes = &valBytes
case "Unevictable:":
m.Unevictable = &v
m.Unevictable = &val
m.UnevictableBytes = &valBytes
case "Mlocked:":
m.Mlocked = &v
m.Mlocked = &val
m.MlockedBytes = &valBytes
case "SwapTotal:":
m.SwapTotal = &v
m.SwapTotal = &val
m.SwapTotalBytes = &valBytes
case "SwapFree:":
m.SwapFree = &v
m.SwapFree = &val
m.SwapFreeBytes = &valBytes
case "Dirty:":
m.Dirty = &v
m.Dirty = &val
m.DirtyBytes = &valBytes
case "Writeback:":
m.Writeback = &v
m.Writeback = &val
m.WritebackBytes = &valBytes
case "AnonPages:":
m.AnonPages = &v
m.AnonPages = &val
m.AnonPagesBytes = &valBytes
case "Mapped:":
m.Mapped = &v
m.Mapped = &val
m.MappedBytes = &valBytes
case "Shmem:":
m.Shmem = &v
m.Shmem = &val
m.ShmemBytes = &valBytes
case "Slab:":
m.Slab = &v
m.Slab = &val
m.SlabBytes = &valBytes
case "SReclaimable:":
m.SReclaimable = &v
m.SReclaimable = &val
m.SReclaimableBytes = &valBytes
case "SUnreclaim:":
m.SUnreclaim = &v
m.SUnreclaim = &val
m.SUnreclaimBytes = &valBytes
case "KernelStack:":
m.KernelStack = &v
m.KernelStack = &val
m.KernelStackBytes = &valBytes
case "PageTables:":
m.PageTables = &v
m.PageTables = &val
m.PageTablesBytes = &valBytes
case "NFS_Unstable:":
m.NFSUnstable = &v
m.NFSUnstable = &val
m.NFSUnstableBytes = &valBytes
case "Bounce:":
m.Bounce = &v
m.Bounce = &val
m.BounceBytes = &valBytes
case "WritebackTmp:":
m.WritebackTmp = &v
m.WritebackTmp = &val
m.WritebackTmpBytes = &valBytes
case "CommitLimit:":
m.CommitLimit = &v
m.CommitLimit = &val
m.CommitLimitBytes = &valBytes
case "Committed_AS:":
m.CommittedAS = &v
m.CommittedAS = &val
m.CommittedASBytes = &valBytes
case "VmallocTotal:":
m.VmallocTotal = &v
m.VmallocTotal = &val
m.VmallocTotalBytes = &valBytes
case "VmallocUsed:":
m.VmallocUsed = &v
m.VmallocUsed = &val
m.VmallocUsedBytes = &valBytes
case "VmallocChunk:":
m.VmallocChunk = &v
m.VmallocChunk = &val
m.VmallocChunkBytes = &valBytes
case "HardwareCorrupted:":
m.HardwareCorrupted = &v
m.HardwareCorrupted = &val
m.HardwareCorruptedBytes = &valBytes
case "AnonHugePages:":
m.AnonHugePages = &v
m.AnonHugePages = &val
m.AnonHugePagesBytes = &valBytes
case "ShmemHugePages:":
m.ShmemHugePages = &v
m.ShmemHugePages = &val
m.ShmemHugePagesBytes = &valBytes
case "ShmemPmdMapped:":
m.ShmemPmdMapped = &v
m.ShmemPmdMapped = &val
m.ShmemPmdMappedBytes = &valBytes
case "CmaTotal:":
m.CmaTotal = &v
m.CmaTotal = &val
m.CmaTotalBytes = &valBytes
case "CmaFree:":
m.CmaFree = &v
m.CmaFree = &val
m.CmaFreeBytes = &valBytes
case "HugePages_Total:":
m.HugePagesTotal = &v
m.HugePagesTotal = &val
m.HugePagesTotalBytes = &valBytes
case "HugePages_Free:":
m.HugePagesFree = &v
m.HugePagesFree = &val
m.HugePagesFreeBytes = &valBytes
case "HugePages_Rsvd:":
m.HugePagesRsvd = &v
m.HugePagesRsvd = &val
m.HugePagesRsvdBytes = &valBytes
case "HugePages_Surp:":
m.HugePagesSurp = &v
m.HugePagesSurp = &val
m.HugePagesSurpBytes = &valBytes
case "Hugepagesize:":
m.Hugepagesize = &v
m.Hugepagesize = &val
m.HugepagesizeBytes = &valBytes
case "DirectMap4k:":
m.DirectMap4k = &v
m.DirectMap4k = &val
m.DirectMap4kBytes = &valBytes
case "DirectMap2M:":
m.DirectMap2M = &v
m.DirectMap2M = &val
m.DirectMap2MBytes = &valBytes
case "DirectMap1G:":
m.DirectMap1G = &v
m.DirectMap1G = &val
m.DirectMap1GBytes = &valBytes
}
}

Expand Down
43 changes: 43 additions & 0 deletions meminfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,49 @@ func TestMeminfo(t *testing.T) {
Hugepagesize: newuint64(2048),
DirectMap4k: newuint64(91136),
DirectMap2M: newuint64(16039936),

MemTotalBytes: newuint64(15666184000),
MemFreeBytes: newuint64(440324000),
BuffersBytes: newuint64(1020128000),
CachedBytes: newuint64(12007640000),
SwapCachedBytes: newuint64(0),
ActiveBytes: newuint64(6761276000),
InactiveBytes: newuint64(6532708000),
ActiveAnonBytes: newuint64(267256000),
InactiveAnonBytes: newuint64(268000),
ActiveFileBytes: newuint64(6494020000),
InactiveFileBytes: newuint64(6532440000),
UnevictableBytes: newuint64(0),
MlockedBytes: newuint64(0),
SwapTotalBytes: newuint64(0),
SwapFreeBytes: newuint64(0),
DirtyBytes: newuint64(768000),
WritebackBytes: newuint64(0),
AnonPagesBytes: newuint64(266216000),
MappedBytes: newuint64(44204000),
ShmemBytes: newuint64(1308000),
SlabBytes: newuint64(1807264000),
SReclaimableBytes: newuint64(1738124000),
SUnreclaimBytes: newuint64(69140000),
KernelStackBytes: newuint64(1616000),
PageTablesBytes: newuint64(5288000),
NFSUnstableBytes: newuint64(0),
BounceBytes: newuint64(0),
WritebackTmpBytes: newuint64(0),
CommitLimitBytes: newuint64(7833092000),
CommittedASBytes: newuint64(530844000),
VmallocTotalBytes: newuint64(34359738367000),
VmallocUsedBytes: newuint64(36596000),
VmallocChunkBytes: newuint64(34359637840000),
HardwareCorruptedBytes: newuint64(0),
AnonHugePagesBytes: newuint64(12288000),
HugePagesTotalBytes: newuint64(0),
HugePagesFreeBytes: newuint64(0),
HugePagesRsvdBytes: newuint64(0),
HugePagesSurpBytes: newuint64(0),
HugepagesizeBytes: newuint64(2048000),
DirectMap4kBytes: newuint64(91136000),
DirectMap2MBytes: newuint64(16039936000),
}

have, err := getProcFixtures(t).Meminfo()
Expand Down

0 comments on commit 765860f

Please sign in to comment.