telegraf/plugins/inputs/procstat/process.go

233 lines
5.2 KiB
Go

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{})
}