github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/converter.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package storage 22 23 import ( 24 "bytes" 25 "fmt" 26 "sort" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/generated/proto/annotation" 30 "github.com/m3db/m3/src/query/generated/proto/prompb" 31 "github.com/m3db/m3/src/query/models" 32 "github.com/m3db/m3/src/query/ts" 33 xtime "github.com/m3db/m3/src/x/time" 34 35 "github.com/prometheus/common/model" 36 ) 37 38 var ( 39 // The default name for the name and bucket tags in Prometheus metrics. 40 // This can be overwritten by setting tagOptions in the config. 41 promDefaultName = []byte(model.MetricNameLabel) // __name__ 42 promDefaultBucketName = []byte(model.BucketLabel) // le 43 44 // The suffix of count metric name in Prometheus histogram/summary metric families. 45 promCountSuffix = []byte("_count") 46 // The suffix of sum metric name in Prometheus histogram/summary metric families. 47 promSumSuffix = []byte("_sum") 48 49 // The suffix of gauge count metric name in Open Metrics GaugeHistogram metric families. 50 openMetricsGaugeCountSuffix = []byte("_gcount") 51 // The suffix of count metric name in Open Metrics Summary metric families. 52 openMetricsCountSuffix = []byte("_count") 53 // The suffix of count metric name in Open Metrics Summary metric families. 54 openMetricsSumSuffix = []byte("_sum") 55 // The suffix of created metric name in Open Metrics Counter/Histogram/Summary metric families. 56 openMetricsCreatedSuffix = []byte("_created") 57 ) 58 59 // PromLabelsToM3Tags converts Prometheus labels to M3 tags 60 func PromLabelsToM3Tags( 61 labels []prompb.Label, 62 tagOptions models.TagOptions, 63 ) models.Tags { 64 tags := models.NewTags(len(labels), tagOptions) 65 tagList := make([]models.Tag, 0, len(labels)) 66 for _, label := range labels { 67 name := label.Name 68 // If this label corresponds to the Prometheus name or bucket name, 69 // instead set it as the given name tag from the config file. 70 if bytes.Equal(promDefaultName, name) { 71 tags = tags.SetName(label.Value) 72 } else if bytes.Equal(promDefaultBucketName, name) { 73 tags = tags.SetBucket(label.Value) 74 } else { 75 tagList = append(tagList, models.Tag{ 76 Name: name, 77 Value: label.Value, 78 }) 79 } 80 } 81 82 return tags.AddTags(tagList) 83 } 84 85 // PromTimeSeriesToSeriesAttributes extracts the series info from a prometheus 86 // timeseries. 87 func PromTimeSeriesToSeriesAttributes(series prompb.TimeSeries) (ts.SeriesAttributes, error) { 88 switch series.Source { 89 case prompb.Source_PROMETHEUS: 90 return seriesAttributesForPrometheusSource(series) 91 92 case prompb.Source_OPEN_METRICS: 93 return seriesAttributesForOpenMetricsSource(series) 94 95 case prompb.Source_GRAPHITE: 96 return seriesAttributesForGraphiteSource(series) 97 98 default: 99 return ts.SeriesAttributes{}, fmt.Errorf("invalid source type %s", series.Source) 100 } 101 } 102 103 func seriesAttributesForPrometheusSource(series prompb.TimeSeries) (ts.SeriesAttributes, error) { 104 var ( 105 promMetricType ts.PromMetricType 106 handleValueResets bool 107 ) 108 109 switch series.Type { 110 case prompb.MetricType_UNKNOWN: 111 promMetricType = ts.PromMetricTypeUnknown 112 113 case prompb.MetricType_COUNTER: 114 promMetricType = ts.PromMetricTypeCounter 115 handleValueResets = true 116 117 case prompb.MetricType_GAUGE: 118 promMetricType = ts.PromMetricTypeGauge 119 120 case prompb.MetricType_HISTOGRAM: 121 promMetricType = ts.PromMetricTypeHistogram 122 handleValueResets = true 123 124 case prompb.MetricType_GAUGE_HISTOGRAM: 125 promMetricType = ts.PromMetricTypeGaugeHistogram 126 name := metricNameFromLabels(series.Labels) 127 handleValueResets = bytes.HasSuffix(name, promCountSuffix) || 128 bytes.HasSuffix(name, openMetricsGaugeCountSuffix) 129 130 case prompb.MetricType_SUMMARY: 131 promMetricType = ts.PromMetricTypeSummary 132 name := metricNameFromLabels(series.Labels) 133 handleValueResets = bytes.HasSuffix(name, promCountSuffix) || 134 bytes.HasSuffix(name, promSumSuffix) 135 136 case prompb.MetricType_INFO: 137 promMetricType = ts.PromMetricTypeInfo 138 139 case prompb.MetricType_STATESET: 140 promMetricType = ts.PromMetricTypeStateSet 141 142 default: 143 return ts.SeriesAttributes{}, fmt.Errorf("invalid metric type for Prometheus: %s", series.Type) 144 } 145 146 m3MetricType, err := convertM3Type(series.M3Type) 147 if err != nil { 148 return ts.SeriesAttributes{}, err 149 } 150 151 return ts.SeriesAttributes{ 152 Source: ts.SourceTypePrometheus, 153 M3Type: m3MetricType, 154 PromType: promMetricType, 155 HandleValueResets: handleValueResets, 156 }, nil 157 } 158 159 func seriesAttributesForOpenMetricsSource(series prompb.TimeSeries) (ts.SeriesAttributes, error) { 160 var ( 161 promMetricType ts.PromMetricType 162 handleValueResets bool 163 ) 164 165 // https://github.com/OpenObservability/OpenMetrics/blob/2bd6413e040/specification/OpenMetrics.md 166 switch series.Type { 167 case prompb.MetricType_UNKNOWN: 168 promMetricType = ts.PromMetricTypeUnknown 169 170 case prompb.MetricType_COUNTER: 171 promMetricType = ts.PromMetricTypeCounter 172 name := metricNameFromLabels(series.Labels) 173 handleValueResets = !bytes.HasSuffix(name, openMetricsCreatedSuffix) 174 175 case prompb.MetricType_GAUGE: 176 promMetricType = ts.PromMetricTypeGauge 177 178 case prompb.MetricType_HISTOGRAM: 179 promMetricType = ts.PromMetricTypeHistogram 180 name := metricNameFromLabels(series.Labels) 181 handleValueResets = !bytes.HasSuffix(name, openMetricsCreatedSuffix) 182 183 case prompb.MetricType_GAUGE_HISTOGRAM: 184 promMetricType = ts.PromMetricTypeGaugeHistogram 185 name := metricNameFromLabels(series.Labels) 186 handleValueResets = bytes.HasSuffix(name, openMetricsGaugeCountSuffix) 187 188 case prompb.MetricType_SUMMARY: 189 promMetricType = ts.PromMetricTypeSummary 190 name := metricNameFromLabels(series.Labels) 191 handleValueResets = bytes.HasSuffix(name, openMetricsCountSuffix) || 192 bytes.HasSuffix(name, openMetricsSumSuffix) 193 194 case prompb.MetricType_INFO: 195 promMetricType = ts.PromMetricTypeInfo 196 197 case prompb.MetricType_STATESET: 198 promMetricType = ts.PromMetricTypeStateSet 199 200 default: 201 return ts.SeriesAttributes{}, fmt.Errorf("invalid metric type for Open Metrics: %s", series.Type) 202 } 203 204 m3MetricType, err := convertM3Type(series.M3Type) 205 if err != nil { 206 return ts.SeriesAttributes{}, err 207 } 208 209 return ts.SeriesAttributes{ 210 Source: ts.SourceTypeOpenMetrics, 211 PromType: promMetricType, 212 M3Type: m3MetricType, 213 HandleValueResets: handleValueResets, 214 }, nil 215 } 216 217 func seriesAttributesForGraphiteSource(series prompb.TimeSeries) (ts.SeriesAttributes, error) { 218 m3MetricType, err := convertM3Type(series.M3Type) 219 if err != nil { 220 return ts.SeriesAttributes{}, err 221 } 222 223 var promMetricType ts.PromMetricType 224 switch series.M3Type { 225 case prompb.M3Type_M3_COUNTER: 226 promMetricType = ts.PromMetricTypeCounter 227 case prompb.M3Type_M3_GAUGE: 228 promMetricType = ts.PromMetricTypeGauge 229 case prompb.M3Type_M3_TIMER: 230 promMetricType = ts.PromMetricTypeUnknown 231 } 232 233 return ts.SeriesAttributes{ 234 Source: ts.SourceTypeGraphite, 235 M3Type: m3MetricType, 236 PromType: promMetricType, 237 HandleValueResets: false, 238 }, nil 239 } 240 241 func convertM3Type(m3Type prompb.M3Type) (ts.M3MetricType, error) { 242 switch m3Type { 243 case prompb.M3Type_M3_GAUGE: 244 return ts.M3MetricTypeGauge, nil 245 246 case prompb.M3Type_M3_COUNTER: 247 return ts.M3MetricTypeCounter, nil 248 249 case prompb.M3Type_M3_TIMER: 250 return ts.M3MetricTypeTimer, nil 251 252 default: 253 return 0, fmt.Errorf("invalid M3 metric type: %s", m3Type) 254 } 255 } 256 257 var ( 258 promMetricTypeToProto = map[ts.PromMetricType]annotation.OpenMetricsFamilyType{ 259 ts.PromMetricTypeUnknown: annotation.OpenMetricsFamilyType_UNKNOWN, 260 ts.PromMetricTypeCounter: annotation.OpenMetricsFamilyType_COUNTER, 261 ts.PromMetricTypeGauge: annotation.OpenMetricsFamilyType_GAUGE, 262 ts.PromMetricTypeHistogram: annotation.OpenMetricsFamilyType_HISTOGRAM, 263 ts.PromMetricTypeGaugeHistogram: annotation.OpenMetricsFamilyType_GAUGE_HISTOGRAM, 264 ts.PromMetricTypeSummary: annotation.OpenMetricsFamilyType_SUMMARY, 265 ts.PromMetricTypeInfo: annotation.OpenMetricsFamilyType_INFO, 266 ts.PromMetricTypeStateSet: annotation.OpenMetricsFamilyType_STATESET, 267 } 268 269 graphiteMetricTypeToProto = map[ts.M3MetricType]annotation.GraphiteType{ 270 ts.M3MetricTypeGauge: annotation.GraphiteType_GRAPHITE_GAUGE, 271 ts.M3MetricTypeCounter: annotation.GraphiteType_GRAPHITE_COUNTER, 272 ts.M3MetricTypeTimer: annotation.GraphiteType_GRAPHITE_TIMER, 273 } 274 ) 275 276 // SeriesAttributesToAnnotationPayload converts ts.SeriesAttributes into an annotation.Payload. 277 func SeriesAttributesToAnnotationPayload(seriesAttributes ts.SeriesAttributes) (annotation.Payload, error) { 278 if seriesAttributes.Source == ts.SourceTypeGraphite { 279 metricType, ok := graphiteMetricTypeToProto[seriesAttributes.M3Type] 280 if !ok { 281 return annotation.Payload{}, fmt.Errorf( 282 "invalid Graphite metric type %d", seriesAttributes.M3Type) 283 } 284 285 return annotation.Payload{ 286 SourceFormat: annotation.SourceFormat_GRAPHITE, 287 GraphiteType: metricType, 288 }, nil 289 } 290 291 metricType, ok := promMetricTypeToProto[seriesAttributes.PromType] 292 if !ok { 293 return annotation.Payload{}, fmt.Errorf( 294 "invalid Prometheus metric type %d", seriesAttributes.PromType) 295 } 296 297 return annotation.Payload{ 298 SourceFormat: annotation.SourceFormat_OPEN_METRICS, 299 OpenMetricsFamilyType: metricType, 300 OpenMetricsHandleValueResets: seriesAttributes.HandleValueResets, 301 }, nil 302 } 303 304 // PromSamplesToM3Datapoints converts Prometheus samples to M3 datapoints 305 func PromSamplesToM3Datapoints(samples []prompb.Sample) ts.Datapoints { 306 datapoints := make(ts.Datapoints, 0, len(samples)) 307 for _, sample := range samples { 308 timestamp := promTimestampToUnixNanos(sample.Timestamp) 309 datapoints = append(datapoints, ts.Datapoint{Timestamp: timestamp, Value: sample.Value}) 310 } 311 312 return datapoints 313 } 314 315 // PromReadQueryToM3 converts a prometheus read query to m3 read query 316 func PromReadQueryToM3(query *prompb.Query) (*FetchQuery, error) { 317 tagMatchers, err := PromMatchersToM3(query.Matchers) 318 if err != nil { 319 return nil, err 320 } 321 322 start := PromTimestampToTime(query.StartTimestampMs) 323 end := PromTimestampToTime(query.EndTimestampMs) 324 if start.After(end) { 325 start = time.Time{} 326 end = time.Now() 327 } 328 329 return &FetchQuery{ 330 TagMatchers: tagMatchers, 331 Start: start, 332 End: end, 333 }, nil 334 } 335 336 // PromMatchersToM3 converts prometheus label matchers to m3 matchers 337 func PromMatchersToM3(matchers []*prompb.LabelMatcher) (models.Matchers, error) { 338 tagMatchers := make(models.Matchers, len(matchers)) 339 var err error 340 for idx, matcher := range matchers { 341 tagMatchers[idx], err = PromMatcherToM3(matcher) 342 if err != nil { 343 return nil, err 344 } 345 } 346 347 return tagMatchers, nil 348 } 349 350 // PromMatcherToM3 converts a prometheus label matcher to m3 matcher 351 func PromMatcherToM3(matcher *prompb.LabelMatcher) (models.Matcher, error) { 352 matchType, err := PromTypeToM3(matcher.Type) 353 if err != nil { 354 return models.Matcher{}, err 355 } 356 357 return models.NewMatcher(matchType, matcher.Name, matcher.Value) 358 } 359 360 // PromTypeToM3 converts a prometheus label type to m3 matcher type 361 func PromTypeToM3(labelType prompb.LabelMatcher_Type) (models.MatchType, error) { 362 switch labelType { 363 case prompb.LabelMatcher_EQ: 364 return models.MatchEqual, nil 365 case prompb.LabelMatcher_NEQ: 366 return models.MatchNotEqual, nil 367 case prompb.LabelMatcher_RE: 368 return models.MatchRegexp, nil 369 case prompb.LabelMatcher_NRE: 370 return models.MatchNotRegexp, nil 371 372 default: 373 return 0, fmt.Errorf("unknown match type: %v", labelType) 374 } 375 } 376 377 // PromTimestampToTime converts a prometheus timestamp to time.Time. 378 func PromTimestampToTime(timestampMS int64) time.Time { 379 return promTimestampToUnixNanos(timestampMS).ToTime() 380 } 381 382 func promTimestampToUnixNanos(timestampMS int64) xtime.UnixNano { 383 // NB: prometheus format is in milliseconds; convert to unix nanos. 384 return xtime.UnixNano(timestampMS * int64(time.Millisecond)) 385 } 386 387 // TimeToPromTimestamp converts a xtime.UnixNano to prometheus timestamp. 388 func TimeToPromTimestamp(timestamp xtime.UnixNano) int64 { 389 // Significantly faster than time.Truncate() 390 return int64(timestamp) / int64(time.Millisecond) 391 } 392 393 // FetchResultToPromResult converts fetch results from M3 to Prometheus result. 394 func FetchResultToPromResult( 395 result *FetchResult, 396 keepEmpty bool, 397 ) *prompb.QueryResult { 398 // Perform bulk allocation upfront then convert to pointers afterwards 399 // to reduce total number of allocations. See BenchmarkFetchResultToPromResult 400 // if modifying. 401 timeseries := make([]prompb.TimeSeries, 0, len(result.SeriesList)) 402 for _, series := range result.SeriesList { 403 if !keepEmpty && series.Len() == 0 { 404 continue 405 } 406 407 promTs := SeriesToPromTS(series) 408 timeseries = append(timeseries, promTs) 409 } 410 411 timeSeriesPointers := make([]*prompb.TimeSeries, 0, len(result.SeriesList)) 412 for i := range timeseries { 413 timeSeriesPointers = append(timeSeriesPointers, ×eries[i]) 414 } 415 416 return &prompb.QueryResult{ 417 Timeseries: timeSeriesPointers, 418 } 419 } 420 421 // SeriesToPromTS converts a series to prometheus timeseries. 422 func SeriesToPromTS(series *ts.Series) prompb.TimeSeries { 423 labels := TagsToPromLabels(series.Tags) 424 samples := SeriesToPromSamples(series) 425 return prompb.TimeSeries{Labels: labels, Samples: samples} 426 } 427 428 type sortableLabels []prompb.Label 429 430 func (t sortableLabels) Len() int { return len(t) } 431 func (t sortableLabels) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 432 func (t sortableLabels) Less(i, j int) bool { 433 return bytes.Compare(t[i].Name, t[j].Name) == -1 434 } 435 436 // TagsToPromLabels converts tags to prometheus labels. 437 func TagsToPromLabels(tags models.Tags) []prompb.Label { 438 l := tags.Len() 439 labels := make([]prompb.Label, 0, l) 440 441 metricName := tags.Opts.MetricName() 442 bucketName := tags.Opts.BucketName() 443 for _, t := range tags.Tags { 444 if bytes.Equal(t.Name, metricName) { 445 labels = append(labels, 446 prompb.Label{Name: promDefaultName, Value: t.Value}) 447 } else if bytes.Equal(t.Name, bucketName) { 448 labels = append(labels, 449 prompb.Label{Name: promDefaultBucketName, Value: t.Value}) 450 } else { 451 labels = append(labels, prompb.Label{Name: t.Name, Value: t.Value}) 452 } 453 } 454 455 // Sort here since name and label may be added in a different order in tags 456 // if default metric name or bucket names are overridden. 457 sort.Sort(sortableLabels(labels)) 458 459 return labels 460 } 461 462 // SeriesToPromSamples series datapoints to prometheus samples.SeriesToPromSamples. 463 func SeriesToPromSamples(series *ts.Series) []prompb.Sample { 464 var ( 465 seriesLen = series.Len() 466 values = series.Values() 467 datapoints = values.Datapoints() 468 samples = make([]prompb.Sample, 0, seriesLen) 469 ) 470 for _, dp := range datapoints { 471 samples = append(samples, prompb.Sample{ 472 Timestamp: TimeToPromTimestamp(dp.Timestamp), 473 Value: dp.Value, 474 }) 475 } 476 477 return samples 478 } 479 480 func metricNameFromLabels(labels []prompb.Label) []byte { 481 for _, label := range labels { 482 if bytes.Equal(promDefaultName, label.GetName()) { 483 return label.GetValue() 484 } 485 } 486 return nil 487 }