github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prom/read.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 prom provides custom handlers that support the prometheus 22 // query endpoints. 23 package prom 24 25 import ( 26 "context" 27 "errors" 28 "net/http" 29 "sync" 30 31 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 32 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/native" 33 "github.com/m3db/m3/src/query/api/v1/options" 34 "github.com/m3db/m3/src/query/block" 35 queryerrors "github.com/m3db/m3/src/query/errors" 36 "github.com/m3db/m3/src/query/models" 37 "github.com/m3db/m3/src/query/storage" 38 "github.com/m3db/m3/src/query/storage/prometheus" 39 xerrors "github.com/m3db/m3/src/x/errors" 40 xhttp "github.com/m3db/m3/src/x/net/http" 41 42 errs "github.com/pkg/errors" 43 "github.com/prometheus/prometheus/promql" 44 "github.com/prometheus/prometheus/promql/parser" 45 promstorage "github.com/prometheus/prometheus/storage" 46 "github.com/uber-go/tally" 47 "go.uber.org/zap" 48 ) 49 50 // NewQueryFn creates a new promql Query. 51 type NewQueryFn func(params models.RequestParams) (promql.Query, error) 52 53 var ( 54 newRangeQueryFn = func( 55 engineFn options.PromQLEngineFn, 56 queryable promstorage.Queryable, 57 ) NewQueryFn { 58 return func(params models.RequestParams) (promql.Query, error) { 59 engine, err := engineFn(params.LookbackDuration) 60 if err != nil { 61 return nil, err 62 } 63 return engine.NewRangeQuery( 64 queryable, 65 params.Query, 66 params.Start.ToTime(), 67 params.End.ToTime(), 68 params.Step) 69 } 70 } 71 72 newInstantQueryFn = func( 73 engineFn options.PromQLEngineFn, 74 queryable promstorage.Queryable, 75 ) NewQueryFn { 76 return func(params models.RequestParams) (promql.Query, error) { 77 engine, err := engineFn(params.LookbackDuration) 78 if err != nil { 79 return nil, err 80 } 81 return engine.NewInstantQuery( 82 queryable, 83 params.Query, 84 params.Now) 85 } 86 } 87 ) 88 89 type readHandler struct { 90 hOpts options.HandlerOptions 91 scope tally.Scope 92 logger *zap.Logger 93 opts opts 94 returnedDataMetrics native.PromReadReturnedDataMetrics 95 } 96 97 func newReadHandler( 98 hOpts options.HandlerOptions, 99 options opts, 100 ) (http.Handler, error) { 101 scope := hOpts.InstrumentOpts().MetricsScope().Tagged( 102 map[string]string{"handler": "prometheus-read"}, 103 ) 104 return &readHandler{ 105 hOpts: hOpts, 106 opts: options, 107 scope: scope, 108 logger: hOpts.InstrumentOpts().Logger(), 109 returnedDataMetrics: native.NewPromReadReturnedDataMetrics(scope), 110 }, nil 111 } 112 113 func (h *readHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 114 ctx := r.Context() 115 ctx, request, err := native.ParseRequest(ctx, r, h.opts.instant, h.hOpts) 116 if err != nil { 117 xhttp.WriteError(w, err) 118 return 119 } 120 121 params := request.Params 122 fetchOptions := request.FetchOpts 123 124 // NB (@shreyas): We put the FetchOptions in context so it can be 125 // retrieved in the queryable object as there is no other way to pass 126 // that through. 127 // 128 // We also put a function into the context that allows callers to safely 129 // pass back result metadata concurrently so that they can be combined 130 // for later reporting. 131 var resultMetadataMutex sync.Mutex 132 resultMetadata := block.NewResultMetadata() 133 resultMetadataReceiveFn := func(m block.ResultMetadata) { 134 resultMetadataMutex.Lock() 135 defer resultMetadataMutex.Unlock() 136 resultMetadata = resultMetadata.CombineMetadata(m) 137 } 138 ctx = context.WithValue(ctx, prometheus.FetchOptionsContextKey, fetchOptions) 139 ctx = context.WithValue(ctx, prometheus.BlockResultMetadataFnKey, resultMetadataReceiveFn) 140 141 qry, err := h.opts.newQueryFn(params) 142 if err != nil { 143 h.logger.Error("error creating query", 144 zap.Error(err), zap.String("query", params.Query), 145 zap.Bool("instant", h.opts.instant)) 146 xhttp.WriteError(w, xerrors.NewInvalidParamsError(err)) 147 return 148 } 149 defer qry.Close() 150 151 res := qry.Exec(ctx) 152 if res.Err != nil { 153 h.logger.Error("error executing query", 154 zap.Error(res.Err), zap.String("query", params.Query), 155 zap.Bool("instant", h.opts.instant)) 156 var sErr *prometheus.StorageErr 157 if errors.As(res.Err, &sErr) { 158 // If the error happened in the m3 storage layer, propagate the causing error as is. 159 err := sErr.Unwrap() 160 if queryerrors.IsTimeout(err) { 161 xhttp.WriteError(w, queryerrors.NewErrQueryTimeout(err)) 162 } else { 163 xhttp.WriteError(w, err) 164 } 165 } else { 166 promErr := errs.Cause(res.Err) 167 switch promErr.(type) { //nolint:errorlint 168 case promql.ErrQueryTimeout: 169 promErr = queryerrors.NewErrQueryTimeout(promErr) 170 case promql.ErrQueryCanceled: 171 default: 172 // Assume any prometheus library error is a 4xx, since there are no remote calls. 173 promErr = xerrors.NewInvalidParamsError(res.Err) 174 } 175 xhttp.WriteError(w, promErr) 176 } 177 return 178 } 179 180 for _, warn := range resultMetadata.Warnings { 181 res.Warnings = append(res.Warnings, errors.New(warn.Message)) 182 } 183 184 query := params.Query 185 err = ApplyRangeWarnings(query, &resultMetadata) 186 if err != nil { 187 h.logger.Warn("error applying range warnings", 188 zap.Error(err), zap.String("query", query), 189 zap.Bool("instant", h.opts.instant)) 190 } 191 192 err = handleroptions.AddDBResultResponseHeaders(w, resultMetadata, fetchOptions) 193 if err != nil { 194 h.logger.Error("error writing database limit headers", zap.Error(err)) 195 xhttp.WriteError(w, err) 196 return 197 } 198 199 returnedDataLimited := h.limitReturnedData(query, res, fetchOptions) 200 h.returnedDataMetrics.FetchDatapoints.RecordValue(float64(returnedDataLimited.Datapoints)) 201 h.returnedDataMetrics.FetchSeries.RecordValue(float64(returnedDataLimited.Series)) 202 203 limited := &handleroptions.ReturnedDataLimited{ 204 Limited: returnedDataLimited.Limited, 205 Series: returnedDataLimited.Series, 206 TotalSeries: returnedDataLimited.TotalSeries, 207 Datapoints: returnedDataLimited.Datapoints, 208 } 209 err = handleroptions.AddReturnedLimitResponseHeaders(w, limited, nil) 210 if err != nil { 211 h.logger.Error("error writing response headers", 212 zap.Error(err), zap.String("query", query), 213 zap.Bool("instant", h.opts.instant)) 214 xhttp.WriteError(w, err) 215 return 216 } 217 218 if err := Respond(w, &QueryData{ 219 Result: res.Value, 220 ResultType: res.Value.Type(), 221 }, res.Warnings); err != nil { 222 h.logger.Error("error writing prom response", 223 zap.Error(err), 224 zap.String("query", params.Query), 225 zap.Bool("instant", h.opts.instant)) 226 } 227 } 228 229 func (h *readHandler) limitReturnedData(query string, 230 res *promql.Result, 231 fetchOpts *storage.FetchOptions, 232 ) native.ReturnedDataLimited { 233 var ( 234 seriesLimit = fetchOpts.ReturnedSeriesLimit 235 datapointsLimit = fetchOpts.ReturnedDatapointsLimit 236 237 limited = false 238 series int 239 datapoints int 240 seriesTotal int 241 ) 242 switch res.Value.Type() { 243 case parser.ValueTypeVector: 244 v, err := res.Vector() 245 if err != nil { 246 h.logger.Error("error parsing vector for returned data limits", 247 zap.Error(err), zap.String("query", query), 248 zap.Bool("instant", h.opts.instant)) 249 break 250 } 251 252 // Determine maxSeries based on either series or datapoints limit. Vector has one datapoint per 253 // series and so the datapoint limit behaves the same way as the series one. 254 switch { 255 case seriesLimit > 0 && datapointsLimit == 0: 256 series = seriesLimit 257 case seriesLimit == 0 && datapointsLimit > 0: 258 series = datapointsLimit 259 case seriesLimit == 0 && datapointsLimit == 0: 260 // Set max to the actual size if no limits. 261 series = len(v) 262 default: 263 // Take the min of the two limits if both present. 264 series = seriesLimit 265 if seriesLimit > datapointsLimit { 266 series = datapointsLimit 267 } 268 } 269 270 seriesTotal = len(v) 271 limited = series < seriesTotal 272 273 if limited { 274 limitedSeries := v[:series] 275 res.Value = limitedSeries 276 datapoints = len(limitedSeries) 277 } else { 278 series = seriesTotal 279 datapoints = seriesTotal 280 } 281 case parser.ValueTypeMatrix: 282 m, err := res.Matrix() 283 if err != nil { 284 h.logger.Error("error parsing vector for returned data limits", 285 zap.Error(err), zap.String("query", query), 286 zap.Bool("instant", h.opts.instant)) 287 break 288 } 289 290 for _, d := range m { 291 datapointCount := len(d.Points) 292 if fetchOpts.ReturnedSeriesLimit > 0 && series+1 > fetchOpts.ReturnedSeriesLimit { 293 limited = true 294 break 295 } 296 if fetchOpts.ReturnedDatapointsLimit > 0 && datapoints+datapointCount > fetchOpts.ReturnedDatapointsLimit { 297 limited = true 298 break 299 } 300 series++ 301 datapoints += datapointCount 302 } 303 seriesTotal = len(m) 304 305 if series < seriesTotal { 306 res.Value = m[:series] 307 } 308 default: 309 } 310 311 return native.ReturnedDataLimited{ 312 Limited: limited, 313 Series: series, 314 Datapoints: datapoints, 315 TotalSeries: seriesTotal, 316 } 317 }