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

Add host memory and CPU usage metrics #711

Merged
merged 1 commit into from
Dec 4, 2015
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
25 changes: 16 additions & 9 deletions probe/host/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"runtime"
"time"

"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/report"
)

Expand All @@ -18,17 +19,16 @@ const (
Load1 = "load1"
Load5 = "load5"
Load15 = "load15"
CPUUsage = "cpu_usage_percent"
MemUsage = "mem_usage_percent"
)

// Exposed for testing.
const (
ProcUptime = "/proc/uptime"
ProcLoad = "/proc/loadavg"
)

// Exposed for testing.
var (
Now = func() string { return time.Now().UTC().Format(time.RFC3339Nano) }
ProcUptime = "/proc/uptime"
ProcLoad = "/proc/loadavg"
ProcStat = "/proc/stat"
ProcMemInfo = "/proc/meminfo"
)

// Reporter generates Reports containing the host topology.
Expand Down Expand Up @@ -72,15 +72,22 @@ func (r *Reporter) Report() (report.Report, error) {
return rep, err
}

now := mtime.Now()
metrics := GetLoad(now)
cpuUsage, max := GetCPUUsagePercent()
metrics[CPUUsage] = report.MakeMetric().Add(now, cpuUsage).WithMax(max)
memUsage, max := GetMemoryUsagePercent()
metrics[MemUsage] = report.MakeMetric().Add(now, memUsage).WithMax(max)

rep.Host.AddNode(report.MakeHostNodeID(r.hostID), report.MakeNodeWith(map[string]string{
Timestamp: Now(),
Timestamp: mtime.Now().UTC().Format(time.RFC3339Nano),
HostName: r.hostName,
OS: runtime.GOOS,
KernelVersion: kernel,
Uptime: uptime.String(),
}).WithSets(report.Sets{
LocalNetworks: report.MakeStringSet(localCIDRs...),
}).WithMetrics(GetLoad()))
}).WithMetrics(metrics))

return rep, nil
}
32 changes: 20 additions & 12 deletions probe/host/reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
Expand All @@ -18,40 +19,47 @@ func TestReporter(t *testing.T) {
version = "version"
network = "192.168.0.0/16"
hostID = "hostid"
now = "now"
hostname = "hostname"
timestamp = time.Now()
load = report.Metrics{
host.Load1: report.MakeMetric().Add(timestamp, 1.0),
host.Load5: report.MakeMetric().Add(timestamp, 5.0),
host.Load15: report.MakeMetric().Add(timestamp, 15.0),
host.Load1: report.MakeMetric().Add(timestamp, 1.0),
host.Load5: report.MakeMetric().Add(timestamp, 5.0),
host.Load15: report.MakeMetric().Add(timestamp, 15.0),
host.CPUUsage: report.MakeMetric().Add(timestamp, 30.0).WithMax(100.0),
host.MemUsage: report.MakeMetric().Add(timestamp, 60.0).WithMax(100.0),
}
uptime = "278h55m43s"
kernel = "release version"
_, ipnet, _ = net.ParseCIDR(network)
localNets = report.Networks([]*net.IPNet{ipnet})
)

mtime.NowForce(timestamp)
defer mtime.NowReset()

var (
oldGetKernelVersion = host.GetKernelVersion
oldGetLoad = host.GetLoad
oldGetUptime = host.GetUptime
oldNow = host.Now
oldGetKernelVersion = host.GetKernelVersion
oldGetLoad = host.GetLoad
oldGetUptime = host.GetUptime
oldGetCPUUsagePercent = host.GetCPUUsagePercent
oldGetMemoryUsagePercent = host.GetMemoryUsagePercent
)
defer func() {
host.GetKernelVersion = oldGetKernelVersion
host.GetLoad = oldGetLoad
host.GetUptime = oldGetUptime
host.Now = oldNow
host.GetCPUUsagePercent = oldGetCPUUsagePercent
host.GetMemoryUsagePercent = oldGetMemoryUsagePercent
}()
host.GetKernelVersion = func() (string, error) { return release + " " + version, nil }
host.GetLoad = func() report.Metrics { return load }
host.GetLoad = func(time.Time) report.Metrics { return load }
host.GetUptime = func() (time.Duration, error) { return time.ParseDuration(uptime) }
host.Now = func() string { return now }
host.GetCPUUsagePercent = func() (float64, float64) { return 30.0, 100.0 }
host.GetMemoryUsagePercent = func() (float64, float64) { return 60.0, 100.0 }

want := report.MakeReport()
want.Host.AddNode(report.MakeHostNodeID(hostID), report.MakeNodeWith(map[string]string{
host.Timestamp: now,
host.Timestamp: timestamp.UTC().Format(time.RFC3339Nano),
host.HostName: hostname,
host.OS: runtime.GOOS,
host.Uptime: uptime,
Expand Down
13 changes: 11 additions & 2 deletions probe/host/system_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ var GetKernelVersion = func() (string, error) {
}

// GetLoad returns the current load averages as metrics.
var GetLoad = func() report.Metrics {
var GetLoad = func(now time.Time) report.Metrics {
out, err := exec.Command("w").CombinedOutput()
if err != nil {
return nil
}
now := time.Now()
matches := loadRe.FindAllStringSubmatch(string(out), -1)
if matches == nil || len(matches) < 1 || len(matches[0]) < 4 {
return nil
Expand Down Expand Up @@ -84,3 +83,13 @@ var GetUptime = func() (time.Duration, error) {
}
return (time.Duration(d) * 24 * time.Hour) + (time.Duration(h) * time.Hour) + (time.Duration(m) * time.Minute), nil
}

// GetCPUUsagePercent returns the percent cpu usage and max (ie #cpus * 100)
var GetCPUUsagePercent = func() (float64, float64) {
return 0.0, 0.0
}

// GetMemoryUsagePercent returns the percent memory usage and max (ie 100)
var GetMemoryUsagePercent = func() (float64, float64) {
return 0.0, 0.0
}
44 changes: 42 additions & 2 deletions probe/host/system_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"syscall"
"time"

linuxproc "github.com/c9s/goprocinfo/linux"

"github.com/weaveworks/scope/report"
)

Expand All @@ -24,12 +26,11 @@ var GetKernelVersion = func() (string, error) {
}

// GetLoad returns the current load averages as metrics.
var GetLoad = func() report.Metrics {
var GetLoad = func(now time.Time) report.Metrics {
buf, err := ioutil.ReadFile("/proc/loadavg")
if err != nil {
return nil
}
now := time.Now()
toks := strings.Fields(string(buf))
if len(toks) < 3 {
return nil
Expand Down Expand Up @@ -72,3 +73,42 @@ var GetUptime = func() (time.Duration, error) {

return time.Duration(uptime) * time.Second, nil
}

var previousStat = linuxproc.CPUStat{}

// GetCPUUsagePercent returns the percent cpu usage and max (ie #cpus * 100)
var GetCPUUsagePercent = func() (float64, float64) {
stat, err := linuxproc.ReadStat(ProcStat)
if err != nil {
return 0.0, 0.0
}

// From http://stackoverflow.com/questions/23367857/accurate-calculation-of-cpu-usage-given-in-percentage-in-linux
var (
currentStat = stat.CPUStatAll
prevIdle = previousStat.Idle + previousStat.IOWait
idle = currentStat.Idle + currentStat.IOWait
prevNonIdle = (previousStat.User + previousStat.Nice + previousStat.System +
previousStat.IRQ + previousStat.SoftIRQ + previousStat.Steal)
nonIdle = (currentStat.User + currentStat.Nice + currentStat.System +
currentStat.IRQ + currentStat.SoftIRQ + currentStat.Steal)
prevTotal = prevIdle + prevNonIdle
total = idle + nonIdle
// differentiate: actual value minus the previous one
totald = total - prevTotal
idled = idle - prevIdle
)
previousStat = currentStat
return float64(totald-idled) * 100. / float64(totald), float64(len(stat.CPUStats)) * 100.
}

// GetMemoryUsagePercent returns the percent memory usage and max (ie 100)
var GetMemoryUsagePercent = func() (float64, float64) {
meminfo, err := linuxproc.ReadMemInfo(ProcMemInfo)
if err != nil {
return 0.0, 0.0
}

used := meminfo.MemTotal - meminfo.MemFree - meminfo.Buffers - meminfo.Cached
return float64(used) * 100. / float64(meminfo.MemTotal), 100.
}
3 changes: 2 additions & 1 deletion probe/host/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package host_test
import (
"strings"
"testing"
"time"

"github.com/weaveworks/scope/probe/host"
)
Expand All @@ -19,7 +20,7 @@ func TestGetKernelVersion(t *testing.T) {
}

func TestGetLoad(t *testing.T) {
have := host.GetLoad()
have := host.GetLoad(time.Now())
if len(have) != 3 {
t.Fatalf("Expected 3 metrics, but got: %v", have)
}
Expand Down
15 changes: 14 additions & 1 deletion render/detailed_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,9 @@ func processOriginTable(nmd report.Node, addHostTag bool, addContainerTag bool)
}, len(rows) > 0 || commFound || pidFound
}

func sparklineRow(human string, metric report.Metric, format func(report.Metric) (report.Metric, string)) Row {
type formatter func(report.Metric) (report.Metric, string)

func sparklineRow(human string, metric report.Metric, format formatter) Row {
if format == nil {
format = formatDefault
}
Expand Down Expand Up @@ -520,6 +522,17 @@ func hostOriginTable(nmd report.Node) (Table, bool) {
rows = append(rows, sparklineRow(tuple.human, val, nil))
}
}
for _, tuple := range []struct {
key, human string
fmt formatter
}{
{host.CPUUsage, "CPU Usage", formatPercent},
{host.MemUsage, "Memory Usage", formatPercent},
} {
if val, ok := nmd.Metrics[tuple.key]; ok {
rows = append(rows, sparklineRow(tuple.human, val, tuple.fmt))
}
}
for _, tuple := range []struct{ key, human string }{
{host.OS, "Operating system"},
{host.KernelVersion, "Kernel version"},
Expand Down
11 changes: 11 additions & 0 deletions report/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ func (m Metric) WithFirst(t time.Time) Metric {
}
}

// WithMax returns a fresh copy of m, with Max set to max
func (m Metric) WithMax(max float64) Metric {
return Metric{
Samples: m.Samples,
Max: max,
Min: m.Min,
First: m.First,
Last: m.Last,
}
}

// Len returns the number of samples in the metric.
func (m Metric) Len() int {
if m.Samples == nil {
Expand Down
Loading