feat(inputs.internal): Add Go metric collection option (#13793)
This commit is contained in:
parent
dec4a90b07
commit
d4a00dd8d6
|
|
@ -21,6 +21,10 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||||
[[inputs.internal]]
|
[[inputs.internal]]
|
||||||
## If true, collect telegraf memory stats.
|
## If true, collect telegraf memory stats.
|
||||||
# collect_memstats = true
|
# collect_memstats = true
|
||||||
|
|
||||||
|
## If true, collect metrics from Go's runtime.metrics. For a full list see:
|
||||||
|
## https://pkg.go.dev/runtime/metrics
|
||||||
|
# collect_gostats = false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/metrics"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
|
|
@ -16,13 +18,8 @@ import (
|
||||||
var sampleConfig string
|
var sampleConfig string
|
||||||
|
|
||||||
type Self struct {
|
type Self struct {
|
||||||
CollectMemstats bool
|
CollectMemstats bool `toml:"collect_memstats"`
|
||||||
}
|
CollectGostats bool `toml:"collect_gostats"`
|
||||||
|
|
||||||
func NewSelf() telegraf.Input {
|
|
||||||
return &Self{
|
|
||||||
CollectMemstats: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Self) SampleConfig() string {
|
func (*Self) SampleConfig() string {
|
||||||
|
|
@ -30,42 +27,116 @@ func (*Self) SampleConfig() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Self) Gather(acc telegraf.Accumulator) error {
|
func (s *Self) Gather(acc telegraf.Accumulator) error {
|
||||||
if s.CollectMemstats {
|
|
||||||
m := &runtime.MemStats{}
|
|
||||||
runtime.ReadMemStats(m)
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"alloc_bytes": m.Alloc, // bytes allocated and not yet freed
|
|
||||||
"total_alloc_bytes": m.TotalAlloc, // bytes allocated (even if freed)
|
|
||||||
"sys_bytes": m.Sys, // bytes obtained from system (sum of XxxSys below)
|
|
||||||
"pointer_lookups": m.Lookups, // number of pointer lookups
|
|
||||||
"mallocs": m.Mallocs, // number of mallocs
|
|
||||||
"frees": m.Frees, // number of frees
|
|
||||||
// Main allocation heap statistics.
|
|
||||||
"heap_alloc_bytes": m.HeapAlloc, // bytes allocated and not yet freed (same as Alloc above)
|
|
||||||
"heap_sys_bytes": m.HeapSys, // bytes obtained from system
|
|
||||||
"heap_idle_bytes": m.HeapIdle, // bytes in idle spans
|
|
||||||
"heap_in_use_bytes": m.HeapInuse, // bytes in non-idle span
|
|
||||||
"heap_released_bytes": m.HeapReleased, // bytes released to the OS
|
|
||||||
"heap_objects": m.HeapObjects, // total number of allocated objects
|
|
||||||
"num_gc": m.NumGC,
|
|
||||||
}
|
|
||||||
acc.AddFields("internal_memstats", fields, map[string]string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
telegrafVersion := inter.Version
|
|
||||||
goVersion := strings.TrimPrefix(runtime.Version(), "go")
|
|
||||||
|
|
||||||
for _, m := range selfstat.Metrics() {
|
for _, m := range selfstat.Metrics() {
|
||||||
if m.Name() == "internal_agent" {
|
if m.Name() == "internal_agent" {
|
||||||
m.AddTag("go_version", goVersion)
|
m.AddTag("go_version", strings.TrimPrefix(runtime.Version(), "go"))
|
||||||
}
|
}
|
||||||
m.AddTag("version", telegrafVersion)
|
m.AddTag("version", inter.Version)
|
||||||
acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
|
acc.AddFields(m.Name(), m.Fields(), m.Tags(), m.Time())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.CollectMemstats {
|
||||||
|
collectMemStat(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.CollectGostats {
|
||||||
|
collectGoStat(acc)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func collectMemStat(acc telegraf.Accumulator) {
|
||||||
inputs.Add("internal", NewSelf)
|
m := &runtime.MemStats{}
|
||||||
|
runtime.ReadMemStats(m)
|
||||||
|
fields := map[string]any{
|
||||||
|
"alloc_bytes": m.Alloc, // bytes allocated and not yet freed
|
||||||
|
"total_alloc_bytes": m.TotalAlloc, // bytes allocated (even if freed)
|
||||||
|
"sys_bytes": m.Sys, // bytes obtained from system (sum of XxxSys below)
|
||||||
|
"pointer_lookups": m.Lookups, // number of pointer lookups
|
||||||
|
"mallocs": m.Mallocs, // number of mallocs
|
||||||
|
"frees": m.Frees, // number of frees
|
||||||
|
|
||||||
|
// Main allocation heap statistics.
|
||||||
|
"heap_alloc_bytes": m.HeapAlloc, // bytes allocated and not yet freed (same as Alloc above)
|
||||||
|
"heap_sys_bytes": m.HeapSys, // bytes obtained from system
|
||||||
|
"heap_idle_bytes": m.HeapIdle, // bytes in idle spans
|
||||||
|
"heap_in_use_bytes": m.HeapInuse, // bytes in non-idle span
|
||||||
|
"heap_released_bytes": m.HeapReleased, // bytes released to the OS
|
||||||
|
"heap_objects": m.HeapObjects, // total number of allocated objects
|
||||||
|
"num_gc": m.NumGC,
|
||||||
|
}
|
||||||
|
acc.AddFields("internal_memstats", fields, map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectGoStat(acc telegraf.Accumulator) {
|
||||||
|
descs := metrics.All()
|
||||||
|
samples := make([]metrics.Sample, len(descs))
|
||||||
|
for i := range samples {
|
||||||
|
samples[i].Name = descs[i].Name
|
||||||
|
}
|
||||||
|
metrics.Read(samples)
|
||||||
|
|
||||||
|
fields := map[string]any{}
|
||||||
|
for _, sample := range samples {
|
||||||
|
name := sanitizeName(sample.Name)
|
||||||
|
|
||||||
|
switch sample.Value.Kind() {
|
||||||
|
case metrics.KindUint64:
|
||||||
|
fields[name] = sample.Value.Uint64()
|
||||||
|
case metrics.KindFloat64:
|
||||||
|
fields[name] = sample.Value.Float64()
|
||||||
|
case metrics.KindFloat64Histogram:
|
||||||
|
// The histogram may be quite large, so let's just pull out
|
||||||
|
// a crude estimate for the median for the sake of this example.
|
||||||
|
fields[name] = medianBucket(sample.Value.Float64Histogram())
|
||||||
|
default:
|
||||||
|
// This may happen as new metrics get added.
|
||||||
|
//
|
||||||
|
// The safest thing to do here is to simply log it somewhere
|
||||||
|
// as something to look into, but ignore it for now.
|
||||||
|
// In the worst case, you might temporarily miss out on a new metric.
|
||||||
|
fmt.Printf("%s: unexpected metric Kind: %v\n", name, sample.Value.Kind())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := map[string]string{
|
||||||
|
"go_version": strings.TrimPrefix(runtime.Version(), "go"),
|
||||||
|
}
|
||||||
|
acc.AddFields("internal_gostats", fields, tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts /cpu/classes/gc/mark/assist:cpu-seconds to cpu_classes_gc_mark_assist_cpu_seconds
|
||||||
|
func sanitizeName(name string) string {
|
||||||
|
name = strings.TrimPrefix(name, "/")
|
||||||
|
name = strings.ReplaceAll(name, "/", "_")
|
||||||
|
name = strings.ReplaceAll(name, ":", "_")
|
||||||
|
name = strings.ReplaceAll(name, "-", "_")
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func medianBucket(h *metrics.Float64Histogram) float64 {
|
||||||
|
total := uint64(0)
|
||||||
|
for _, count := range h.Counts {
|
||||||
|
total += count
|
||||||
|
}
|
||||||
|
thresh := total / 2
|
||||||
|
total = 0
|
||||||
|
for i, count := range h.Counts {
|
||||||
|
total += count
|
||||||
|
if total >= thresh {
|
||||||
|
return h.Buckets[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default value in case something above did not work
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("internal", func() telegraf.Input {
|
||||||
|
return &Self{
|
||||||
|
CollectMemstats: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf/selfstat"
|
"github.com/influxdata/telegraf/selfstat"
|
||||||
|
|
@ -10,7 +11,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSelfPlugin(t *testing.T) {
|
func TestSelfPlugin(t *testing.T) {
|
||||||
s := NewSelf()
|
s := Self{
|
||||||
|
CollectMemstats: true,
|
||||||
|
}
|
||||||
acc := &testutil.Accumulator{}
|
acc := &testutil.Accumulator{}
|
||||||
|
|
||||||
require.NoError(t, s.Gather(acc))
|
require.NoError(t, s.Gather(acc))
|
||||||
|
|
@ -64,3 +67,48 @@ func TestSelfPlugin(t *testing.T) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoMemStat(t *testing.T) {
|
||||||
|
s := Self{
|
||||||
|
CollectMemstats: false,
|
||||||
|
CollectGostats: false,
|
||||||
|
}
|
||||||
|
acc := &testutil.Accumulator{}
|
||||||
|
|
||||||
|
require.NoError(t, s.Gather(acc))
|
||||||
|
require.False(t, acc.HasMeasurement("internal_memstats"))
|
||||||
|
require.False(t, acc.HasMeasurement("internal_gostats"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGostats(t *testing.T) {
|
||||||
|
s := Self{
|
||||||
|
CollectMemstats: false,
|
||||||
|
CollectGostats: true,
|
||||||
|
}
|
||||||
|
acc := &testutil.Accumulator{}
|
||||||
|
|
||||||
|
require.NoError(t, s.Gather(acc))
|
||||||
|
require.False(t, acc.HasMeasurement("internal_memstats"))
|
||||||
|
require.True(t, acc.HasMeasurement("internal_gostats"))
|
||||||
|
|
||||||
|
var metric *testutil.Metric
|
||||||
|
for _, m := range acc.Metrics {
|
||||||
|
if m.Measurement == "internal_gostats" {
|
||||||
|
metric = m
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotNil(t, metric)
|
||||||
|
require.Equal(t, metric.Measurement, "internal_gostats")
|
||||||
|
require.Equal(t, len(metric.Tags), 1)
|
||||||
|
require.Contains(t, metric.Tags, "go_version")
|
||||||
|
|
||||||
|
for name, value := range metric.Fields {
|
||||||
|
switch value.(type) {
|
||||||
|
case int64, uint64, float64:
|
||||||
|
default:
|
||||||
|
require.True(t, false, fmt.Sprintf("field %s is of non-numeric type %T\n", name, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,7 @@
|
||||||
[[inputs.internal]]
|
[[inputs.internal]]
|
||||||
## If true, collect telegraf memory stats.
|
## If true, collect telegraf memory stats.
|
||||||
# collect_memstats = true
|
# collect_memstats = true
|
||||||
|
|
||||||
|
## If true, collect metrics from Go's runtime.metrics. For a full list see:
|
||||||
|
## https://pkg.go.dev/runtime/metrics
|
||||||
|
# collect_gostats = false
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue