github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/read.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 native 22 23 import ( 24 "net/http" 25 26 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 27 "github.com/m3db/m3/src/query/api/v1/options" 28 "github.com/m3db/m3/src/query/api/v1/route" 29 "github.com/m3db/m3/src/query/errors" 30 "github.com/m3db/m3/src/query/util/json" 31 "github.com/m3db/m3/src/query/util/logging" 32 xhttp "github.com/m3db/m3/src/x/net/http" 33 xopentracing "github.com/m3db/m3/src/x/opentracing" 34 35 opentracingext "github.com/opentracing/opentracing-go/ext" 36 opentracinglog "github.com/opentracing/opentracing-go/log" 37 "go.uber.org/zap" 38 ) 39 40 const ( 41 // PromReadURL is the URL for native prom read handler, this matches the 42 // default URL for the query range endpoint found on a Prometheus server. 43 PromReadURL = route.QueryRangeURL 44 45 // PromReadInstantURL is the URL for native instantaneous prom read 46 // handler, this matches the default URL for the query endpoint 47 // found on a Prometheus server. 48 PromReadInstantURL = route.QueryURL 49 50 // PrometheusReadURL is the URL for native prom read handler. 51 PrometheusReadURL = "/prometheus" + PromReadURL 52 53 // PrometheusReadInstantURL is the URL for native instantaneous prom read handler. 54 PrometheusReadInstantURL = "/prometheus" + PromReadInstantURL 55 56 // M3QueryReadURL is the URL for native m3 query read handler. 57 M3QueryReadURL = "/m3query" + PromReadURL 58 59 // M3QueryReadInstantURL is the URL for native instantaneous m3 query read handler. 60 M3QueryReadInstantURL = "/m3query" + PromReadInstantURL 61 ) 62 63 var ( 64 // PromReadHTTPMethods are the HTTP methods for the read handler. 65 PromReadHTTPMethods = []string{ 66 http.MethodGet, 67 http.MethodPost, 68 } 69 70 // PromReadInstantHTTPMethods are the HTTP methods for the instant handler. 71 PromReadInstantHTTPMethods = []string{ 72 http.MethodGet, 73 http.MethodPost, 74 } 75 ) 76 77 // promReadHandler represents a handler for prometheus read endpoint. 78 type promReadHandler struct { 79 instant bool 80 promReadMetrics promReadMetrics 81 opts options.HandlerOptions 82 } 83 84 // NewPromReadHandler returns a new prometheus-compatible read handler. 85 func NewPromReadHandler(opts options.HandlerOptions) http.Handler { 86 return newHandler(opts, false) 87 } 88 89 // NewPromReadInstantHandler returns a new pro instance of handler. 90 func NewPromReadInstantHandler(opts options.HandlerOptions) http.Handler { 91 return newHandler(opts, true) 92 } 93 94 // newHandler returns a new pro instance of handler. 95 func newHandler(opts options.HandlerOptions, instant bool) http.Handler { 96 name := "native-read" 97 if instant { 98 name = "native-instant-read" 99 } 100 101 taggedScope := opts.InstrumentOpts().MetricsScope(). 102 Tagged(map[string]string{"handler": name}) 103 h := &promReadHandler{ 104 promReadMetrics: newPromReadMetrics(taggedScope), 105 opts: opts, 106 instant: instant, 107 } 108 return h 109 } 110 111 func (h *promReadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 112 timer := h.promReadMetrics.fetchTimerSuccess.Start() 113 defer timer.Stop() 114 115 iOpts := h.opts.InstrumentOpts() 116 logger := logging.WithContext(r.Context(), iOpts) 117 118 ctx, parsedOptions, rErr := ParseRequest(r.Context(), r, h.instant, h.opts) 119 if rErr != nil { 120 h.promReadMetrics.incError(rErr) 121 logger.Error("could not parse request", zap.Error(rErr)) 122 xhttp.WriteError(w, rErr) 123 return 124 } 125 ctx = logging.NewContext(ctx, 126 iOpts, 127 zap.String("query", parsedOptions.Params.Query), 128 zap.Time("start", parsedOptions.Params.Start.ToTime()), 129 zap.Time("end", parsedOptions.Params.End.ToTime()), 130 zap.Duration("step", parsedOptions.Params.Step), 131 zap.Duration("timeout", parsedOptions.Params.Timeout), 132 zap.Duration("fetchTimeout", parsedOptions.FetchOpts.Timeout), 133 ) 134 135 result, err := read(ctx, parsedOptions, h.opts) 136 if err != nil { 137 sp := xopentracing.SpanFromContextOrNoop(ctx) 138 sp.LogFields(opentracinglog.Error(err)) 139 opentracingext.Error.Set(sp, true) 140 logger.Error("m3 query error", 141 zap.Error(err), 142 zap.Any("parsedOptions", parsedOptions)) 143 h.promReadMetrics.incError(err) 144 145 if errors.IsTimeout(err) { 146 err = errors.NewErrQueryTimeout(err) 147 } 148 xhttp.WriteError(w, err) 149 return 150 } 151 152 w.Header().Set(xhttp.HeaderContentType, xhttp.ContentTypeJSON) 153 154 h.promReadMetrics.fetchSuccess.Inc(1) 155 156 err = handleroptions.AddDBResultResponseHeaders(w, result.Meta, parsedOptions.FetchOpts) 157 if err != nil { 158 logger.Error("error writing database limit headers", zap.Error(err)) 159 xhttp.WriteError(w, err) 160 return 161 } 162 163 keepNaNs := h.opts.Config().ResultOptions.KeepNaNs 164 if !keepNaNs { 165 keepNaNs = result.Meta.KeepNaNs 166 } 167 168 renderOpts := RenderResultsOptions{ 169 Start: parsedOptions.Params.Start, 170 End: parsedOptions.Params.End, 171 KeepNaNs: keepNaNs, 172 ReturnedSeriesLimit: parsedOptions.FetchOpts.ReturnedSeriesLimit, 173 ReturnedDatapointsLimit: parsedOptions.FetchOpts.ReturnedDatapointsLimit, 174 } 175 176 // First invoke the results rendering with a noop writer in order to 177 // check the returned-data limits. This must be done before the actual rendering 178 // so that we can add the returned-data-limited header which must precede body writing. 179 var ( 180 renderResult RenderResultsResult 181 noopWriter = json.NewNoopWriter() 182 ) 183 if h.instant { 184 renderResult = renderResultsInstantaneousJSON(noopWriter, result, renderOpts) 185 } else { 186 renderResult = RenderResultsJSON(noopWriter, result, renderOpts) 187 } 188 189 h.promReadMetrics.returnedDataMetrics.FetchDatapoints.RecordValue(float64(renderResult.Datapoints)) 190 h.promReadMetrics.returnedDataMetrics.FetchSeries.RecordValue(float64(renderResult.Series)) 191 192 limited := &handleroptions.ReturnedDataLimited{ 193 Limited: renderResult.LimitedMaxReturnedData, 194 Series: renderResult.Series, 195 TotalSeries: renderResult.TotalSeries, 196 Datapoints: renderResult.Datapoints, 197 } 198 err = handleroptions.AddReturnedLimitResponseHeaders(w, limited, nil) 199 if err != nil { 200 logger.Error("error writing returned data limited header", zap.Error(err)) 201 xhttp.WriteError(w, err) 202 return 203 } 204 205 // Write the actual results after having checked for limits and wrote headers if needed. 206 responseWriter := json.NewWriter(w) 207 if h.instant { 208 _ = renderResultsInstantaneousJSON(responseWriter, result, renderOpts) 209 } else { 210 _ = RenderResultsJSON(responseWriter, result, renderOpts) 211 } 212 213 if err := responseWriter.Close(); err != nil { 214 w.WriteHeader(http.StatusInternalServerError) 215 logger.Error("failed to render results", zap.Error(err)) 216 } else { 217 w.WriteHeader(http.StatusOK) 218 } 219 }