github.com/go-graphite/carbonapi@v0.17.0/zipper/protocols/v2/protobuf_group.go (about)

     1  package v2
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"math"
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  
    11  	"github.com/ansel1/merry"
    12  
    13  	protov2 "github.com/go-graphite/protocol/carbonapi_v2_pb"
    14  	protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb"
    15  
    16  	"github.com/go-graphite/carbonapi/limiter"
    17  	utilctx "github.com/go-graphite/carbonapi/util/ctx"
    18  	"github.com/go-graphite/carbonapi/zipper/helper"
    19  	"github.com/go-graphite/carbonapi/zipper/httpHeaders"
    20  	"github.com/go-graphite/carbonapi/zipper/metadata"
    21  	"github.com/go-graphite/carbonapi/zipper/types"
    22  
    23  	"go.uber.org/zap"
    24  )
    25  
    26  const (
    27  	format = "protobuf"
    28  )
    29  
    30  func init() {
    31  	aliases := []string{"carbonapi_v2_pb", "proto_v2_pb", "v2_pb", "pb", "pb3", "protobuf", "protobuf3"}
    32  	metadata.Metadata.Lock()
    33  	for _, name := range aliases {
    34  		metadata.Metadata.SupportedProtocols[name] = struct{}{}
    35  		metadata.Metadata.ProtocolInits[name] = New
    36  		metadata.Metadata.ProtocolInitsWithLimiter[name] = NewWithLimiter
    37  	}
    38  	defer metadata.Metadata.Unlock()
    39  }
    40  
    41  // RoundRobin is used to connect to backends inside clientGroups, implements BackendServer interface
    42  type ClientProtoV2Group struct {
    43  	groupName string
    44  	servers   []string
    45  
    46  	client *http.Client
    47  
    48  	limiter              limiter.ServerLimiter
    49  	logger               *zap.Logger
    50  	timeout              types.Timeouts
    51  	maxTries             int
    52  	maxMetricsPerRequest int
    53  
    54  	httpQuery *helper.HttpQuery
    55  }
    56  
    57  func (c *ClientProtoV2Group) Children() []types.BackendServer {
    58  	return []types.BackendServer{c}
    59  }
    60  
    61  func NewWithLimiter(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool, l limiter.ServerLimiter) (types.BackendServer, merry.Error) {
    62  	logger = logger.With(zap.String("type", "protoV2Group"), zap.String("name", config.GroupName))
    63  
    64  	httpClient := helper.GetHTTPClient(logger, config)
    65  
    66  	httpLimiter := limiter.NewServerLimiter(config.Servers, *config.ConcurrencyLimit)
    67  	httpQuery := helper.NewHttpQuery(config.GroupName, config.Servers, *config.MaxTries, httpLimiter, httpClient, httpHeaders.ContentTypeCarbonAPIv2PB)
    68  
    69  	c := &ClientProtoV2Group{
    70  		groupName:            config.GroupName,
    71  		servers:              config.Servers,
    72  		timeout:              *config.Timeouts,
    73  		maxTries:             *config.MaxTries,
    74  		maxMetricsPerRequest: *config.MaxBatchSize,
    75  
    76  		client:  httpClient,
    77  		limiter: l,
    78  		logger:  logger,
    79  
    80  		httpQuery: httpQuery,
    81  	}
    82  	return c, nil
    83  }
    84  
    85  func New(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool) (types.BackendServer, merry.Error) {
    86  	if config.ConcurrencyLimit == nil {
    87  		return nil, types.ErrConcurrencyLimitNotSet
    88  	}
    89  	if len(config.Servers) == 0 {
    90  		return nil, types.ErrNoServersSpecified
    91  	}
    92  	limiter := limiter.NewServerLimiter(config.Servers, *config.ConcurrencyLimit)
    93  
    94  	return NewWithLimiter(logger, config, tldCacheDisabled, requireSuccessAll, limiter)
    95  }
    96  
    97  func (c ClientProtoV2Group) MaxMetricsPerRequest() int {
    98  	return c.maxMetricsPerRequest
    99  }
   100  
   101  func (c ClientProtoV2Group) Name() string {
   102  	return c.groupName
   103  }
   104  
   105  func (c ClientProtoV2Group) Backends() []string {
   106  	return c.servers
   107  }
   108  
   109  type queryBatch struct {
   110  	pathExpression string
   111  	from           int64
   112  	until          int64
   113  }
   114  
   115  func (c *ClientProtoV2Group) Fetch(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) {
   116  	logger := c.logger.With(zap.String("type", "fetch"), zap.String("request", request.String()), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   117  	stats := &types.Stats{}
   118  	rewrite, _ := url.Parse("http://127.0.0.1/render/")
   119  
   120  	batches := make(map[queryBatch][]string)
   121  	for _, m := range request.Metrics {
   122  		b := queryBatch{
   123  			pathExpression: m.PathExpression,
   124  			from:           m.StartTime,
   125  			until:          m.StopTime,
   126  		}
   127  
   128  		batches[b] = append(batches[b], m.Name)
   129  	}
   130  
   131  	var r protov3.MultiFetchResponse
   132  	var e merry.Error
   133  	for batch, targets := range batches {
   134  		v := url.Values{
   135  			"target": targets,
   136  			"format": []string{format},
   137  			"from":   []string{strconv.Itoa(int(batch.from))},
   138  			"until":  []string{strconv.Itoa(int(batch.until))},
   139  		}
   140  		rewrite.RawQuery = v.Encode()
   141  		stats.RenderRequests += 1
   142  		res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   143  		if err != nil {
   144  			stats.RenderErrors += 1
   145  			if merry.Is(err, types.ErrTimeoutExceeded) {
   146  				stats.Timeouts += 1
   147  				stats.RenderTimeouts += 1
   148  			}
   149  			if e == nil {
   150  				e = err
   151  			} else {
   152  				e = e.WithCause(err)
   153  			}
   154  			continue
   155  		}
   156  
   157  		var metrics protov2.MultiFetchResponse
   158  		marshalErr := metrics.Unmarshal(res.Response)
   159  		if marshalErr != nil {
   160  			stats.RenderErrors += 1
   161  			if e == nil {
   162  				e = types.ErrUnmarshalFailed.WithCause(marshalErr)
   163  			} else {
   164  				e = e.WithCause(marshalErr)
   165  			}
   166  			continue
   167  		}
   168  
   169  		for _, m := range metrics.Metrics {
   170  			for i, v := range m.IsAbsent {
   171  				if v {
   172  					m.Values[i] = math.NaN()
   173  				}
   174  			}
   175  			r.Metrics = append(r.Metrics, protov3.FetchResponse{
   176  				Name:              m.Name,
   177  				PathExpression:    batch.pathExpression,
   178  				ConsolidationFunc: "Average",
   179  				StopTime:          int64(m.StopTime),
   180  				StartTime:         int64(m.StartTime),
   181  				StepTime:          int64(m.StepTime),
   182  				Values:            m.Values,
   183  				XFilesFactor:      0.0,
   184  				RequestStartTime:  batch.from,
   185  				RequestStopTime:   batch.until,
   186  			})
   187  		}
   188  	}
   189  
   190  	if e != nil {
   191  		logger.Warn("errors occurred while getting results",
   192  			zap.Any("errors", e),
   193  		)
   194  		return &r, stats, e
   195  	}
   196  	return &r, stats, nil
   197  }
   198  
   199  func (c *ClientProtoV2Group) Find(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.MultiGlobResponse, *types.Stats, merry.Error) {
   200  	logger := c.logger.With(zap.String("type", "find"), zap.Strings("request", request.Metrics), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   201  	stats := &types.Stats{}
   202  	rewrite, _ := url.Parse("http://127.0.0.1/metrics/find/")
   203  
   204  	var r protov3.MultiGlobResponse
   205  	r.Metrics = make([]protov3.GlobResponse, 0)
   206  	var e merry.Error
   207  	for _, query := range request.Metrics {
   208  		logger.Debug("will do query",
   209  			zap.String("query", query),
   210  		)
   211  		v := url.Values{
   212  			"query":  []string{query},
   213  			"format": []string{format},
   214  		}
   215  		rewrite.RawQuery = v.Encode()
   216  		stats.FindRequests += 1
   217  		res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   218  		if err != nil {
   219  			stats.FindErrors += 1
   220  			if merry.Is(err, types.ErrTimeoutExceeded) {
   221  				stats.Timeouts += 1
   222  				stats.FindTimeouts += 1
   223  			}
   224  			if e == nil {
   225  				e = err
   226  			} else {
   227  				e = e.WithCause(err)
   228  			}
   229  			continue
   230  		}
   231  		var globs protov2.GlobResponse
   232  		marshalErr := globs.Unmarshal(res.Response)
   233  		if marshalErr != nil {
   234  			stats.FindErrors += 1
   235  			if e == nil {
   236  				e = types.ErrUnmarshalFailed.WithCause(marshalErr)
   237  			} else {
   238  				e = e.WithCause(marshalErr)
   239  			}
   240  			continue
   241  		}
   242  		stats.Servers = append(stats.Servers, res.Server)
   243  		matches := make([]protov3.GlobMatch, 0, len(globs.Matches))
   244  		for _, m := range globs.Matches {
   245  			matches = append(matches, protov3.GlobMatch{
   246  				Path:   m.Path,
   247  				IsLeaf: m.IsLeaf,
   248  			})
   249  		}
   250  		if len(matches) != 0 {
   251  			r.Metrics = append(r.Metrics, protov3.GlobResponse{
   252  				Name:    globs.Name,
   253  				Matches: matches,
   254  			})
   255  		}
   256  	}
   257  
   258  	if e != nil {
   259  		logger.Warn("errors occurred while getting results",
   260  			zap.Any("errors", e),
   261  		)
   262  		return nil, stats, e
   263  	}
   264  	return &r, stats, nil
   265  }
   266  
   267  func (c *ClientProtoV2Group) Info(ctx context.Context, request *protov3.MultiMetricsInfoRequest) (*protov3.ZipperInfoResponse, *types.Stats, merry.Error) {
   268  	logger := c.logger.With(zap.String("type", "info"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   269  	stats := &types.Stats{}
   270  	rewrite, _ := url.Parse("http://127.0.0.1/info/")
   271  
   272  	var r protov3.ZipperInfoResponse
   273  	var e merry.Error
   274  	r.Info = make(map[string]protov3.MultiMetricsInfoResponse)
   275  	data := protov3.MultiMetricsInfoResponse{}
   276  	server := c.groupName
   277  	if len(c.servers) == 1 {
   278  		server = c.servers[0]
   279  	}
   280  
   281  	for _, query := range request.Names {
   282  		v := url.Values{
   283  			"target": []string{query},
   284  			"format": []string{format},
   285  		}
   286  		rewrite.RawQuery = v.Encode()
   287  		stats.InfoRequests += 1
   288  		res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   289  		if err != nil {
   290  			stats.InfoErrors += 1
   291  			if merry.Is(err, types.ErrTimeoutExceeded) {
   292  				stats.Timeouts += 1
   293  				stats.InfoTimeouts += 1
   294  			}
   295  			if e == nil {
   296  				e = err
   297  			} else {
   298  				e = e.WithCause(err)
   299  			}
   300  			continue
   301  		}
   302  
   303  		var info protov2.InfoResponse
   304  		marshalErr := info.Unmarshal(res.Response)
   305  		if marshalErr != nil {
   306  			stats.InfoErrors += 1
   307  			if e == nil {
   308  				e = types.ErrUnmarshalFailed.WithCause(marshalErr)
   309  			} else {
   310  				e = e.WithCause(marshalErr)
   311  			}
   312  			continue
   313  		}
   314  		stats.Servers = append(stats.Servers, res.Server)
   315  
   316  		if info.AggregationMethod == "" {
   317  			info.AggregationMethod = "average"
   318  		}
   319  		infoV3 := protov3.MetricsInfoResponse{
   320  			Name:              info.Name,
   321  			ConsolidationFunc: info.AggregationMethod,
   322  			XFilesFactor:      info.XFilesFactor,
   323  			MaxRetention:      int64(info.MaxRetention),
   324  		}
   325  
   326  		for _, r := range info.Retentions {
   327  			newR := protov3.Retention{
   328  				SecondsPerPoint: int64(r.SecondsPerPoint),
   329  				NumberOfPoints:  int64(r.NumberOfPoints),
   330  			}
   331  			infoV3.Retentions = append(infoV3.Retentions, newR)
   332  		}
   333  
   334  		data.Metrics = append(data.Metrics, infoV3)
   335  	}
   336  	r.Info[server] = data
   337  
   338  	if e != nil {
   339  		logger.Warn("errors occurred while getting results",
   340  			zap.Any("errors", e),
   341  		)
   342  		return &r, stats, e
   343  	}
   344  
   345  	logger.Debug("got client response",
   346  		zap.Any("response", r),
   347  	)
   348  
   349  	return &r, stats, nil
   350  }
   351  
   352  func (c *ClientProtoV2Group) doTagQuery(ctx context.Context, isTagName bool, query string, limit int64) ([]string, merry.Error) {
   353  	logger := c.logger
   354  	var rewrite *url.URL
   355  	if isTagName {
   356  		logger = logger.With(zap.String("type", "tagName"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   357  		rewrite, _ = url.Parse("http://127.0.0.1/tags/autoComplete/tags")
   358  	} else {
   359  		logger = logger.With(zap.String("type", "tagValues"), zap.String("carbonapi_uuid", utilctx.GetUUID(ctx)))
   360  		rewrite, _ = url.Parse("http://127.0.0.1/tags/autoComplete/values")
   361  	}
   362  
   363  	var r []string
   364  
   365  	rewrite.RawQuery = query
   366  	res, e := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   367  	if e != nil {
   368  		return r, e
   369  	}
   370  
   371  	err := json.Unmarshal(res.Response, &r)
   372  	if err != nil {
   373  		return r, merry.Wrap(err)
   374  	}
   375  
   376  	logger.Debug("got client response",
   377  		zap.Strings("response", r),
   378  	)
   379  
   380  	return r, nil
   381  }
   382  
   383  func (c *ClientProtoV2Group) TagNames(ctx context.Context, query string, limit int64) ([]string, merry.Error) {
   384  	return c.doTagQuery(ctx, true, query, limit)
   385  }
   386  
   387  func (c *ClientProtoV2Group) TagValues(ctx context.Context, query string, limit int64) ([]string, merry.Error) {
   388  	return c.doTagQuery(ctx, false, query, limit)
   389  }
   390  
   391  func (c *ClientProtoV2Group) List(ctx context.Context) (*protov3.ListMetricsResponse, *types.Stats, merry.Error) {
   392  	return nil, nil, types.ErrNotImplementedYet
   393  }
   394  func (c *ClientProtoV2Group) Stats(ctx context.Context) (*protov3.MetricDetailsResponse, *types.Stats, merry.Error) {
   395  	return nil, nil, types.ErrNotImplementedYet
   396  }
   397  
   398  func (c *ClientProtoV2Group) ProbeTLDs(ctx context.Context) ([]string, merry.Error) {
   399  	logger := c.logger.With(zap.String("function", "prober"))
   400  	req := &protov3.MultiGlobRequest{
   401  		Metrics: []string{"*"},
   402  	}
   403  
   404  	logger.Debug("doing request",
   405  		zap.Strings("request", req.Metrics),
   406  	)
   407  
   408  	res, _, err := c.Find(ctx, req)
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  
   413  	var tlds []string
   414  	for _, m := range res.Metrics {
   415  		for _, v := range m.Matches {
   416  			tlds = append(tlds, v.Path)
   417  		}
   418  	}
   419  
   420  	logger.Debug("will return data",
   421  		zap.Strings("tlds", tlds),
   422  	)
   423  
   424  	return tlds, nil
   425  }