receiver/hostmetricsreceiver/internal/scraper/cpuscraper/cpu_scraper.go (81 lines of code) (raw):
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package cpuscraper // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper"
import (
"context"
"fmt"
"time"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/host"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scrapererror"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/internal/metadata"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/hostmetricsreceiver/internal/scraper/cpuscraper/ucal"
)
const (
metricsLen = 2
hzInAMHz = 1_000_000
)
// cpuScraper for CPU Metrics
type cpuScraper struct {
settings scraper.Settings
config *Config
mb *metadata.MetricsBuilder
ucal *ucal.CPUUtilizationCalculator
// for mocking
bootTime func(context.Context) (uint64, error)
times func(context.Context, bool) ([]cpu.TimesStat, error)
now func() time.Time
}
type cpuInfo struct {
frequency float64
processor uint
}
// newCPUScraper creates a set of CPU related metrics
func newCPUScraper(_ context.Context, settings scraper.Settings, cfg *Config) *cpuScraper {
return &cpuScraper{settings: settings, config: cfg, bootTime: host.BootTimeWithContext, times: cpu.TimesWithContext, ucal: &ucal.CPUUtilizationCalculator{}, now: time.Now}
}
func (s *cpuScraper) start(ctx context.Context, _ component.Host) error {
bootTime, err := s.bootTime(ctx)
if err != nil {
return err
}
s.mb = metadata.NewMetricsBuilder(s.config.MetricsBuilderConfig, s.settings, metadata.WithStartTime(pcommon.Timestamp(bootTime*1e9)))
return nil
}
func (s *cpuScraper) scrape(ctx context.Context) (pmetric.Metrics, error) {
now := pcommon.NewTimestampFromTime(s.now())
cpuTimes, err := s.times(ctx, true /*percpu=*/)
if err != nil {
return pmetric.NewMetrics(), scrapererror.NewPartialScrapeError(err, metricsLen)
}
for _, cpuTime := range cpuTimes {
s.recordCPUTimeStateDataPoints(now, cpuTime)
}
err = s.ucal.CalculateAndRecord(now, cpuTimes, s.recordCPUUtilization)
if err != nil {
return pmetric.NewMetrics(), scrapererror.NewPartialScrapeError(err, metricsLen)
}
if s.config.Metrics.SystemCPUPhysicalCount.Enabled {
numCPU, err := cpu.Counts(false)
if err != nil {
return pmetric.NewMetrics(), scrapererror.NewPartialScrapeError(err, metricsLen)
}
s.mb.RecordSystemCPUPhysicalCountDataPoint(now, int64(numCPU))
}
if s.config.Metrics.SystemCPULogicalCount.Enabled {
numCPU, err := cpu.Counts(true)
if err != nil {
return pmetric.NewMetrics(), scrapererror.NewPartialScrapeError(err, metricsLen)
}
s.mb.RecordSystemCPULogicalCountDataPoint(now, int64(numCPU))
}
if s.config.Metrics.SystemCPUFrequency.Enabled {
cpuInfos, err := s.getCPUInfo()
if err != nil {
return pmetric.NewMetrics(), scrapererror.NewPartialScrapeError(err, metricsLen)
}
for _, cInfo := range cpuInfos {
s.mb.RecordSystemCPUFrequencyDataPoint(now, cInfo.frequency*hzInAMHz, fmt.Sprintf("cpu%d", cInfo.processor))
}
}
return s.mb.Emit(), nil
}