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

     1  package prometheus
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/go-graphite/carbonapi/zipper/protocols/prometheus/helpers"
    14  	prometheusTypes "github.com/go-graphite/carbonapi/zipper/protocols/prometheus/types"
    15  
    16  	"github.com/ansel1/merry"
    17  	protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb"
    18  
    19  	"github.com/go-graphite/carbonapi/limiter"
    20  	"github.com/go-graphite/carbonapi/zipper/helper"
    21  	"github.com/go-graphite/carbonapi/zipper/httpHeaders"
    22  	"github.com/go-graphite/carbonapi/zipper/metadata"
    23  	"github.com/go-graphite/carbonapi/zipper/types"
    24  
    25  	"go.uber.org/zap"
    26  )
    27  
    28  func init() {
    29  	aliases := []string{"prometheus"}
    30  	metadata.Metadata.Lock()
    31  	for _, name := range aliases {
    32  		metadata.Metadata.SupportedProtocols[name] = struct{}{}
    33  		metadata.Metadata.ProtocolInits[name] = New
    34  		metadata.Metadata.ProtocolInitsWithLimiter[name] = NewWithLimiter
    35  	}
    36  	defer metadata.Metadata.Unlock()
    37  }
    38  
    39  type StartDelay struct {
    40  	IsSet      bool
    41  	IsDuration bool
    42  	D          time.Duration
    43  	T          int64
    44  	S          string
    45  }
    46  
    47  func (s *StartDelay) String() string {
    48  	if s.IsDuration {
    49  		return strconv.FormatInt(time.Now().Add(s.D).Unix(), 10)
    50  	}
    51  	if s.S == "" {
    52  		s.S = strconv.FormatInt(s.T, 10)
    53  	}
    54  	return s.S
    55  }
    56  
    57  // RoundRobin is used to connect to backends inside clientGroups, implements BackendServer interface
    58  type PrometheusGroup struct {
    59  	groupName string
    60  	servers   []string
    61  	protocol  string
    62  
    63  	client *http.Client
    64  
    65  	limiter              limiter.ServerLimiter
    66  	logger               *zap.Logger
    67  	timeout              types.Timeouts
    68  	maxTries             int
    69  	maxMetricsPerRequest int
    70  
    71  	step                 int64
    72  	maxPointsPerQuery    int64
    73  	forceMinStepInterval time.Duration
    74  
    75  	startDelay StartDelay
    76  
    77  	httpQuery *helper.HttpQuery
    78  }
    79  
    80  func NewWithLimiter(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool, limiter limiter.ServerLimiter) (types.BackendServer, merry.Error) {
    81  	logger = logger.With(zap.String("type", "prometheus"), zap.String("protocol", config.Protocol), zap.String("name", config.GroupName))
    82  
    83  	logger.Warn("support for this backend protocol is experimental, use with caution")
    84  	httpClient := helper.GetHTTPClient(logger, config)
    85  
    86  	step := int64(15)
    87  	stepI, ok := config.BackendOptions["step"]
    88  	if ok {
    89  		stepNew, ok := stepI.(string)
    90  		if ok {
    91  			if stepNew[len(stepNew)-1] >= '0' && stepNew[len(stepNew)-1] <= '9' {
    92  				stepNew += "s"
    93  			}
    94  			t, err := time.ParseDuration(stepNew)
    95  			if err != nil {
    96  				logger.Fatal("failed to parse option",
    97  					zap.String("option_name", "step"),
    98  					zap.String("option_value", stepNew),
    99  					zap.Error(err),
   100  				)
   101  			}
   102  			step = int64(t.Seconds())
   103  		} else {
   104  			logger.Fatal("failed to parse step",
   105  				zap.String("type_parsed", fmt.Sprintf("%T", stepI)),
   106  				zap.String("type_expected", "string"),
   107  			)
   108  		}
   109  	}
   110  
   111  	maxPointsPerQuery := int64(11000)
   112  	mppqI, ok := config.BackendOptions["max_points_per_query"]
   113  	if ok {
   114  		mppq, ok := mppqI.(int)
   115  		if !ok {
   116  			logger.Fatal("failed to parse max_points_per_query",
   117  				zap.String("type_parsed", fmt.Sprintf("%T", mppqI)),
   118  				zap.String("type_expected", "int"),
   119  			)
   120  		}
   121  
   122  		maxPointsPerQuery = int64(mppq)
   123  	}
   124  
   125  	var forceMinStepInterval time.Duration
   126  	fmsiI, ok := config.BackendOptions["force_min_step_interval"]
   127  	if ok {
   128  		fmsiS, ok := fmsiI.(string)
   129  		if !ok {
   130  			logger.Fatal("failed to parse force_min_step_interval",
   131  				zap.String("type_parsed", fmt.Sprintf("%T", fmsiI)),
   132  				zap.String("type_expected", "time.Duration"),
   133  			)
   134  		}
   135  		var err error
   136  		forceMinStepInterval, err = time.ParseDuration(fmsiS)
   137  		if err != nil {
   138  			logger.Fatal("failed to parse force_min_step_interval",
   139  				zap.String("value_provided", fmsiS),
   140  				zap.String("type_expected", "time.Duration"),
   141  			)
   142  		}
   143  	}
   144  
   145  	delay := StartDelay{
   146  		IsSet:      false,
   147  		IsDuration: false,
   148  		T:          -1,
   149  	}
   150  	startI, ok := config.BackendOptions["start"]
   151  	if ok {
   152  		delay.IsSet = true
   153  		startNew, ok := startI.(string)
   154  		if ok {
   155  			startNewInt, err := strconv.Atoi(startNew)
   156  			if err != nil {
   157  				d, err2 := time.ParseDuration(startNew)
   158  				if err2 != nil {
   159  					logger.Fatal("failed to parse option",
   160  						zap.String("option_name", "start"),
   161  						zap.String("option_value", startNew),
   162  						zap.Errors("errors", []error{err, err2}),
   163  					)
   164  				}
   165  				delay.IsDuration = true
   166  				delay.D = d
   167  			} else {
   168  				delay.T = int64(startNewInt)
   169  			}
   170  		}
   171  	}
   172  
   173  	httpQuery := helper.NewHttpQuery(config.GroupName, config.Servers, *config.MaxTries, limiter, httpClient, httpHeaders.ContentTypeCarbonAPIv2PB)
   174  
   175  	return NewWithEverythingInitialized(logger, config, tldCacheDisabled, requireSuccessAll, limiter, step, maxPointsPerQuery, forceMinStepInterval, delay, httpQuery, httpClient)
   176  }
   177  
   178  func NewWithEverythingInitialized(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool, limiter limiter.ServerLimiter, step, maxPointsPerQuery int64, forceMinStepInterval time.Duration, delay StartDelay, httpQuery *helper.HttpQuery, httpClient *http.Client) (types.BackendServer, merry.Error) {
   179  	c := &PrometheusGroup{
   180  		groupName:            config.GroupName,
   181  		servers:              config.Servers,
   182  		protocol:             config.Protocol,
   183  		timeout:              *config.Timeouts,
   184  		maxTries:             *config.MaxTries,
   185  		maxMetricsPerRequest: *config.MaxBatchSize,
   186  		step:                 step,
   187  		forceMinStepInterval: forceMinStepInterval,
   188  		maxPointsPerQuery:    maxPointsPerQuery,
   189  		startDelay:           delay,
   190  
   191  		client:  httpClient,
   192  		limiter: limiter,
   193  		logger:  logger,
   194  
   195  		httpQuery: httpQuery,
   196  	}
   197  	return c, nil
   198  }
   199  
   200  func New(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool) (types.BackendServer, merry.Error) {
   201  	if config.ConcurrencyLimit == nil {
   202  		return nil, types.ErrConcurrencyLimitNotSet
   203  	}
   204  	if len(config.Servers) == 0 {
   205  		return nil, types.ErrNoServersSpecified
   206  	}
   207  	l := limiter.NewServerLimiter([]string{config.GroupName}, *config.ConcurrencyLimit)
   208  
   209  	return NewWithLimiter(logger, config, tldCacheDisabled, requireSuccessAll, l)
   210  }
   211  
   212  func (c *PrometheusGroup) Children() []types.BackendServer {
   213  	return []types.BackendServer{c}
   214  }
   215  
   216  func (c PrometheusGroup) MaxMetricsPerRequest() int {
   217  	return c.maxMetricsPerRequest
   218  }
   219  
   220  func (c PrometheusGroup) Name() string {
   221  	return c.groupName
   222  }
   223  
   224  func (c PrometheusGroup) Backends() []string {
   225  	return c.servers
   226  }
   227  
   228  func (c *PrometheusGroup) Fetch(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) {
   229  	logger := c.logger.With(zap.String("type", "fetch"), zap.String("request", request.String()))
   230  	stats := &types.Stats{}
   231  	rewrite, _ := url.Parse("http://127.0.0.1/api/v1/query_range")
   232  
   233  	pathExprToTargets := make(map[string][]string)
   234  	for _, m := range request.Metrics {
   235  		targets := pathExprToTargets[m.PathExpression]
   236  		pathExprToTargets[m.PathExpression] = append(targets, m.Name)
   237  	}
   238  
   239  	var r protov3.MultiFetchResponse
   240  	var e merry.Error
   241  
   242  	start := request.Metrics[0].StartTime
   243  	stop := request.Metrics[0].StopTime
   244  
   245  	maxPointsPerQuery := c.maxPointsPerQuery
   246  	if len(request.Metrics) > 0 && request.Metrics[0].MaxDataPoints != 0 {
   247  		maxPointsPerQuery = request.Metrics[0].MaxDataPoints
   248  	}
   249  	step := helpers.AdjustStep(start, stop, maxPointsPerQuery, c.step, c.forceMinStepInterval)
   250  
   251  	stepStr := strconv.FormatInt(step, 10)
   252  	for pathExpr, targets := range pathExprToTargets {
   253  		for _, target := range targets {
   254  			logger.Debug("got some target to query",
   255  				zap.Any("pathExpr", pathExpr),
   256  				zap.Any("target", target),
   257  			)
   258  			// rewrite metric for Tag
   259  			// Make local copy
   260  			stepLocalStr := stepStr
   261  			if strings.HasPrefix(target, "seriesByTag") {
   262  				stepLocalStr, target = helpers.SeriesByTagToPromQL(stepLocalStr, target)
   263  			} else {
   264  				reQuery := helpers.ConvertGraphiteTargetToPromQL(target)
   265  				target = fmt.Sprintf("{__name__=~%q}", reQuery)
   266  			}
   267  			if stepLocalStr[len(stepLocalStr)-1] >= '0' && stepLocalStr[len(stepLocalStr)-1] <= '9' {
   268  				stepLocalStr += "s"
   269  			}
   270  			t, err := time.ParseDuration(stepLocalStr)
   271  			if err != nil {
   272  				stats.RenderErrors++
   273  				logger.Debug("failed to parse step",
   274  					zap.String("step", stepLocalStr),
   275  					zap.Error(err),
   276  				)
   277  				if e == nil {
   278  					e = merry.Wrap(err)
   279  				}
   280  				continue
   281  			}
   282  			stepLocal := int64(t.Seconds())
   283  			/*
   284  				newStep, err3 := strToStep(stepStr)
   285  				if err3 == nil {
   286  					step = newStep
   287  				}
   288  			*/
   289  			logger.Debug("will do query",
   290  				zap.String("query", target),
   291  				zap.Int64("start", start),
   292  				zap.Int64("stop", stop),
   293  				zap.String("step", stepLocalStr),
   294  			)
   295  			v := url.Values{
   296  				"query": []string{target},
   297  				"start": []string{strconv.Itoa(int(start))},
   298  				"end":   []string{strconv.Itoa(int(stop))},
   299  				"step":  []string{stepLocalStr},
   300  			}
   301  
   302  			rewrite.RawQuery = v.Encode()
   303  			stats.RenderRequests++
   304  			res, err2 := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   305  			if err2 != nil {
   306  				stats.RenderErrors++
   307  				if merry.Is(err, types.ErrTimeoutExceeded) {
   308  					stats.Timeouts++
   309  					stats.RenderTimeouts++
   310  				}
   311  				if e == nil {
   312  					e = err2
   313  				} else {
   314  					e = e.WithCause(err2)
   315  				}
   316  				continue
   317  			}
   318  
   319  			var response prometheusTypes.HTTPResponse
   320  			err = json.Unmarshal(res.Response, &response)
   321  			if err != nil {
   322  				stats.RenderErrors++
   323  				c.logger.Debug("failed to unmarshal response",
   324  					zap.Error(err),
   325  				)
   326  				if e == nil {
   327  					e = err2
   328  				} else {
   329  					e = e.WithCause(err2)
   330  				}
   331  				continue
   332  			}
   333  
   334  			if response.Status != "success" {
   335  				stats.RenderErrors++
   336  				if e == nil {
   337  					e = types.ErrFailedToFetch.WithMessage(response.Status).WithValue("query", target).WithValue("status", response.Status)
   338  				} else {
   339  					e = e.WithCause(err2).WithValue("query", target).WithValue("status", response.Status)
   340  				}
   341  				continue
   342  			}
   343  
   344  			for _, m := range response.Data.Result {
   345  				// We always should trust backend's response (to mimic behavior of graphite for grahpite native protoocols)
   346  				// See https://github.com/go-graphite/carbonapi/issues/504 and https://github.com/go-graphite/carbonapi/issues/514
   347  				realStart := start
   348  				realStop := stop
   349  				if len(m.Values) > 0 {
   350  					realStart = int64(m.Values[0].Timestamp)
   351  					realStop = int64(m.Values[len(m.Values)-1].Timestamp)
   352  				}
   353  				alignedValues := helpers.AlignValues(realStart, realStop, stepLocal, m.Values)
   354  
   355  				r.Metrics = append(r.Metrics, protov3.FetchResponse{
   356  					Name:              helpers.PromMetricToGraphite(m.Metric),
   357  					PathExpression:    pathExpr,
   358  					ConsolidationFunc: "Average",
   359  					StartTime:         realStart,
   360  					StopTime:          realStop,
   361  					StepTime:          stepLocal,
   362  					Values:            alignedValues,
   363  					XFilesFactor:      0.0,
   364  				})
   365  			}
   366  		}
   367  	}
   368  
   369  	if e != nil {
   370  		stats.FailedServers = []string{c.groupName}
   371  		logger.Error("errors occurred while getting results",
   372  			zap.Any("errors", e),
   373  		)
   374  		return &r, stats, e
   375  	}
   376  	return &r, stats, nil
   377  }
   378  
   379  func (c *PrometheusGroup) Find(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.MultiGlobResponse, *types.Stats, merry.Error) {
   380  	logger := c.logger.With(zap.String("type", "find"), zap.Strings("request", request.Metrics))
   381  	stats := &types.Stats{}
   382  	rewrite, _ := url.Parse("http://127.0.0.1/api/v1/series")
   383  
   384  	r := protov3.MultiGlobResponse{
   385  		Metrics: make([]protov3.GlobResponse, 0),
   386  	}
   387  	var e merry.Error
   388  	uniqueMetrics := make(map[string]bool)
   389  	for _, query := range request.Metrics {
   390  		// Convert query to Prometheus-compatible regex
   391  		if !strings.HasSuffix(query, "*") {
   392  			query = query + "*"
   393  		}
   394  
   395  		reQuery := helpers.ConvertGraphiteTargetToPromQL(query)
   396  		matchQuery := fmt.Sprintf("{__name__=~%q}", reQuery)
   397  		v := url.Values{
   398  			"match[]": []string{matchQuery},
   399  		}
   400  
   401  		if c.startDelay.IsSet {
   402  			v.Add("start", c.startDelay.String())
   403  		}
   404  
   405  		rewrite.RawQuery = v.Encode()
   406  		stats.FindRequests += 1
   407  		res, err := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   408  		if err != nil {
   409  			stats.FindErrors += 1
   410  			if merry.Is(err, types.ErrTimeoutExceeded) {
   411  				stats.Timeouts += 1
   412  				stats.FindTimeouts += 1
   413  			}
   414  			if e == nil {
   415  				e = err
   416  			} else {
   417  				e = e.WithCause(err)
   418  			}
   419  			continue
   420  		}
   421  
   422  		var pr prometheusTypes.PrometheusFindResponse
   423  
   424  		err2 := json.Unmarshal(res.Response, &pr)
   425  		if err2 != nil {
   426  			stats.FindErrors += 1
   427  			if e == nil {
   428  				e = err
   429  			} else {
   430  				e = e.WithCause(err)
   431  			}
   432  			continue
   433  		}
   434  
   435  		if pr.Status != "success" {
   436  			stats.FindErrors += 1
   437  			if e == nil {
   438  				e = types.ErrFailedToFetch.WithMessage(pr.Error).WithValue("query", matchQuery).WithValue("error_type", pr.ErrorType).WithValue("error", pr.Error)
   439  			} else {
   440  				e = e.WithCause(err2).WithValue("query", matchQuery).WithValue("error_type", pr.ErrorType).WithValue("error", pr.Error)
   441  			}
   442  			continue
   443  		}
   444  
   445  		querySplit := strings.Split(query, ".")
   446  		resp := protov3.GlobResponse{
   447  			Name:    query,
   448  			Matches: make([]protov3.GlobMatch, 0),
   449  		}
   450  		for _, m := range pr.Data {
   451  			name, ok := m["__name__"]
   452  			if !ok {
   453  				continue
   454  			}
   455  			nameSplit := strings.Split(name, ".")
   456  
   457  			if len(querySplit) > len(nameSplit) {
   458  				continue
   459  			}
   460  
   461  			isLeaf := false
   462  			if len(nameSplit) == len(querySplit) {
   463  				isLeaf = true
   464  			}
   465  
   466  			uniqueMetrics[strings.Join(nameSplit[:len(querySplit)], ".")] = isLeaf
   467  		}
   468  
   469  		for k, v := range uniqueMetrics {
   470  			resp.Matches = append(resp.Matches, protov3.GlobMatch{
   471  				IsLeaf: v,
   472  				Path:   k,
   473  			})
   474  			r.Metrics = append(r.Metrics, resp)
   475  		}
   476  	}
   477  
   478  	if e != nil {
   479  		stats.FailedServers = []string{c.groupName}
   480  		logger.Error("errors occurred while getting results",
   481  			zap.Any("errors", e),
   482  		)
   483  		return &r, stats, e
   484  	}
   485  	return &r, stats, nil
   486  }
   487  
   488  func (c *PrometheusGroup) Info(ctx context.Context, request *protov3.MultiMetricsInfoRequest) (*protov3.ZipperInfoResponse, *types.Stats, merry.Error) {
   489  	return nil, nil, types.ErrNotSupportedByBackend
   490  }
   491  
   492  func (c *PrometheusGroup) List(ctx context.Context) (*protov3.ListMetricsResponse, *types.Stats, merry.Error) {
   493  	return nil, nil, types.ErrNotImplementedYet
   494  }
   495  func (c *PrometheusGroup) Stats(ctx context.Context) (*protov3.MetricDetailsResponse, *types.Stats, merry.Error) {
   496  	return nil, nil, types.ErrNotSupportedByBackend
   497  }
   498  
   499  func (c *PrometheusGroup) doSimpleTagQuery(ctx context.Context, logger *zap.Logger, isTagName bool, params map[string][]string, limit int64) ([]string, merry.Error) {
   500  	var rewrite *url.URL
   501  
   502  	if isTagName {
   503  		logger = logger.With(zap.String("type", "tagName"))
   504  		rewrite, _ = url.Parse("http://127.0.0.1/api/v1/labels")
   505  	} else {
   506  		logger = logger.With(zap.String("type", "tagValues"))
   507  		if tag, ok := params["Tag"]; ok {
   508  			rewrite, _ = url.Parse(fmt.Sprintf("http://127.0.0.1/api/v1/label/%s/values", tag[0]))
   509  		} else {
   510  			return []string{}, types.ErrNoTagSpecified
   511  		}
   512  	}
   513  
   514  	var r prometheusTypes.PrometheusTagResponse
   515  
   516  	res, e := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   517  	if e != nil {
   518  		return []string{}, e
   519  	}
   520  
   521  	err := json.Unmarshal(res.Response, &r)
   522  	if err != nil {
   523  		return []string{}, merry.Wrap(err)
   524  	}
   525  
   526  	if r.Status != "success" {
   527  		return []string{}, merry.New("request returned an error").WithValue("status", r.Status).WithValue("error_type", r.ErrorType).WithValue("error", r.Error)
   528  	}
   529  
   530  	if isTagName {
   531  		if v, ok := params["tagPrefix"]; ok {
   532  			data := make([]string, 0)
   533  			for _, t := range r.Data {
   534  				if strings.HasPrefix(t, v[0]) {
   535  					data = append(data, t)
   536  				}
   537  			}
   538  			r.Data = data
   539  		}
   540  	} else {
   541  		if v, ok := params["valuePrefix"]; ok {
   542  			data := make([]string, 0)
   543  			for _, t := range r.Data {
   544  				if strings.HasPrefix(t, v[0]) {
   545  					data = append(data, t)
   546  				}
   547  			}
   548  			r.Data = data
   549  		}
   550  	}
   551  
   552  	if limit > 0 && len(r.Data) > int(limit) {
   553  		r.Data = r.Data[:int(limit)]
   554  	}
   555  
   556  	logger.Debug("got client response",
   557  		zap.Any("result", r),
   558  	)
   559  
   560  	return r.Data, nil
   561  }
   562  
   563  func (c *PrometheusGroup) doComplexTagQuery(ctx context.Context, isTagName bool, params map[string][]string, limit int64) ([]string, merry.Error) {
   564  	logger := c.logger
   565  	var rewrite *url.URL
   566  
   567  	if isTagName {
   568  		logger = logger.With(zap.String("type", "tagName"))
   569  	} else {
   570  		logger = logger.With(zap.String("type", "tagValues"))
   571  		if _, ok := params["Tag"]; !ok {
   572  			return []string{}, types.ErrNoTagSpecified
   573  		}
   574  	}
   575  
   576  	matches := make([]string, 0, len(params["expr"]))
   577  	for _, e := range params["expr"] {
   578  		name, t := helpers.PromethizeTagValue(e)
   579  		matches = append(matches, "{"+name+t.OP+"\""+t.TagValue+"\"}")
   580  	}
   581  
   582  	rewrite, _ = url.Parse("http://127.0.0.1/api/v1/series")
   583  	v := url.Values{
   584  		"match[]": matches,
   585  	}
   586  	rewrite.RawQuery = v.Encode()
   587  
   588  	result := make([]string, 0)
   589  	var r prometheusTypes.PrometheusFindResponse
   590  
   591  	res, e := c.httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), nil)
   592  	if e != nil {
   593  		return []string{}, e
   594  	}
   595  
   596  	err := json.Unmarshal(res.Response, &r)
   597  	if err != nil {
   598  		return []string{}, merry.Wrap(err)
   599  	}
   600  
   601  	if r.Status != "success" {
   602  		return []string{}, merry.New("request returned an error").WithValue("status", r.Status).WithValue("error_type", r.ErrorType).WithValue("error", r.Error)
   603  	}
   604  
   605  	var prefix string
   606  	if isTagName {
   607  		if prefixArr, ok := params["tagPrefix"]; ok {
   608  			prefix = prefixArr[0]
   609  		}
   610  
   611  		uniqueTagNames := make(map[string]struct{})
   612  		for _, d := range r.Data {
   613  			for k := range d {
   614  				if strings.HasPrefix(k, prefix) {
   615  					uniqueTagNames[k] = struct{}{}
   616  				}
   617  			}
   618  		}
   619  		for k := range uniqueTagNames {
   620  			result = append(result, k)
   621  		}
   622  	} else {
   623  		if prefixArr, ok := params["valuePrefix"]; ok {
   624  			prefix = prefixArr[0]
   625  		}
   626  
   627  		uniqueTagValues := make(map[string]struct{})
   628  		tag := params["Tag"][0]
   629  		for _, d := range r.Data {
   630  			if v, ok := d[tag]; ok {
   631  				if strings.HasPrefix(v, prefix) {
   632  					uniqueTagValues[v] = struct{}{}
   633  				}
   634  			}
   635  		}
   636  		for v := range uniqueTagValues {
   637  			result = append(result, v)
   638  		}
   639  	}
   640  
   641  	if limit > 0 && len(result) > int(limit) {
   642  		result = result[:int(limit)]
   643  	}
   644  
   645  	logger.Debug("got client response",
   646  		zap.Any("result", result),
   647  	)
   648  
   649  	return result, nil
   650  }
   651  
   652  func (c *PrometheusGroup) doTagQuery(ctx context.Context, isTagName bool, query string, limit int64) ([]string, merry.Error) {
   653  	logger := c.logger
   654  	params := make(map[string][]string)
   655  	queryDecoded, _ := url.QueryUnescape(query)
   656  	querySplit := strings.Split(queryDecoded, "&")
   657  	for _, qvRaw := range querySplit {
   658  		idx := strings.Index(qvRaw, "=")
   659  		//no parameters passed
   660  		if idx < 1 {
   661  			continue
   662  		}
   663  		k := qvRaw[:idx]
   664  		v := qvRaw[idx+1:]
   665  		if v2, ok := params[qvRaw[:idx]]; !ok {
   666  			params[k] = []string{v}
   667  		} else {
   668  			v2 = append(v2, v)
   669  			params[k] = v2
   670  		}
   671  	}
   672  	logger.Debug("doTagQuery",
   673  		zap.Any("query", queryDecoded),
   674  		zap.Any("params", params),
   675  	)
   676  
   677  	if _, ok := params["expr"]; !ok {
   678  		return c.doSimpleTagQuery(ctx, logger, isTagName, params, limit)
   679  	}
   680  
   681  	return c.doComplexTagQuery(ctx, isTagName, params, limit)
   682  }
   683  
   684  func (c *PrometheusGroup) TagNames(ctx context.Context, query string, limit int64) ([]string, merry.Error) {
   685  	return c.doTagQuery(ctx, true, query, limit)
   686  }
   687  
   688  func (c *PrometheusGroup) TagValues(ctx context.Context, query string, limit int64) ([]string, merry.Error) {
   689  	return c.doTagQuery(ctx, false, query, limit)
   690  }
   691  
   692  func (c *PrometheusGroup) ProbeTLDs(ctx context.Context) ([]string, merry.Error) {
   693  	logger := c.logger.With(zap.String("function", "prober"))
   694  	req := &protov3.MultiGlobRequest{
   695  		Metrics: []string{"*"},
   696  	}
   697  
   698  	logger.Debug("doing request",
   699  		zap.Strings("request", req.Metrics),
   700  	)
   701  
   702  	res, _, err := c.Find(ctx, req)
   703  	if err != nil {
   704  		return nil, err
   705  	}
   706  
   707  	var tlds []string
   708  	for _, m := range res.Metrics {
   709  		for _, v := range m.Matches {
   710  			tlds = append(tlds, v.Path)
   711  		}
   712  	}
   713  
   714  	logger.Debug("will return data",
   715  		zap.Strings("tlds", tlds),
   716  	)
   717  
   718  	return tlds, nil
   719  }