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

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