opentelemetry-collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper/disk_scraper_windows.go

440 lines
14 KiB
Go

// Copyright The OpenTelemetry Authors
//
// Licensed 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 diskscraper
import (
"context"
"fmt"
"reflect"
"time"
"go.opentelemetry.io/collector/component/componenterror"
"go.opentelemetry.io/collector/consumer/pdata"
"go.opentelemetry.io/collector/internal/dataold"
"go.opentelemetry.io/collector/internal/processor/filterset"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/third_party/telegraf/win_perf_counters"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/windows/pdh"
)
const (
logicalDiskReadsPerSecPath = `\LogicalDisk(*)\Disk Reads/sec`
logicalDiskWritesPerSecPath = `\LogicalDisk(*)\Disk Writes/sec`
logicalDiskReadBytesPerSecPath = `\LogicalDisk(*)\Disk Read Bytes/sec`
logicalDiskWriteBytesPerSecPath = `\LogicalDisk(*)\Disk Write Bytes/sec`
logicalAvgDiskSecsPerReadPath = `\LogicalDisk(*)\Avg. Disk sec/Read`
logicalAvgDiskSecsPerWritePath = `\LogicalDisk(*)\Avg. Disk sec/Write`
logicalDiskQueueLengthPath = `\LogicalDisk(*)\Current Disk Queue Length`
)
// scraper for Disk Metrics
type scraper struct {
config *Config
startTime pdata.TimestampUnixNano
prevScrapeTime time.Time
includeFS filterset.FilterSet
excludeFS filterset.FilterSet
diskReadBytesPerSecCounter pdh.PerfCounterScraper
diskWriteBytesPerSecCounter pdh.PerfCounterScraper
diskReadsPerSecCounter pdh.PerfCounterScraper
diskWritesPerSecCounter pdh.PerfCounterScraper
avgDiskSecsPerReadCounter pdh.PerfCounterScraper
avgDiskSecsPerWriteCounter pdh.PerfCounterScraper
diskQueueLengthCounter pdh.PerfCounterScraper
cumulativeDiskIO cumulativeDiskValues
cumulativeDiskOps cumulativeDiskValues
cumulativeDiskTime cumulativeDiskValues
}
type cumulativeDiskValues map[string]*value
func (cv cumulativeDiskValues) getOrAdd(k string) *value {
if v, ok := cv[k]; ok {
return v
}
v := &value{}
cv[k] = v
return v
}
type value struct {
read float64
write float64
}
// newDiskScraper creates a Disk Scraper
func newDiskScraper(_ context.Context, cfg *Config) (*scraper, error) {
scraper := &scraper{
config: cfg,
cumulativeDiskIO: map[string]*value{},
cumulativeDiskOps: map[string]*value{},
cumulativeDiskTime: map[string]*value{},
}
var err error
if len(cfg.Include.Devices) > 0 {
scraper.includeFS, err = filterset.CreateFilterSet(cfg.Include.Devices, &cfg.Include.Config)
if err != nil {
return nil, fmt.Errorf("error creating device include filters: %w", err)
}
}
if len(cfg.Exclude.Devices) > 0 {
scraper.excludeFS, err = filterset.CreateFilterSet(cfg.Exclude.Devices, &cfg.Exclude.Config)
if err != nil {
return nil, fmt.Errorf("error creating device exclude filters: %w", err)
}
}
return scraper, nil
}
// Initialize
func (s *scraper) Initialize(_ context.Context) error {
s.startTime = internal.TimeToUnixNano(time.Now())
s.prevScrapeTime = time.Now()
var err error
s.diskReadBytesPerSecCounter, err = pdh.NewPerfCounter(logicalDiskReadBytesPerSecPath, true)
if err != nil {
return err
}
s.diskWriteBytesPerSecCounter, err = pdh.NewPerfCounter(logicalDiskWriteBytesPerSecPath, true)
if err != nil {
return err
}
s.diskReadsPerSecCounter, err = pdh.NewPerfCounter(logicalDiskReadsPerSecPath, true)
if err != nil {
return err
}
s.diskWritesPerSecCounter, err = pdh.NewPerfCounter(logicalDiskWritesPerSecPath, true)
if err != nil {
return err
}
s.avgDiskSecsPerReadCounter, err = pdh.NewPerfCounter(logicalAvgDiskSecsPerReadPath, true)
if err != nil {
return err
}
s.avgDiskSecsPerWriteCounter, err = pdh.NewPerfCounter(logicalAvgDiskSecsPerWritePath, true)
if err != nil {
return err
}
s.diskQueueLengthCounter, err = pdh.NewPerfCounter(logicalDiskQueueLengthPath, true)
if err != nil {
return err
}
return nil
}
// Close
func (s *scraper) Close(_ context.Context) error {
var errors []error
if s.diskReadBytesPerSecCounter != nil && !reflect.ValueOf(s.diskReadBytesPerSecCounter).IsNil() {
if err := s.diskReadBytesPerSecCounter.Close(); err != nil {
errors = append(errors, err)
}
}
if s.diskWriteBytesPerSecCounter != nil && !reflect.ValueOf(s.diskWriteBytesPerSecCounter).IsNil() {
if err := s.diskWriteBytesPerSecCounter.Close(); err != nil {
errors = append(errors, err)
}
}
if s.diskReadsPerSecCounter != nil && !reflect.ValueOf(s.diskReadsPerSecCounter).IsNil() {
if err := s.diskReadsPerSecCounter.Close(); err != nil {
errors = append(errors, err)
}
}
if s.diskWritesPerSecCounter != nil && !reflect.ValueOf(s.diskWritesPerSecCounter).IsNil() {
if err := s.diskWritesPerSecCounter.Close(); err != nil {
errors = append(errors, err)
}
}
if s.avgDiskSecsPerReadCounter != nil && !reflect.ValueOf(s.avgDiskSecsPerReadCounter).IsNil() {
if err := s.avgDiskSecsPerReadCounter.Close(); err != nil {
errors = append(errors, err)
}
}
if s.avgDiskSecsPerWriteCounter != nil && !reflect.ValueOf(s.avgDiskSecsPerWriteCounter).IsNil() {
if err := s.avgDiskSecsPerWriteCounter.Close(); err != nil {
errors = append(errors, err)
}
}
if s.diskQueueLengthCounter != nil && !reflect.ValueOf(s.diskQueueLengthCounter).IsNil() {
if err := s.diskQueueLengthCounter.Close(); err != nil {
errors = append(errors, err)
}
}
return componenterror.CombineErrors(errors)
}
// ScrapeMetrics
func (s *scraper) ScrapeMetrics(_ context.Context) (dataold.MetricSlice, error) {
now := time.Now()
durationSinceLastScraped := now.Sub(s.prevScrapeTime).Seconds()
s.prevScrapeTime = now
nowUnixTime := pdata.TimestampUnixNano(uint64(now.UnixNano()))
metrics := dataold.NewMetricSlice()
var errors []error
err := s.scrapeAndAppendDiskIOMetric(metrics, nowUnixTime, durationSinceLastScraped)
if err != nil {
errors = append(errors, err)
}
err = s.scrapeAndAppendDiskOpsMetric(metrics, nowUnixTime, durationSinceLastScraped)
if err != nil {
errors = append(errors, err)
}
err = s.scrapeAndAppendDiskPendingOperationsMetric(metrics, nowUnixTime)
if err != nil {
errors = append(errors, err)
}
return metrics, componenterror.CombineErrors(errors)
}
func (s *scraper) scrapeAndAppendDiskIOMetric(metrics dataold.MetricSlice, now pdata.TimestampUnixNano, durationSinceLastScraped float64) error {
diskReadBytesPerSecValues, err := s.diskReadBytesPerSecCounter.ScrapeData()
if err != nil {
return err
}
diskWriteBytesPerSecValues, err := s.diskWriteBytesPerSecCounter.ScrapeData()
if err != nil {
return err
}
for _, diskReadBytesPerSec := range diskReadBytesPerSecValues {
if s.includeDevice(diskReadBytesPerSec.InstanceName) {
s.cumulativeDiskIO.getOrAdd(diskReadBytesPerSec.InstanceName).read += diskReadBytesPerSec.Value * durationSinceLastScraped
}
}
for _, diskWriteBytesPerSec := range diskWriteBytesPerSecValues {
if s.includeDevice(diskWriteBytesPerSec.InstanceName) {
s.cumulativeDiskIO.getOrAdd(diskWriteBytesPerSec.InstanceName).write += diskWriteBytesPerSec.Value * durationSinceLastScraped
}
}
if len(s.cumulativeDiskIO) == 0 {
return nil
}
idx := metrics.Len()
metrics.Resize(idx + 1)
initializeDiskInt64Metric(metrics.At(idx), diskIODescriptor, s.startTime, now, s.cumulativeDiskIO)
return nil
}
func (s *scraper) scrapeAndAppendDiskOpsMetric(metrics dataold.MetricSlice, now pdata.TimestampUnixNano, durationSinceLastScraped float64) error {
diskReadsPerSecValues, err := s.diskReadsPerSecCounter.ScrapeData()
if err != nil {
return err
}
diskWritesPerSecValues, err := s.diskWritesPerSecCounter.ScrapeData()
if err != nil {
return err
}
avgDiskSecsPerReadValues, err := s.avgDiskSecsPerReadCounter.ScrapeData()
if err != nil {
return err
}
avgDiskSecsPerReadMap := toMap(avgDiskSecsPerReadValues)
avgDiskSecsPerWriteValues, err := s.avgDiskSecsPerWriteCounter.ScrapeData()
if err != nil {
return err
}
avgDiskSecsPerWriteMap := toMap(avgDiskSecsPerWriteValues)
for _, diskReadsPerSec := range diskReadsPerSecValues {
device := diskReadsPerSec.InstanceName
if !s.includeDevice(device) {
continue
}
deltaReadOperations := diskReadsPerSec.Value * durationSinceLastScraped
s.cumulativeDiskOps.getOrAdd(device).read += deltaReadOperations
if avgDiskSecsPerRead, ok := avgDiskSecsPerReadMap[device]; ok {
s.cumulativeDiskTime.getOrAdd(device).read += deltaReadOperations * avgDiskSecsPerRead
}
}
for _, diskWritesPerSec := range diskWritesPerSecValues {
device := diskWritesPerSec.InstanceName
if !s.includeDevice(device) {
continue
}
deltaWriteOperations := diskWritesPerSec.Value * durationSinceLastScraped
s.cumulativeDiskOps.getOrAdd(device).write += deltaWriteOperations
if avgDiskSecsPerWrite, ok := avgDiskSecsPerWriteMap[device]; ok {
s.cumulativeDiskTime.getOrAdd(device).write += deltaWriteOperations * avgDiskSecsPerWrite
}
}
idx := metrics.Len()
if len(s.cumulativeDiskIO) > 0 {
metrics.Resize(idx + 1)
initializeDiskInt64Metric(metrics.At(idx), diskOpsDescriptor, s.startTime, now, s.cumulativeDiskOps)
idx++
}
if len(s.cumulativeDiskTime) > 0 {
metrics.Resize(idx + 1)
initializeDiskDoubleMetric(metrics.At(idx), diskTimeDescriptor, s.startTime, now, s.cumulativeDiskTime)
idx++
}
return nil
}
func (s *scraper) scrapeAndAppendDiskPendingOperationsMetric(metrics dataold.MetricSlice, now pdata.TimestampUnixNano) error {
diskQueueLengthValues, err := s.diskQueueLengthCounter.ScrapeData()
if err != nil {
return err
}
filteredDiskQueueLengthValues := s.filterByDevice(diskQueueLengthValues)
if len(filteredDiskQueueLengthValues) == 0 {
return nil
}
idx := metrics.Len()
metrics.Resize(idx + 1)
initializeDiskPendingOperationsMetric(metrics.At(idx), now, filteredDiskQueueLengthValues)
return nil
}
func initializeDiskInt64Metric(metric dataold.Metric, descriptor dataold.MetricDescriptor, startTime, now pdata.TimestampUnixNano, ops cumulativeDiskValues) {
descriptor.CopyTo(metric.MetricDescriptor())
idps := metric.Int64DataPoints()
idps.Resize(2 * len(ops))
idx := 0
for device, value := range ops {
initializeInt64DataPoint(idps.At(idx+0), startTime, now, device, readDirectionLabelValue, int64(value.read))
initializeInt64DataPoint(idps.At(idx+1), startTime, now, device, writeDirectionLabelValue, int64(value.write))
idx += 2
}
}
func initializeDiskDoubleMetric(metric dataold.Metric, descriptor dataold.MetricDescriptor, startTime, now pdata.TimestampUnixNano, ops cumulativeDiskValues) {
descriptor.CopyTo(metric.MetricDescriptor())
ddps := metric.DoubleDataPoints()
ddps.Resize(2 * len(ops))
idx := 0
for device, value := range ops {
initializeDoubleDataPoint(ddps.At(idx+0), startTime, now, device, readDirectionLabelValue, value.read)
initializeDoubleDataPoint(ddps.At(idx+1), startTime, now, device, writeDirectionLabelValue, value.write)
idx += 2
}
}
func initializeDiskPendingOperationsMetric(metric dataold.Metric, now pdata.TimestampUnixNano, avgDiskQueueLengthValues []win_perf_counters.CounterValue) {
diskPendingOperationsDescriptor.CopyTo(metric.MetricDescriptor())
idps := metric.Int64DataPoints()
idps.Resize(len(avgDiskQueueLengthValues))
for idx, avgDiskQueueLengthValue := range avgDiskQueueLengthValues {
initializeDiskPendingDataPoint(idps.At(idx), now, avgDiskQueueLengthValue.InstanceName, int64(avgDiskQueueLengthValue.Value))
}
}
func initializeInt64DataPoint(dataPoint dataold.Int64DataPoint, startTime, now pdata.TimestampUnixNano, deviceLabel string, directionLabel string, value int64) {
labelsMap := dataPoint.LabelsMap()
labelsMap.Insert(deviceLabelName, deviceLabel)
labelsMap.Insert(directionLabelName, directionLabel)
dataPoint.SetStartTime(startTime)
dataPoint.SetTimestamp(now)
dataPoint.SetValue(value)
}
func initializeDoubleDataPoint(dataPoint dataold.DoubleDataPoint, startTime, now pdata.TimestampUnixNano, deviceLabel string, directionLabel string, value float64) {
labelsMap := dataPoint.LabelsMap()
labelsMap.Insert(deviceLabelName, deviceLabel)
labelsMap.Insert(directionLabelName, directionLabel)
dataPoint.SetStartTime(startTime)
dataPoint.SetTimestamp(now)
dataPoint.SetValue(value)
}
func initializeDiskPendingDataPoint(dataPoint dataold.Int64DataPoint, now pdata.TimestampUnixNano, deviceLabel string, value int64) {
labelsMap := dataPoint.LabelsMap()
labelsMap.Insert(deviceLabelName, deviceLabel)
dataPoint.SetTimestamp(now)
dataPoint.SetValue(value)
}
func (s *scraper) filterByDevice(values []win_perf_counters.CounterValue) []win_perf_counters.CounterValue {
if s.includeFS == nil && s.excludeFS == nil {
return values
}
filteredValues := make([]win_perf_counters.CounterValue, 0, len(values))
for _, value := range values {
if s.includeDevice(value.InstanceName) {
filteredValues = append(filteredValues, value)
}
}
return filteredValues
}
func (s *scraper) includeDevice(deviceName string) bool {
return (s.includeFS == nil || s.includeFS.Matches(deviceName)) &&
(s.excludeFS == nil || !s.excludeFS.Matches(deviceName))
}
func toMap(values []win_perf_counters.CounterValue) map[string]float64 {
mp := make(map[string]float64, len(values))
for _, value := range values {
mp[value.InstanceName] = value.Value
}
return mp
}