Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport: Re-factor to fix high CPU usage on windows (#1562) #1598

Merged
merged 1 commit into from
May 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ https://github.com/elastic/beats/compare/v1.2.2...1.2[Check the HEAD diff]

*Topbeat*

- Fixed high CPU usage when using filtering under Windows. {pull}1562[1562]

*Filebeat*

*Winlogbeat*
Expand Down
69 changes: 39 additions & 30 deletions topbeat/beat/sigar.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,52 +244,61 @@ func getProcState(b byte) string {
return "unknown"
}

func GetProcess(pid int, cmdline string) (*Process, error) {
// newProcess creates a new Process object based on the state information.
func newProcess(pid int) (*Process, error) {

state := sigar.ProcState{}
if err := state.Get(pid); err != nil {
return nil, fmt.Errorf("error getting process state for pid=%d: %v", pid, err)
}

proc := Process{
Pid: pid,
Ppid: state.Ppid,
Name: state.Name,
State: getProcState(byte(state.State)),
Username: state.Username,
ctime: time.Now(),
}

return &proc, nil
}

// getDetails fills in CPU, memory, and command line details for the process
func (proc *Process) getDetails(cmdline string) error {

mem := sigar.ProcMem{}
if err := mem.Get(pid); err != nil {
return nil, fmt.Errorf("error getting process mem for pid=%d: %v", pid, err)
if err := mem.Get(proc.Pid); err != nil {
return fmt.Errorf("error getting process mem for pid=%d: %v", proc.Pid, err)
}
proc.Mem = &ProcMemStat{
Size: mem.Size,
Rss: mem.Resident,
Share: mem.Share,
}

cpu := sigar.ProcTime{}
if err := cpu.Get(pid); err != nil {
return nil, fmt.Errorf("error getting process cpu time for pid=%d: %v", pid, err)
if err := cpu.Get(proc.Pid); err != nil {
return fmt.Errorf("error getting process cpu time for pid=%d: %v", proc.Pid, err)
}
proc.Cpu = &ProcCpuTime{
Start: cpu.FormatStartTime(),
Total: cpu.Total,
User: cpu.User,
System: cpu.Sys,
}

if cmdline == "" {
args := sigar.ProcArgs{}
if err := args.Get(pid); err != nil {
return nil, fmt.Errorf("error getting process arguments for pid=%d: %v", pid, err)
if err := args.Get(proc.Pid); err != nil {
return fmt.Errorf("error getting process arguments for pid=%d: %v", proc.Pid, err)
}
cmdline = strings.Join(args.List, " ")
}

proc := Process{
Pid: pid,
Ppid: state.Ppid,
Name: state.Name,
State: getProcState(byte(state.State)),
Username: state.Username,
CmdLine: cmdline,
Mem: &ProcMemStat{
Size: mem.Size,
Rss: mem.Resident,
Share: mem.Share,
},
Cpu: &ProcCpuTime{
Start: cpu.FormatStartTime(),
Total: cpu.Total,
User: cpu.User,
System: cpu.Sys,
},
ctime: time.Now(),
proc.CmdLine = strings.Join(args.List, " ")
} else {
proc.CmdLine = cmdline
}

return &proc, nil
return nil
}

func GetFileSystemList() ([]sigar.FileSystem, error) {
Expand Down
11 changes: 8 additions & 3 deletions topbeat/beat/sigar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,13 @@ func TestGetProcess(t *testing.T) {

for _, pid := range pids {

process, err := GetProcess(pid, "")

process, err := newProcess(pid)
if err != nil {
continue
}
assert.NotNil(t, process)
err = process.getDetails("")
assert.NoError(t, err)

assert.True(t, (process.Pid > 0))
assert.True(t, (process.Ppid >= 0))
Expand Down Expand Up @@ -166,7 +167,11 @@ func BenchmarkGetProcess(b *testing.B) {
cmdline = p.CmdLine
}

process, err := GetProcess(pid, cmdline)
process, err := newProcess(pid)
if err != nil {
continue
}
err = process.getDetails(cmdline)
if err != nil {
continue
}
Expand Down
44 changes: 34 additions & 10 deletions topbeat/beat/topbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package beat

import (
"errors"
"fmt"
"math"
"regexp"
"strconv"
Expand All @@ -21,6 +22,7 @@ type ProcsMap map[int]*Process
type Topbeat struct {
period time.Duration
procs []string
regexps []*regexp.Regexp
procsMap ProcsMap
lastCpuTimes *CpuTimes
lastCpuTimesList []CpuTimes
Expand Down Expand Up @@ -101,9 +103,10 @@ func (tb *Topbeat) Setup(b *beat.Beat) error {
}

func (t *Topbeat) Run(b *beat.Beat) error {
var err error

t.initProcStats()
err := t.initProcStats()
if err != nil {
return err
}

ticker := time.NewTicker(t.period)
defer ticker.Stop()
Expand Down Expand Up @@ -157,12 +160,21 @@ func (t *Topbeat) Stop() {
close(t.done)
}

func (t *Topbeat) initProcStats() {
func (t *Topbeat) initProcStats() error {

t.procsMap = make(ProcsMap)

if len(t.procs) == 0 {
return
return nil
}

t.regexps = []*regexp.Regexp{}
for _, pattern := range t.procs {
reg, err := regexp.Compile(pattern)
if err != nil {
return fmt.Errorf("Failed to compile regexp [%s]: %v", pattern, err)
}
t.regexps = append(t.regexps, reg)
}

pids, err := Pids()
Expand All @@ -171,13 +183,20 @@ func (t *Topbeat) initProcStats() {
}

for _, pid := range pids {
process, err := GetProcess(pid, "")
process, err := newProcess(pid)
if err != nil {
logp.Debug("topbeat", "Skip process pid=%d: %v", pid, err)
continue
}
err = process.getDetails("")
if err != nil {
logp.Err("Error getting process details pid=%d: %v", pid, err)
continue
}
t.procsMap[process.Pid] = process
}

return nil
}

func (t *Topbeat) exportProcStats() error {
Expand All @@ -199,14 +218,20 @@ func (t *Topbeat) exportProcStats() error {
cmdline = previousProc.CmdLine
}

process, err := GetProcess(pid, cmdline)
process, err := newProcess(pid)
if err != nil {
logp.Debug("topbeat", "Skip process pid=%d: %v", pid, err)
continue
}

if t.MatchProcess(process.Name) {

err = process.getDetails(cmdline)
if err != nil {
logp.Err("Error getting process details. pid=%d: %v", process.Pid, err)
continue
}

t.addProcCpuPercentage(process)
t.addProcMemPercentage(process, 0 /*read total mem usage */)

Expand Down Expand Up @@ -334,9 +359,8 @@ func collectFileSystemStats(fss []sigar.FileSystem) []common.MapStr {

func (t *Topbeat) MatchProcess(name string) bool {

for _, reg := range t.procs {
matched, _ := regexp.MatchString(reg, name)
if matched {
for _, reg := range t.regexps {
if reg.MatchString(name) {
return true
}
}
Expand Down
3 changes: 3 additions & 0 deletions topbeat/beat/topbeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ func TestMatchProcs(t *testing.T) {
var beat = Topbeat{}

beat.procs = []string{".*"}
beat.initProcStats()
assert.True(t, beat.MatchProcess("topbeat"))

beat.procs = []string{"topbeat"}
beat.initProcStats()
assert.False(t, beat.MatchProcess("burn"))

// match no processes
beat.procs = []string{"$^"}
beat.initProcStats()
assert.False(t, beat.MatchProcess("burn"))
}

Expand Down