package procstat import ( "errors" "runtime" "strconv" "time" "github.com/shirou/gopsutil/v3/process" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/metric" ) type Process interface { PID() PID Name() (string, error) SetTag(string, string) MemoryMaps(bool) (*[]process.MemoryMapsStat, error) Metric(prefix string, tagging map[string]bool, solarisMode bool) telegraf.Metric } type PIDFinder interface { PidFile(path string) ([]PID, error) Pattern(pattern string) ([]PID, error) UID(user string) ([]PID, error) FullPattern(path string) ([]PID, error) Children(pid PID) ([]PID, error) } type Proc struct { hasCPUTimes bool tags map[string]string *process.Process } func newProc(pid PID) (Process, error) { p, err := process.NewProcess(int32(pid)) if err != nil { return nil, err } proc := &Proc{ Process: p, hasCPUTimes: false, tags: make(map[string]string), } return proc, nil } func (p *Proc) PID() PID { return PID(p.Process.Pid) } func (p *Proc) SetTag(k, v string) { p.tags[k] = v } func (p *Proc) percent(_ time.Duration) (float64, error) { cpuPerc, err := p.Process.Percent(time.Duration(0)) if !p.hasCPUTimes && err == nil { p.hasCPUTimes = true return 0, errors.New("must call Percent twice to compute percent cpu") } return cpuPerc, err } // Add metrics a single Process func (p *Proc) Metric(prefix string, tagging map[string]bool, solarisMode bool) telegraf.Metric { if prefix != "" { prefix += "_" } fields := make(map[string]interface{}) numThreads, err := p.NumThreads() if err == nil { fields[prefix+"num_threads"] = numThreads } fds, err := p.NumFDs() if err == nil { fields[prefix+"num_fds"] = fds } ctx, err := p.NumCtxSwitches() if err == nil { fields[prefix+"voluntary_context_switches"] = ctx.Voluntary fields[prefix+"involuntary_context_switches"] = ctx.Involuntary } faults, err := p.PageFaults() if err == nil { fields[prefix+"minor_faults"] = faults.MinorFaults fields[prefix+"major_faults"] = faults.MajorFaults fields[prefix+"child_minor_faults"] = faults.ChildMinorFaults fields[prefix+"child_major_faults"] = faults.ChildMajorFaults } io, err := p.IOCounters() if err == nil { fields[prefix+"read_count"] = io.ReadCount fields[prefix+"write_count"] = io.WriteCount fields[prefix+"read_bytes"] = io.ReadBytes fields[prefix+"write_bytes"] = io.WriteBytes } createdAt, err := p.CreateTime() // returns epoch in ms if err == nil { fields[prefix+"created_at"] = createdAt * 1000000 // ms to ns } cpuTime, err := p.Times() if err == nil { fields[prefix+"cpu_time_user"] = cpuTime.User fields[prefix+"cpu_time_system"] = cpuTime.System fields[prefix+"cpu_time_iowait"] = cpuTime.Iowait // only reported on Linux } cpuPerc, err := p.percent(time.Duration(0)) if err == nil { if solarisMode { fields[prefix+"cpu_usage"] = cpuPerc / float64(runtime.NumCPU()) } else { fields[prefix+"cpu_usage"] = cpuPerc } } // This only returns values for RSS and VMS mem, err := p.MemoryInfo() if err == nil { fields[prefix+"memory_rss"] = mem.RSS fields[prefix+"memory_vms"] = mem.VMS } collectMemmap(p, prefix, fields) memPerc, err := p.MemoryPercent() if err == nil { fields[prefix+"memory_usage"] = memPerc } rlims, err := p.RlimitUsage(true) if err == nil { for _, rlim := range rlims { var name string switch rlim.Resource { case process.RLIMIT_CPU: name = "cpu_time" case process.RLIMIT_DATA: name = "memory_data" case process.RLIMIT_STACK: name = "memory_stack" case process.RLIMIT_RSS: name = "memory_rss" case process.RLIMIT_NOFILE: name = "num_fds" case process.RLIMIT_MEMLOCK: name = "memory_locked" case process.RLIMIT_AS: name = "memory_vms" case process.RLIMIT_LOCKS: name = "file_locks" case process.RLIMIT_SIGPENDING: name = "signals_pending" case process.RLIMIT_NICE: name = "nice_priority" case process.RLIMIT_RTPRIO: name = "realtime_priority" default: continue } fields[prefix+"rlimit_"+name+"_soft"] = rlim.Soft fields[prefix+"rlimit_"+name+"_hard"] = rlim.Hard if name != "file_locks" { // gopsutil doesn't currently track the used file locks count fields[prefix+name] = rlim.Used } } } // Add the tags as requested by the user cmdline, err := p.Cmdline() if err == nil { if tagging["cmdline"] { p.tags["cmdline"] = cmdline } else { fields[prefix+"cmdline"] = cmdline } } if tagging["pid"] { p.tags["pid"] = strconv.Itoa(int(p.Pid)) } else { fields["pid"] = p.Pid } ppid, err := p.Ppid() if err == nil { if tagging["ppid"] { p.tags["ppid"] = strconv.Itoa(int(ppid)) } else { fields[prefix+"ppid"] = ppid } } status, err := p.Status() if err == nil { if tagging["status"] { p.tags["status"] = status[0] } else { fields[prefix+"status"] = status[0] } } user, err := p.Username() if err == nil { if tagging["user"] { p.tags["user"] = user } else { fields[prefix+"user"] = user } } if _, exists := p.tags["process_name"]; !exists { name, err := p.Name() if err == nil { p.tags["process_name"] = name } } return metric.New("procstat", p.tags, fields, time.Time{}) }