2022-04-21 16:39:53 +08:00
|
|
|
package procstat
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2022-06-13 17:35:39 +08:00
|
|
|
"fmt"
|
2022-04-21 16:39:53 +08:00
|
|
|
"log"
|
2022-04-21 18:13:16 +08:00
|
|
|
"runtime"
|
2022-04-21 16:39:53 +08:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
2022-04-21 18:13:16 +08:00
|
|
|
"time"
|
2022-04-21 16:39:53 +08:00
|
|
|
|
|
|
|
"flashcat.cloud/categraf/config"
|
|
|
|
"flashcat.cloud/categraf/inputs"
|
|
|
|
"flashcat.cloud/categraf/types"
|
2022-04-21 18:13:16 +08:00
|
|
|
"github.com/shirou/gopsutil/v3/process"
|
2022-04-21 16:39:53 +08:00
|
|
|
"github.com/toolkits/pkg/container/list"
|
|
|
|
)
|
|
|
|
|
|
|
|
const inputName = "procstat"
|
|
|
|
|
|
|
|
type PID int32
|
|
|
|
|
|
|
|
type Instance struct {
|
|
|
|
SearchExecSubstring string `toml:"search_exec_substring"`
|
|
|
|
SearchCmdlineSubstring string `toml:"search_cmdline_substring"`
|
|
|
|
SearchWinService string `toml:"search_win_service"`
|
|
|
|
Labels map[string]string `toml:"labels"`
|
|
|
|
IntervalTimes int64 `toml:"interval_times"`
|
|
|
|
Mode string `toml:"mode"`
|
2022-06-13 17:35:39 +08:00
|
|
|
GatherTotal bool `toml:"gather_total"`
|
|
|
|
GatherPerPid bool `toml:"gather_per_pid"`
|
2022-04-21 18:13:16 +08:00
|
|
|
GatherMoreMetrics []string `toml:"gather_more_metrics"`
|
2022-04-21 16:39:53 +08:00
|
|
|
|
|
|
|
searchString string
|
|
|
|
solarisMode bool
|
2022-04-21 22:12:19 +08:00
|
|
|
procs map[PID]Process
|
2022-04-21 16:39:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ins *Instance) Init() error {
|
|
|
|
if ins.Mode == "" {
|
|
|
|
ins.Mode = "irix"
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.ToLower(ins.Mode) == "solaris" {
|
|
|
|
ins.solarisMode = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if ins.SearchExecSubstring != "" {
|
|
|
|
ins.searchString = ins.SearchExecSubstring
|
|
|
|
log.Println("I! procstat: search_exec_substring:", ins.SearchExecSubstring)
|
|
|
|
} else if ins.SearchCmdlineSubstring != "" {
|
|
|
|
ins.searchString = ins.SearchCmdlineSubstring
|
|
|
|
log.Println("I! procstat: search_cmdline_substring:", ins.SearchCmdlineSubstring)
|
|
|
|
} else if ins.SearchWinService != "" {
|
|
|
|
ins.searchString = ins.SearchWinService
|
|
|
|
log.Println("I! procstat: search_win_service:", ins.SearchWinService)
|
|
|
|
} else {
|
|
|
|
return errors.New("the fields should not be all blank: search_exec_substring, search_cmdline_substring, search_win_service")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type Procstat struct {
|
2022-04-29 00:02:20 +08:00
|
|
|
config.Interval
|
|
|
|
Instances []*Instance `toml:"instances"`
|
2022-04-21 16:39:53 +08:00
|
|
|
Counter uint64
|
|
|
|
wg sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
inputs.Add(inputName, func() inputs.Input {
|
|
|
|
return &Procstat{}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-28 15:24:48 +08:00
|
|
|
func (s *Procstat) Prefix() string {
|
2022-04-21 16:39:53 +08:00
|
|
|
return inputName
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Procstat) Init() error {
|
|
|
|
if len(s.Instances) == 0 {
|
2022-04-25 12:08:56 +08:00
|
|
|
return types.ErrInstancesEmpty
|
2022-04-21 16:39:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < len(s.Instances); i++ {
|
|
|
|
if err := s.Instances[i].Init(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Procstat) Drop() {}
|
|
|
|
|
2022-04-25 15:34:15 +08:00
|
|
|
func (s *Procstat) Gather(slist *list.SafeList) {
|
2022-04-21 16:39:53 +08:00
|
|
|
atomic.AddUint64(&s.Counter, 1)
|
|
|
|
for i := range s.Instances {
|
|
|
|
ins := s.Instances[i]
|
|
|
|
s.wg.Add(1)
|
|
|
|
go s.gatherOnce(slist, ins)
|
|
|
|
}
|
|
|
|
s.wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Procstat) gatherOnce(slist *list.SafeList, ins *Instance) {
|
|
|
|
defer s.wg.Done()
|
|
|
|
|
|
|
|
if ins.IntervalTimes > 0 {
|
|
|
|
counter := atomic.LoadUint64(&s.Counter)
|
|
|
|
if counter%uint64(ins.IntervalTimes) != 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
pids []PID
|
|
|
|
err error
|
|
|
|
tags = map[string]string{"search_string": ins.searchString}
|
|
|
|
)
|
|
|
|
|
2022-04-21 16:46:46 +08:00
|
|
|
for k, v := range ins.Labels {
|
|
|
|
tags[k] = v
|
|
|
|
}
|
|
|
|
|
2022-04-21 16:39:53 +08:00
|
|
|
pg, _ := NewNativeFinder()
|
|
|
|
if ins.SearchExecSubstring != "" {
|
|
|
|
pids, err = pg.Pattern(ins.SearchExecSubstring)
|
|
|
|
} else if ins.SearchCmdlineSubstring != "" {
|
|
|
|
pids, err = pg.FullPattern(ins.SearchCmdlineSubstring)
|
|
|
|
} else if ins.SearchWinService != "" {
|
|
|
|
pids, err = s.winServicePIDs(ins.SearchWinService)
|
|
|
|
} else {
|
|
|
|
log.Println("E! Oops... search string not found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Println("E! procstat: failed to lookup pids, search string:", ins.searchString, "error:", err)
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("lookup_count", 0, tags))
|
2022-04-21 16:39:53 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("lookup_count", len(pids), tags))
|
2022-04-21 18:18:17 +08:00
|
|
|
if len(pids) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-04-21 18:13:16 +08:00
|
|
|
if len(ins.GatherMoreMetrics) == 0 {
|
2022-04-21 16:39:53 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-04-21 22:12:19 +08:00
|
|
|
s.updateProcesses(ins, pids)
|
2022-04-21 18:13:16 +08:00
|
|
|
|
|
|
|
for _, field := range ins.GatherMoreMetrics {
|
|
|
|
switch field {
|
|
|
|
case "threads":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherThreads(slist, ins.procs, tags)
|
2022-04-21 18:13:16 +08:00
|
|
|
case "fd":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherFD(slist, ins.procs, tags)
|
2022-04-21 18:13:16 +08:00
|
|
|
case "io":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherIO(slist, ins.procs, tags)
|
2022-04-21 18:13:16 +08:00
|
|
|
case "uptime":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherUptime(slist, ins.procs, tags)
|
2022-04-21 18:13:16 +08:00
|
|
|
case "cpu":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherCPU(slist, ins.procs, tags, ins.solarisMode)
|
2022-04-21 18:13:16 +08:00
|
|
|
case "mem":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherMem(slist, ins.procs, tags)
|
2022-04-21 18:13:16 +08:00
|
|
|
case "limit":
|
2022-06-13 17:35:39 +08:00
|
|
|
ins.gatherLimit(slist, ins.procs, tags)
|
2022-04-21 18:13:16 +08:00
|
|
|
default:
|
|
|
|
log.Println("unknown choice in gather_more_metrics:", field)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-21 22:12:19 +08:00
|
|
|
func (s *Procstat) updateProcesses(ins *Instance, pids []PID) {
|
|
|
|
procs := make(map[PID]Process)
|
|
|
|
|
|
|
|
for _, pid := range pids {
|
|
|
|
old, has := ins.procs[pid]
|
|
|
|
if has {
|
|
|
|
if name, err := old.Name(); err != nil || name == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
procs[pid] = old
|
|
|
|
} else {
|
|
|
|
proc, err := NewProc(pid)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if name, err := proc.Name(); err != nil || name == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
procs[pid] = proc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ins.procs = procs
|
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherThreads(slist *list.SafeList, procs map[PID]Process, tags map[string]string) {
|
2022-04-21 18:13:16 +08:00
|
|
|
var val int32
|
|
|
|
for pid := range procs {
|
|
|
|
v, err := procs[pid].NumThreads()
|
|
|
|
if err == nil {
|
|
|
|
val += v
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherPerPid {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("num_threads", val, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
}
|
2022-06-13 17:35:39 +08:00
|
|
|
|
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("num_threads_total", val, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherFD(slist *list.SafeList, procs map[PID]Process, tags map[string]string) {
|
2022-04-21 18:13:16 +08:00
|
|
|
var val int32
|
|
|
|
for pid := range procs {
|
|
|
|
v, err := procs[pid].NumFDs()
|
|
|
|
if err == nil {
|
|
|
|
val += v
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherPerPid {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("num_fds", val, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
}
|
2022-06-13 17:35:39 +08:00
|
|
|
|
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("num_fds_total", val, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherIO(slist *list.SafeList, procs map[PID]Process, tags map[string]string) {
|
2022-04-21 18:13:16 +08:00
|
|
|
var (
|
|
|
|
readCount uint64
|
|
|
|
writeCount uint64
|
|
|
|
readBytes uint64
|
|
|
|
writeBytes uint64
|
|
|
|
)
|
|
|
|
|
|
|
|
for pid := range procs {
|
|
|
|
io, err := procs[pid].IOCounters()
|
|
|
|
if err == nil {
|
|
|
|
readCount += io.ReadCount
|
|
|
|
writeCount += io.WriteCount
|
|
|
|
readBytes += io.ReadBytes
|
|
|
|
writeBytes += io.WriteBytes
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherPerPid {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("read_count", readCount, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
|
|
|
slist.PushFront(types.NewSample("write_count", writeCount, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
|
|
|
slist.PushFront(types.NewSample("read_bytes", readBytes, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
|
|
|
slist.PushFront(types.NewSample("write_bytes", writeBytes, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("read_count_total", readCount, tags))
|
|
|
|
slist.PushFront(types.NewSample("write_count_total", writeCount, tags))
|
|
|
|
slist.PushFront(types.NewSample("read_bytes_total", readBytes, tags))
|
|
|
|
slist.PushFront(types.NewSample("write_bytes_total", writeBytes, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherUptime(slist *list.SafeList, procs map[PID]Process, tags map[string]string) {
|
2022-04-21 18:13:16 +08:00
|
|
|
// use the smallest one
|
|
|
|
var value int64 = -1
|
|
|
|
for pid := range procs {
|
|
|
|
v, err := procs[pid].CreateTime() // returns epoch in ms
|
|
|
|
if err == nil {
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherPerPid {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("uptime", value, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
if value == -1 {
|
|
|
|
value = v
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if value > v {
|
|
|
|
value = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-13 17:35:39 +08:00
|
|
|
|
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("uptime_minimum", value, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherCPU(slist *list.SafeList, procs map[PID]Process, tags map[string]string, solarisMode bool) {
|
2022-04-21 18:13:16 +08:00
|
|
|
var value float64
|
|
|
|
for pid := range procs {
|
|
|
|
v, err := procs[pid].Percent(time.Duration(0))
|
|
|
|
if err == nil {
|
|
|
|
if solarisMode {
|
|
|
|
value += v / float64(runtime.NumCPU())
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("cpu_usage", v/float64(runtime.NumCPU()), map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-04-21 18:13:16 +08:00
|
|
|
} else {
|
|
|
|
value += v
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("cpu_usage", v, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-13 17:35:39 +08:00
|
|
|
|
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("cpu_usage_total", value, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherMem(slist *list.SafeList, procs map[PID]Process, tags map[string]string) {
|
2022-04-21 18:13:16 +08:00
|
|
|
var value float32
|
|
|
|
for pid := range procs {
|
|
|
|
v, err := procs[pid].MemoryPercent()
|
|
|
|
if err == nil {
|
|
|
|
value += v
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherPerPid {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("mem_usage", v, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
}
|
2022-06-13 17:35:39 +08:00
|
|
|
|
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("mem_usage_total", value, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
|
2022-06-13 17:35:39 +08:00
|
|
|
func (ins *Instance) gatherLimit(slist *list.SafeList, procs map[PID]Process, tags map[string]string) {
|
|
|
|
var softMin, hardMin uint64
|
2022-04-21 18:13:16 +08:00
|
|
|
for pid := range procs {
|
|
|
|
rlims, err := procs[pid].RlimitUsage(false)
|
|
|
|
if err == nil {
|
|
|
|
for _, rlim := range rlims {
|
|
|
|
if rlim.Resource == process.RLIMIT_NOFILE {
|
2022-06-13 17:35:39 +08:00
|
|
|
if ins.GatherPerPid {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("rlimit_num_fds_soft", rlim.Soft, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
|
|
|
slist.PushFront(types.NewSample("rlimit_num_fds_hard", rlim.Hard, map[string]string{"pid": fmt.Sprint(pid)}, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if softMin == 0 {
|
|
|
|
softMin = rlim.Soft
|
|
|
|
hardMin = rlim.Hard
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if softMin > rlim.Soft {
|
|
|
|
softMin = rlim.Soft
|
|
|
|
}
|
|
|
|
|
|
|
|
if hardMin > rlim.Hard {
|
|
|
|
hardMin = rlim.Hard
|
|
|
|
}
|
2022-04-21 18:13:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-13 17:35:39 +08:00
|
|
|
|
|
|
|
if ins.GatherTotal {
|
2022-07-03 22:01:41 +08:00
|
|
|
slist.PushFront(types.NewSample("rlimit_num_fds_soft_minimum", softMin, tags))
|
|
|
|
slist.PushFront(types.NewSample("rlimit_num_fds_hard_minimum", hardMin, tags))
|
2022-06-13 17:35:39 +08:00
|
|
|
}
|
2022-04-21 16:39:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Procstat) winServicePIDs(winService string) ([]PID, error) {
|
|
|
|
var pids []PID
|
|
|
|
|
|
|
|
pid, err := queryPidWithWinServiceName(winService)
|
|
|
|
if err != nil {
|
|
|
|
return pids, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pids = append(pids, PID(pid))
|
|
|
|
|
|
|
|
return pids, nil
|
|
|
|
}
|