github.com/go-graphite/carbonapi@v0.17.0/zipper/protocols/prometheus/helpers/helpers.go (about) 1 package helpers 2 3 import ( 4 "math" 5 "regexp" 6 "sort" 7 "strings" 8 "time" 9 10 "github.com/go-graphite/carbonapi/zipper/protocols/prometheus/types" 11 ) 12 13 // ConvertGraphiteTargetToPromQL - converts graphite target string to PromQL friendly format 14 func ConvertGraphiteTargetToPromQL(query string) string { 15 var sb strings.Builder 16 17 for { 18 n := strings.IndexAny(query, "*[{") 19 if n < 0 { 20 sb.WriteString(regexp.QuoteMeta(query)) 21 return sb.String() 22 } 23 24 sb.WriteString(regexp.QuoteMeta(query[:n])) 25 ch := query[n] 26 query = query[n+1:] 27 28 switch ch { 29 case '*': 30 if query == "" { 31 // needed to support find requests when asterisk is the last character and dots should be included 32 sb.WriteString(".*") 33 break 34 } 35 36 sb.WriteString("[^.]*?") 37 38 case '[': 39 n = strings.Index(query, "]") 40 if n < 0 { 41 sb.WriteString(regexp.QuoteMeta("[" + query)) 42 return sb.String() 43 } 44 sb.WriteString("[" + query[:n+1]) 45 query = query[n+1:] 46 47 case '{': 48 n = strings.Index(query, "}") 49 if n < 0 { 50 sb.WriteString(regexp.QuoteMeta("{" + query)) 51 return sb.String() 52 } 53 alts := strings.Split(query[:n], ",") 54 query = query[n+1:] 55 for i := range alts { 56 alts[i] = regexp.QuoteMeta(alts[i]) 57 } 58 sb.WriteString("(" + strings.Join(alts, "|") + ")") 59 } 60 } 61 } 62 63 // AlignValues inserts math.NaN() in place of gaps in data from Prometheus 64 func AlignValues(startTime, stopTime, step int64, promValues []types.Value) []float64 { 65 var ( 66 promValuesCtr = 0 67 resValues = make([]float64, (stopTime-startTime)/step+1) 68 ) 69 70 for i := range resValues { 71 nextTimestamp := float64(startTime + int64(i)*step) 72 73 if promValuesCtr < len(promValues) && promValues[promValuesCtr].Timestamp == nextTimestamp { 74 resValues[i] = promValues[promValuesCtr].Value 75 promValuesCtr++ 76 continue 77 } 78 79 resValues[i] = math.NaN() 80 } 81 82 return resValues 83 } 84 85 // AdjustStep adjusts step keeping in mind default/configurable limit of maximum points per query 86 // Steps sequence is aligned with Grafana. Step progresses in the following order: 87 // minimal configured step if not default => 20 => 30 => 60 => 120 => 300 => 600 => 900 => 1200 => 1800 => 3600 => 7200 => 10800 => 21600 => 43200 => 86400 88 func AdjustStep(start, stop, maxPointsPerQuery, minStep int64, forceMinStepInterval time.Duration) int64 { 89 90 interval := float64(stop - start) 91 92 if forceMinStepInterval.Seconds() > interval { 93 return minStep 94 } 95 96 safeStep := int64(math.Ceil(interval / float64(maxPointsPerQuery))) 97 98 step := minStep 99 if safeStep > minStep { 100 step = safeStep 101 } 102 103 switch { 104 case step <= minStep: 105 return minStep // minimal configured step 106 case step <= 20: 107 return 20 // 20s 108 case step <= 30: 109 return 30 // 30s 110 case step <= 60: 111 return 60 // 1m 112 case step <= 120: 113 return 120 // 2m 114 case step <= 300: 115 return 300 // 5m 116 case step <= 600: 117 return 600 // 10m 118 case step <= 900: 119 return 900 // 15m 120 case step <= 1200: 121 return 1200 // 20m 122 case step <= 1800: 123 return 1800 // 30m 124 case step <= 3600: 125 return 3600 // 1h 126 case step <= 7200: 127 return 7200 // 2h 128 case step <= 10800: 129 return 10800 // 3h 130 case step <= 21600: 131 return 21600 // 6h 132 case step <= 43200: 133 return 43200 // 12h 134 default: 135 return 86400 // 24h 136 } 137 } 138 139 // PromethizeTagValue - accept 'Tag=value' or 'Tag=~value' string and return sanitized version of it 140 func PromethizeTagValue(tagValue string) (string, types.Tag) { 141 // Handle = and =~ 142 var ( 143 t types.Tag 144 tagName string 145 idx = strings.Index(tagValue, "=") 146 ) 147 148 if idx < 0 { 149 return tagName, t 150 } 151 152 if idx > 0 && tagValue[idx-1] == '!' { 153 t.OP = "!" 154 tagName = tagValue[:idx-1] 155 } else { 156 tagName = tagValue[:idx] 157 } 158 159 switch { 160 case idx+1 == len(tagValue): // != or = with empty value 161 t.OP += "=" 162 case tagValue[idx+1] == '~': 163 if len(t.OP) > 0 { // !=~ 164 t.OP += "~" 165 } else { // =~ 166 t.OP = "=~" 167 } 168 169 if idx+2 < len(tagValue) { // check is not empty value 170 t.TagValue = tagValue[idx+2:] 171 } 172 default: // != or = with value 173 t.OP += "=" 174 t.TagValue = tagValue[idx+1:] 175 } 176 177 return tagName, t 178 } 179 180 // SplitTagValues - For given tag-value list converts it to more usable map[string]Tag, where string is TagName 181 func SplitTagValues(query string) map[string]types.Tag { 182 tags := strings.Split(query, ",") 183 result := make(map[string]types.Tag) 184 for _, tvString := range tags { 185 tvString = strings.TrimSpace(tvString) 186 name, tag := PromethizeTagValue(tvString[1 : len(tvString)-1]) 187 result[name] = tag 188 } 189 return result 190 } 191 192 // PromMetricToGraphite converts prometheus metric name to a format expected by graphite 193 func PromMetricToGraphite(metric map[string]string) string { 194 var res strings.Builder 195 196 res.WriteString(metric["__name__"]) 197 delete(metric, "__name__") 198 199 keys := make([]string, 0, len(metric)) 200 for k := range metric { 201 keys = append(keys, k) 202 } 203 204 sort.Strings(keys) 205 206 for _, k := range keys { 207 res.WriteString(";" + k + "=" + metric[k]) 208 } 209 210 return res.String() 211 } 212 213 // SeriesByTagToPromQL converts graphite SeriesByTag to PromQL 214 // will return step if __step__ is passed 215 func SeriesByTagToPromQL(step, target string) (string, string) { 216 firstTag := true 217 var queryBuilder strings.Builder 218 tagsString := target[len("seriesByTag(") : len(target)-1] 219 tvs := SplitTagValues(tagsString) 220 // It's ok to have empty "__name__" 221 if v, ok := tvs["__name__"]; ok { 222 if v.OP == "=" { 223 queryBuilder.WriteString(v.TagValue) 224 } else { 225 firstTag = false 226 queryBuilder.WriteByte('{') 227 queryBuilder.WriteString("__name__" + v.OP + "\"" + v.TagValue + "\"") 228 } 229 230 delete(tvs, "__name__") 231 } 232 for tagName, t := range tvs { 233 if tagName == "__step__" { 234 step = t.TagValue 235 continue 236 } 237 if firstTag { 238 firstTag = false 239 queryBuilder.WriteByte('{') 240 queryBuilder.WriteString(tagName + t.OP + "\"" + t.TagValue + "\"") 241 } else { 242 queryBuilder.WriteString(", " + tagName + t.OP + "\"" + t.TagValue + "\"") 243 } 244 } 245 if !firstTag { 246 queryBuilder.WriteByte('}') 247 } 248 return step, queryBuilder.String() 249 }