github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/storage/prometheus/prometheus_storage.go (about) 1 // Copyright (c) 2020 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 prometheus 22 23 import ( 24 "context" 25 "sort" 26 "time" 27 28 "github.com/pkg/errors" 29 "github.com/prometheus/common/model" 30 "github.com/prometheus/prometheus/model/labels" 31 promstorage "github.com/prometheus/prometheus/storage" 32 "github.com/prometheus/prometheus/tsdb/chunkenc" 33 "github.com/uber-go/tally" 34 "go.uber.org/zap" 35 36 "github.com/m3db/m3/src/query/block" 37 "github.com/m3db/m3/src/query/generated/proto/prompb" 38 "github.com/m3db/m3/src/query/models" 39 "github.com/m3db/m3/src/query/parser/promql" 40 "github.com/m3db/m3/src/query/storage" 41 "github.com/m3db/m3/src/x/instrument" 42 ) 43 44 type prometheusQueryable struct { 45 storage storage.Storage 46 scope tally.Scope 47 logger *zap.Logger 48 } 49 50 // PrometheusOptions are options to create a prometheus queryable backed by 51 // a m3 storage. 52 type PrometheusOptions struct { 53 Storage storage.Storage 54 InstrumentOptions instrument.Options 55 } 56 57 // StorageErr wraps all errors returned by the storage layer. 58 // This allows the http handlers that call the Prometheus library directly to distinguish prometheus library errors 59 // and remote storage errors. 60 type StorageErr struct { 61 inner error 62 } 63 64 // NewStorageErr wraps the provided error as a StorageErr. 65 func NewStorageErr(err error) *StorageErr { 66 return &StorageErr{inner: err} 67 } 68 69 // Unwrap returns the underlying error. 70 func (e *StorageErr) Unwrap() error { 71 return e.inner 72 } 73 74 // Error returns the error string for the underlying error. 75 func (e *StorageErr) Error() string { 76 return e.inner.Error() 77 } 78 79 // NewPrometheusQueryable returns a new prometheus queryable backed by a m3 80 // storage. 81 func NewPrometheusQueryable(opts PrometheusOptions) promstorage.Queryable { 82 scope := opts.InstrumentOptions.MetricsScope().Tagged(map[string]string{"storage": "prometheus_storage"}) 83 return &prometheusQueryable{ 84 storage: opts.Storage, 85 scope: scope, 86 logger: opts.InstrumentOptions.Logger(), 87 } 88 } 89 90 // Querier returns a prometheus storage Querier. 91 func (q *prometheusQueryable) Querier( 92 ctx context.Context, _, _ int64, 93 ) (promstorage.Querier, error) { 94 return newQuerier(ctx, q.storage, q.logger), nil 95 } 96 97 type querier struct { 98 ctx context.Context 99 storage storage.Storage 100 logger *zap.Logger 101 } 102 103 func newQuerier( 104 ctx context.Context, 105 storage storage.Storage, 106 logger *zap.Logger, 107 ) promstorage.Querier { 108 return &querier{ 109 ctx: ctx, 110 storage: storage, 111 logger: logger, 112 } 113 } 114 115 func (q *querier) Select( 116 sortSeries bool, 117 hints *promstorage.SelectHints, 118 labelMatchers ...*labels.Matcher, 119 ) promstorage.SeriesSet { 120 matchers, err := promql.LabelMatchersToModelMatcher(labelMatchers, models.NewTagOptions()) 121 if err != nil { 122 return promstorage.ErrSeriesSet(err) 123 } 124 125 query := &storage.FetchQuery{ 126 TagMatchers: matchers, 127 Start: time.Unix(0, hints.Start*int64(time.Millisecond)), 128 End: time.Unix(0, hints.End*int64(time.Millisecond)), 129 Interval: time.Duration(hints.Step) * time.Millisecond, 130 } 131 132 // NB (@shreyas): The fetch options builder sets it up from the request 133 // which we do not have access to here. 134 fetchOptions, err := fetchOptions(q.ctx) 135 if err != nil { 136 q.logger.Error("fetch options not provided in context", zap.Error(err)) 137 return promstorage.ErrSeriesSet(err) 138 } 139 140 result, err := q.storage.FetchProm(q.ctx, query, fetchOptions) 141 if err != nil { 142 return promstorage.ErrSeriesSet(NewStorageErr(err)) 143 } 144 seriesSet := fromQueryResult(sortSeries, result.PromResult, result.Metadata) 145 146 receiveResultMetadataFn, err := resultMetadataReceiveFn(q.ctx) 147 if err != nil { 148 q.logger.Error("result metadata not set in context", zap.Error(err)) 149 return promstorage.ErrSeriesSet(err) 150 } 151 if receiveResultMetadataFn == nil { 152 err := errors.New("result metadata receive function nil for context") 153 q.logger.Error(err.Error()) 154 return promstorage.ErrSeriesSet(err) 155 } 156 157 // Pass the result.Metadata back using the receive function. 158 // This handles concurrent updates to a single result metadata. 159 receiveResultMetadataFn(result.Metadata) 160 161 return seriesSet 162 } 163 164 func (q *querier) LabelValues(string, ...*labels.Matcher) ([]string, promstorage.Warnings, error) { 165 // TODO (@shreyas): Implement this. 166 q.logger.Warn("calling unsupported LabelValues method") 167 return nil, nil, errors.New("not implemented") 168 } 169 170 func (q *querier) LabelNames(...*labels.Matcher) ([]string, promstorage.Warnings, error) { 171 // TODO (@shreyas): Implement this. 172 q.logger.Warn("calling unsupported LabelNames method") 173 return nil, nil, errors.New("not implemented") 174 } 175 176 func (q *querier) Close() error { 177 return nil 178 } 179 180 func fromWarningStrings(warnings []string) []error { 181 errs := make([]error, 0, len(warnings)) 182 for _, warning := range warnings { 183 errs = append(errs, errors.New(warning)) 184 } 185 return errs 186 } 187 188 // This is a copy of the prometheus remote.FromQueryResult method. Need to 189 // copy so that this can understand m3 prompb struct. 190 func fromQueryResult(sortSeries bool, res *prompb.QueryResult, metadata block.ResultMetadata) promstorage.SeriesSet { 191 series := make([]promstorage.Series, 0, len(res.Timeseries)) 192 for _, ts := range res.Timeseries { 193 labels := labelProtosToLabels(ts.Labels) 194 if err := validateLabelsAndMetricName(labels); err != nil { 195 return promstorage.ErrSeriesSet(err) 196 } 197 198 series = append(series, &concreteSeries{ 199 labels: labels, 200 samples: ts.Samples, 201 }) 202 } 203 204 if sortSeries { 205 sort.Sort(byLabel(series)) 206 } 207 208 warnings := fromWarningStrings(metadata.WarningStrings()) 209 210 return &concreteSeriesSet{ 211 series: series, 212 warnings: warnings, 213 } 214 } 215 216 type byLabel []promstorage.Series 217 218 func (a byLabel) Len() int { return len(a) } 219 func (a byLabel) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 220 func (a byLabel) Less(i, j int) bool { return labels.Compare(a[i].Labels(), a[j].Labels()) < 0 } 221 222 func labelProtosToLabels(labelPairs []prompb.Label) labels.Labels { 223 result := make(labels.Labels, 0, len(labelPairs)) 224 for _, l := range labelPairs { 225 result = append(result, labels.Label{ 226 Name: string(l.Name), 227 Value: string(l.Value), 228 }) 229 } 230 sort.Sort(result) 231 return result 232 } 233 234 // errSeriesSet implements storage.SeriesSet, just returning an error. 235 type errSeriesSet struct { 236 err error 237 } 238 239 func (errSeriesSet) Next() bool { 240 return false 241 } 242 243 func (errSeriesSet) At() promstorage.Series { 244 return nil 245 } 246 247 func (e errSeriesSet) Err() error { 248 return e.err 249 } 250 251 // concreteSeriesSet implements storage.SeriesSet. 252 type concreteSeriesSet struct { 253 cur int 254 series []promstorage.Series 255 warnings promstorage.Warnings 256 } 257 258 func (c *concreteSeriesSet) Next() bool { 259 c.cur++ 260 return c.cur-1 < len(c.series) 261 } 262 263 func (c *concreteSeriesSet) At() promstorage.Series { 264 return c.series[c.cur-1] 265 } 266 267 func (c *concreteSeriesSet) Err() error { 268 return nil 269 } 270 271 func (c *concreteSeriesSet) Warnings() promstorage.Warnings { 272 return c.warnings 273 } 274 275 // concreteSeries implements storage.Series. 276 type concreteSeries struct { 277 labels labels.Labels 278 samples []prompb.Sample 279 } 280 281 func (c *concreteSeries) Labels() labels.Labels { 282 return labels.New(c.labels...) 283 } 284 285 func (c *concreteSeries) Iterator() chunkenc.Iterator { 286 return newConcreteSeriersIterator(c) 287 } 288 289 // concreteSeriesIterator implements storage.SeriesIterator. 290 type concreteSeriesIterator struct { 291 cur int 292 series *concreteSeries 293 } 294 295 func newConcreteSeriersIterator(series *concreteSeries) chunkenc.Iterator { 296 return &concreteSeriesIterator{ 297 cur: -1, 298 series: series, 299 } 300 } 301 302 // Seek implements storage.SeriesIterator. 303 func (c *concreteSeriesIterator) Seek(t int64) bool { 304 c.cur = sort.Search(len(c.series.samples), func(n int) bool { 305 return c.series.samples[n].Timestamp >= t 306 }) 307 return c.cur < len(c.series.samples) 308 } 309 310 // At implements storage.SeriesIterator. 311 func (c *concreteSeriesIterator) At() (t int64, v float64) { 312 s := c.series.samples[c.cur] 313 return s.Timestamp, s.Value 314 } 315 316 // Next implements storage.SeriesIterator. 317 func (c *concreteSeriesIterator) Next() bool { 318 c.cur++ 319 return c.cur < len(c.series.samples) 320 } 321 322 // Err implements storage.SeriesIterator. 323 func (c *concreteSeriesIterator) Err() error { 324 return nil 325 } 326 327 // validateLabelsAndMetricName validates the label names/values and metric names returned from remote read, 328 // also making sure that there are no labels with duplicate names 329 func validateLabelsAndMetricName(ls labels.Labels) error { 330 for i, l := range ls { 331 if l.Name == labels.MetricName && !model.IsValidMetricName(model.LabelValue(l.Value)) { 332 return errors.Errorf("invalid metric name: %v", l.Value) 333 } 334 if !model.LabelName(l.Name).IsValid() { 335 return errors.Errorf("invalid label name: %v", l.Name) 336 } 337 if !model.LabelValue(l.Value).IsValid() { 338 return errors.Errorf("invalid label value: %v", l.Value) 339 } 340 if i > 0 && l.Name == ls[i-1].Name { 341 return errors.Errorf("duplicate label with name: %v", l.Name) 342 } 343 } 344 return nil 345 }