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

Adds CPU busy time and percentages #111

Closed
wants to merge 1 commit into from
Closed
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
62 changes: 59 additions & 3 deletions plugins/system/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ import (
"fmt"

"github.com/influxdb/telegraf/plugins"
"github.com/influxdb/telegraf/plugins/system/ps/cpu"
)

type CPUStats struct {
ps PS
ps PS
lastStats []cpu.CPUTimesStat
}

func NewCPUStats(ps PS) *CPUStats {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know a lot of the plugins don't conform to this at the moment (this is something I would like to improve), but all exported functions should be commented

times, _ := ps.CPUTimes()
stats := CPUStats{
ps: ps,
lastStats: times,
}

return &stats
}

func (_ *CPUStats) Description() string {
Expand All @@ -18,15 +30,19 @@ func (_ *CPUStats) SampleConfig() string { return "" }

func (s *CPUStats) Gather(acc plugins.Accumulator) error {
times, err := s.ps.CPUTimes()

if err != nil {
return fmt.Errorf("error getting CPU info: %s", err)
}

for _, cts := range times {
for i, cts := range times {
tags := map[string]string{
"cpu": cts.CPU,
}

busy, total := busyAndTotalCpuTime(cts)

// Add total cpu numbers
add(acc, "user", cts.User, tags)
add(acc, "system", cts.System, tags)
add(acc, "idle", cts.Idle, tags)
Expand All @@ -38,13 +54,53 @@ func (s *CPUStats) Gather(acc plugins.Accumulator) error {
add(acc, "guest", cts.Guest, tags)
add(acc, "guestNice", cts.GuestNice, tags)
add(acc, "stolen", cts.Stolen, tags)
add(acc, "busy", busy, tags)

// Add in percentage
lastCts := s.lastStats[i]
lastBusy, lastTotal := busyAndTotalCpuTime(lastCts)
busyDelta := busy - lastBusy
totalDelta := total - lastTotal

if totalDelta < 0 {
return fmt.Errorf("Error: current total CPU time is less than previous total CPU time")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this should be an error, will this happen in the case of a reboot? Or after reboot will lastStats start over at 0?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and if it should be an error, can you write a unit test to verify that it get's raised properly?

}

if totalDelta == 0 {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be continue instead of return nil, otherwise if we can't get percentages for one CPU then we get no stats at all for the rest.

}

add(acc, "percentageUser", 100*(cts.User-lastCts.User)/totalDelta, tags)
add(acc, "percentageSystem", 100*(cts.System-lastCts.System)/totalDelta, tags)
add(acc, "percentageIdle", 100*(cts.Idle-lastCts.Idle)/totalDelta, tags)
add(acc, "percentageNice", 100*(cts.Nice-lastCts.Nice)/totalDelta, tags)
add(acc, "percentageIowait", 100*(cts.Iowait-lastCts.Iowait)/totalDelta, tags)
add(acc, "percentageIrq", 100*(cts.Irq-lastCts.Irq)/totalDelta, tags)
add(acc, "percentageSoftirq", 100*(cts.Softirq-lastCts.Softirq)/totalDelta, tags)
add(acc, "percentageSteal", 100*(cts.Steal-lastCts.Steal)/totalDelta, tags)
add(acc, "percentageGuest", 100*(cts.Guest-lastCts.Guest)/totalDelta, tags)
add(acc, "percentageGuestNice", 100*(cts.GuestNice-lastCts.GuestNice)/totalDelta, tags)
add(acc, "percentageStolen", 100*(cts.Stolen-lastCts.Stolen)/totalDelta, tags)

add(acc, "percentageBusy", 100*busyDelta/totalDelta, tags)

}

s.lastStats = times

return nil
}

func busyAndTotalCpuTime(t cpu.CPUTimesStat) (float64, float64) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please document this function with a comment stating what the two float64 values are

busy := t.User + t.System + t.Nice + t.Iowait + t.Irq + t.Softirq + t.Steal +
t.Guest + t.GuestNice + t.Stolen

return busy, busy + t.Idle
}

func init() {
plugins.Add("cpu", func() plugins.Plugin {
return &CPUStats{ps: &systemPS{}}
realPS := &systemPS{}
return NewCPUStats(realPS)
})
}
128 changes: 116 additions & 12 deletions plugins/system/system_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package system

import (
"fmt"
"reflect"
"testing"

"github.com/influxdb/telegraf/plugins/system/ps/cpu"
Expand Down Expand Up @@ -44,6 +46,21 @@ func TestSystemStats_GenerateStats(t *testing.T) {
Stolen: 0.051,
}

cts2 := cpu.CPUTimesStat{
CPU: "cpu0",
User: 11.4, // increased by 8.3
System: 10.9, // increased by 2.7
Idle: 158.8699, // increased by 78.7699 (for total increase of 100)
Nice: 2.5, // increased by 1.2
Iowait: 0.7, // increased by 0.5
Irq: 1.2, // increased by 1.1
Softirq: 0.31, // increased by 0.2
Steal: 0.0002, // increased by 0.0001
Guest: 12.9, // increased by 4.8
GuestNice: 2.524, // increased by 2.2
Stolen: 0.281, // increased by 0.23
}

mps.On("CPUTimes").Return([]cpu.CPUTimesStat{cts}, nil)

du := &disk.DiskUsageStat{
Expand Down Expand Up @@ -171,26 +188,72 @@ func TestSystemStats_GenerateStats(t *testing.T) {
assert.True(t, acc.CheckValue("load5", 1.5))
assert.True(t, acc.CheckValue("load15", 0.8))

cs := &CPUStats{ps: &mps}
cs := NewCPUStats(&mps)

cputags := map[string]string{
"cpu": "cpu0",
}

preCPUPoints := len(acc.Points)
err = cs.Gather(&acc)
require.NoError(t, err)
numCPUPoints := len(acc.Points) - preCPUPoints

expectedCPUPoints := 12
assert.Equal(t, numCPUPoints, expectedCPUPoints)

// Computed values are checked with delta > 0 becasue of floating point arithmatic
// imprecision
assertContainsTaggedFloat(t, acc, "user", 3.1, 0, cputags)
assertContainsTaggedFloat(t, acc, "system", 8.2, 0, cputags)
assertContainsTaggedFloat(t, acc, "idle", 80.1, 0, cputags)
assertContainsTaggedFloat(t, acc, "nice", 1.3, 0, cputags)
assertContainsTaggedFloat(t, acc, "iowait", 0.2, 0, cputags)
assertContainsTaggedFloat(t, acc, "irq", 0.1, 0, cputags)
assertContainsTaggedFloat(t, acc, "softirq", 0.11, 0, cputags)
assertContainsTaggedFloat(t, acc, "steal", 0.0001, 0, cputags)
assertContainsTaggedFloat(t, acc, "guest", 8.1, 0, cputags)
assertContainsTaggedFloat(t, acc, "guestNice", 0.324, 0, cputags)
assertContainsTaggedFloat(t, acc, "stolen", 0.051, 0, cputags)
assertContainsTaggedFloat(t, acc, "busy", 21.4851, 0.0005, cputags)

mps2 := MockPS{}
mps2.On("CPUTimes").Return([]cpu.CPUTimesStat{cts2}, nil)
cs.ps = &mps2

// Should have added cpu percentages too
err = cs.Gather(&acc)
require.NoError(t, err)

assert.True(t, acc.CheckTaggedValue("user", 3.1, cputags))
assert.True(t, acc.CheckTaggedValue("system", 8.2, cputags))
assert.True(t, acc.CheckTaggedValue("idle", 80.1, cputags))
assert.True(t, acc.CheckTaggedValue("nice", 1.3, cputags))
assert.True(t, acc.CheckTaggedValue("iowait", 0.2, cputags))
assert.True(t, acc.CheckTaggedValue("irq", 0.1, cputags))
assert.True(t, acc.CheckTaggedValue("softirq", 0.11, cputags))
assert.True(t, acc.CheckTaggedValue("steal", 0.0001, cputags))
assert.True(t, acc.CheckTaggedValue("guest", 8.1, cputags))
assert.True(t, acc.CheckTaggedValue("guestNice", 0.324, cputags))
assert.True(t, acc.CheckTaggedValue("stolen", 0.051, cputags))
numCPUPoints = len(acc.Points) - (preCPUPoints + numCPUPoints)
expectedCPUPoints = 24
assert.Equal(t, numCPUPoints, expectedCPUPoints)

assertContainsTaggedFloat(t, acc, "user", 11.4, 0, cputags)
assertContainsTaggedFloat(t, acc, "system", 10.9, 0, cputags)
assertContainsTaggedFloat(t, acc, "idle", 158.8699, 0, cputags)
assertContainsTaggedFloat(t, acc, "nice", 2.5, 0, cputags)
assertContainsTaggedFloat(t, acc, "iowait", 0.7, 0, cputags)
assertContainsTaggedFloat(t, acc, "irq", 1.2, 0, cputags)
assertContainsTaggedFloat(t, acc, "softirq", 0.31, 0, cputags)
assertContainsTaggedFloat(t, acc, "steal", 0.0002, 0, cputags)
assertContainsTaggedFloat(t, acc, "guest", 12.9, 0, cputags)
assertContainsTaggedFloat(t, acc, "guestNice", 2.524, 0, cputags)
assertContainsTaggedFloat(t, acc, "stolen", 0.281, 0, cputags)
assertContainsTaggedFloat(t, acc, "busy", 42.7152, 0.0005, cputags)

assertContainsTaggedFloat(t, acc, "percentageUser", 8.3, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageSystem", 2.7, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageIdle", 78.7699, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageNice", 1.2, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageIowait", 0.5, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageIrq", 1.1, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageSoftirq", 0.2, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageSteal", 0.0001, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageGuest", 4.8, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageGuestNice", 2.2, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageStolen", 0.23, 0.0005, cputags)
assertContainsTaggedFloat(t, acc, "percentageBusy", 21.2301, 0.0005, cputags)

err = (&DiskStats{&mps}).Gather(&acc)
require.NoError(t, err)
Expand Down Expand Up @@ -319,3 +382,44 @@ func TestSystemStats_GenerateStats(t *testing.T) {
assert.True(t, acc.CheckTaggedValue("total_active_file", uint64(26), dockertags))
assert.True(t, acc.CheckTaggedValue("total_unevictable", uint64(27), dockertags))
}

// Asserts that a given accumulator contains a measurment of type float64 with
// specific tags within a certain distance of a given expected value. Asserts a failure
// if the measurement is of the wrong type, or if no matching measurements are found
//
// Paramaters:
// t *testing.T : Testing object to use
// acc testutil.Accumulator: Accumulator to examine
// measurement string : Name of the measurement to examine
// expectedValue float64 : Value to search for within the measurement
// delta float64 : Maximum acceptable distance of an accumulated value
// from the expectedValue parameter. Useful when
// floating-point arithmatic imprecision makes looking
// for an exact match impractical
// tags map[string]string : Tag set the found measurement must have. Set to nil to
// ignore the tag set.
func assertContainsTaggedFloat(
t *testing.T,
acc testutil.Accumulator,
measurement string,
expectedValue float64,
delta float64,
tags map[string]string,
) {
for _, pt := range acc.Points {
if pt.Measurement == measurement {
if (tags == nil) || reflect.DeepEqual(pt.Tags, tags) {
if value, ok := pt.Values["value"].(float64); ok {
if (value >= expectedValue-delta) && (value <= expectedValue+delta) {
// Found the point, return without failing
return
}
} else {
assert.Fail(t, fmt.Sprintf("Measurement \"%s\" does not have type float64", measurement))
}

}
}
}
assert.Fail(t, fmt.Sprintf("Could not find measurement \"%s\" with requested tags within %f of %f", measurement, delta, expectedValue))
}