Skip to content

Commit

Permalink
New metric: CPU usage (#5545)
Browse files Browse the repository at this point in the history
* libbeat: add CPU usage to metrics
* Add both percentage and normalized percentage of CPU.
* Move Round function to common.
* turn off crosscompile tests
  • Loading branch information
kvch authored and ruflin committed Jan 5, 2018
1 parent fd0e654 commit e18cff5
Show file tree
Hide file tree
Showing 27 changed files with 425 additions and 224 deletions.
18 changes: 10 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ jobs:
env: TARGETS="-C libbeat testsuite"
go: $GO_VERSION
stage: test
- os: linux
env: TARGETS="-C libbeat crosscompile"
go: $GO_VERSION
stage: test
# Skip tests: see https://github.com/elastic/beats/issues/5984
# - os: linux
# env: TARGETS="-C libbeat crosscompile"
# go: $GO_VERSION
# stage: test
- os: linux
env: TARGETS="-C libbeat stress-tests"
go: $GO_VERSION
Expand All @@ -81,10 +82,11 @@ jobs:
env: TARGETS="TEST_ENVIRONMENT=0 -C metricbeat testsuite"
go: $GO_VERSION
stage: test
- os: linux
env: TARGETS="-C metricbeat crosscompile"
go: $GO_VERSION
stage: test
# Skip tests: see https://github.com/elastic/beats/issues/5984
# - os: linux
# env: TARGETS="-C metricbeat crosscompile"
# go: $GO_VERSION
# stage: test

# Packetbeat
- os: linux
Expand Down
5 changes: 5 additions & 0 deletions libbeat/cmd/instance/beat.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ func (b *Beat) createBeater(bt beat.Creator) (beat.Beater, error) {
reg = monitoring.Default.NewRegistry("libbeat")
}

err = setupMetrics(b.Info.Beat)
if err != nil {
return nil, err
}

debugf("Initializing output plugins")
pipeline, err := pipeline.Load(b.Info, reg, b.Config.Pipeline, b.Config.Output)
if err != nil {
Expand Down
134 changes: 129 additions & 5 deletions libbeat/cmd/instance/metrics.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
package instance

import (
"fmt"
"runtime"
"time"

"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/libbeat/metric/system/cpu"
"github.com/elastic/beats/libbeat/metric/system/process"
"github.com/elastic/beats/libbeat/monitoring"
"github.com/elastic/beats/libbeat/monitoring/report/log"
)

var startTime = time.Now()
var (
cpuMonitor *cpu.Monitor
beatProcessStats *process.Stats
)

func init() {
metrics := monitoring.Default.NewRegistry("beat")
beatMetrics := monitoring.Default.NewRegistry("beat")
monitoring.NewFunc(beatMetrics, "memstats", reportMemStats, monitoring.Report)
monitoring.NewFunc(beatMetrics, "cpu", reportBeatCPU, monitoring.Report)
monitoring.NewFunc(beatMetrics, "info", reportInfo, monitoring.Report)

systemMetrics := monitoring.Default.NewRegistry("system")
monitoring.NewFunc(systemMetrics, "load", reportSystemLoadAverage, monitoring.Report)
monitoring.NewFunc(systemMetrics, "cpu", reportSystemCPUUsage, monitoring.Report)
}

func setupMetrics(name string) error {
cpuMonitor = new(cpu.Monitor)

beatProcessStats = &process.Stats{
Procs: []string{name},
EnvWhitelist: nil,
CpuTicks: false,
CacheCmdLine: true,
IncludeTop: process.IncludeTopConfig{},
}
err := beatProcessStats.Init()

monitoring.NewFunc(metrics, "memstats", reportMemStats, monitoring.Report)
monitoring.NewFunc(metrics, "info", reportInfo, monitoring.Report)
return err
}

func reportMemStats(m monitoring.Mode, V monitoring.Visitor) {
Expand All @@ -34,7 +61,104 @@ func reportInfo(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()

delta := time.Since(startTime)
delta := time.Since(log.StartTime)
uptime := int64(delta / time.Millisecond)
monitoring.ReportInt(V, "uptime.ms", uptime)
}

func reportBeatCPU(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()

cpuUsage, cpuUsageNorm, totalCPUUsage, err := getCPUPercentages()
if err != nil {
logp.Err("Error retrieving CPU percentages: %v", err)
return
}
monitoring.ReportFloat(V, "total.pct", cpuUsage)
monitoring.ReportFloat(V, "total.norm.pct", cpuUsageNorm)
monitoring.ReportFloat(V, "total.value", totalCPUUsage)

}

func getCPUPercentages() (float64, float64, float64, error) {
state, err := beatProcessStats.Get()
if err != nil {
return 0.0, 0.0, 0.0, fmt.Errorf("error retrieving process stats")
}

if len(state) != 1 {
return 0.0, 0.0, 0.0, fmt.Errorf("more or less than one processes: %v", len(state))
}

beatState := state[0]
iCPUUsage, err := beatState.GetValue("cpu.total.pct")
if err != nil {
return 0.0, 0.0, 0.0, fmt.Errorf("error getting total CPU usage: %v", err)
}
iCPUUsageNorm, err := beatState.GetValue("cpu.total.norm.pct")
if err != nil {
return 0.0, 0.0, 0.0, fmt.Errorf("error getting normalized CPU percentage: %v", err)
}

iTotalCPUUsage, err := beatState.GetValue("cpu.total.value")
if err != nil {
return 0.0, 0.0, 0.0, fmt.Errorf("error getting total CPU since start: %v", err)
}

cpuUsage, ok := iCPUUsage.(float64)
if !ok {
return 0.0, 0.0, 0.0, fmt.Errorf("error converting value of CPU usage")
}

cpuUsageNorm, ok := iCPUUsageNorm.(float64)
if !ok {
return 0.0, 0.0, 0.0, fmt.Errorf("error converting value of normalized CPU usage")
}

totalCPUUsage, ok := iTotalCPUUsage.(float64)
if !ok {
return 0.0, 0.0, 0.0, fmt.Errorf("error converting value of CPU usage since start")
}

return cpuUsage, cpuUsageNorm, totalCPUUsage, nil
}

func reportSystemLoadAverage(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()

load, err := cpu.Load()
if err != nil {
logp.Err("Error retrieving load average: %v", err)
return
}
avgs := load.Averages()
monitoring.ReportFloat(V, "1", avgs.OneMinute)
monitoring.ReportFloat(V, "5", avgs.FiveMinute)
monitoring.ReportFloat(V, "15", avgs.FifteenMinute)

normAvgs := load.NormalizedAverages()
monitoring.ReportFloat(V, "norm.1", normAvgs.OneMinute)
monitoring.ReportFloat(V, "norm.5", normAvgs.FiveMinute)
monitoring.ReportFloat(V, "norm.15", normAvgs.FifteenMinute)
}

func reportSystemCPUUsage(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()

sample, err := cpuMonitor.Sample()
if err != nil {
logp.Err("Error retrieving CPU usage of the host: %v", err)
return
}

pct := sample.Percentages()
monitoring.ReportFloat(V, "total.pct", pct.Total)

normalizedPct := sample.NormalizedPercentages()
monitoring.ReportFloat(V, "total.norm.pct", normalizedPct.Total)

monitoring.ReportInt(V, "cores", int64(process.NumCPU))
}
22 changes: 22 additions & 0 deletions libbeat/common/math.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package common

import "math"

// DefaultDecimalPlacesCount is the default number of decimal places
const DefaultDecimalPlacesCount = 4

// Round rounds the given float64 value and ensures that it has a maximum of
// "precision" decimal places.
func Round(val float64, precision int64) (newVal float64) {
var round float64
pow := math.Pow(10, float64(precision))
digit := pow * val
_, div := math.Modf(digit)
if div >= 0.5 {
round = math.Ceil(digit)
} else {
round = math.Floor(digit)
}
newVal = round / pow
return
}
17 changes: 17 additions & 0 deletions libbeat/common/math_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package common

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestRound(t *testing.T) {
assert.EqualValues(t, 0.5, Round(0.5, DefaultDecimalPlacesCount))
assert.EqualValues(t, 0.5, Round(0.50004, DefaultDecimalPlacesCount))
assert.EqualValues(t, 0.5001, Round(0.50005, DefaultDecimalPlacesCount))

assert.EqualValues(t, 1234.5, Round(1234.5, DefaultDecimalPlacesCount))
assert.EqualValues(t, 1234.5, Round(1234.50004, DefaultDecimalPlacesCount))
assert.EqualValues(t, 1234.5001, Round(1234.50005, DefaultDecimalPlacesCount))
}
Loading

0 comments on commit e18cff5

Please sign in to comment.