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 }