233 lines
5.2 KiB
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{})
|
|
}
|