Skip to content

Commit

Permalink
Merge pull request #142 from PierreF/add-smaps
Browse files Browse the repository at this point in the history
Add proportional resident memory size
  • Loading branch information
ncabatoff authored May 18, 2020
2 parents 9760f38 + 06487c9 commit d14dfc6
Show file tree
Hide file tree
Showing 16 changed files with 967 additions and 62 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,15 @@ Number of bytes of memory used. The extra label `memtype` can have three values

*swapped*: Field VmSwap from /proc/[pid]/status, translated from KB to bytes.

If gathering smaps file is enabled, two additional values for `memtype` are added:

*proportionalResident*: Sum of "Pss" fields from /proc/[pid]/smaps, whose doc says:

> The "proportional set size" (PSS) of a process is the count of pages it has
> in memory, where each page is divided by the number of processes sharing it.
*proportionalSwapped*: Sum of "SwapPss" fields from /proc/[pid]/smaps

### open_filedesc gauge

Number of file descriptors, based on counting how many entries are in the directory
Expand Down
54 changes: 40 additions & 14 deletions cmd/process-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ func main() {
"if a proc is tracked, track with it any children that aren't part of their own group")
threads = flag.Bool("threads", true,
"report on per-threadname metrics as well")
smaps = flag.Bool("gather-smaps", true,
"gather metrics from smaps file, which contains proportional resident memory size")
man = flag.Bool("man", false,
"print manual")
configPath = flag.String("config.path", "",
Expand Down Expand Up @@ -355,7 +357,17 @@ func main() {
matchnamer = namemapper
}

pc, err := NewProcessCollector(*procfsPath, *children, *threads, matchnamer, *recheck, *debug)
pc, err := NewProcessCollector(
ProcessCollectorOption{
ProcFSPath: *procfsPath,
Children: *children,
Threads: *threads,
GatherSMaps: *smaps,
Namer: matchnamer,
Recheck: *recheck,
Debug: *debug,
},
)
if err != nil {
log.Fatalf("Error initializing: %v", err)
}
Expand Down Expand Up @@ -395,10 +407,21 @@ type (
done chan struct{}
}

ProcessCollectorOption struct {
ProcFSPath string
Children bool
Threads bool
GatherSMaps bool
Namer common.MatchNamer
Recheck bool
Debug bool
}

NamedProcessCollector struct {
scrapeChan chan scrapeRequest
*proc.Grouper
threads bool
smaps bool
source proc.Source
scrapeErrors int
scrapeProcReadErrors int
Expand All @@ -407,29 +430,25 @@ type (
}
)

func NewProcessCollector(
procfsPath string,
children bool,
threads bool,
n common.MatchNamer,
recheck bool,
debug bool,
) (*NamedProcessCollector, error) {
fs, err := proc.NewFS(procfsPath, debug)
func NewProcessCollector(options ProcessCollectorOption) (*NamedProcessCollector, error) {
fs, err := proc.NewFS(options.ProcFSPath, options.Debug)
if err != nil {
return nil, err
}

fs.GatherSMaps = options.GatherSMaps
p := &NamedProcessCollector{
scrapeChan: make(chan scrapeRequest),
Grouper: proc.NewGrouper(n, children, threads, recheck, debug),
Grouper: proc.NewGrouper(options.Namer, options.Children, options.Threads, options.Recheck, options.Debug),
source: fs,
threads: threads,
debug: debug,
threads: options.Threads,
smaps: options.GatherSMaps,
debug: options.Debug,
}

colErrs, _, err := p.Update(p.source.AllProcs())
if err != nil {
if debug {
if options.Debug {
log.Print(err)
}
return nil, err
Expand Down Expand Up @@ -540,6 +559,13 @@ func (p *NamedProcessCollector) scrape(ch chan<- prometheus.Metric) {
prometheus.GaugeValue, float64(count), gname, wchan)
}

if p.smaps {
ch <- prometheus.MustNewConstMetric(membytesDesc,
prometheus.GaugeValue, float64(gcounts.Memory.ProportionalBytes), gname, "proportionalResident")
ch <- prometheus.MustNewConstMetric(membytesDesc,
prometheus.GaugeValue, float64(gcounts.Memory.ProportionalSwapBytes), gname, "proportionalSwapped")
}

if p.threads {
for _, thr := range gcounts.Threads {
ch <- prometheus.MustNewConstMetric(threadCountDesc,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/prometheus/client_golang v0.8.0
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect
github.com/prometheus/procfs v0.0.12-0.20200430211241-ea64cd222793
github.com/prometheus/procfs v0.0.12-0.20200505152635-9654394ca94a
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
gopkg.in/yaml.v2 v2.2.1
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.12-0.20200430211241-ea64cd222793 h1:eaK6XvR/+8o6UtStUEOJNg0WOO0WfgnAbhnuokPfnrY=
github.com/prometheus/procfs v0.0.12-0.20200430211241-ea64cd222793/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.0.12-0.20200505152635-9654394ca94a h1:zTuAihdFbve/GW/PAf5sLu2vEO5kRYKLxasyYn53o8c=
github.com/prometheus/procfs v0.0.12-0.20200505152635-9654394ca94a/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
Expand Down
2 changes: 2 additions & 0 deletions proc/grouper.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func groupadd(grp Group, ts Update) Group {
grp.Memory.ResidentBytes += ts.Memory.ResidentBytes
grp.Memory.VirtualBytes += ts.Memory.VirtualBytes
grp.Memory.VmSwapBytes += ts.Memory.VmSwapBytes
grp.Memory.ProportionalBytes += ts.Memory.ProportionalBytes
grp.Memory.ProportionalSwapBytes += ts.Memory.ProportionalSwapBytes
if ts.Filedesc.Open != -1 {
grp.OpenFDs += uint64(ts.Filedesc.Open)
}
Expand Down
42 changes: 21 additions & 21 deletions proc/grouper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,30 @@ func TestGrouperBasic(t *testing.T) {
}{
{
[]IDInfo{
piinfost(p1, n1, Counts{1, 2, 3, 4, 5, 6, 0, 0}, Memory{7, 8, 0},
piinfost(p1, n1, Counts{1, 2, 3, 4, 5, 6, 0, 0}, Memory{7, 8, 0, 0, 0},
Filedesc{4, 400}, 2, States{Other: 1}),
piinfost(p2, n2, Counts{2, 3, 4, 5, 6, 7, 0, 0}, Memory{8, 9, 0},
piinfost(p2, n2, Counts{2, 3, 4, 5, 6, 7, 0, 0}, Memory{8, 9, 0, 0, 0},
Filedesc{40, 400}, 3, States{Waiting: 1}),
},
GroupByName{
"g1": Group{Counts{}, States{Other: 1}, msi{}, 1, Memory{7, 8, 0}, starttime,
"g1": Group{Counts{}, States{Other: 1}, msi{}, 1, Memory{7, 8, 0, 0, 0}, starttime,
4, 0.01, 2, nil},
"g2": Group{Counts{}, States{Waiting: 1}, msi{}, 1, Memory{8, 9, 0}, starttime,
"g2": Group{Counts{}, States{Waiting: 1}, msi{}, 1, Memory{8, 9, 0, 0, 0}, starttime,
40, 0.1, 3, nil},
},
},
{
[]IDInfo{
piinfost(p1, n1, Counts{2, 3, 4, 5, 6, 7, 0, 0},
Memory{6, 7, 0}, Filedesc{100, 400}, 4, States{Zombie: 1}),
Memory{6, 7, 0, 0, 0}, Filedesc{100, 400}, 4, States{Zombie: 1}),
piinfost(p2, n2, Counts{4, 5, 6, 7, 8, 9, 0, 0},
Memory{9, 8, 0}, Filedesc{400, 400}, 2, States{Running: 1}),
Memory{9, 8, 0, 0, 0}, Filedesc{400, 400}, 2, States{Running: 1}),
},
GroupByName{
"g1": Group{Counts{1, 1, 1, 1, 1, 1, 0, 0}, States{Zombie: 1}, msi{}, 1,
Memory{6, 7, 0}, starttime, 100, 0.25, 4, nil},
Memory{6, 7, 0, 0, 0}, starttime, 100, 0.25, 4, nil},
"g2": Group{Counts{2, 2, 2, 2, 2, 2, 0, 0}, States{Running: 1}, msi{}, 1,
Memory{9, 8, 0}, starttime, 400, 1, 2, nil},
Memory{9, 8, 0, 0, 0}, starttime, 400, 1, 2, nil},
},
},
}
Expand All @@ -95,35 +95,35 @@ func TestGrouperProcJoin(t *testing.T) {
}{
{
[]IDInfo{
piinfo(p1, n1, Counts{1, 2, 3, 4, 5, 6, 0, 0}, Memory{3, 4, 0}, Filedesc{4, 400}, 2),
piinfo(p1, n1, Counts{1, 2, 3, 4, 5, 6, 0, 0}, Memory{3, 4, 0, 0, 0}, Filedesc{4, 400}, 2),
},
GroupByName{
"g1": Group{Counts{}, States{}, msi{}, 1, Memory{3, 4, 0}, starttime, 4, 0.01, 2, nil},
"g1": Group{Counts{}, States{}, msi{}, 1, Memory{3, 4, 0, 0, 0}, starttime, 4, 0.01, 2, nil},
},
}, {
// The counts for pid2 won't be factored into the total yet because we only add
// to counts starting with the second time we see a proc. Memory and FDs are
// affected though.
[]IDInfo{
piinfost(p1, n1, Counts{3, 4, 5, 6, 7, 8, 0, 0},
Memory{3, 4, 0}, Filedesc{4, 400}, 2, States{Running: 1}),
Memory{3, 4, 0, 0, 0}, Filedesc{4, 400}, 2, States{Running: 1}),
piinfost(p2, n2, Counts{1, 1, 1, 1, 1, 1, 0, 0},
Memory{1, 2, 0}, Filedesc{40, 400}, 3, States{Sleeping: 1}),
Memory{1, 2, 0, 0, 0}, Filedesc{40, 400}, 3, States{Sleeping: 1}),
},
GroupByName{
"g1": Group{Counts{2, 2, 2, 2, 2, 2, 0, 0}, States{Running: 1, Sleeping: 1}, msi{}, 2,
Memory{4, 6, 0}, starttime, 44, 0.1, 5, nil},
Memory{4, 6, 0, 0, 0}, starttime, 44, 0.1, 5, nil},
},
}, {
[]IDInfo{
piinfost(p1, n1, Counts{4, 5, 6, 7, 8, 9, 0, 0},
Memory{1, 5, 0}, Filedesc{4, 400}, 2, States{Running: 1}),
Memory{1, 5, 0, 0, 0}, Filedesc{4, 400}, 2, States{Running: 1}),
piinfost(p2, n2, Counts{2, 2, 2, 2, 2, 2, 0, 0},
Memory{2, 4, 0}, Filedesc{40, 400}, 3, States{Running: 1}),
Memory{2, 4, 0, 0, 0}, Filedesc{40, 400}, 3, States{Running: 1}),
},
GroupByName{
"g1": Group{Counts{4, 4, 4, 4, 4, 4, 0, 0}, States{Running: 2}, msi{}, 2,
Memory{3, 9, 0}, starttime, 44, 0.1, 5, nil},
Memory{3, 9, 0, 0, 0}, starttime, 44, 0.1, 5, nil},
},
},
}
Expand All @@ -150,18 +150,18 @@ func TestGrouperNonDecreasing(t *testing.T) {
}{
{
[]IDInfo{
piinfo(p1, n1, Counts{3, 4, 5, 6, 7, 8, 0, 0}, Memory{3, 4, 0}, Filedesc{4, 400}, 2),
piinfo(p2, n2, Counts{1, 1, 1, 1, 1, 1, 0, 0}, Memory{1, 2, 0}, Filedesc{40, 400}, 3),
piinfo(p1, n1, Counts{3, 4, 5, 6, 7, 8, 0, 0}, Memory{3, 4, 0, 0, 0}, Filedesc{4, 400}, 2),
piinfo(p2, n2, Counts{1, 1, 1, 1, 1, 1, 0, 0}, Memory{1, 2, 0, 0, 0}, Filedesc{40, 400}, 3),
},
GroupByName{
"g1": Group{Counts{}, States{}, msi{}, 2, Memory{4, 6, 0}, starttime, 44, 0.1, 5, nil},
"g1": Group{Counts{}, States{}, msi{}, 2, Memory{4, 6, 0, 0, 0}, starttime, 44, 0.1, 5, nil},
},
}, {
[]IDInfo{
piinfo(p1, n1, Counts{4, 5, 6, 7, 8, 9, 0, 0}, Memory{1, 5, 0}, Filedesc{4, 400}, 2),
piinfo(p1, n1, Counts{4, 5, 6, 7, 8, 9, 0, 0}, Memory{1, 5, 0, 0, 0}, Filedesc{4, 400}, 2),
},
GroupByName{
"g1": Group{Counts{1, 1, 1, 1, 1, 1, 0, 0}, States{}, msi{}, 1, Memory{1, 5, 0}, starttime, 4, 0.01, 2, nil},
"g1": Group{Counts{1, 1, 1, 1, 1, 1, 0, 0}, States{}, msi{}, 1, Memory{1, 5, 0, 0, 0}, starttime, 4, 0.01, 2, nil},
},
}, {
[]IDInfo{},
Expand Down
41 changes: 28 additions & 13 deletions proc/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ type (

// Memory describes a proc's memory usage.
Memory struct {
ResidentBytes uint64
VirtualBytes uint64
VmSwapBytes uint64
ResidentBytes uint64
VirtualBytes uint64
VmSwapBytes uint64
ProportionalBytes uint64
ProportionalSwapBytes uint64
}

// Filedesc describes a proc's file descriptor usage and soft limit.
Expand Down Expand Up @@ -187,9 +189,10 @@ type (
// FS implements Source.
FS struct {
procfs.FS
BootTime uint64
MountPoint string
debug bool
BootTime uint64
MountPoint string
GatherSMaps bool
debug bool
}
)

Expand Down Expand Up @@ -474,13 +477,25 @@ func (p proc) GetMetrics() (Metrics, int, error) {
softerrors |= 1
}

memory := Memory{
ResidentBytes: uint64(stat.ResidentMemory()),
VirtualBytes: uint64(stat.VirtualMemory()),
VmSwapBytes: uint64(status.VmSwap),
}

if p.proccache.fs.GatherSMaps {
smaps, err := p.Proc.ProcSMapsRollup()
if err != nil {
softerrors |= 1
} else {
memory.ProportionalBytes = smaps.Pss
memory.ProportionalSwapBytes = smaps.SwapPss
}
}

return Metrics{
Counts: counts,
Memory: Memory{
ResidentBytes: uint64(stat.ResidentMemory()),
VirtualBytes: uint64(stat.VirtualMemory()),
VmSwapBytes: uint64(status.VmSwap),
},
Memory: memory,
Filedesc: Filedesc{
Open: int64(numfds),
Limit: uint64(limits.OpenFiles),
Expand Down Expand Up @@ -554,7 +569,7 @@ func NewFS(mountPoint string, debug bool) (*FS, error) {
if err != nil {
return nil, err
}
return &FS{fs, stat.BootTime, mountPoint, debug}, nil
return &FS{fs, stat.BootTime, mountPoint, false, debug}, nil
}

func (fs *FS) threadFs(pid int) (*FS, error) {
Expand All @@ -563,7 +578,7 @@ func (fs *FS) threadFs(pid int) (*FS, error) {
if err != nil {
return nil, err
}
return &FS{tfs, fs.BootTime, mountPoint, false}, nil
return &FS{tfs, fs.BootTime, mountPoint, fs.GatherSMaps, false}, nil
}

// AllProcs implements Source.
Expand Down
8 changes: 4 additions & 4 deletions proc/tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ func TestTrackerMetrics(t *testing.T) {
want Update
}{
{
piinfost(p, n, Counts{1, 2, 3, 4, 5, 6, 0, 0}, Memory{7, 8, 0},
piinfost(p, n, Counts{1, 2, 3, 4, 5, 6, 0, 0}, Memory{7, 8, 0, 0, 0},
Filedesc{1, 10}, 9, States{Sleeping: 1}),
Update{n, Delta{}, Memory{7, 8, 0}, Filedesc{1, 10}, tm,
Update{n, Delta{}, Memory{7, 8, 0, 0, 0}, Filedesc{1, 10}, tm,
9, States{Sleeping: 1}, msi{}, nil},
},
{
piinfost(p, n, Counts{2, 3, 4, 5, 6, 7, 0, 0}, Memory{1, 2, 0},
piinfost(p, n, Counts{2, 3, 4, 5, 6, 7, 0, 0}, Memory{1, 2, 0, 0, 0},
Filedesc{2, 20}, 1, States{Running: 1}),
Update{n, Delta{1, 1, 1, 1, 1, 1, 0, 0}, Memory{1, 2, 0},
Update{n, Delta{1, 1, 1, 1, 1, 1, 0, 0}, Memory{1, 2, 0, 0, 0},
Filedesc{2, 20}, tm, 1, States{Running: 1}, msi{}, nil},
},
}
Expand Down
Loading

0 comments on commit d14dfc6

Please # to comment.