builtin_metrics.go (124 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.
package apm // import "go.elastic.co/apm/v2"
import (
"context"
"runtime"
sysinfo "github.com/elastic/go-sysinfo"
"github.com/elastic/go-sysinfo/types"
)
// builtinMetricsGatherer is an MetricsGatherer which gathers builtin metrics:
// - goroutines
// - memstats (allocations, usage, GC, etc.)
// - system and process CPU and memory usage
type builtinMetricsGatherer struct {
tracer *Tracer
lastSysMetrics sysMetrics
}
func newBuiltinMetricsGatherer(t *Tracer) *builtinMetricsGatherer {
g := &builtinMetricsGatherer{tracer: t}
if metrics, err := gatherSysMetrics(); err == nil {
g.lastSysMetrics = metrics
}
return g
}
// GatherMetrics gathers mem metrics into m.
func (g *builtinMetricsGatherer) GatherMetrics(ctx context.Context, m *Metrics) error {
m.Add("golang.goroutines", nil, float64(runtime.NumGoroutine()))
g.gatherSystemMetrics(m)
g.gatherMemStatsMetrics(m)
g.tracer.breakdownMetrics.gather(m)
return nil
}
func (g *builtinMetricsGatherer) gatherSystemMetrics(m *Metrics) {
metrics, err := gatherSysMetrics()
if err != nil {
return
}
systemCPU, processCPU := calculateCPUUsage(metrics.cpu, g.lastSysMetrics.cpu)
m.Add("system.cpu.total.norm.pct", nil, systemCPU)
m.Add("system.process.cpu.total.norm.pct", nil, processCPU)
m.Add("system.memory.total", nil, float64(metrics.mem.system.Total))
m.Add("system.memory.actual.free", nil, float64(metrics.mem.system.Available))
m.Add("system.process.memory.size", nil, float64(metrics.mem.process.Virtual))
m.Add("system.process.memory.rss.bytes", nil, float64(metrics.mem.process.Resident))
g.lastSysMetrics = metrics
}
func (g *builtinMetricsGatherer) gatherMemStatsMetrics(m *Metrics) {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
addUint64 := func(name string, v uint64) {
m.Add(name, nil, float64(v))
}
add := func(name string, v float64) {
m.Add(name, nil, v)
}
addUint64("golang.heap.allocations.mallocs", mem.Mallocs)
addUint64("golang.heap.allocations.frees", mem.Frees)
addUint64("golang.heap.allocations.objects", mem.HeapObjects)
addUint64("golang.heap.allocations.total", mem.TotalAlloc)
addUint64("golang.heap.allocations.allocated", mem.HeapAlloc)
addUint64("golang.heap.allocations.idle", mem.HeapIdle)
addUint64("golang.heap.allocations.active", mem.HeapInuse)
addUint64("golang.heap.system.total", mem.Sys)
addUint64("golang.heap.system.obtained", mem.HeapSys)
addUint64("golang.heap.system.stack", mem.StackSys)
addUint64("golang.heap.system.released", mem.HeapReleased)
addUint64("golang.heap.gc.next_gc_limit", mem.NextGC)
addUint64("golang.heap.gc.total_count", uint64(mem.NumGC))
addUint64("golang.heap.gc.total_pause.ns", mem.PauseTotalNs)
add("golang.heap.gc.cpu_fraction", mem.GCCPUFraction)
}
func calculateCPUUsage(current, last cpuMetrics) (systemUsage, processUsage float64) {
idleDelta := current.system.Idle + current.system.IOWait - last.system.Idle - last.system.IOWait
systemTotalDelta := current.system.Total() - last.system.Total()
if systemTotalDelta <= 0 {
return 0, 0
}
idlePercent := float64(idleDelta) / float64(systemTotalDelta)
systemUsage = 1 - idlePercent
processTotalDelta := current.process.Total() - last.process.Total()
processUsage = float64(processTotalDelta) / float64(systemTotalDelta)
return systemUsage, processUsage
}
type sysMetrics struct {
cpu cpuMetrics
mem memoryMetrics
}
type cpuMetrics struct {
process types.CPUTimes
system types.CPUTimes
}
type memoryMetrics struct {
process types.MemoryInfo
system *types.HostMemoryInfo
}
func gatherSysMetrics() (sysMetrics, error) {
proc, err := sysinfo.Self()
if err != nil {
return sysMetrics{}, err
}
host, err := sysinfo.Host()
if err != nil {
return sysMetrics{}, err
}
hostTimes, err := host.CPUTime()
if err != nil {
return sysMetrics{}, err
}
hostMemory, err := host.Memory()
if err != nil {
return sysMetrics{}, err
}
procTimes, err := proc.CPUTime()
if err != nil {
return sysMetrics{}, err
}
procMemory, err := proc.Memory()
if err != nil {
return sysMetrics{}, err
}
return sysMetrics{
cpu: cpuMetrics{
system: hostTimes,
process: procTimes,
},
mem: memoryMetrics{
system: hostMemory,
process: procMemory,
},
}, nil
}