440 lines
14 KiB
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
|
|
}
|