github.com/observiq/bindplane-agent@v1.51.0/internal/report/snapshot/filter_metrics.go (about)

     1  // Copyright observIQ, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package snapshot
    16  
    17  import (
    18  	"strings"
    19  	"time"
    20  
    21  	"go.opentelemetry.io/collector/pdata/pcommon"
    22  	"go.opentelemetry.io/collector/pdata/pmetric"
    23  )
    24  
    25  // filterMetrics filters the metrics by the given query and timestamp.
    26  // The returned payload cannot be assumed to be a copy, so it should not be modified.
    27  func filterMetrics(m pmetric.Metrics, searchQuery *string, minimumTimestamp *time.Time) pmetric.Metrics {
    28  	// No filters specified, filtered metrics are trivially the same as input metrics
    29  	if searchQuery == nil && minimumTimestamp == nil {
    30  		return m
    31  	}
    32  
    33  	filteredMetrics := pmetric.NewMetrics()
    34  	resourceMetrics := m.ResourceMetrics()
    35  	for i := 0; i < resourceMetrics.Len(); i++ {
    36  		filteredResourceLogs := filterResourceMetrics(resourceMetrics.At(i), searchQuery, minimumTimestamp)
    37  
    38  		// Don't append empty resource metrics
    39  		if filteredResourceLogs.ScopeMetrics().Len() != 0 {
    40  			filteredResourceLogs.MoveTo(filteredMetrics.ResourceMetrics().AppendEmpty())
    41  		}
    42  	}
    43  
    44  	return filteredMetrics
    45  }
    46  
    47  func filterResourceMetrics(rm pmetric.ResourceMetrics, searchQuery *string, minimumTimestamp *time.Time) pmetric.ResourceMetrics {
    48  	filteredResourceMetrics := pmetric.NewResourceMetrics()
    49  
    50  	// Copy old resource to filtered resource
    51  	resource := rm.Resource()
    52  	resource.CopyTo(filteredResourceMetrics.Resource())
    53  
    54  	// Apply query to resource
    55  	queryMatchesResource := true // default to true if no query specified
    56  	if searchQuery != nil {
    57  		queryMatchesResource = queryMatchesMap(resource.Attributes(), *searchQuery)
    58  	}
    59  
    60  	scopeMetrics := rm.ScopeMetrics()
    61  	for i := 0; i < scopeMetrics.Len(); i++ {
    62  		filteredScopeMetrics := filterScopeMetrics(rm.ScopeMetrics().At(i), queryMatchesResource, searchQuery, minimumTimestamp)
    63  
    64  		// Don't append empty scope metrics
    65  		if filteredScopeMetrics.Metrics().Len() != 0 {
    66  			filteredScopeMetrics.MoveTo(filteredResourceMetrics.ScopeMetrics().AppendEmpty())
    67  		}
    68  	}
    69  
    70  	return filteredResourceMetrics
    71  }
    72  
    73  func filterScopeMetrics(sm pmetric.ScopeMetrics, queryMatchesResource bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.ScopeMetrics {
    74  	filteredScopeMetrics := pmetric.NewScopeMetrics()
    75  	metrics := sm.Metrics()
    76  	for i := 0; i < metrics.Len(); i++ {
    77  		m := metrics.At(i)
    78  		filteredMetric := filterMetric(m, queryMatchesResource, searchQuery, minimumTimestamp)
    79  
    80  		if !metricIsEmpty(filteredMetric) {
    81  			filteredMetric.MoveTo(filteredScopeMetrics.Metrics().AppendEmpty())
    82  		}
    83  	}
    84  
    85  	return filteredScopeMetrics
    86  }
    87  
    88  func filterMetric(m pmetric.Metric, queryMatchesResource bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.Metric {
    89  	filteredMetric := pmetric.NewMetric()
    90  	// Copy metric to filtered metric
    91  	filteredMetric.SetName(m.Name())
    92  	filteredMetric.SetDescription(m.Description())
    93  	filteredMetric.SetUnit(m.Unit())
    94  
    95  	// Apply query to metric
    96  	queryMatchesMetric := true // default to true if no query specified
    97  	// Skip if we already know the query matches the resource
    98  	if !queryMatchesResource && searchQuery != nil {
    99  		queryMatchesMetric = metricMatchesQuery(m, *searchQuery)
   100  	}
   101  
   102  	switch m.Type() {
   103  	case pmetric.MetricTypeGauge:
   104  		filteredGauge := filterGauge(m.Gauge(), queryMatchesResource, queryMatchesMetric, searchQuery, minimumTimestamp)
   105  		filteredGauge.MoveTo(filteredMetric.SetEmptyGauge())
   106  	case pmetric.MetricTypeSum:
   107  		filteredSum := filterSum(m.Sum(), queryMatchesResource, queryMatchesMetric, searchQuery, minimumTimestamp)
   108  		filteredSum.MoveTo(filteredMetric.SetEmptySum())
   109  	case pmetric.MetricTypeHistogram:
   110  		filteredHistogram := filterHistogram(m.Histogram(), queryMatchesResource, queryMatchesMetric, searchQuery, minimumTimestamp)
   111  		filteredHistogram.MoveTo(filteredMetric.SetEmptyHistogram())
   112  	case pmetric.MetricTypeExponentialHistogram:
   113  		filteredExponentialHistogram := filterExponentialHistogram(m.ExponentialHistogram(), queryMatchesResource, queryMatchesMetric, searchQuery, minimumTimestamp)
   114  		filteredExponentialHistogram.MoveTo(filteredMetric.SetEmptyExponentialHistogram())
   115  	case pmetric.MetricTypeSummary:
   116  		filteredSummary := filterSummary(m.Summary(), queryMatchesResource, queryMatchesMetric, searchQuery, minimumTimestamp)
   117  		filteredSummary.MoveTo(filteredMetric.SetEmptySummary())
   118  	case pmetric.MetricTypeEmpty:
   119  		// Ignore empty
   120  	}
   121  
   122  	return filteredMetric
   123  }
   124  
   125  func filterGauge(g pmetric.Gauge, queryMatchesResource, queryMatchesName bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.Gauge {
   126  	filteredGauge := pmetric.NewGauge()
   127  
   128  	dps := g.DataPoints()
   129  	for i := 0; i < dps.Len(); i++ {
   130  		dp := dps.At(i)
   131  		if datapointMatches(dp, queryMatchesResource, queryMatchesName, searchQuery, minimumTimestamp) {
   132  			dp.CopyTo(filteredGauge.DataPoints().AppendEmpty())
   133  		}
   134  	}
   135  
   136  	return filteredGauge
   137  }
   138  
   139  func filterSum(s pmetric.Sum, queryMatchesResource, queryMatchesName bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.Sum {
   140  	filteredSum := pmetric.NewSum()
   141  
   142  	dps := s.DataPoints()
   143  	for i := 0; i < dps.Len(); i++ {
   144  		dp := dps.At(i)
   145  		if datapointMatches(dp, queryMatchesResource, queryMatchesName, searchQuery, minimumTimestamp) {
   146  			dp.CopyTo(filteredSum.DataPoints().AppendEmpty())
   147  		}
   148  	}
   149  
   150  	return filteredSum
   151  }
   152  
   153  func filterHistogram(h pmetric.Histogram, queryMatchesResource, queryMatchesName bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.Histogram {
   154  	filteredHistogram := pmetric.NewHistogram()
   155  
   156  	dps := h.DataPoints()
   157  	for i := 0; i < dps.Len(); i++ {
   158  		dp := dps.At(i)
   159  		if datapointMatches(dp, queryMatchesResource, queryMatchesName, searchQuery, minimumTimestamp) {
   160  			dp.CopyTo(filteredHistogram.DataPoints().AppendEmpty())
   161  		}
   162  	}
   163  
   164  	return filteredHistogram
   165  }
   166  
   167  func filterExponentialHistogram(eh pmetric.ExponentialHistogram, queryMatchesResource, queryMatchesName bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.ExponentialHistogram {
   168  	filteredExponentialHistogram := pmetric.NewExponentialHistogram()
   169  
   170  	dps := eh.DataPoints()
   171  	for i := 0; i < dps.Len(); i++ {
   172  		dp := dps.At(i)
   173  		if datapointMatches(dp, queryMatchesResource, queryMatchesName, searchQuery, minimumTimestamp) {
   174  			dp.CopyTo(filteredExponentialHistogram.DataPoints().AppendEmpty())
   175  		}
   176  	}
   177  
   178  	return filteredExponentialHistogram
   179  }
   180  
   181  func filterSummary(s pmetric.Summary, queryMatchesResource, queryMatchesName bool, searchQuery *string, minimumTimestamp *time.Time) pmetric.Summary {
   182  	filteredSummary := pmetric.NewSummary()
   183  
   184  	dps := s.DataPoints()
   185  	for i := 0; i < dps.Len(); i++ {
   186  		dp := dps.At(i)
   187  		if datapointMatches(dp, queryMatchesResource, queryMatchesName, searchQuery, minimumTimestamp) {
   188  			dp.CopyTo(filteredSummary.DataPoints().AppendEmpty())
   189  		}
   190  	}
   191  
   192  	return filteredSummary
   193  }
   194  
   195  func metricMatchesQuery(m pmetric.Metric, query string) bool {
   196  	// Match query against metric name
   197  	return strings.Contains(m.Name(), query)
   198  }
   199  
   200  // datapoint is an interface that every concrete datapoint type implements
   201  type datapoint interface {
   202  	Attributes() pcommon.Map
   203  	Timestamp() pcommon.Timestamp
   204  }
   205  
   206  func datapointMatches(dp datapoint, queryMatchesResource, queryMatchesName bool, searchQuery *string, minimumTimestamp *time.Time) bool {
   207  	queryAlreadyMatched := queryMatchesResource || queryMatchesName
   208  
   209  	queryMatchesDatapoint := true
   210  	if !queryAlreadyMatched && searchQuery != nil {
   211  		queryMatchesDatapoint = datapointMatchesQuery(dp, *searchQuery)
   212  	}
   213  
   214  	matchesTimestamp := true
   215  	if minimumTimestamp != nil {
   216  		matchesTimestamp = datapointMatchesTimestamp(dp, *minimumTimestamp)
   217  	}
   218  
   219  	matchesQuery := queryMatchesResource || queryMatchesName || queryMatchesDatapoint
   220  
   221  	return matchesQuery && matchesTimestamp
   222  }
   223  
   224  func datapointMatchesTimestamp(dp datapoint, minimumTimestamp time.Time) bool {
   225  	return dp.Timestamp() > pcommon.NewTimestampFromTime(minimumTimestamp)
   226  }
   227  
   228  func datapointMatchesQuery(dp datapoint, searchQuery string) bool {
   229  	return queryMatchesMap(dp.Attributes(), searchQuery)
   230  }
   231  
   232  func metricIsEmpty(m pmetric.Metric) bool {
   233  	switch m.Type() {
   234  	case pmetric.MetricTypeGauge:
   235  		return m.Gauge().DataPoints().Len() == 0
   236  	case pmetric.MetricTypeSum:
   237  		return m.Sum().DataPoints().Len() == 0
   238  	case pmetric.MetricTypeHistogram:
   239  		return m.Histogram().DataPoints().Len() == 0
   240  	case pmetric.MetricTypeExponentialHistogram:
   241  		return m.ExponentialHistogram().DataPoints().Len() == 0
   242  	case pmetric.MetricTypeSummary:
   243  		return m.Summary().DataPoints().Len() == 0
   244  	case pmetric.MetricTypeEmpty:
   245  		return true
   246  	}
   247  	return false
   248  }