github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/protocol/otlp/metric_helper.go (about) 1 // Copyright 2022 iLogtail Authors 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 otlp 16 17 import ( 18 "fmt" 19 "sort" 20 "strconv" 21 "strings" 22 23 "github.com/alibaba/ilogtail/pkg/models" 24 ) 25 26 var ( 27 errInvalidBucket = fmt.Errorf("invalid_bucket_boundary") 28 ) 29 30 // ComposeBucketFieldName generates the bucket count field name for histogram metrics. 31 func ComposeBucketFieldName(lower, upper float64, isPositive bool) string { 32 if !isPositive { 33 return fmt.Sprintf("[%v,%v)", -upper, -lower) 34 } 35 return fmt.Sprintf("(%v,%v]", lower, upper) 36 } 37 38 // ComputeBuckets computes the bucket boundarys and counts. 39 func ComputeBuckets(multiValues models.MetricFloatValues, isPositive bool) (bucketBounds []float64, bucketCounts []float64) { 40 valueMap := multiValues.Iterator() 41 bucketMap := make(map[float64]float64, len(bucketCounts)) 42 for k, v := range valueMap { 43 bucketBound, err := ComputeBucketBoundary(k, isPositive) 44 if err != nil { 45 continue 46 } 47 bucketMap[bucketBound] = v 48 bucketBounds = append(bucketBounds, bucketBound) 49 } 50 51 sort.Float64s(bucketBounds) 52 if !isPositive { 53 ReverseSlice(bucketBounds) 54 } 55 for _, v := range bucketBounds { 56 bucketCounts = append(bucketCounts, bucketMap[v]) 57 } 58 return 59 } 60 61 // ComputeBucketBoundary computes the bucket boundary from a field name. 62 func ComputeBucketBoundary(fieldName string, isPositive bool) (float64, error) { 63 if !isPositive { 64 if !strings.HasPrefix(fieldName, "[") || !strings.HasSuffix(fieldName, ")") { 65 return 0, errInvalidBucket 66 } 67 68 fieldName = strings.TrimLeft(fieldName, "[") 69 fieldName = strings.TrimRight(fieldName, ")") 70 buckets := strings.Split(fieldName, ",") 71 if len(buckets) != 2 { 72 return 0, errInvalidBucket 73 } 74 return strconv.ParseFloat(buckets[1], 64) 75 } 76 77 if !strings.HasPrefix(fieldName, "(") || !strings.HasSuffix(fieldName, "]") { 78 return 0, errInvalidBucket 79 } 80 81 fieldName = strings.TrimLeft(fieldName, "(") 82 fieldName = strings.TrimRight(fieldName, "]") 83 buckets := strings.Split(fieldName, ",") 84 if len(buckets) != 2 { 85 return 0, errInvalidBucket 86 } 87 return strconv.ParseFloat(buckets[0], 64) 88 } 89 90 func ReverseSlice[T comparable](s []T) { 91 sort.SliceStable(s, func(i, j int) bool { 92 return i > j 93 }) 94 } 95 96 // When count is less than 20, direct comparison faster than map. 97 func IsInternalTag(tagname string) bool { 98 return tagname == TagKeyMetricAggregationTemporality || 99 tagname == TagKeyScopeDroppedAttributesCount || 100 tagname == TagKeyScopeName || 101 tagname == TagKeyScopeVersion || 102 tagname == TagKeySpanDroppedAttrsCount || 103 tagname == TagKeySpanDroppedEventsCount || 104 tagname == TagKeySpanDroppedLinksCount || 105 tagname == TagKeySpanStatusMessage || 106 tagname == TagKeyMetricIsMonotonic || 107 tagname == TagKeyLogFlag 108 } 109 110 func IsInternalField(fieldname string) bool { 111 return fieldname == FieldCount || 112 fieldname == FieldMax || 113 fieldname == FieldMin || 114 fieldname == FieldSum || 115 fieldname == FieldScale || 116 fieldname == FieldPositiveOffset || 117 fieldname == FieldNegativeOffset || 118 fieldname == FieldZeroCount 119 }