github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/stats.go (about) 1 package queryrange 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "net" 8 "net/http" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/go-kit/log" 14 "github.com/go-kit/log/level" 15 promql_parser "github.com/prometheus/prometheus/promql/parser" 16 "github.com/weaveworks/common/middleware" 17 18 "github.com/grafana/loki/pkg/logql" 19 "github.com/grafana/loki/pkg/logqlmodel" 20 "github.com/grafana/loki/pkg/logqlmodel/stats" 21 "github.com/grafana/loki/pkg/querier/queryrange/queryrangebase" 22 util_log "github.com/grafana/loki/pkg/util/log" 23 "github.com/grafana/loki/pkg/util/spanlogger" 24 ) 25 26 type ctxKeyType string 27 28 const ctxKey ctxKeyType = "stats" 29 30 const ( 31 queryTypeLog = "log" 32 queryTypeMetric = "metric" 33 queryTypeSeries = "series" 34 queryTypeLabel = "label" 35 ) 36 37 var ( 38 defaultMetricRecorder = metricRecorderFn(func(data *queryData) { 39 recordQueryMetrics(data) 40 }) 41 42 StatsHTTPMiddleware middleware.Interface = statsHTTPMiddleware(defaultMetricRecorder) 43 ) 44 45 // recordQueryMetrics will be called from Query Frontend middleware chain for any type of query. 46 func recordQueryMetrics(data *queryData) { 47 logger := log.With(util_log.Logger, "component", "frontend") 48 49 switch data.queryType { 50 case queryTypeLog, queryTypeMetric: 51 logql.RecordRangeAndInstantQueryMetrics(data.ctx, logger, data.params, data.status, *data.statistics, data.result) 52 case queryTypeLabel: 53 logql.RecordLabelQueryMetrics(data.ctx, logger, data.params.Start(), data.params.End(), data.label, data.status, *data.statistics) 54 case queryTypeSeries: 55 logql.RecordSeriesQueryMetrics(data.ctx, logger, data.params.Start(), data.params.End(), data.match, data.status, *data.statistics) 56 default: 57 level.Error(logger).Log("msg", "failed to record query metrics", "err", fmt.Errorf("expected one of the *LokiRequest, *LokiInstantRequest, *LokiSeriesRequest, *LokiLabelNamesRequest, got %s", data.queryType)) 58 } 59 } 60 61 type metricRecorder interface { 62 Record(data *queryData) 63 } 64 65 type metricRecorderFn func(data *queryData) 66 67 func (m metricRecorderFn) Record(data *queryData) { 68 m(data) 69 } 70 71 type queryData struct { 72 ctx context.Context 73 params logql.Params 74 statistics *stats.Result 75 result promql_parser.Value 76 status string 77 queryType string 78 match []string // used in `series` query. 79 label string // used in `labels` query 80 81 recorded bool 82 } 83 84 func statsHTTPMiddleware(recorder metricRecorder) middleware.Interface { 85 return middleware.Func(func(next http.Handler) http.Handler { 86 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 87 data := &queryData{} 88 interceptor := &interceptor{ResponseWriter: w, statusCode: http.StatusOK} 89 r = r.WithContext(context.WithValue(r.Context(), ctxKey, data)) 90 next.ServeHTTP( 91 interceptor, 92 r, 93 ) 94 if data.recorded { 95 if data.statistics == nil { 96 data.statistics = &stats.Result{} 97 } 98 data.ctx = r.Context() 99 data.status = strconv.Itoa(interceptor.statusCode) 100 recorder.Record(data) 101 } 102 }) 103 }) 104 } 105 106 // StatsCollectorMiddleware compute the stats summary based on the actual duration of the request and inject it in the request context. 107 func StatsCollectorMiddleware() queryrangebase.Middleware { 108 return queryrangebase.MiddlewareFunc(func(next queryrangebase.Handler) queryrangebase.Handler { 109 return queryrangebase.HandlerFunc(func(ctx context.Context, req queryrangebase.Request) (queryrangebase.Response, error) { 110 logger := spanlogger.FromContext(ctx) 111 start := time.Now() 112 113 // start a new statistics context to be used by middleware, which we will merge with the response's statistics 114 st, statsCtx := stats.NewContext(ctx) 115 116 // execute the request 117 resp, err := next.Do(statsCtx, req) 118 119 // collect stats and status 120 var statistics *stats.Result 121 var res promql_parser.Value 122 var queryType string 123 var totalEntries int 124 125 if resp != nil { 126 switch r := resp.(type) { 127 case *LokiResponse: 128 statistics = &r.Statistics 129 totalEntries = int(logqlmodel.Streams(r.Data.Result).Lines()) 130 queryType = queryTypeLog 131 case *LokiPromResponse: 132 statistics = &r.Statistics 133 if r.Response != nil { 134 totalEntries = len(r.Response.Data.Result) 135 } 136 queryType = queryTypeMetric 137 case *LokiSeriesResponse: 138 statistics = &r.Statistics 139 totalEntries = len(r.Data) 140 queryType = queryTypeSeries 141 case *LokiLabelNamesResponse: 142 statistics = &r.Statistics 143 totalEntries = len(r.Data) 144 queryType = queryTypeLabel 145 default: 146 level.Warn(logger).Log("msg", fmt.Sprintf("cannot compute stats, unexpected type: %T", resp)) 147 } 148 } 149 150 if statistics != nil { 151 // merge the response's statistics with the stats collected by the middleware 152 statistics.Merge(st.Result(time.Since(start), 0, totalEntries)) 153 154 // Re-calculate the summary: the queueTime result is already merged so should not be updated 155 // Log and record metrics for the current query 156 statistics.ComputeSummary(time.Since(start), 0, totalEntries) 157 statistics.Log(level.Debug(logger)) 158 } 159 ctxValue := ctx.Value(ctxKey) 160 if data, ok := ctxValue.(*queryData); ok { 161 data.recorded = true 162 data.statistics = statistics 163 data.result = res 164 data.queryType = queryType 165 p, errReq := paramsFromRequest(req) 166 if errReq != nil { 167 return nil, errReq 168 } 169 data.params = p 170 171 // Record information for metadata queries. 172 switch r := req.(type) { 173 case *LokiLabelNamesRequest: 174 data.label = getLabelNameFromLabelsQuery(r.Path) 175 case *LokiSeriesRequest: 176 data.match = r.Match 177 } 178 179 } 180 return resp, err 181 }) 182 }) 183 } 184 185 func getLabelNameFromLabelsQuery(path string) string { 186 if strings.HasSuffix(path, "/values") { 187 188 toks := strings.FieldsFunc(path, func(r rune) bool { 189 return r == '/' 190 }) 191 192 // now assuming path has suffix `/values` label name should be second last to the suffix 193 // **if** there exists the second last. 194 length := len(toks) 195 if length >= 2 { 196 return toks[length-2] 197 } 198 199 } 200 201 return "" 202 } 203 204 // interceptor implements WriteHeader to intercept status codes. WriteHeader 205 // may not be called on success, so initialize statusCode with the status you 206 // want to report on success, i.e. http.StatusOK. 207 // 208 // interceptor also implements net.Hijacker, to let the downstream Handler 209 // hijack the connection. This is needed, for example, for working with websockets. 210 type interceptor struct { 211 http.ResponseWriter 212 statusCode int 213 recorded bool 214 } 215 216 func (i *interceptor) WriteHeader(code int) { 217 if !i.recorded { 218 i.statusCode = code 219 i.recorded = true 220 } 221 i.ResponseWriter.WriteHeader(code) 222 } 223 224 func (i *interceptor) Hijack() (net.Conn, *bufio.ReadWriter, error) { 225 hj, ok := i.ResponseWriter.(http.Hijacker) 226 if !ok { 227 return nil, nil, fmt.Errorf("interceptor: can't cast parent ResponseWriter to Hijacker") 228 } 229 return hj.Hijack() 230 }