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  }