report/report.go (212 lines of code) (raw):
// 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.
//go:build (darwin && cgo) || (freebsd && cgo) || linux || windows
// +build darwin,cgo freebsd,cgo linux windows
package report
import (
"errors"
"os"
"runtime"
"github.com/elastic/elastic-agent-libs/logp"
"github.com/elastic/elastic-agent-libs/monitoring"
"github.com/elastic/elastic-agent-system-metrics/metric/system/cgroup"
"github.com/elastic/elastic-agent-system-metrics/metric/system/cpu"
"github.com/elastic/elastic-agent-system-metrics/metric/system/numcpu"
"github.com/elastic/elastic-agent-system-metrics/metric/system/process"
"github.com/elastic/elastic-agent-system-metrics/metric/system/resolve"
)
func MemStatsReporter(logger *logp.Logger, processStats *process.Stats) func(monitoring.Mode, monitoring.Visitor) {
return func(m monitoring.Mode, V monitoring.Visitor) {
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
V.OnRegistryStart()
defer V.OnRegistryFinished()
monitoring.ReportInt(V, "memory_total", int64(stats.TotalAlloc))
if m == monitoring.Full {
monitoring.ReportInt(V, "memory_alloc", int64(stats.Alloc))
monitoring.ReportInt(V, "memory_sys", int64(stats.Sys))
monitoring.ReportInt(V, "gc_next", int64(stats.NextGC))
}
state, err := processStats.GetSelf()
if err != nil {
logger.Errorf("Error while getting memory usage: %v", err)
return
}
rss := state.Memory.Rss.Bytes.ValueOr(0)
monitoring.ReportInt(V, "rss", int64(rss))
}
}
func InstanceCPUReporter(logger *logp.Logger, processStats *process.Stats) func(monitoring.Mode, monitoring.Visitor) {
return func(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()
state, err := processStats.GetSelf()
if err != nil {
logger.Errorf("Error retrieving CPU percentages: %v", err)
return
}
monitoring.ReportNamespace(V, "user", func() {
monitoring.ReportInt(V, "ticks", int64(state.CPU.User.Ticks.ValueOr(0)))
monitoring.ReportNamespace(V, "time", func() {
monitoring.ReportInt(V, "ms", int64(state.CPU.User.Ticks.ValueOr(0)))
})
})
monitoring.ReportNamespace(V, "system", func() {
monitoring.ReportInt(V, "ticks", int64(state.CPU.System.Ticks.ValueOr(0)))
monitoring.ReportNamespace(V, "time", func() {
monitoring.ReportInt(V, "ms", int64(state.CPU.System.Ticks.ValueOr(0)))
})
})
monitoring.ReportNamespace(V, "total", func() {
monitoring.ReportFloat(V, "value", state.CPU.Total.Value.ValueOr(0))
monitoring.ReportInt(V, "ticks", int64(state.CPU.Total.Ticks.ValueOr(0)))
monitoring.ReportNamespace(V, "time", func() {
monitoring.ReportInt(V, "ms", int64(state.CPU.Total.Ticks.ValueOr(0)))
})
})
}
}
func ReportSystemLoadAverage(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()
load, err := cpu.Load()
if err != nil {
logp.Err("Error retrieving load average: %v", err)
return
}
avgs := load.Averages()
monitoring.ReportFloat(V, "1", avgs.OneMinute)
monitoring.ReportFloat(V, "5", avgs.FiveMinute)
monitoring.ReportFloat(V, "15", avgs.FifteenMinute)
normAvgs := load.NormalizedAverages()
monitoring.ReportNamespace(V, "norm", func() {
monitoring.ReportFloat(V, "1", normAvgs.OneMinute)
monitoring.ReportFloat(V, "5", normAvgs.FiveMinute)
monitoring.ReportFloat(V, "15", normAvgs.FifteenMinute)
})
}
func ReportSystemCPUUsage(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()
monitoring.ReportInt(V, "cores", int64(numcpu.NumCPU()))
}
func ReportRuntime(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()
monitoring.ReportInt(V, "goroutines", int64(runtime.NumGoroutine()))
}
func InstanceCroupsReporter(logger *logp.Logger, override string) func(monitoring.Mode, monitoring.Visitor) {
return func(_ monitoring.Mode, V monitoring.Visitor) {
V.OnRegistryStart()
defer V.OnRegistryFinished()
// PID shouldn't use hostfs, at least for now.
// containerization schemes should provide their own /proc/ that will serve containerized processess
pid := os.Getpid()
cgroups, err := cgroup.NewReaderOptions(cgroup.ReaderOptions{
RootfsMountpoint: resolve.NewTestResolver("/"),
IgnoreRootCgroups: true,
CgroupsHierarchyOverride: os.Getenv(override),
})
if err != nil {
if errors.Is(err, cgroup.ErrCgroupsMissing) {
logger.Warnf("cgroup data collection disabled in internal monitoring: %v", err)
} else {
logger.Errorf("cgroup data collection disabled in internal monitoring: %v", err)
}
return
}
cgv, err := cgroups.CgroupsVersion(pid)
if err != nil {
logger.Errorf("error determining cgroups version for internal monitoring: %v", err)
return
}
if cgv == cgroup.CgroupsV1 {
ReportMetricsCGV1(logger, pid, cgroups, V)
} else {
ReportMetricsCGV2(logger, pid, cgroups, V)
}
}
}
func ReportMetricsCGV1(logger *logp.Logger, pid int, cgroups *cgroup.Reader, V monitoring.Visitor) {
selfStats, err := cgroups.GetV1StatsForProcess(pid)
if err != nil {
logger.Errorf("error getting cgroup stats for V1: %v", err)
}
// GetStatsForProcess returns a nil selfStats and no error when there's no stats
if selfStats == nil {
return
}
if cpu := selfStats.CPU; cpu != nil {
monitoring.ReportNamespace(V, "cpu", func() {
if cpu.ID != "" {
monitoring.ReportString(V, "id", cpu.ID)
}
monitoring.ReportNamespace(V, "cfs", func() {
monitoring.ReportNamespace(V, "period", func() {
monitoring.ReportInt(V, "us", int64(cpu.CFS.PeriodMicros.Us))
})
monitoring.ReportNamespace(V, "quota", func() {
monitoring.ReportInt(V, "us", int64(cpu.CFS.QuotaMicros.Us))
})
})
monitoring.ReportNamespace(V, "stats", func() {
monitoring.ReportInt(V, "periods", int64(cpu.Stats.Periods))
monitoring.ReportNamespace(V, "throttled", func() {
monitoring.ReportInt(V, "periods", int64(cpu.Stats.Throttled.Periods))
monitoring.ReportInt(V, "ns", int64(cpu.Stats.Throttled.Us))
})
})
})
}
if cpuacct := selfStats.CPUAccounting; cpuacct != nil {
monitoring.ReportNamespace(V, "cpuacct", func() {
if cpuacct.ID != "" {
monitoring.ReportString(V, "id", cpuacct.ID)
}
monitoring.ReportNamespace(V, "total", func() {
monitoring.ReportInt(V, "ns", int64(cpuacct.Total.NS))
})
})
}
if memory := selfStats.Memory; memory != nil {
monitoring.ReportNamespace(V, "memory", func() {
if memory.ID != "" {
monitoring.ReportString(V, "id", memory.ID)
}
monitoring.ReportNamespace(V, "mem", func() {
monitoring.ReportNamespace(V, "limit", func() {
monitoring.ReportInt(V, "bytes", int64(memory.Mem.Limit.Bytes))
})
monitoring.ReportNamespace(V, "usage", func() {
monitoring.ReportInt(V, "bytes", int64(memory.Mem.Usage.Bytes))
})
})
})
}
}
func ReportMetricsCGV2(logger *logp.Logger, pid int, cgroups *cgroup.Reader, V monitoring.Visitor) {
selfStats, err := cgroups.GetV2StatsForProcess(pid)
if err != nil {
logger.Errorf("error getting cgroup stats for V2: %v", err)
return
}
if cpu := selfStats.CPU; cpu != nil {
monitoring.ReportNamespace(V, "cpu", func() {
if cpu.ID != "" {
monitoring.ReportString(V, "id", cpu.ID)
}
monitoring.ReportNamespace(V, "stats", func() {
monitoring.ReportInt(V, "periods", int64(cpu.Stats.Periods.ValueOr(0)))
monitoring.ReportNamespace(V, "throttled", func() {
monitoring.ReportInt(V, "periods", int64(cpu.Stats.Throttled.Periods.ValueOr(0)))
monitoring.ReportInt(V, "ns", int64(cpu.Stats.Throttled.Us.ValueOr(0)))
})
})
})
}
if memory := selfStats.Memory; memory != nil {
monitoring.ReportNamespace(V, "memory", func() {
if memory.ID != "" {
monitoring.ReportString(V, "id", memory.ID)
}
monitoring.ReportNamespace(V, "mem", func() {
monitoring.ReportNamespace(V, "usage", func() {
monitoring.ReportInt(V, "bytes", int64(memory.Mem.Usage.Bytes))
})
})
})
}
}