diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index e87e3c5d8ab..ef0ec2bcf37 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -499,6 +499,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Change vsphere.datastore.capacity.used.pct value to betweeen 0 and 1. {pull}23148[23148] - Update config in `windows.yml` file. {issue}23027[23027]{pull}23327[23327] - Fix metric grouping for windows/perfmon module {issue}23489[23489] {pull}23505[23505] +- Major refactor of system/cpu and system/core metrics. {pull}25771[25771] *Packetbeat* diff --git a/Vagrantfile b/Vagrantfile index 7452eea7a29..bc0037655ff 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -307,6 +307,7 @@ Vagrant.configure("2") do |config| c.vm.provision "shell", inline: $unixProvision, privileged: false c.vm.provision "shell", inline: $freebsdShellUpdate, privileged: true c.vm.provision "shell", inline: gvmProvision(arch="amd64", os="freebsd"), privileged: false + c.vm.provision "shell", inline: "sudo mount -t linprocfs /dev/null /proc", privileged: false end # OpenBSD 6.0 diff --git a/libbeat/metric/system/cpu/cpu.go b/libbeat/metric/system/cpu/cpu.go index c5f0695c2af..c27687a85ce 100644 --- a/libbeat/metric/system/cpu/cpu.go +++ b/libbeat/metric/system/cpu/cpu.go @@ -26,165 +26,6 @@ import ( sigar "github.com/elastic/gosigar" ) -// CPU Monitor - -// Monitor is used to monitor the overall CPU usage of the system. -type Monitor struct { - lastSample *sigar.Cpu -} - -// Sample collects a new sample of the CPU usage metrics. -func (m *Monitor) Sample() (*Metrics, error) { - cpuSample := &sigar.Cpu{} - if err := cpuSample.Get(); err != nil { - return nil, err - } - - oldLastSample := m.lastSample - m.lastSample = cpuSample - return &Metrics{oldLastSample, cpuSample}, nil -} - -// Percentages stores all CPU values in percentages collected by a Beat. -type Percentages struct { - User float64 - System float64 - Idle float64 - IOWait float64 - IRQ float64 - Nice float64 - SoftIRQ float64 - Steal float64 - Total float64 -} - -// Ticks stores all CPU values in number of tick collected by a Beat. -type Ticks struct { - User uint64 - System uint64 - Idle uint64 - IOWait uint64 - IRQ uint64 - Nice uint64 - SoftIRQ uint64 - Steal uint64 -} - -// Metrics stores the current and the last sample collected by a Beat. -type Metrics struct { - previousSample *sigar.Cpu - currentSample *sigar.Cpu -} - -// NormalizedPercentages returns CPU percentage usage information that is -// normalized by the number of CPU cores. The values will range from -// 0 to 100%. -func (m *Metrics) NormalizedPercentages() Percentages { - return cpuPercentages(m.previousSample, m.currentSample, 1) -} - -// Percentages returns CPU percentage usage information. The values range from -// 0 to 100% * NumCPU. -func (m *Metrics) Percentages() Percentages { - return cpuPercentages(m.previousSample, m.currentSample, runtime.NumCPU()) -} - -// cpuPercentages calculates the amount of CPU time used between the two given -// samples. The CPU percentages are divided by given numCPU value and rounded -// using Round. -func cpuPercentages(s0, s1 *sigar.Cpu, numCPU int) Percentages { - if s0 == nil || s1 == nil { - return Percentages{} - } - - // timeDelta is the total amount of CPU time available across all CPU cores. - timeDelta := s1.Total() - s0.Total() - if timeDelta <= 0 { - return Percentages{} - } - - calculatePct := func(v0, v1 uint64) float64 { - cpuDelta := int64(v1 - v0) - pct := float64(cpuDelta) / float64(timeDelta) - return common.Round(pct*float64(numCPU), common.DefaultDecimalPlacesCount) - } - - calculateTotalPct := func() float64 { - // IOWait time is excluded from the total as per #7627. - idle := calculatePct(s0.Idle, s1.Idle) + calculatePct(s0.Wait, s1.Wait) - return common.Round(float64(numCPU)-idle, common.DefaultDecimalPlacesCount) - } - - return Percentages{ - User: calculatePct(s0.User, s1.User), - System: calculatePct(s0.Sys, s1.Sys), - Idle: calculatePct(s0.Idle, s1.Idle), - IOWait: calculatePct(s0.Wait, s1.Wait), - IRQ: calculatePct(s0.Irq, s1.Irq), - Nice: calculatePct(s0.Nice, s1.Nice), - SoftIRQ: calculatePct(s0.SoftIrq, s1.SoftIrq), - Steal: calculatePct(s0.Stolen, s1.Stolen), - Total: calculateTotalPct(), - } -} - -// Ticks returns the number of CPU ticks from the last collected sample. -func (m *Metrics) Ticks() Ticks { - return Ticks{ - User: m.currentSample.User, - System: m.currentSample.Sys, - Idle: m.currentSample.Idle, - IOWait: m.currentSample.Wait, - IRQ: m.currentSample.Irq, - Nice: m.currentSample.Nice, - SoftIRQ: m.currentSample.SoftIrq, - Steal: m.currentSample.Stolen, - } -} - -// CPU Core Monitor - -// CoreMetrics is used to monitor the usage of individual CPU cores. -type CoreMetrics Metrics - -// Percentages returns CPU percentage usage information for the core. The values -// range from [0, 100%]. -func (m *CoreMetrics) Percentages() Percentages { return (*Metrics)(m).NormalizedPercentages() } - -// Ticks returns the raw number of "ticks". The value is a counter (though it -// may roll overfunc (m *CoreMetrics) Ticks() Ticks { return (*Metrics)(m).Ticks() } -func (m *CoreMetrics) Ticks() Ticks { return (*Metrics)(m).Ticks() } - -// CoresMonitor is used to monitor the usage information of all the CPU -// cores in the system. -type CoresMonitor struct { - lastSample []sigar.Cpu -} - -// Sample collects a new sample of the metrics from all CPU cores. -func (m *CoresMonitor) Sample() ([]CoreMetrics, error) { - var cores sigar.CpuList - if err := cores.Get(); err != nil { - return nil, err - } - - lastSample := m.lastSample - m.lastSample = cores.List - - cpuMetrics := make([]CoreMetrics, len(cores.List)) - for i := 0; i < len(cores.List); i++ { - if len(lastSample) > i { - cpuMetrics[i] = CoreMetrics{&lastSample[i], &cores.List[i]} - } else { - cpuMetrics[i] = CoreMetrics{nil, &cores.List[i]} - } - } - - return cpuMetrics, nil -} - -// CPU Load - // Load returns CPU load information for the previous 1, 5, and 15 minute // periods. func Load() (*LoadMetrics, error) { diff --git a/libbeat/metric/system/cpu/cpu_test.go b/libbeat/metric/system/cpu/cpu_test.go deleted file mode 100644 index 05e3ce035bf..00000000000 --- a/libbeat/metric/system/cpu/cpu_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -// +build !integration -// +build darwin freebsd linux openbsd windows - -package cpu - -import ( - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/elastic/gosigar" -) - -var ( - // numCores is the number of CPU cores in the system. Changes to operating - // system CPU allocation after process startup are not reflected. - numCores = runtime.NumCPU() -) - -func TestMonitorSample(t *testing.T) { - cpu := &Monitor{lastSample: &gosigar.Cpu{}} - s, err := cpu.Sample() - if err != nil { - t.Fatal(err) - } - - pct := s.Percentages() - assert.True(t, pct.User > 0) - assert.True(t, pct.System > 0) - - normPct := s.NormalizedPercentages() - assert.True(t, normPct.User > 0) - assert.True(t, normPct.System > 0) - assert.True(t, normPct.User <= 100) - assert.True(t, normPct.System <= 100) - - assert.True(t, pct.User > normPct.User) - assert.True(t, pct.System > normPct.System) - - ticks := s.Ticks() - assert.True(t, ticks.User > 0) - assert.True(t, ticks.System > 0) -} - -func TestCoresMonitorSample(t *testing.T) { - cores := &CoresMonitor{lastSample: make([]gosigar.Cpu, numCores)} - sample, err := cores.Sample() - if err != nil { - t.Fatal(err) - } - - for _, s := range sample { - normPct := s.Percentages() - assert.True(t, normPct.User > 0) - assert.True(t, normPct.User <= 100) - assert.True(t, normPct.System > 0) - assert.True(t, normPct.System <= 100) - assert.True(t, normPct.Idle > 0) - assert.True(t, normPct.Idle <= 100) - assert.True(t, normPct.Total > 0) - assert.True(t, normPct.Total <= 100) - - ticks := s.Ticks() - assert.True(t, ticks.User > 0) - assert.True(t, ticks.System > 0) - } -} - -// TestMetricsRounding tests that the returned percentages are rounded to -// four decimal places. -func TestMetricsRounding(t *testing.T) { - sample := Metrics{ - previousSample: &gosigar.Cpu{ - User: 10855311, - Sys: 2021040, - Idle: 17657874, - }, - currentSample: &gosigar.Cpu{ - User: 10855693, - Sys: 2021058, - Idle: 17657876, - }, - } - - pct := sample.NormalizedPercentages() - assert.Equal(t, pct.User, 0.9502) - assert.Equal(t, pct.System, 0.0448) -} - -// TestMetricsPercentages tests that Metrics returns the correct -// percentages and normalized percentages. -func TestMetricsPercentages(t *testing.T) { - numCores = 10 - defer func() { numCores = runtime.NumCPU() }() - - // This test simulates 30% user and 70% system (normalized), or 3% and 7% - // respectively when there are 10 CPUs. - const user, system = 30., 70. - - s0 := gosigar.Cpu{ - User: 10000000, - Sys: 10000000, - Idle: 20000000, - Nice: 0, - } - s1 := gosigar.Cpu{ - User: s0.User + uint64(user), - Sys: s0.Sys + uint64(system), - Idle: s0.Idle, - Nice: 0, - } - sample := Metrics{ - previousSample: &s0, - currentSample: &s1, - } - - pct := sample.NormalizedPercentages() - assert.EqualValues(t, .3, pct.User) - assert.EqualValues(t, .7, pct.System) - assert.EqualValues(t, .0, pct.Idle) - assert.EqualValues(t, 1., pct.Total) - - //bypass the Metrics API so we can have a constant CPU value - pct = cpuPercentages(&s0, &s1, numCores) - assert.EqualValues(t, .3*float64(numCores), pct.User) - assert.EqualValues(t, .7*float64(numCores), pct.System) - assert.EqualValues(t, .0*float64(numCores), pct.Idle) - assert.EqualValues(t, 1.*float64(numCores), pct.Total) -} diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 5ce8bad7f2c..379bd3bca3e 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -49313,6 +49313,18 @@ type: long -- +*`system.core.total.pct`*:: ++ +-- +Total active time spent by the core + + +type: scaled_float + +format: percent + +-- + *`system.core.user.pct`*:: + -- diff --git a/metricbeat/internal/metrics/cpu/doc.go b/metricbeat/internal/metrics/cpu/doc.go new file mode 100644 index 00000000000..ce8f92d5dc6 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/doc.go @@ -0,0 +1,18 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu diff --git a/metricbeat/internal/metrics/cpu/metrics.go b/metricbeat/internal/metrics/cpu/metrics.go new file mode 100644 index 00000000000..aca40fcebf7 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics.go @@ -0,0 +1,203 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu + +import ( + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +// CPU manages the CPU metrics from /proc/stat +// If a given metric isn't available on a given platform, +// The value will be null. All methods that use these fields +// should assume that any value can be null. +// The values are in "ticks", which translates to milliseconds of CPU time +type CPU struct { + User metrics.OptUint `struct:"user,omitempty"` + Sys metrics.OptUint `struct:"system,omitempty"` + Idle metrics.OptUint `struct:"idle,omitempty"` + Nice metrics.OptUint `struct:"nice,omitempty"` // Linux, Darwin, BSD + Irq metrics.OptUint `struct:"irq,omitempty"` // Linux and openbsd + Wait metrics.OptUint `struct:"iowait,omitempty"` // Linux and AIX + SoftIrq metrics.OptUint `struct:"softirq,omitempty"` // Linux only + Stolen metrics.OptUint `struct:"steal,omitempty"` // Linux only +} + +// MetricOpts defines the fields that are passed along to the formatted output +type MetricOpts struct { + Ticks bool + Percentages bool + NormalizedPercentages bool +} + +// CPUMetrics carries global and per-core CPU metrics +type CPUMetrics struct { + totals CPU + // list carries the same data, broken down by CPU + list []CPU +} + +// Total returns the total CPU time in ticks as scraped by the API +func (cpu CPU) Total() uint64 { + // it's generally safe to blindly sum these up, + // As we're just trying to get a total of all CPU time. + return metrics.SumOptUint(cpu.User, cpu.Nice, cpu.Sys, cpu.Idle, cpu.Wait, cpu.Irq, cpu.SoftIrq, cpu.Stolen) +} + +/* +The below code implements a "metrics tracker" that gives us the ability to +calculate CPU percentages, as we average usage across a time period. +*/ + +// Monitor is used to monitor the overall CPU usage of the system over time. +type Monitor struct { + lastSample CPUMetrics + Hostfs string +} + +// New returns a new CPU metrics monitor +// Hostfs is only relevant on linux and freebsd. +func New(hostfs string) *Monitor { + return &Monitor{Hostfs: hostfs} +} + +// Fetch collects a new sample of the CPU usage metrics. +// This will overwrite the currently stored samples. +func (m *Monitor) Fetch() (Metrics, error) { + metric, err := Get(m.Hostfs) + if err != nil { + return Metrics{}, errors.Wrap(err, "Error fetching CPU metrics") + } + + oldLastSample := m.lastSample + m.lastSample = metric + + return Metrics{previousSample: oldLastSample.totals, currentSample: metric.totals, count: len(metric.list), isTotals: true}, nil +} + +// FetchCores collects a new sample of CPU usage metrics per-core +// This will overwrite the currently stored samples. +func (m *Monitor) FetchCores() ([]Metrics, error) { + + metric, err := Get(m.Hostfs) + if err != nil { + return nil, errors.Wrap(err, "Error fetching CPU metrics") + } + + coreMetrics := make([]Metrics, len(metric.list)) + for i := 0; i < len(metric.list); i++ { + lastMetric := CPU{} + // Count of CPUs can change + if len(m.lastSample.list) > i { + lastMetric = m.lastSample.list[i] + } + coreMetrics[i] = Metrics{ + currentSample: metric.list[i], + previousSample: lastMetric, + isTotals: false, + } + } + m.lastSample = metric + return coreMetrics, nil +} + +// Metrics stores the current and the last sample collected by a Beat. +type Metrics struct { + previousSample CPU + currentSample CPU + count int + isTotals bool +} + +// Format returns the final MapStr data object for the metrics. +func (metric Metrics) Format(opts MetricOpts) (common.MapStr, error) { + + timeDelta := metric.currentSample.Total() - metric.previousSample.Total() + if timeDelta <= 0 { + return nil, errors.New("Previous sample is newer than current sample") + } + normCPU := metric.count + if !metric.isTotals { + normCPU = 1 + } + + formattedMetrics := common.MapStr{} + + reportOptMetric := func(name string, current, previous metrics.OptUint, norm int) { + if !current.IsZero() { + formattedMetrics[name] = fillMetric(opts, current, previous, timeDelta, norm) + } + } + + if opts.Percentages { + formattedMetrics.Put("total.pct", createTotal(metric.previousSample, metric.currentSample, timeDelta, normCPU)) + } + if opts.NormalizedPercentages { + formattedMetrics.Put("total.norm.pct", createTotal(metric.previousSample, metric.currentSample, timeDelta, 1)) + } + + reportOptMetric("user", metric.currentSample.User, metric.previousSample.User, normCPU) + reportOptMetric("system", metric.currentSample.Sys, metric.previousSample.Sys, normCPU) + reportOptMetric("idle", metric.currentSample.Idle, metric.previousSample.Idle, normCPU) + reportOptMetric("nice", metric.currentSample.Nice, metric.previousSample.Nice, normCPU) + reportOptMetric("irq", metric.currentSample.Irq, metric.previousSample.Irq, normCPU) + reportOptMetric("iowait", metric.currentSample.Wait, metric.previousSample.Wait, normCPU) + reportOptMetric("softirq", metric.currentSample.SoftIrq, metric.previousSample.SoftIrq, normCPU) + reportOptMetric("steal", metric.currentSample.Stolen, metric.previousSample.Stolen, normCPU) + + return formattedMetrics, nil +} + +func createTotal(prev, cur CPU, timeDelta uint64, numCPU int) float64 { + idleTime := cpuMetricTimeDelta(prev.Idle, cur.Idle, timeDelta, numCPU) + // Subtract wait time from total + // Wait time is not counted from the total as per #7627. + if !cur.Wait.IsZero() { + idleTime = idleTime + cpuMetricTimeDelta(prev.Wait, cur.Wait, timeDelta, numCPU) + } + return common.Round(float64(numCPU)-idleTime, common.DefaultDecimalPlacesCount) +} + +func fillMetric(opts MetricOpts, cur, prev metrics.OptUint, timeDelta uint64, numCPU int) common.MapStr { + event := common.MapStr{} + if opts.Ticks { + event.Put("ticks", cur.ValueOr(0)) + } + if opts.Percentages { + event.Put("pct", cpuMetricTimeDelta(prev, cur, timeDelta, numCPU)) + } + if opts.NormalizedPercentages { + event.Put("norm.pct", cpuMetricTimeDelta(prev, cur, timeDelta, 1)) + } + + return event +} + +// CPUCount returns the count of CPUs. When available, use this instead of runtime.NumCPU() +func (m *Metrics) CPUCount() int { + return m.count +} + +// cpuMetricTimeDelta is a helper used by fillTicks to calculate the delta between two CPU tick values +func cpuMetricTimeDelta(prev, current metrics.OptUint, timeDelta uint64, numCPU int) float64 { + cpuDelta := int64(current.ValueOr(0) - prev.ValueOr(0)) + pct := float64(cpuDelta) / float64(timeDelta) + return common.Round(pct*float64(numCPU), common.DefaultDecimalPlacesCount) +} diff --git a/metricbeat/internal/metrics/cpu/metrics_aix.go b/metricbeat/internal/metrics/cpu/metrics_aix.go new file mode 100644 index 00000000000..0b231ce6a34 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_aix.go @@ -0,0 +1,128 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu + +/* +#cgo LDFLAGS: -L/usr/lib -lperfstat + +#include +#include +#include +#include +#include +#include +#include +#include + +*/ +import "C" + +import ( + "fmt" + "os" + + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +func init() { + // sysconf(_SC_CLK_TCK) returns the number of ticks by second. + system.ticks = uint64(C.sysconf(C._SC_CLK_TCK)) + system.pagesize = uint64(os.Getpagesize()) +} + +var system struct { + ticks uint64 + btime uint64 + pagesize uint64 +} + +func tick2msec(val uint64) uint64 { + ticks := val * 1000 / system.ticks + return ticks +} + +// Get returns a metrics object for CPU data +func Get(_ string) (CPUMetrics, error) { + + totals, err := getCPUTotals() + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error getting CPU totals") + } + + list, err := getPerCPUMetrics() + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error getting per-cpu metrics") + } + + return CPUMetrics{totals: totals, list: list}, nil + +} + +// getCPUTotals gets the global CPU stats +func getCPUTotals() (CPU, error) { + cpudata := C.perfstat_cpu_total_t{} + + if _, err := C.perfstat_cpu_total(nil, &cpudata, C.sizeof_perfstat_cpu_total_t, 1); err != nil { + return CPU{}, fmt.Errorf("perfstat_cpu_total: %s", err) + } + + totals := CPU{} + totals.User = metrics.OptUintWith((uint64(cpudata.user))) + totals.Sys = metrics.OptUintWith(tick2msec(uint64(cpudata.sys))) + totals.Idle = metrics.OptUintWith(tick2msec(uint64(cpudata.idle))) + totals.Wait = metrics.OptUintWith(tick2msec(uint64(cpudata.wait))) + + return totals, nil +} + +// getPerCPUMetrics gets per-CPU metrics +func getPerCPUMetrics() ([]CPU, error) { + cpudata := C.perfstat_cpu_t{} + id := C.perfstat_id_t{} + id.name[0] = 0 + + // Retrieve the number of cpu using perfstat_cpu + capacity, err := C.perfstat_cpu(nil, nil, C.sizeof_perfstat_cpu_t, 0) + if err != nil { + return nil, fmt.Errorf("error while retrieving CPU number: %s", err) + } + list := make([]CPU, 0, capacity) + + for { + if _, err := C.perfstat_cpu(&id, &cpudata, C.sizeof_perfstat_cpu_t, 1); err != nil { + return nil, fmt.Errorf("perfstat_cpu: %s", err) + } + + cpu := CPU{} + cpu.User = metrics.OptUintWith(tick2msec(uint64(cpudata.user))) + cpu.Sys = metrics.OptUintWith(tick2msec(uint64(cpudata.sys))) + cpu.Idle = metrics.OptUintWith(tick2msec(uint64(cpudata.idle))) + cpu.Wait = metrics.OptUintWith(tick2msec(uint64(cpudata.wait))) + + list = append(list, cpu) + + if id.name[0] == 0 { + break + } + } + + return list, nil + +} diff --git a/metricbeat/internal/metrics/cpu/metrics_darwin.go b/metricbeat/internal/metrics/cpu/metrics_darwin.go new file mode 100644 index 00000000000..39c9d5ca453 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_darwin.go @@ -0,0 +1,57 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu + +import ( + "github.com/pkg/errors" + "github.com/shirou/gopsutil/cpu" + + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +// Get is the Darwin implementation of Get +func Get(_ string) (CPUMetrics, error) { + // We're using the gopsutil library here. + // The code used by both gosigar and go-sysinfo appears to be + // the same code as gopsutil, including copy-pasted comments. + // For the sake of just reducing complexity, + sum, err := cpu.Times(false) + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error fetching CPU summary data") + } + perCPU, err := cpu.Times(true) + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error fetching per-CPU data") + } + + cpulist := []CPU{} + for _, cpu := range perCPU { + cpulist = append(cpulist, fillCPU(cpu)) + } + return CPUMetrics{totals: fillCPU(sum[0]), list: cpulist}, nil +} + +func fillCPU(raw cpu.TimesStat) CPU { + totalCPU := CPU{ + Sys: metrics.OptUintWith(uint64(raw.System)), + User: metrics.OptUintWith(uint64(raw.User)), + Idle: metrics.OptUintWith(uint64(raw.Idle)), + Nice: metrics.OptUintWith(uint64(raw.Nice)), + } + return totalCPU +} diff --git a/metricbeat/internal/metrics/cpu/metrics_freebsd.go b/metricbeat/internal/metrics/cpu/metrics_freebsd.go new file mode 100644 index 00000000000..aef380d9cf8 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_freebsd.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu + +import ( + "bufio" + "fmt" + "strings" + + "github.com/joeshaw/multierror" + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +func scanStatFile(scanner *bufio.Scanner) (CPUMetrics, error) { + cpuData, err := statScanner(scanner, parseCPULine) + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error scanning stat file") + } + return cpuData, nil +} + +func parseCPULine(line string) (CPU, error) { + cpuData := CPU{} + fields := strings.Fields(line) + var errs multierror.Errors + + tryParseUint := func(name, field string) (v metrics.OptUint) { + u, err := touint(field) + if err != nil { + errs = append(errs, fmt.Errorf("failed to parse %v: %s", name, field)) + } else { + v = metrics.OptUintWith(u) + } + return v + } + + cpuData.User = tryParseUint("user", fields[1]) + cpuData.Nice = tryParseUint("nice", fields[2]) + cpuData.Sys = tryParseUint("sys", fields[3]) + cpuData.Idle = tryParseUint("idle", fields[4]) + + return cpuData, errs.Err() +} diff --git a/metricbeat/internal/metrics/cpu/metrics_linux.go b/metricbeat/internal/metrics/cpu/metrics_linux.go new file mode 100644 index 00000000000..9c8fae9bd23 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_linux.go @@ -0,0 +1,65 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu + +import ( + "bufio" + "fmt" + "strings" + + "github.com/joeshaw/multierror" + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +func scanStatFile(scanner *bufio.Scanner) (CPUMetrics, error) { + cpuData, err := statScanner(scanner, parseCPULine) + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error scanning stat file") + } + return cpuData, nil +} + +func parseCPULine(line string) (CPU, error) { + + var errs multierror.Errors + tryParseUint := func(name, field string) (v metrics.OptUint) { + u, err := touint(field) + if err != nil { + errs = append(errs, fmt.Errorf("failed to parse %v: %s", name, field)) + } else { + v = metrics.OptUintWith(u) + } + return v + } + + cpuData := CPU{} + fields := strings.Fields(line) + + cpuData.User = tryParseUint("user", fields[1]) + cpuData.Nice = tryParseUint("nice", fields[2]) + cpuData.Sys = tryParseUint("sys", fields[3]) + cpuData.Idle = tryParseUint("idle", fields[4]) + cpuData.Wait = tryParseUint("wait", fields[5]) + cpuData.Irq = tryParseUint("irq", fields[6]) + cpuData.SoftIrq = tryParseUint("softirq", fields[7]) + cpuData.Stolen = tryParseUint("stolen", fields[8]) + + return cpuData, errs.Err() +} diff --git a/metricbeat/internal/metrics/cpu/metrics_openbsd.go b/metricbeat/internal/metrics/cpu/metrics_openbsd.go new file mode 100644 index 00000000000..a5e9145ba2f --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_openbsd.go @@ -0,0 +1,134 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build openbsd + +package cpu + +/* +#include +#include +#include +#include +#include +#include +#include +#include +*/ +import "C" + +import ( + "syscall" + "unsafe" + + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +// Get is the OpenBSD implementation of get +func Get(_ string) (CPUMetrics, error) { + + // see man 2 sysctl + loadGlobal := [C.CPUSTATES]C.long{ + C.CP_USER, + C.CP_NICE, + C.CP_SYS, + C.CP_INTR, + C.CP_IDLE, + } + + // first, fetch global CPU data + err := sysctlGetCPUTimes(0, 0, &loadGlobal) + if err != nil { + return CPUMetrics{}, err + } + self := CPU{} + self.User = metrics.OptUintWith(loadGlobal[0]) + self.Nice = metrics.OptUintWith(loadGlobal[1]) + self.Sys = metrics.OptUintWith(loadGlobal[2]) + self.Irq = metrics.OptUintWith(loadGlobal[3]) + self.Idle = metrics.OptUintWith(loadGlobal[4]) + // Get count of available CPUs + ncpuMIB := [2]int32{C.CTL_HW, C.HW_NCPU} + callSize := uintptr(0) + var ncpu int + // The first call nulls out the retun pointer, so we instead fetch the expected size of expected return value. + _, _, errno := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&ncpuMIB[0])), 2, 0, uintptr(unsafe.Pointer(&callSize)), 0, 0) + + if errno != 0 || callSize == 0 { + return CPUMetrics{}, errno + } + + // Populate the cpu count + _, _, errno = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&ncpuMIB[0])), 2, uintptr(unsafe.Pointer(&ncpu)), uintptr(unsafe.Pointer(&callSize)), 0, 0) + + if errno != 0 || callSize == 0 { + return CPUMetrics{}, errno + } + + loadPerCPU := [C.CPUSTATES]C.long{ + C.CP_USER, + C.CP_NICE, + C.CP_SYS, + C.CP_INTR, + C.CP_IDLE, + } + + perCPU := make([]CPU, ncpu) + + // iterate over metrics for each CPU + for i := 0; i < ncpu; i++ { + sysctlGetCPUTimes(ncpu, i, &loadPerCPU) + perCPU[i].User = metrics.OptUintWith(loadGlobal[0]) + perCPU[i].Nice = metrics.OptUintWith(loadGlobal[1]) + perCPU[i].Sys = metrics.OptUintWith(loadGlobal[2]) + perCPU[i].Irq = metrics.OptUintWith(loadGlobal[3]) + perCPU[i].Idle = metrics.OptUintWith(loadGlobal[4]) + } + + metrics := CPUMetrics{totals: self, list: perCPU} + + return metrics, nil +} + +// sysctlGetCPUTimes runs the CTL_KERN::KERN_CPTIME sysctl command against the host. +func sysctlGetCPUTimes(ncpu int, curcpu int, load *[C.CPUSTATES]C.long) error { + var mib []int32 + + // Use the correct mib based on the number of CPUs and fill out the + // current CPU number in case of SMP. (0 indexed cf. self.List) + if ncpu == 0 { + mib = []int32{C.CTL_KERN, C.KERN_CPTIME} + } else { + mib = []int32{C.CTL_KERN, C.KERN_CPTIME2, int32(curcpu)} + } + + len := len(mib) + + n := uintptr(0) + // First we determine how much memory we'll need to pass later on (via `n`) + _, _, errno := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), uintptr(len), 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if errno != 0 || n == 0 { + return errno + } + + _, _, errno = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), uintptr(len), uintptr(unsafe.Pointer(load)), uintptr(unsafe.Pointer(&n)), 0, 0) + if errno != 0 || n == 0 { + return errno + } + + return nil +} diff --git a/metricbeat/internal/metrics/cpu/metrics_procfs_common.go b/metricbeat/internal/metrics/cpu/metrics_procfs_common.go new file mode 100644 index 00000000000..5f368f9c702 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_procfs_common.go @@ -0,0 +1,85 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// +build freebsd linux + +package cpu + +import ( + "bufio" + "os" + "path/filepath" + "strconv" + + "github.com/pkg/errors" +) + +// Get returns a metrics object for CPU data +func Get(procfs string) (CPUMetrics, error) { + path := filepath.Join(procfs, "/proc/stat") + fd, err := os.Open(path) + if err != nil { + return CPUMetrics{}, errors.Wrapf(err, "error opening file %s", path) + } + + return scanStatFile(bufio.NewScanner(fd)) + +} + +// statScanner iterates through a /proc/stat entry, reading both the global lines and per-CPU lines, each time calling lineReader, which implements the OS-specific code for parsing individual lines +func statScanner(scanner *bufio.Scanner, lineReader func(string) (CPU, error)) (CPUMetrics, error) { + cpuData := CPUMetrics{} + var err error + + for scanner.Scan() { + text := scanner.Text() + // Check to see if this is the global CPU line + if isCPUGlobalLine(text) { + cpuData.totals, err = lineReader(text) + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error parsing global CPU line") + } + } + if isCPULine(text) { + perCPU, err := lineReader(text) + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "error parsing CPU line") + } + cpuData.list = append(cpuData.list, perCPU) + + } + } + return cpuData, nil +} + +func isCPUGlobalLine(line string) bool { + if len(line) > 4 && line[0:4] == "cpu " { + return true + } + return false +} + +func isCPULine(line string) bool { + if len(line) > 3 && line[0:3] == "cpu" && line[3] != ' ' { + return true + } + return false +} + +func touint(val string) (uint64, error) { + return strconv.ParseUint(val, 10, 64) +} diff --git a/metricbeat/internal/metrics/cpu/metrics_test.go b/metricbeat/internal/metrics/cpu/metrics_test.go new file mode 100644 index 00000000000..ca2355d798a --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_test.go @@ -0,0 +1,160 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package cpu + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/metricbeat/internal/metrics" +) + +func TestMonitorSample(t *testing.T) { + cpu := &Monitor{lastSample: CPUMetrics{}} + s, err := cpu.Fetch() + if err != nil { + t.Fatal(err) + } + metricOpts := MetricOpts{Percentages: true, NormalizedPercentages: true, Ticks: true} + evt, err := s.Format(metricOpts) + assert.NoError(t, err, "error in Format") + testPopulatedEvent(evt, t, true) +} + +func TestCoresMonitorSample(t *testing.T) { + + cpuMetrics, err := Get("") + assert.NoError(t, err, "error in Get()") + + cores := &Monitor{lastSample: CPUMetrics{list: make([]CPU, len(cpuMetrics.list))}} + sample, err := cores.FetchCores() + if err != nil { + t.Fatal(err) + } + + for _, s := range sample { + evt := common.MapStr{} + metricOpts := MetricOpts{Percentages: true, Ticks: true} + evt, err := s.Format(metricOpts) + assert.NoError(t, err, "error in Format") + testPopulatedEvent(evt, t, false) + } +} + +func testPopulatedEvent(evt common.MapStr, t *testing.T, norm bool) { + user, err := evt.GetValue("user.pct") + assert.NoError(t, err, "error getting user.pct") + system, err := evt.GetValue("system.pct") + assert.NoError(t, err, "error getting system.pct") + assert.True(t, user.(float64) > 0) + assert.True(t, system.(float64) > 0) + + if norm { + normUser, err := evt.GetValue("user.norm.pct") + assert.NoError(t, err, "error getting user.norm.pct") + assert.True(t, normUser.(float64) > 0) + normSystem, err := evt.GetValue("system.norm.pct") + assert.NoError(t, err, "error getting system.norm.pct") + assert.True(t, normSystem.(float64) > 0) + assert.True(t, normUser.(float64) <= 100) + assert.True(t, normSystem.(float64) <= 100) + + assert.True(t, user.(float64) > normUser.(float64)) + assert.True(t, system.(float64) > normSystem.(float64)) + } + + userTicks, err := evt.GetValue("user.ticks") + assert.NoError(t, err, "error getting user.ticks") + assert.True(t, userTicks.(uint64) > 0) + systemTicks, err := evt.GetValue("system.ticks") + assert.NoError(t, err, "error getting system.ticks") + assert.True(t, systemTicks.(uint64) > 0) +} + +// TestMetricsRounding tests that the returned percentages are rounded to +// four decimal places. +func TestMetricsRounding(t *testing.T) { + + sample := Metrics{ + previousSample: CPU{ + User: metrics.OptUintWith(10855311), + Sys: metrics.OptUintWith(2021040), + Idle: metrics.OptUintWith(17657874), + }, + currentSample: CPU{ + User: metrics.OptUintWith(10855693), + Sys: metrics.OptUintWith(2021058), + Idle: metrics.OptUintWith(17657876), + }, + } + + evt, err := sample.Format(MetricOpts{NormalizedPercentages: true}) + normUser, err := evt.GetValue("user.norm.pct") + assert.NoError(t, err, "error getting user.norm.pct") + normSystem, err := evt.GetValue("system.norm.pct") + assert.NoError(t, err, "error getting system.norm.pct") + + assert.Equal(t, normUser.(float64), 0.9502) + assert.Equal(t, normSystem.(float64), 0.0448) +} + +// TestMetricsPercentages tests that Metrics returns the correct +// percentages and normalized percentages. +func TestMetricsPercentages(t *testing.T) { + numCores := 10 + // This test simulates 30% user and 70% system (normalized), or 3% and 7% + // respectively when there are 10 CPUs. + const userTest, systemTest = 30., 70. + + s0 := CPU{ + User: metrics.OptUintWith(10000000), + Sys: metrics.OptUintWith(10000000), + Idle: metrics.OptUintWith(20000000), + Nice: metrics.OptUintWith(0), + } + s1 := CPU{ + User: metrics.OptUintWith(s0.User.ValueOr(0) + uint64(userTest)), + Sys: metrics.OptUintWith(s0.Sys.ValueOr(0) + uint64(systemTest)), + Idle: s0.Idle, + Nice: metrics.OptUintWith(0), + } + sample := Metrics{ + count: numCores, + isTotals: true, + previousSample: s0, + currentSample: s1, + } + + evt, err := sample.Format(MetricOpts{NormalizedPercentages: true, Percentages: true}) + assert.NoError(t, err, "error in Format") + + user, err := evt.GetValue("user.norm.pct") + assert.NoError(t, err, "error getting user.norm.pct") + system, err := evt.GetValue("system.norm.pct") + assert.NoError(t, err, "error getting system.norm.pct") + idle, err := evt.GetValue("idle.norm.pct") + assert.NoError(t, err, "error getting idle.norm.pct") + total, err := evt.GetValue("total.norm.pct") + assert.NoError(t, err, "error getting total.norm.pct") + assert.EqualValues(t, .3, user.(float64)) + assert.EqualValues(t, .7, system.(float64)) + assert.EqualValues(t, .0, idle.(float64)) + assert.EqualValues(t, 1., total.(float64)) +} diff --git a/metricbeat/internal/metrics/cpu/metrics_windows.go b/metricbeat/internal/metrics/cpu/metrics_windows.go new file mode 100644 index 00000000000..dfb986a8a58 --- /dev/null +++ b/metricbeat/internal/metrics/cpu/metrics_windows.go @@ -0,0 +1,68 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/* +For testing via the win2012 vagrant box: +vagrant winrm -s cmd -e -c "cd C:\\Gopath\src\\github.com\\elastic\\beats\\metricbeat\\module\\system\\cpu; go test -v -tags=integration -run TestFetch" win2012 +*/ + +package cpu + +import ( + "time" + + "github.com/pkg/errors" + + "github.com/elastic/beats/v7/metricbeat/internal/metrics" + "github.com/elastic/gosigar/sys/windows" +) + +// Get fetches Windows CPU system times +func Get(_ string) (CPUMetrics, error) { + idle, kernel, user, err := windows.GetSystemTimes() + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "GetSystemTimes failed") + } + + globalMetrics := CPUMetrics{} + //convert from duration to ticks + idleMetric := uint64(idle / time.Millisecond) + sysMetric := uint64(kernel / time.Millisecond) + userMetrics := uint64(user / time.Millisecond) + globalMetrics.totals.Idle = metrics.OptUintWith(idleMetric) + globalMetrics.totals.Sys = metrics.OptUintWith(sysMetric) + globalMetrics.totals.User = metrics.OptUintWith(userMetrics) + + // get per-cpu data + cpus, err := windows.NtQuerySystemProcessorPerformanceInformation() + if err != nil { + return CPUMetrics{}, errors.Wrap(err, "NtQuerySystemProcessorPerformanceInformation failed") + } + globalMetrics.list = make([]CPU, 0, len(cpus)) + for _, cpu := range cpus { + idleMetric := uint64(cpu.IdleTime / time.Millisecond) + sysMetric := uint64(cpu.KernelTime / time.Millisecond) + userMetrics := uint64(cpu.UserTime / time.Millisecond) + globalMetrics.list = append(globalMetrics.list, CPU{ + Idle: metrics.OptUintWith(idleMetric), + Sys: metrics.OptUintWith(sysMetric), + User: metrics.OptUintWith(userMetrics), + }) + } + + return globalMetrics, nil +} diff --git a/metricbeat/internal/metrics/opt.go b/metricbeat/internal/metrics/opt.go new file mode 100644 index 00000000000..f2cdfb0f05c --- /dev/null +++ b/metricbeat/internal/metrics/opt.go @@ -0,0 +1,65 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package metrics + +// OptUint is a wrapper for "optional" types, with the bool value indicating +// if the stored int is a legitimate value. +type OptUint struct { + exists bool + value uint64 +} + +// NewNone returns a new OptUint wrapper +func NewNone() OptUint { + return OptUint{ + exists: false, + value: 0, + } +} + +// OptUintWith returns a new OptUint wrapper with a given int +func OptUintWith(i uint64) OptUint { + return OptUint{ + exists: true, + value: i, + } +} + +// IsZero returns true if the underlying value nil +func (opt OptUint) IsZero() bool { + return !opt.exists +} + +// ValueOr returns the stored value, or a given int +// Please do not use this for populating reported data, +// as we actually want to avoid sending zeros where values are functionally null +func (opt OptUint) ValueOr(i uint64) uint64 { + if opt.exists { + return opt.value + } + return i +} + +// SumOptUint sums a list of OptUint values +func SumOptUint(opts ...OptUint) uint64 { + var sum uint64 + for _, opt := range opts { + sum += opt.ValueOr(0) + } + return sum +} diff --git a/metricbeat/module/system/core/_meta/data.json b/metricbeat/module/system/core/_meta/data.json index bb382dc9cec..413bf5f85fa 100644 --- a/metricbeat/module/system/core/_meta/data.json +++ b/metricbeat/module/system/core/_meta/data.json @@ -1,16 +1,13 @@ { "@timestamp": "2017-10-12T08:05:34.853Z", - "agent": { - "hostname": "host.example.com", - "name": "host.example.com" - }, "event": { "dataset": "system.core", "duration": 115000, "module": "system" }, "metricset": { - "name": "core" + "name": "core", + "period": 10000 }, "service": { "type": "system" @@ -19,36 +16,39 @@ "core": { "id": 0, "idle": { - "pct": 0.84, - "ticks": 69564411 + "pct": 0.9638, + "ticks": 240932600 }, "iowait": { "pct": 0, - "ticks": 419844 + "ticks": 10534 }, "irq": { - "pct": 0, - "ticks": 0 + "pct": 0.002, + "ticks": 379774 }, "nice": { - "pct": 0, - "ticks": 45632 + "pct": 0.002, + "ticks": 70354 }, "softirq": { - "pct": 0, - "ticks": 189966 + "pct": 0.002, + "ticks": 498856 }, "steal": { "pct": 0, "ticks": 0 }, "system": { - "pct": 0.08, - "ticks": 6211665 + "pct": 0.0201, + "ticks": 4072735 + }, + "total": { + "pct": 0.0362 }, "user": { - "pct": 0.08, - "ticks": 18833962 + "pct": 0.0101, + "ticks": 7020807 } } } diff --git a/metricbeat/module/system/core/_meta/fields.yml b/metricbeat/module/system/core/_meta/fields.yml index 110fadb1ada..21f13b01a2b 100644 --- a/metricbeat/module/system/core/_meta/fields.yml +++ b/metricbeat/module/system/core/_meta/fields.yml @@ -10,6 +10,12 @@ CPU Core number. # Percentages + - name: total.pct + type: scaled_float + format: percent + description: > + Total active time spent by the core + - name: user.pct type: scaled_float format: percent diff --git a/metricbeat/module/system/core/config.go b/metricbeat/module/system/core/config.go index e47b7d7cd5c..ae1b01e32de 100644 --- a/metricbeat/module/system/core/config.go +++ b/metricbeat/module/system/core/config.go @@ -23,6 +23,7 @@ import ( "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + metrics "github.com/elastic/beats/v7/metricbeat/internal/metrics/cpu" ) // Core metric types. @@ -38,25 +39,29 @@ type Config struct { } // Validate validates the core config. -func (c Config) Validate() error { +func (c Config) Validate() (metrics.MetricOpts, error) { + opts := metrics.MetricOpts{} if c.CPUTicks != nil { cfgwarn.Deprecate("6.1.0", "cpu_ticks is deprecated. Add 'ticks' to the core.metrics list.") } if len(c.Metrics) == 0 { - return errors.New("core.metrics cannot be empty") + return opts, errors.New("core.metrics cannot be empty") } for _, metric := range c.Metrics { switch strings.ToLower(metric) { - case percentages, ticks: + case percentages: + opts.Percentages = true + case ticks: + opts.Ticks = true default: - return errors.Errorf("invalid core.metrics value '%v' (valid "+ + return opts, errors.Errorf("invalid core.metrics value '%v' (valid "+ "options are %v and %v)", metric, percentages, ticks) } } - return nil + return opts, nil } var defaultConfig = Config{ diff --git a/metricbeat/module/system/core/core.go b/metricbeat/module/system/core/core.go index 9fdb3178a16..fddb0162739 100644 --- a/metricbeat/module/system/core/core.go +++ b/metricbeat/module/system/core/core.go @@ -15,17 +15,14 @@ // specific language governing permissions and limitations // under the License. -// +build darwin freebsd linux openbsd windows +// +build darwin freebsd linux openbsd windows aix package core import ( - "strings" - "github.com/pkg/errors" - "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/metric/system/cpu" + metrics "github.com/elastic/beats/v7/metricbeat/internal/metrics/cpu" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/mb/parse" ) @@ -39,8 +36,8 @@ func init() { // MetricSet for fetching system core metrics. type MetricSet struct { mb.BaseMetricSet - config Config - cores *cpu.CoresMonitor + opts metrics.MetricOpts + cores *metrics.Monitor } // New returns a new core MetricSet. @@ -50,53 +47,36 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, err } + opts, err := config.Validate() + if err != nil { + return nil, errors.Wrap(err, "error validating config") + } + if config.CPUTicks != nil && *config.CPUTicks { config.Metrics = append(config.Metrics, "ticks") } return &MetricSet{ BaseMetricSet: base, - config: config, - cores: new(cpu.CoresMonitor), + opts: opts, + cores: metrics.New(""), }, nil } // Fetch fetches CPU core metrics from the OS. func (m *MetricSet) Fetch(report mb.ReporterV2) error { - samples, err := m.cores.Sample() + samples, err := m.cores.FetchCores() if err != nil { return errors.Wrap(err, "failed to sample CPU core times") } for id, sample := range samples { - event := common.MapStr{"id": id} - - for _, metric := range m.config.Metrics { - switch strings.ToLower(metric) { - case percentages: - // Use NormalizedPercentages here because per core metrics range on [0, 100%]. - pct := sample.Percentages() - event.Put("user.pct", pct.User) - event.Put("system.pct", pct.System) - event.Put("idle.pct", pct.Idle) - event.Put("iowait.pct", pct.IOWait) - event.Put("irq.pct", pct.IRQ) - event.Put("nice.pct", pct.Nice) - event.Put("softirq.pct", pct.SoftIRQ) - event.Put("steal.pct", pct.Steal) - case ticks: - ticks := sample.Ticks() - event.Put("user.ticks", ticks.User) - event.Put("system.ticks", ticks.System) - event.Put("idle.ticks", ticks.Idle) - event.Put("iowait.ticks", ticks.IOWait) - event.Put("irq.ticks", ticks.IRQ) - event.Put("nice.ticks", ticks.Nice) - event.Put("softirq.ticks", ticks.SoftIRQ) - event.Put("steal.ticks", ticks.Steal) - } + event, err := sample.Format(m.opts) + if err != nil { + return errors.Wrap(err, "error formatting core data") } + event.Put("id", id) isOpen := report.Event(mb.Event{ MetricSetFields: event, diff --git a/metricbeat/module/system/core/core_test.go b/metricbeat/module/system/core/core_test.go index 3e8a66f7bd8..dd403375711 100644 --- a/metricbeat/module/system/core/core_test.go +++ b/metricbeat/module/system/core/core_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -// +build darwin freebsd linux openbsd windows +// +build darwin freebsd linux openbsd windows aix package core @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" + "github.com/elastic/beats/v7/metricbeat/mb/testing/flags" ) func TestFetch(t *testing.T) { @@ -41,10 +42,13 @@ func TestFetch(t *testing.T) { } func TestData(t *testing.T) { + if !*flags.DataFlag { + return + } f := mbtest.NewReportingMetricSetV2Error(t, getConfig()) mbtest.ReportingFetchV2Error(f) - time.Sleep(500 * time.Millisecond) + time.Sleep(5 * time.Second) err := mbtest.WriteEventsReporterV2Error(f, t, ".") if err != nil { diff --git a/metricbeat/module/system/cpu/_meta/data.json b/metricbeat/module/system/cpu/_meta/data.json index aa41a0401bc..0bfe85a7a58 100644 --- a/metricbeat/module/system/cpu/_meta/data.json +++ b/metricbeat/module/system/cpu/_meta/data.json @@ -7,7 +7,7 @@ }, "host": { "cpu": { - "usage": 0.1828 + "usage": 0.0467 } }, "metricset": { @@ -19,15 +19,43 @@ }, "system": { "cpu": { - "cores": 12, + "cores": 24, "idle": { "norm": { - "pct": 0.8172 + "pct": 0.9532 }, - "pct": 9.8063, - "ticks": 2704392356 + "pct": 22.8778, + "ticks": 5843152812 + }, + "iowait": { + "norm": { + "pct": 0.0001 + }, + "pct": 0.002, + "ticks": 335866 + }, + "irq": { + "norm": { + "pct": 0.0017 + }, + "pct": 0.0401, + "ticks": 7025540 }, "nice": { + "norm": { + "pct": 0.0003 + }, + "pct": 0.006, + "ticks": 1812301 + }, + "softirq": { + "norm": { + "pct": 0.0008 + }, + "pct": 0.02, + "ticks": 3581641 + }, + "steal": { "norm": { "pct": 0 }, @@ -36,23 +64,23 @@ }, "system": { "norm": { - "pct": 0.0543 + "pct": 0.0169 }, - "pct": 0.6511, - "ticks": 147798591 + "pct": 0.4068, + "ticks": 67901836 }, "total": { "norm": { - "pct": 0.1828 + "pct": 0.0467 }, - "pct": 2.1937 + "pct": 1.1202 }, "user": { "norm": { - "pct": 0.1285 + "pct": 0.027 }, - "pct": 1.5426, - "ticks": 123396960 + "pct": 0.6472, + "ticks": 126846063 } } } diff --git a/metricbeat/module/system/cpu/config.go b/metricbeat/module/system/cpu/config.go index 4e19f25f512..a9f7025b3e2 100644 --- a/metricbeat/module/system/cpu/config.go +++ b/metricbeat/module/system/cpu/config.go @@ -23,6 +23,7 @@ import ( "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + metrics "github.com/elastic/beats/v7/metricbeat/internal/metrics/cpu" ) // CPU metric types. @@ -39,26 +40,32 @@ type Config struct { } // Validate validates the cpu config. -func (c Config) Validate() error { +func (c Config) Validate() (metrics.MetricOpts, error) { + opts := metrics.MetricOpts{} if c.CPUTicks != nil { cfgwarn.Deprecate("6.1.0", "cpu_ticks is deprecated. Add 'ticks' to the cpu.metrics list.") } if len(c.Metrics) == 0 { - return errors.New("cpu.metrics cannot be empty") + return opts, errors.New("cpu.metrics cannot be empty") } for _, metric := range c.Metrics { switch strings.ToLower(metric) { - case percentages, normalizedPercentages, ticks: + case percentages: + opts.Percentages = true + case normalizedPercentages: + opts.NormalizedPercentages = true + case ticks: + opts.Ticks = true default: - return errors.Errorf("invalid cpu.metrics value '%v' (valid "+ + return opts, errors.Errorf("invalid cpu.metrics value '%v' (valid "+ "options are %v, %v, and %v)", metric, percentages, normalizedPercentages, ticks) } } - return nil + return opts, nil } var defaultConfig = Config{ diff --git a/metricbeat/module/system/cpu/cpu.go b/metricbeat/module/system/cpu/cpu.go index 7650b65cd80..ebf1f64d846 100644 --- a/metricbeat/module/system/cpu/cpu.go +++ b/metricbeat/module/system/cpu/cpu.go @@ -15,14 +15,15 @@ // specific language governing permissions and limitations // under the License. -// +build darwin freebsd linux openbsd windows +// +build darwin freebsd linux openbsd windows aix package cpu import ( "github.com/pkg/errors" - "github.com/elastic/beats/v7/libbeat/metric/system/cpu" + "github.com/elastic/beats/v7/libbeat/common" + metrics "github.com/elastic/beats/v7/metricbeat/internal/metrics/cpu" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/metricbeat/mb/parse" ) @@ -37,8 +38,8 @@ func init() { // MetricSet for fetching system CPU metrics. type MetricSet struct { mb.BaseMetricSet - config Config - cpu *cpu.Monitor + opts metrics.MetricOpts + cpu *metrics.Monitor } // New is a mb.MetricSetFactory that returns a cpu.MetricSet. @@ -48,25 +49,67 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, err } + opts, err := config.Validate() + if err != nil { + return nil, errors.Wrap(err, "error validating config") + } + if config.CPUTicks != nil && *config.CPUTicks { config.Metrics = append(config.Metrics, "ticks") } return &MetricSet{ BaseMetricSet: base, - config: config, - cpu: new(cpu.Monitor), + opts: opts, + cpu: metrics.New(""), }, nil } // Fetch fetches CPU metrics from the OS. func (m *MetricSet) Fetch(r mb.ReporterV2) error { - sample, err := m.cpu.Sample() + sample, err := m.cpu.Fetch() if err != nil { return errors.Wrap(err, "failed to fetch CPU times") } - r.Event(collectCPUMetrics(m.config.Metrics, sample)) + event, err := sample.Format(m.opts) + if err != nil { + return errors.Wrap(err, "error formatting metrics") + } + event.Put("cores", sample.CPUCount()) + + //generate the host fields here, since we don't want users disabling it. + hostEvent, err := sample.Format(metrics.MetricOpts{NormalizedPercentages: true}) + if err != nil { + return errors.Wrap(err, "error creating host fields") + } + hostFields := common.MapStr{} + err = copyFieldsOrDefault(hostEvent, hostFields, "total.norm.pct", "host.cpu.usage", 0) + if err != nil { + return errors.Wrap(err, "error fetching normalized CPU percent") + } + + r.Event(mb.Event{ + RootFields: hostFields, + MetricSetFields: event, + }) return nil } + +// copyFieldsOrDefault copies the field specified by key to the given map. It will +// overwrite the key if it exists. It will update the map with a default value if +// the key does not exist in the source map. +func copyFieldsOrDefault(from, to common.MapStr, key, newkey string, value interface{}) error { + v, err := from.GetValue(key) + if errors.Is(err, common.ErrKeyNotFound) { + _, err = to.Put(newkey, value) + return err + } + if err != nil { + return err + } + _, err = to.Put(newkey, v) + return err + +} diff --git a/metricbeat/module/system/cpu/cpu_test.go b/metricbeat/module/system/cpu/cpu_test.go index e3b7ba6645c..861be685bb5 100644 --- a/metricbeat/module/system/cpu/cpu_test.go +++ b/metricbeat/module/system/cpu/cpu_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -// +build darwin freebsd linux openbsd windows +// +build darwin freebsd linux openbsd windows aix package cpu @@ -45,7 +45,7 @@ func TestData(t *testing.T) { // Do a first fetch to have percentages mbtest.ReportingFetchV2Error(f) - time.Sleep(1 * time.Second) + time.Sleep(5 * time.Second) err := mbtest.WriteEventsReporterV2Error(f, t, ".") if err != nil { diff --git a/metricbeat/module/system/cpu/data.go b/metricbeat/module/system/cpu/data.go deleted file mode 100644 index 13695f93c26..00000000000 --- a/metricbeat/module/system/cpu/data.go +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you under -// the Apache License, Version 2.0 (the "License"); you may -// not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -// +build darwin freebsd linux openbsd windows - -package cpu - -import ( - "runtime" - "strings" - - "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/metric/system/cpu" - "github.com/elastic/beats/v7/metricbeat/mb" -) - -// CPU metrics are highly OS-specific, so we need to build the event per-OS -func getPlatformCPUMetrics(sample *cpu.Metrics, selectors []string, event common.MapStr) { - for _, metric := range selectors { - switch strings.ToLower(metric) { - case percentages: - pct := sample.Percentages() - event.Put("user.pct", pct.User) - event.Put("system.pct", pct.System) - event.Put("idle.pct", pct.Idle) - event.Put("total.pct", pct.Total) - - if runtime.GOOS != "windows" { - event.Put("nice.pct", pct.Nice) - } - if runtime.GOOS == "linux" || runtime.GOOS == "openbsd" { - event.Put("irq.pct", pct.IRQ) - } - if runtime.GOOS == "linux" || runtime.GOOS == "aix" { - event.Put("iowait.pct", pct.IOWait) - } - if runtime.GOOS == "linux" { - event.Put("softirq.pct", pct.SoftIRQ) - event.Put("steal.pct", pct.Steal) - } - case normalizedPercentages: - normalizedPct := sample.NormalizedPercentages() - event.Put("user.norm.pct", normalizedPct.User) - event.Put("system.norm.pct", normalizedPct.System) - event.Put("idle.norm.pct", normalizedPct.Idle) - event.Put("total.norm.pct", normalizedPct.Total) - - if runtime.GOOS != "windows" { - event.Put("nice.norm.pct", normalizedPct.Nice) - } - if runtime.GOOS == "linux" || runtime.GOOS == "openbsd" { - event.Put("irq.norm.pct", normalizedPct.IRQ) - } - if runtime.GOOS == "linux" || runtime.GOOS == "aix" { - event.Put("iowait.norm.pct", normalizedPct.IOWait) - } - if runtime.GOOS == "linux" { - event.Put("softirq.norm.pct", normalizedPct.SoftIRQ) - event.Put("steal.norm.pct", normalizedPct.Steal) - } - case ticks: - ticks := sample.Ticks() - event.Put("user.ticks", ticks.User) - event.Put("system.ticks", ticks.System) - event.Put("idle.ticks", ticks.Idle) - - if runtime.GOOS != "windows" { - event.Put("nice.ticks", ticks.Nice) - } - if runtime.GOOS == "linux" || runtime.GOOS == "openbsd" { - event.Put("irq.ticks", ticks.IRQ) - } - if runtime.GOOS == "linux" || runtime.GOOS == "aix" { - event.Put("iowait.ticks", ticks.IOWait) - } - if runtime.GOOS == "linux" { - event.Put("softirq.ticks", ticks.SoftIRQ) - event.Put("steal.ticks", ticks.Steal) - } - } - } -} - -// gather CPU metrics -func collectCPUMetrics(selectors []string, sample *cpu.Metrics) mb.Event { - event := common.MapStr{"cores": runtime.NumCPU()} - getPlatformCPUMetrics(sample, selectors, event) - - //generate the host fields here, since we don't want users disabling it. - normalizedPct := sample.NormalizedPercentages() - hostFields := common.MapStr{} - hostFields.Put("host.cpu.usage", normalizedPct.Total) - - return mb.Event{ - RootFields: hostFields, - MetricSetFields: event, - } -} diff --git a/metricbeat/module/system/fields.go b/metricbeat/module/system/fields.go index d52d17d7276..fdeeea5f9c6 100644 --- a/metricbeat/module/system/fields.go +++ b/metricbeat/module/system/fields.go @@ -32,5 +32,5 @@ func init() { // AssetSystem returns asset data. // This is the base64 encoded gzipped contents of module/system. func AssetSystem() string { - return "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" + return "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" } diff --git a/metricbeat/module/system/test_system.py b/metricbeat/module/system/test_system.py index 3b7887bc1df..b1c83db81eb 100644 --- a/metricbeat/module/system/test_system.py +++ b/metricbeat/module/system/test_system.py @@ -10,9 +10,11 @@ SYSTEM_CPU_FIELDS_LINUX = ["cores", "idle.pct", "iowait.pct", "irq.pct", "nice.pct", "softirq.pct", "steal.pct", "system.pct", "user.pct", "total.pct"] -SYSTEM_CPU_FIELDS_WINDOWS = ["cores", "idle.pct", "system.pct", "user.pct", "total.pct"] +SYSTEM_CPU_FIELDS_WINDOWS = ["cores", "idle.pct", + "system.pct", "user.pct", "total.pct"] -SYSTEM_CPU_FIELDS_DARWIN = ["cores", "idle.pct", "system.pct", "user.pct", "total.pct", "nice.pct"] +SYSTEM_CPU_FIELDS_DARWIN = ["cores", "idle.pct", + "system.pct", "user.pct", "total.pct", "nice.pct"] SYSTEM_CPU_FIELDS_LINUX_ALL = ["cores", "idle.pct", "idle.ticks", "iowait.pct", "iowait.ticks", "irq.pct", "irq.ticks", "nice.pct", "nice.ticks", "softirq.pct", "softirq.ticks", "steal.pct", "steal.ticks", "system.pct", "system.ticks", "user.pct", "user.ticks", @@ -27,13 +29,25 @@ SYSTEM_LOAD_FIELDS = ["cores", "1", "5", "15", "norm.1", "norm.5", "norm.15"] -SYSTEM_CORE_FIELDS = ["id", "idle.pct", "iowait.pct", "irq.pct", "nice.pct", - "softirq.pct", "steal.pct", "system.pct", "user.pct"] +SYSTEM_CORE_FIELDS_LINUX = ["id", "idle.pct", "iowait.pct", "irq.pct", "nice.pct", + "softirq.pct", "steal.pct", "system.pct", "user.pct", "total.pct"] -SYSTEM_CORE_FIELDS_ALL = SYSTEM_CORE_FIELDS + ["idle.ticks", "iowait.ticks", "irq.ticks", "nice.ticks", - "softirq.ticks", "steal.ticks", "system.ticks", "user.ticks", - "idle.norm.pct", "iowait.norm.pct", "irq.norm.pct", "nice.norm.pct", - "softirq.norm.pct", "steal.norm.pct", "system.norm.pct", "user.norm.pct"] +SYSTEM_CORE_FIELDS_WINDOWS = ["id", "idle.pct", + "system.pct", "user.pct", "total.pct"] + +SYSTEM_CORE_FIELDS_DARWIN = ["id", "idle.pct", + "system.pct", "user.pct", "nice.pct", "total.pct"] + +SYSTEM_CORE_FIELDS_LINUX_ALL = SYSTEM_CORE_FIELDS_LINUX + ["idle.ticks", "iowait.ticks", "irq.ticks", "nice.ticks", + "softirq.ticks", "steal.ticks", "system.ticks", "user.ticks", + "idle.norm.pct", "iowait.norm.pct", "irq.norm.pct", "nice.norm.pct", + "softirq.norm.pct", "steal.norm.pct", "system.norm.pct", "user.norm.pct"] + +SYSTEM_CORE_FIELDS_DARWIN_ALL = SYSTEM_CORE_FIELDS_DARWIN + ["idle.ticks", "nice.ticks", "system.ticks", "user.ticks", + "idle.norm.pct", "nice.norm.pct", "system.norm.pct", "user.norm.pct"] + +SYSTEM_CORE_FIELDS_WINDOWS_ALL = SYSTEM_CORE_FIELDS_WINDOWS + ["idle.ticks", "system.ticks", "user.ticks", + "idle.norm.pct", "system.norm.pct", "user.norm.pct"] SYSTEM_DISKIO_FIELDS = ["name", "read.count", "write.count", "read.bytes", "write.bytes", "read.time", "write.time"] @@ -61,7 +75,8 @@ SYSTEM_CPU_HOST_FIELDS = ["usage"] -SYSTEM_NETWORK_HOST_FIELDS = ["ingress.bytes", "egress.bytes", "ingress.packets", "egress.packets"] +SYSTEM_NETWORK_HOST_FIELDS = ["ingress.bytes", + "egress.bytes", "ingress.packets", "egress.packets"] SYSTEM_DISK_HOST_FIELDS = ["read.bytes", "write.bytes"] @@ -97,14 +112,18 @@ def test_cpu(self): if "system" in evt: cpu = evt["system"]["cpu"] if sys.platform.startswith("linux"): - self.assertCountEqual(self.de_dot(SYSTEM_CPU_FIELDS_LINUX), cpu.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_FIELDS_LINUX), cpu.keys()) elif sys.platform.startswith("darwin"): - self.assertCountEqual(self.de_dot(SYSTEM_CPU_FIELDS_DARWIN), cpu.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_FIELDS_DARWIN), cpu.keys()) elif sys.platform.startswith("win"): - self.assertCountEqual(self.de_dot(SYSTEM_CPU_FIELDS_WINDOWS), cpu.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_FIELDS_WINDOWS), cpu.keys()) else: host_cpu = evt["host"]["cpu"] - self.assertCountEqual(self.de_dot(SYSTEM_CPU_HOST_FIELDS), host_cpu.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_HOST_FIELDS), host_cpu.keys()) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_cpu_ticks_option(self): @@ -131,11 +150,14 @@ def test_cpu_ticks_option(self): self.assert_fields_are_documented(evt) cpuStats = evt["system"]["cpu"] if sys.platform.startswith("linux"): - self.assertCountEqual(self.de_dot(SYSTEM_CPU_FIELDS_LINUX_ALL), cpuStats.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_FIELDS_LINUX_ALL), cpuStats.keys()) elif sys.platform.startswith("win"): - self.assertCountEqual(self.de_dot(SYSTEM_CPU_FIELDS_WINDOWS_ALL), cpuStats.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_FIELDS_WINDOWS_ALL), cpuStats.keys()) elif sys.platform.startswith("darwin"): - self.assertCountEqual(self.de_dot(SYSTEM_CPU_FIELDS_DARWIN_ALL), cpuStats.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_CPU_FIELDS_DARWIN_ALL), cpuStats.keys()) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_core(self): @@ -157,8 +179,16 @@ def test_core(self): for evt in output: self.assert_fields_are_documented(evt) - core = evt["system"]["core"] - self.assertCountEqual(self.de_dot(SYSTEM_CORE_FIELDS), core.keys()) + core_stats = evt["system"]["core"] + if sys.platform.startswith("linux"): + self.assertCountEqual(self.de_dot( + SYSTEM_CORE_FIELDS_LINUX), core_stats.keys()) + elif sys.platform.startswith("win"): + self.assertCountEqual(self.de_dot( + SYSTEM_CORE_FIELDS_WINDOWS), core_stats.keys()) + elif sys.platform.startswith("darwin"): + self.assertCountEqual(self.de_dot( + SYSTEM_CORE_FIELDS_DARWIN), core_stats.keys()) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_core_with_cpu_ticks(self): @@ -183,8 +213,16 @@ def test_core_with_cpu_ticks(self): for evt in output: self.assert_fields_are_documented(evt) - core = evt["system"]["core"] - self.assertCountEqual(self.de_dot(SYSTEM_CORE_FIELDS_ALL), core.keys()) + core_stats = evt["system"]["core"] + if sys.platform.startswith("linux"): + self.assertCountEqual(self.de_dot( + SYSTEM_CORE_FIELDS_LINUX_ALL), core_stats.keys()) + elif sys.platform.startswith("win"): + self.assertCountEqual(self.de_dot( + SYSTEM_CORE_FIELDS_WINDOWS_ALL), core_stats.keys()) + elif sys.platform.startswith("darwin"): + self.assertCountEqual(self.de_dot( + SYSTEM_CORE_FIELDS_DARWIN_ALL), core_stats.keys()) @unittest.skipUnless(re.match("(?i)linux|darwin|freebsd|openbsd", sys.platform), "os") def test_load(self): @@ -232,10 +270,12 @@ def test_diskio(self): if 'error' not in evt: if "system" in evt: diskio = evt["system"]["diskio"] - self.assertCountEqual(self.de_dot(SYSTEM_DISKIO_FIELDS), diskio.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_DISKIO_FIELDS), diskio.keys()) elif "host" in evt: host_disk = evt["host"]["disk"] - self.assertCountEqual(SYSTEM_DISK_HOST_FIELDS, host_disk.keys()) + self.assertCountEqual( + SYSTEM_DISK_HOST_FIELDS, host_disk.keys()) @unittest.skipUnless(re.match("(?i)linux", sys.platform), "os") def test_diskio_linux(self): @@ -259,10 +299,12 @@ def test_diskio_linux(self): self.assert_fields_are_documented(evt) if "system" in evt: diskio = evt["system"]["diskio"] - self.assertCountEqual(self.de_dot(SYSTEM_DISKIO_FIELDS_LINUX), diskio.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_DISKIO_FIELDS_LINUX), diskio.keys()) else: host_disk = evt["host"]["disk"] - self.assertCountEqual(SYSTEM_DISK_HOST_FIELDS, host_disk.keys()) + self.assertCountEqual( + SYSTEM_DISK_HOST_FIELDS, host_disk.keys()) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_filesystem(self): @@ -286,9 +328,11 @@ def test_filesystem(self): self.assert_fields_are_documented(evt) filesystem = evt["system"]["filesystem"] if sys.platform.startswith("win"): - self.assertCountEqual(self.de_dot(SYSTEM_FILESYSTEM_FIELDS_WINDOWS), filesystem.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_FILESYSTEM_FIELDS_WINDOWS), filesystem.keys()) else: - self.assertCountEqual(self.de_dot(SYSTEM_FILESYSTEM_FIELDS), filesystem.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_FILESYSTEM_FIELDS), filesystem.keys()) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd|openbsd", sys.platform), "os") def test_fsstat(self): @@ -375,10 +419,12 @@ def test_network(self): self.assert_fields_are_documented(evt) if "system" in evt: network = evt["system"]["network"] - self.assertCountEqual(self.de_dot(SYSTEM_NETWORK_FIELDS), network.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_NETWORK_FIELDS), network.keys()) else: host_network = evt["host"]["network"] - self.assertCountEqual(self.de_dot(SYSTEM_NETWORK_HOST_FIELDS), host_network.keys()) + self.assertCountEqual(self.de_dot( + SYSTEM_NETWORK_HOST_FIELDS), host_network.keys()) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd", sys.platform), "os") def test_process_summary(self): @@ -412,10 +458,12 @@ def test_process_summary(self): assert isinstance(summary["stopped"], int) assert isinstance(summary["zombie"], int) assert summary["total"] == summary["sleeping"] + summary["running"] + \ - summary["idle"] + summary["stopped"] + summary["zombie"] + summary["unknown"] + summary["idle"] + summary["stopped"] + \ + summary["zombie"] + summary["unknown"] if sys.platform.startswith("windows"): - assert summary["total"] == summary["sleeping"] + summary["running"] + summary["unknown"] + assert summary["total"] == summary["sleeping"] + \ + summary["running"] + summary["unknown"] @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd", sys.platform), "os") def test_process(self): @@ -458,7 +506,8 @@ def test_process(self): self.assertCountEqual(SYSTEM_PROCESS_FIELDS, process.keys()) - self.assertTrue(found_cmdline, "cmdline not found in any process events") + self.assertTrue( + found_cmdline, "cmdline not found in any process events") @unittest.skipUnless(re.match("(?i)linux|darwin|freebsd", sys.platform), "os") def test_process_unix(self): @@ -521,7 +570,8 @@ def test_process_unix(self): self.assertTrue(found_fd, "fd not found in any process events") self.assertTrue(found_env, "env not found in any process events") - self.assertTrue(found_cwd, "working_directory not found in any process events") + self.assertTrue( + found_cwd, "working_directory not found in any process events") @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd", sys.platform), "os") def test_process_metricbeat(self): @@ -543,10 +593,13 @@ def test_process_metricbeat(self): output = self.read_output()[0] assert re.match("(?i)metricbeat.test(.exe)?", output["process.name"]) - assert re.match("(?i).*metricbeat.test(.exe)? -systemTest", output["system.process.cmdline"]) - assert re.match("(?i).*metricbeat.test(.exe)? -systemTest", output["process.command_line"]) + assert re.match("(?i).*metricbeat.test(.exe)? -systemTest", + output["system.process.cmdline"]) + assert re.match("(?i).*metricbeat.test(.exe)? -systemTest", + output["process.command_line"]) assert isinstance(output["system.process.state"], six.string_types) - assert isinstance(output["system.process.cpu.start_time"], six.string_types) + assert isinstance( + output["system.process.cpu.start_time"], six.string_types) self.check_username(output["user.name"]) @unittest.skipUnless(re.match("(?i)win|linux|darwin|freebsd", sys.platform), "os") @@ -593,7 +646,9 @@ def check_username(self, observed, expected=None): if os.name == 'nt': parts = observed.split("\\", 2) - assert len(parts) == 2, "Expected proc.username to be of form DOMAIN\\username, but was %s" % observed + assert len( + parts) == 2, "Expected proc.username to be of form DOMAIN\\username, but was %s" % observed observed = parts[1] - assert expected == observed, "proc.username = %s, but expected %s" % (observed, expected) + assert expected == observed, "proc.username = %s, but expected %s" % ( + observed, expected)