166 lines
4.4 KiB
Go
166 lines
4.4 KiB
Go
package proc
|
|
|
|
import (
|
|
common "github.com/ncabatoff/process-exporter"
|
|
"time"
|
|
)
|
|
|
|
type (
|
|
Grouper struct {
|
|
namer common.MatchNamer
|
|
trackChildren bool
|
|
// track how much was seen last time so we can report the delta
|
|
GroupStats map[string]Counts
|
|
tracker *Tracker
|
|
}
|
|
|
|
GroupCountMap map[string]GroupCounts
|
|
|
|
GroupCounts struct {
|
|
Counts
|
|
Procs int
|
|
Memresident uint64
|
|
Memvirtual uint64
|
|
OldestStartTime time.Time
|
|
}
|
|
)
|
|
|
|
func NewGrouper(trackChildren bool, namer common.MatchNamer) *Grouper {
|
|
g := Grouper{
|
|
trackChildren: trackChildren,
|
|
namer: namer,
|
|
GroupStats: make(map[string]Counts),
|
|
tracker: NewTracker(),
|
|
}
|
|
return &g
|
|
}
|
|
|
|
func (g *Grouper) checkAncestry(idinfo ProcIdInfo, newprocs map[ProcId]ProcIdInfo) string {
|
|
ppid := idinfo.ParentPid
|
|
pProcId := g.tracker.ProcIds[ppid]
|
|
if pProcId.Pid < 1 {
|
|
// Reached root of process tree without finding a tracked parent.
|
|
g.tracker.Ignore(idinfo.ProcId)
|
|
return ""
|
|
}
|
|
|
|
// Is the parent already known to the tracker?
|
|
if ptproc, ok := g.tracker.Tracked[pProcId]; ok {
|
|
if ptproc != nil {
|
|
// We've found a tracked parent.
|
|
g.tracker.Track(ptproc.GroupName, idinfo)
|
|
return ptproc.GroupName
|
|
} else {
|
|
// We've found an untracked parent.
|
|
g.tracker.Ignore(idinfo.ProcId)
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Is the parent another new process?
|
|
if pinfoid, ok := newprocs[pProcId]; ok {
|
|
if name := g.checkAncestry(pinfoid, newprocs); name != "" {
|
|
// We've found a tracked parent, which implies this entire lineage should be tracked.
|
|
g.tracker.Track(name, idinfo)
|
|
return name
|
|
}
|
|
}
|
|
|
|
// Parent is dead, i.e. we never saw it, or there's no tracked proc in our ancestry.
|
|
g.tracker.Ignore(idinfo.ProcId)
|
|
return ""
|
|
|
|
}
|
|
|
|
// Update tracks any new procs that should be according to policy, and updates
|
|
// the metrics for already tracked procs. Permission errors are returned as a
|
|
// count, and will not affect the error return value.
|
|
func (g *Grouper) Update(iter ProcIter) (int, error) {
|
|
newProcs, permErrs, err := g.tracker.Update(iter)
|
|
if err != nil {
|
|
return permErrs, err
|
|
}
|
|
|
|
// Step 1: track any new proc that should be tracked based on its name and cmdline.
|
|
untracked := make(map[ProcId]ProcIdInfo)
|
|
for _, idinfo := range newProcs {
|
|
wanted, gname := g.namer.MatchAndName(common.NameAndCmdline{idinfo.Name, idinfo.Cmdline})
|
|
if !wanted {
|
|
untracked[idinfo.ProcId] = idinfo
|
|
continue
|
|
}
|
|
|
|
g.tracker.Track(gname, idinfo)
|
|
}
|
|
|
|
// Step 2: track any untracked new proc that should be tracked because its parent is tracked.
|
|
if !g.trackChildren {
|
|
return permErrs, nil
|
|
}
|
|
|
|
for _, idinfo := range untracked {
|
|
if _, ok := g.tracker.Tracked[idinfo.ProcId]; ok {
|
|
// Already tracked or ignored
|
|
continue
|
|
}
|
|
|
|
g.checkAncestry(idinfo, untracked)
|
|
}
|
|
return permErrs, nil
|
|
}
|
|
|
|
// groups returns the aggregate metrics for all groups tracked. This reflects
|
|
// solely what's currently running.
|
|
func (g *Grouper) groups() GroupCountMap {
|
|
gcounts := make(GroupCountMap)
|
|
|
|
var zeroTime time.Time
|
|
for _, tinfo := range g.tracker.Tracked {
|
|
if tinfo == nil {
|
|
continue
|
|
}
|
|
cur := gcounts[tinfo.GroupName]
|
|
cur.Procs++
|
|
_, counts, mem, start := tinfo.GetStats()
|
|
cur.Memresident += mem.Resident
|
|
cur.Memvirtual += mem.Virtual
|
|
cur.Counts.Cpu += counts.Cpu
|
|
cur.Counts.ReadBytes += counts.ReadBytes
|
|
cur.Counts.WriteBytes += counts.WriteBytes
|
|
if cur.OldestStartTime == zeroTime || start.Before(cur.OldestStartTime) {
|
|
cur.OldestStartTime = start
|
|
}
|
|
gcounts[tinfo.GroupName] = cur
|
|
}
|
|
|
|
return gcounts
|
|
}
|
|
|
|
// Groups returns GroupCounts with Counts that never decrease in value from one
|
|
// call to the next. Even if processes exit, their CPU and IO contributions up
|
|
// to that point are included in the results. Even if no processes remain
|
|
// in a group it will still be included in the results.
|
|
func (g *Grouper) Groups() GroupCountMap {
|
|
groups := g.groups()
|
|
|
|
// First add any accumulated counts to what was just observed,
|
|
// and update the accumulators.
|
|
for gname, group := range groups {
|
|
if oldcounts, ok := g.GroupStats[gname]; ok {
|
|
group.Counts.Cpu += oldcounts.Cpu
|
|
group.Counts.ReadBytes += oldcounts.ReadBytes
|
|
group.Counts.WriteBytes += oldcounts.WriteBytes
|
|
}
|
|
g.GroupStats[gname] = group.Counts
|
|
groups[gname] = group
|
|
}
|
|
|
|
// Now add any groups that were observed in the past but aren't running now.
|
|
for gname, gcounts := range g.GroupStats {
|
|
if _, ok := groups[gname]; !ok {
|
|
groups[gname] = GroupCounts{Counts: gcounts}
|
|
}
|
|
}
|
|
|
|
return groups
|
|
}
|