github.com/go-graphite/carbonapi@v0.17.0/zipper/protocols/victoriametrics/fetch.go (about) 1 package victoriametrics 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/url" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/ansel1/merry" 13 "github.com/go-graphite/carbonapi/zipper/protocols/prometheus/helpers" 14 prometheusTypes "github.com/go-graphite/carbonapi/zipper/protocols/prometheus/types" 15 "github.com/go-graphite/carbonapi/zipper/types" 16 protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb" 17 "go.uber.org/zap" 18 ) 19 20 type fetchTarget struct { 21 name string 22 start int64 23 stop int64 24 step string 25 } 26 27 func (c *VictoriaMetricsGroup) Fetch(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) { 28 supportedFeatures, _ := c.featureSet.Load().(*vmSupportedFeatures) 29 if !supportedFeatures.SupportOptimizedGraphiteFetch { 30 // VictoriaMetrics <1.53.1 doesn't support graphite find api, reverting back to prometheus code-path 31 return c.BackendServer.Fetch(ctx, request) 32 } 33 logger := c.logger.With(zap.String("type", "fetch"), zap.String("request", request.String())) 34 stats := &types.Stats{} 35 var serverUrl string 36 if len(c.vmClusterTenantID) > 0 { 37 serverUrl = fmt.Sprintf("http://127.0.0.1/select/%s/prometheus/api/v1/query_range", c.vmClusterTenantID) 38 } else { 39 serverUrl = "http://127.0.0.1/api/v1/query_range" 40 } 41 rewrite, _ := url.Parse(serverUrl) 42 43 pathExprToTargets := make(map[string][]*fetchTarget) 44 for _, m := range request.Metrics { 45 var maxPointsPerQuery int64 46 if m.MaxDataPoints != 0 { 47 maxPointsPerQuery = m.MaxDataPoints 48 } else { 49 maxPointsPerQuery = c.maxPointsPerQuery 50 } 51 52 step := helpers.AdjustStep(m.StartTime, m.StopTime, maxPointsPerQuery, c.step, c.forceMinStepInterval) 53 stepStr := strconv.FormatInt(step, 10) 54 55 t := &fetchTarget{ 56 name: m.Name, 57 start: m.StartTime, 58 stop: m.StopTime, 59 step: stepStr, 60 } 61 targets := pathExprToTargets[m.PathExpression] 62 pathExprToTargets[m.PathExpression] = append(targets, t) 63 } 64 65 var r protov3.MultiFetchResponse 66 var e merry.Error 67 68 for pathExpr, targets := range pathExprToTargets { 69 for _, target := range targets { 70 logger.Debug("got some target to query", 71 zap.Any("pathExpr", pathExpr), 72 zap.Any("target", target.name), 73 ) 74 // rewrite metric for Tag 75 // Make local copy 76 stepLocalStr := target.step 77 if strings.HasPrefix(target.name, "seriesByTag") { 78 target.name = strings.ReplaceAll(target.name, "'name=", "'__name__=") 79 stepLocalStr, target.name = helpers.SeriesByTagToPromQL(stepLocalStr, target.name) 80 } else { 81 target.name = fmt.Sprintf("{__graphite__=%q}", target.name) 82 } 83 if stepLocalStr[len(stepLocalStr)-1] >= '0' && stepLocalStr[len(stepLocalStr)-1] <= '9' { 84 stepLocalStr += "s" 85 } 86 t, err := time.ParseDuration(stepLocalStr) 87 if err != nil { 88 stats.RenderErrors += 1 89 logger.Debug("failed to parse step", 90 zap.String("step", stepLocalStr), 91 zap.Error(err), 92 ) 93 if e == nil { 94 e = merry.Wrap(err) 95 } 96 continue 97 } 98 stepLocal := int64(t.Seconds()) 99 100 logger.Debug("will do query", 101 zap.String("query", target.name), 102 zap.Int64("start", target.start), 103 zap.Int64("stop", target.stop), 104 zap.String("step", stepLocalStr), 105 zap.String("max_lookback", stepLocalStr), 106 ) 107 v := url.Values{ 108 "query": []string{target.name}, 109 "start": []string{strconv.Itoa(int(target.start))}, 110 "end": []string{strconv.Itoa(int(target.stop))}, 111 "step": []string{stepLocalStr}, 112 "max_lookback": []string{stepLocalStr}, 113 } 114 115 rewrite.RawQuery = v.Encode() 116 stats.RenderRequests++ 117 res, err2 := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil) 118 if err2 != nil { 119 stats.RenderErrors++ 120 if merry.Is(err, types.ErrTimeoutExceeded) { 121 stats.Timeouts++ 122 stats.RenderTimeouts++ 123 } 124 if e == nil { 125 e = err2 126 } else { 127 e = e.WithCause(err2) 128 } 129 continue 130 } 131 132 var response prometheusTypes.HTTPResponse 133 err = json.Unmarshal(res.Response, &response) 134 if err != nil { 135 stats.RenderErrors += 1 136 c.logger.Debug("failed to unmarshal response", 137 zap.Error(err), 138 ) 139 if e == nil { 140 e = err2 141 } else { 142 e = e.WithCause(err2) 143 } 144 continue 145 } 146 147 if response.Status != "success" { 148 stats.RenderErrors += 1 149 if e == nil { 150 e = types.ErrFailedToFetch.WithMessage(response.Status).WithValue("query", target.name).WithValue("status", response.Status) 151 } else { 152 e = e.WithCause(err2).WithValue("query", target.name).WithValue("status", response.Status) 153 } 154 continue 155 } 156 157 for _, m := range response.Data.Result { 158 // We always should trust backend's response (to mimic behavior of graphite for grahpite native protoocols) 159 // See https://github.com/go-graphite/carbonapi/issues/504 and https://github.com/go-graphite/carbonapi/issues/514 160 realStart := target.start 161 realStop := target.stop 162 if len(m.Values) > 0 { 163 realStart = int64(m.Values[0].Timestamp) 164 realStop = int64(m.Values[len(m.Values)-1].Timestamp) 165 } 166 alignedValues := helpers.AlignValues(realStart, realStop, stepLocal, m.Values) 167 168 r.Metrics = append(r.Metrics, protov3.FetchResponse{ 169 Name: helpers.PromMetricToGraphite(m.Metric), 170 PathExpression: pathExpr, 171 ConsolidationFunc: "Average", 172 StartTime: realStart, 173 StopTime: realStop, 174 StepTime: stepLocal, 175 Values: alignedValues, 176 XFilesFactor: 0.0, 177 RequestStartTime: target.start, 178 RequestStopTime: target.stop, 179 }) 180 } 181 } 182 } 183 184 if e != nil { 185 stats.FailedServers = []string{c.groupName} 186 logger.Error("errors occurred while getting results", 187 zap.Any("errors", e), 188 ) 189 return &r, stats, e 190 } 191 return &r, stats, nil 192 }