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 }