opentelemetry-collector/service/internal/proctelemetry/process_telemetry.go

156 lines
4.1 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package proctelemetry // import "go.opentelemetry.io/collector/service/internal/proctelemetry"
import (
"context"
"errors"
"os"
"runtime"
"sync"
"time"
"github.com/shirou/gopsutil/v4/common"
"github.com/shirou/gopsutil/v4/process"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/service/internal/metadata"
)
// processMetrics is a struct that contains views related to process metrics (cpu, mem, etc)
type processMetrics struct {
startTimeUnixNano int64
proc *process.Process
context context.Context
// mu protects everything bellow.
mu sync.Mutex
lastMsRead time.Time
ms *runtime.MemStats
}
type RegisterOption interface {
apply(*registerOption)
}
type registerOption struct {
hostProc string
}
type registerOptionFunc func(*registerOption)
func (fn registerOptionFunc) apply(set *registerOption) {
fn(set)
}
// WithHostProc overrides the /proc folder on Linux used by process telemetry.
func WithHostProc(hostProc string) RegisterOption {
return registerOptionFunc(func(uo *registerOption) {
uo.hostProc = hostProc
})
}
// RegisterProcessMetrics creates a new set of processMetrics (mem, cpu) that can be used to measure
// basic information about this process.
func RegisterProcessMetrics(cfg component.TelemetrySettings, opts ...RegisterOption) error {
set := registerOption{}
for _, opt := range opts {
opt.apply(&set)
}
var err error
pm := &processMetrics{
startTimeUnixNano: time.Now().UnixNano(),
ms: &runtime.MemStats{},
}
ctx := context.Background()
if set.hostProc != "" {
ctx = context.WithValue(ctx, common.EnvKey, common.EnvMap{common.HostProcEnvKey: set.hostProc})
}
pm.context = ctx
//nolint:gosec
pm.proc, err = process.NewProcessWithContext(pm.context, int32(os.Getpid()))
if err != nil {
return err
}
tb, err := metadata.NewTelemetryBuilder(cfg)
if err != nil {
return err
}
return errors.Join(
tb.RegisterProcessUptimeCallback(pm.updateProcessUptime),
tb.RegisterProcessRuntimeHeapAllocBytesCallback(pm.updateAllocMem),
tb.RegisterProcessRuntimeTotalAllocBytesCallback(pm.updateTotalAllocMem),
tb.RegisterProcessRuntimeTotalSysMemoryBytesCallback(pm.updateSysMem),
tb.RegisterProcessCPUSecondsCallback(pm.updateCPUSeconds),
tb.RegisterProcessMemoryRssCallback(pm.updateRSSMemory),
)
}
func (pm *processMetrics) updateProcessUptime(_ context.Context, obs metric.Float64Observer) error {
now := time.Now().UnixNano()
obs.Observe(float64(now-pm.startTimeUnixNano) / 1e9)
return nil
}
func (pm *processMetrics) updateAllocMem(_ context.Context, obs metric.Int64Observer) error {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.readMemStatsIfNeeded()
//nolint:gosec
obs.Observe(int64(pm.ms.Alloc))
return nil
}
func (pm *processMetrics) updateTotalAllocMem(_ context.Context, obs metric.Int64Observer) error {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.readMemStatsIfNeeded()
//nolint:gosec
obs.Observe(int64(pm.ms.TotalAlloc))
return nil
}
func (pm *processMetrics) updateSysMem(_ context.Context, obs metric.Int64Observer) error {
pm.mu.Lock()
defer pm.mu.Unlock()
pm.readMemStatsIfNeeded()
//nolint:gosec
obs.Observe(int64(pm.ms.Sys))
return nil
}
func (pm *processMetrics) updateCPUSeconds(_ context.Context, obs metric.Float64Observer) error {
times, err := pm.proc.TimesWithContext(pm.context) //nolint:contextcheck
if err != nil {
return err
}
obs.Observe(times.User + times.System + times.Idle + times.Nice +
times.Iowait + times.Irq + times.Softirq + times.Steal)
return nil
}
func (pm *processMetrics) updateRSSMemory(_ context.Context, obs metric.Int64Observer) error {
mem, err := pm.proc.MemoryInfoWithContext(pm.context) //nolint:contextcheck
if err != nil {
return err
}
//nolint:gosec
obs.Observe(int64(mem.RSS))
return nil
}
func (pm *processMetrics) readMemStatsIfNeeded() {
now := time.Now()
// If last time we read was less than one second ago just reuse the values
if now.Sub(pm.lastMsRead) < time.Second {
return
}
pm.lastMsRead = now
runtime.ReadMemStats(pm.ms)
}