github.com/go-graphite/carbonapi@v0.17.0/expr/types/types.go (about)

     1  package types
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"math"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/go-graphite/carbonapi/expr/consolidations"
    13  	"github.com/go-graphite/carbonapi/expr/tags"
    14  	"github.com/go-graphite/carbonapi/expr/types/config"
    15  	pbv2 "github.com/go-graphite/protocol/carbonapi_v2_pb"
    16  	pb "github.com/go-graphite/protocol/carbonapi_v3_pb"
    17  	pickle "github.com/lomik/og-rek"
    18  )
    19  
    20  var (
    21  	// ErrWildcardNotAllowed is an eval error returned when a wildcard/glob argument is found where a single series is required.
    22  	ErrWildcardNotAllowed = errors.New("found wildcard where series expected")
    23  	// ErrTooManyArguments is an eval error returned when too many arguments are provided.
    24  	ErrTooManyArguments = errors.New("too many arguments")
    25  )
    26  
    27  // MetricData contains necessary data to represent parsed metric (ready to be send out or drawn)
    28  type MetricData struct {
    29  	pb.FetchResponse
    30  
    31  	GraphOptions
    32  
    33  	ValuesPerPoint    int
    34  	aggregatedValues  []float64
    35  	Tags              map[string]string
    36  	AggregateFunction func([]float64) float64 `json:"-"`
    37  }
    38  
    39  func appendInt2(b []byte, n int64) []byte {
    40  	if n > 9 {
    41  		return strconv.AppendInt(b, n, 10)
    42  	}
    43  	b = append(b, '0')
    44  	return strconv.AppendInt(b, n, 10)
    45  }
    46  
    47  // MarshalCSV marshals metric data to CSV
    48  func MarshalCSV(results []*MetricData) []byte {
    49  	if len(results) == 0 {
    50  		return []byte("[]")
    51  	}
    52  	n := len(results) * (len(results[0].Name) + len(results[0].PathExpression) + 128*len(results[0].Values) + 128)
    53  	b := make([]byte, 0, n)
    54  
    55  	for _, r := range results {
    56  
    57  		step := r.StepTime
    58  		t := r.StartTime
    59  		for _, v := range r.Values {
    60  			b = append(b, '"')
    61  			b = append(b, r.Name...)
    62  			b = append(b, `",`...)
    63  			tm := time.Unix(t, 0).UTC()
    64  			b = strconv.AppendInt(b, int64(tm.Year()), 10)
    65  			b = append(b, '-')
    66  			b = appendInt2(b, int64(tm.Month()))
    67  			b = append(b, '-')
    68  			b = appendInt2(b, int64(tm.Day()))
    69  			b = append(b, ' ')
    70  			b = appendInt2(b, int64(tm.Hour()))
    71  			b = append(b, ':')
    72  			b = appendInt2(b, int64(tm.Minute()))
    73  			b = append(b, ':')
    74  			b = appendInt2(b, int64(tm.Second()))
    75  			b = append(b, ',')
    76  			if !math.IsNaN(v) {
    77  				b = strconv.AppendFloat(b, v, 'f', -1, 64)
    78  			}
    79  			b = append(b, '\n')
    80  			t += step
    81  		}
    82  	}
    83  	return b
    84  }
    85  
    86  // ConsolidateJSON consolidates values to maxDataPoints size
    87  func ConsolidateJSON(maxDataPoints int64, results []*MetricData) {
    88  	if len(results) == 0 {
    89  		return
    90  	}
    91  	startTime := results[0].StartTime
    92  	endTime := results[0].StopTime
    93  	for _, r := range results {
    94  		t := r.StartTime
    95  		if startTime > t {
    96  			startTime = t
    97  		}
    98  		t = r.StopTime
    99  		if endTime < t {
   100  			endTime = t
   101  		}
   102  	}
   103  
   104  	timeRange := endTime - startTime
   105  
   106  	if timeRange <= 0 {
   107  		return
   108  	}
   109  
   110  	for _, r := range results {
   111  		numberOfDataPoints := math.Floor(float64(timeRange) / float64(r.StepTime))
   112  		if numberOfDataPoints > float64(maxDataPoints) {
   113  			valuesPerPoint := math.Ceil(numberOfDataPoints / float64(maxDataPoints))
   114  			r.SetValuesPerPoint(int(valuesPerPoint))
   115  		}
   116  	}
   117  }
   118  
   119  // MarshalJSON marshals metric data to JSON
   120  func MarshalJSON(results []*MetricData, timestampMultiplier int64, noNullPoints bool) []byte {
   121  	if len(results) == 0 {
   122  		return []byte("[]")
   123  	}
   124  	n := len(results) * (len(results[0].Name) + len(results[0].PathExpression) + 128*len(results[0].Values) + 128)
   125  
   126  	b := make([]byte, 0, n)
   127  	b = append(b, '[')
   128  
   129  	var topComma bool
   130  	for _, r := range results {
   131  		if r == nil {
   132  			continue
   133  		}
   134  
   135  		if topComma {
   136  			b = append(b, ',')
   137  		}
   138  		topComma = true
   139  
   140  		b = append(b, `{"target":`...)
   141  		b = strconv.AppendQuoteToASCII(b, r.Name)
   142  		b = append(b, `,"datapoints":[`...)
   143  
   144  		var innerComma bool
   145  		t := r.AggregatedStartTime() * timestampMultiplier
   146  		for _, v := range r.AggregatedValues() {
   147  			if noNullPoints && math.IsNaN(v) {
   148  				t += r.AggregatedTimeStep() * timestampMultiplier
   149  			} else {
   150  				if innerComma {
   151  					b = append(b, ',')
   152  				}
   153  				innerComma = true
   154  
   155  				b = append(b, '[')
   156  
   157  				if math.IsNaN(v) || math.IsInf(v, 1) || math.IsInf(v, -1) {
   158  					b = append(b, "null"...)
   159  				} else {
   160  					b = strconv.AppendFloat(b, v, 'f', -1, 64)
   161  				}
   162  
   163  				b = append(b, ',')
   164  
   165  				b = strconv.AppendInt(b, t, 10)
   166  
   167  				b = append(b, ']')
   168  
   169  				t += r.AggregatedTimeStep() * timestampMultiplier
   170  			}
   171  		}
   172  
   173  		b = append(b, `],"tags":{`...)
   174  		notFirstTag := false
   175  		responseTags := make([]string, 0, len(r.Tags))
   176  		for tag := range r.Tags {
   177  			responseTags = append(responseTags, tag)
   178  		}
   179  		sort.Strings(responseTags)
   180  		for _, tag := range responseTags {
   181  			v := r.Tags[tag]
   182  			if notFirstTag {
   183  				b = append(b, ',')
   184  			}
   185  			b = strconv.AppendQuoteToASCII(b, tag)
   186  			b = append(b, ':')
   187  			b = strconv.AppendQuoteToASCII(b, v)
   188  			notFirstTag = true
   189  		}
   190  
   191  		b = append(b, `}}`...)
   192  	}
   193  
   194  	b = append(b, ']')
   195  
   196  	return b
   197  }
   198  
   199  // MarshalPickle marshals metric data to pickle format
   200  func MarshalPickle(results []*MetricData) []byte {
   201  
   202  	var p []map[string]interface{}
   203  
   204  	for _, r := range results {
   205  		values := make([]interface{}, len(r.Values))
   206  		for i, v := range r.Values {
   207  			if math.IsNaN(v) {
   208  				values[i] = pickle.None{}
   209  			} else {
   210  				values[i] = v
   211  			}
   212  
   213  		}
   214  		p = append(p, map[string]interface{}{
   215  			"name":              r.Name,
   216  			"pathExpression":    r.PathExpression,
   217  			"consolidationFunc": r.ConsolidationFunc,
   218  			"start":             r.StartTime,
   219  			"end":               r.StopTime,
   220  			"step":              r.StepTime,
   221  			"xFilesFactor":      r.XFilesFactor,
   222  			"values":            values,
   223  		})
   224  	}
   225  
   226  	var buf bytes.Buffer
   227  
   228  	penc := pickle.NewEncoder(&buf)
   229  	_ = penc.Encode(p)
   230  
   231  	return buf.Bytes()
   232  }
   233  
   234  // MarshalProtobufV3 marshals metric data to protobuf
   235  func MarshalProtobufV2(results []*MetricData) ([]byte, error) {
   236  	response := pbv2.MultiFetchResponse{}
   237  	for _, metric := range results {
   238  		fmv3 := metric.FetchResponse
   239  		v := make([]float64, len(fmv3.Values))
   240  		isAbsent := make([]bool, len(fmv3.Values))
   241  		for i := range fmv3.Values {
   242  			if math.IsNaN(fmv3.Values[i]) {
   243  				v[i] = 0
   244  				isAbsent[i] = true
   245  			} else {
   246  				v[i] = fmv3.Values[i]
   247  			}
   248  		}
   249  		fm := pbv2.FetchResponse{
   250  			Name:      fmv3.Name,
   251  			StartTime: int32(fmv3.StartTime),
   252  			StopTime:  int32(fmv3.StopTime),
   253  			StepTime:  int32(fmv3.StepTime),
   254  			Values:    v,
   255  			IsAbsent:  isAbsent,
   256  		}
   257  		response.Metrics = append(response.Metrics, fm)
   258  	}
   259  	b, err := response.Marshal()
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	return b, nil
   265  }
   266  
   267  // MarshalProtobufV3 marshals metric data to protobuf
   268  func MarshalProtobufV3(results []*MetricData) ([]byte, error) {
   269  	response := pb.MultiFetchResponse{}
   270  	for _, metric := range results {
   271  		response.Metrics = append(response.Metrics, metric.FetchResponse)
   272  	}
   273  	b, err := response.Marshal()
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	return b, nil
   279  }
   280  
   281  // MarshalRaw marshals metric data to graphite's internal format, called 'raw'
   282  func MarshalRaw(results []*MetricData) []byte {
   283  	if len(results) == 0 {
   284  		return []byte{}
   285  	}
   286  	n := len(results) * (len(results[0].Name) + len(results[0].PathExpression) + 128*len(results[0].Values) + 128)
   287  	b := make([]byte, 0, n)
   288  
   289  	for _, r := range results {
   290  
   291  		b = append(b, r.Name...)
   292  
   293  		b = append(b, ',')
   294  		b = strconv.AppendInt(b, r.StartTime, 10)
   295  		b = append(b, ',')
   296  		b = strconv.AppendInt(b, r.StopTime, 10)
   297  		b = append(b, ',')
   298  		b = strconv.AppendInt(b, r.StepTime, 10)
   299  		b = append(b, '|')
   300  
   301  		var comma bool
   302  		for _, v := range r.Values {
   303  			if comma {
   304  				b = append(b, ',')
   305  			}
   306  			comma = true
   307  			if math.IsNaN(v) {
   308  				b = append(b, "None"...)
   309  			} else {
   310  				b = strconv.AppendFloat(b, v, 'f', -1, 64)
   311  			}
   312  		}
   313  
   314  		b = append(b, '\n')
   315  	}
   316  	return b
   317  }
   318  
   319  // SetValuesPerPoint sets value per point coefficient.
   320  func (r *MetricData) SetValuesPerPoint(v int) {
   321  	r.ValuesPerPoint = v
   322  	r.aggregatedValues = nil
   323  }
   324  
   325  // AggregatedTimeStep aggregates time step
   326  func (r *MetricData) AggregatedTimeStep() int64 {
   327  	if r.ValuesPerPoint == 1 || r.ValuesPerPoint == 0 {
   328  		return r.StepTime
   329  	}
   330  
   331  	return r.StepTime * int64(r.ValuesPerPoint)
   332  }
   333  
   334  // AggregatedStartTime returns the start time of the aggregated series.
   335  // This can be different from the original start time if NudgeStartTimeOnAggregation
   336  // or UseBucketsHighestTimestampOnAggregation are enabled.
   337  func (r *MetricData) AggregatedStartTime() int64 {
   338  	start := r.StartTime + r.nudgePointsCount()*r.StepTime
   339  	if config.Config.UseBucketsHighestTimestampOnAggregation {
   340  		return start + r.AggregatedTimeStep() - r.StepTime
   341  	}
   342  	return start
   343  }
   344  
   345  // nudgePointsCount returns the number of points to discard at the beginning of
   346  // the series when aggregating. This is done if NudgeStartTimeOnAggregation is
   347  // enabled, and has the purpose of assigning timestamps of a series to buckets
   348  // consistently across different time ranges. To simplify the aggregation
   349  // logic, we discard points at the beginning of the series so that a bucket
   350  // starts right at the beginning. This function calculates how many points to
   351  // discard.
   352  func (r *MetricData) nudgePointsCount() int64 {
   353  	if !config.Config.NudgeStartTimeOnAggregation {
   354  		return 0
   355  	}
   356  
   357  	if len(r.Values) <= 2*r.ValuesPerPoint {
   358  		// There would be less than 2 points after aggregation, removing one would be too impactful.
   359  		return 0
   360  	}
   361  
   362  	// Suppose r.StartTime=4, r.StepTime=3 and aggTimeStep=6.
   363  	// - ts:                       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 ...
   364  	// - original buckets:         -- -- --|        |        |        |        |     |   ...
   365  	// - aggregated buckets:       -- -- --|                 |                 |         ...
   366  
   367  	// We start counting our aggTimeStep buckets at absolute time r.StepTime.
   368  	// Notice the following:
   369  	// - ts:                       00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 ...
   370  	// - bucket #:                  -  -  -  1  1  1  1  1  1  2  2  2  2  2  2  3  3 ...
   371  	// - (ts-step) % aggTimeStep:   -  -  -  0  1  2  3  4  5  0  1  2  3  4  5  0  1 ...
   372  
   373  	// Given a timestamp 'ts', we can calculate how far it is from the beginning
   374  	// of the nearest bucket to the right by doing:
   375  	// * aggTimeStep - ((ts-r.StepTime) % aggTimeStep)
   376  	// Using this, we calculate the 'distance' from r.StartTime to the
   377  	// nearest bucket to the right. If this distance is less than aggTimeStep,
   378  	// then r.StartTime is not the beginning of a bucket. We need to discard
   379  	// dist / r.StepTime points (which could be zero if dist < r.StepTime).
   380  	aggTimeStep := r.AggregatedTimeStep()
   381  	dist := aggTimeStep - ((r.StartTime - r.StepTime) % aggTimeStep)
   382  	if dist < aggTimeStep {
   383  		return dist / r.StepTime
   384  	}
   385  	return 0
   386  }
   387  
   388  // GetAggregateFunction returns MetricData.AggregateFunction and set it, if it's not yet
   389  func (r *MetricData) GetAggregateFunction() func([]float64) float64 {
   390  	if r.AggregateFunction == nil {
   391  		var ok bool
   392  		if r.AggregateFunction, ok = consolidations.ConsolidationToFunc[strings.ToLower(r.ConsolidationFunc)]; !ok {
   393  			// if consolidation function is not known, we should fall back to average
   394  			r.AggregateFunction = consolidations.AvgValue
   395  		}
   396  	}
   397  
   398  	return r.AggregateFunction
   399  }
   400  
   401  // AggregatedValues aggregates values (with cache)
   402  func (r *MetricData) AggregatedValues() []float64 {
   403  	if r.aggregatedValues == nil {
   404  		r.AggregateValues()
   405  	}
   406  	return r.aggregatedValues
   407  }
   408  
   409  // AggregateValues aggregates values
   410  func (r *MetricData) AggregateValues() {
   411  	if r.ValuesPerPoint == 1 || r.ValuesPerPoint == 0 {
   412  		r.aggregatedValues = make([]float64, len(r.Values))
   413  		copy(r.aggregatedValues, r.Values)
   414  		return
   415  	}
   416  	aggFunc := r.GetAggregateFunction()
   417  
   418  	n := len(r.Values)/r.ValuesPerPoint + 1
   419  	aggV := make([]float64, 0, n)
   420  
   421  	nudgeCount := r.nudgePointsCount()
   422  	v := r.Values[nudgeCount:]
   423  
   424  	for len(v) >= r.ValuesPerPoint {
   425  		val := aggFunc(v[:r.ValuesPerPoint])
   426  		aggV = append(aggV, val)
   427  		v = v[r.ValuesPerPoint:]
   428  	}
   429  
   430  	if len(v) > 0 {
   431  		val := aggFunc(v)
   432  		aggV = append(aggV, val)
   433  	}
   434  
   435  	r.aggregatedValues = aggV
   436  }
   437  
   438  // Copy returns the copy of r. If includeValues set to true, it copies values as well.
   439  func (r *MetricData) Copy(includeValues bool) *MetricData {
   440  	var values, aggregatedValues []float64
   441  	values = make([]float64, 0)
   442  	appliedFunctions := make([]string, 0)
   443  	aggregatedValues = nil
   444  
   445  	if includeValues {
   446  		values = make([]float64, len(r.Values))
   447  		copy(values, r.Values)
   448  
   449  		if r.aggregatedValues != nil {
   450  			aggregatedValues = make([]float64, len(r.aggregatedValues))
   451  			copy(aggregatedValues, r.aggregatedValues)
   452  		}
   453  
   454  		appliedFunctions = make([]string, len(r.AppliedFunctions))
   455  		copy(appliedFunctions, r.AppliedFunctions)
   456  	}
   457  
   458  	tags := make(map[string]string)
   459  	for k, v := range r.Tags {
   460  		tags[k] = v
   461  	}
   462  
   463  	return &MetricData{
   464  		FetchResponse: pb.FetchResponse{
   465  			Name:                    r.Name,
   466  			PathExpression:          r.PathExpression,
   467  			ConsolidationFunc:       r.ConsolidationFunc,
   468  			StartTime:               r.StartTime,
   469  			StopTime:                r.StopTime,
   470  			StepTime:                r.StepTime,
   471  			XFilesFactor:            r.XFilesFactor,
   472  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   473  			Values:                  values,
   474  			AppliedFunctions:        appliedFunctions,
   475  			RequestStartTime:        r.RequestStartTime,
   476  			RequestStopTime:         r.RequestStopTime,
   477  		},
   478  		GraphOptions:      r.GraphOptions,
   479  		ValuesPerPoint:    r.ValuesPerPoint,
   480  		aggregatedValues:  aggregatedValues,
   481  		Tags:              tags,
   482  		AggregateFunction: r.AggregateFunction,
   483  	}
   484  }
   485  
   486  func CopyLink(tags map[string]string) map[string]string {
   487  	newTags := make(map[string]string)
   488  	for k, v := range tags {
   489  		newTags[k] = v
   490  	}
   491  	return newTags
   492  }
   493  
   494  // CopyLink returns the copy of MetricData, Values not copied and link from parent. Tags map are copied
   495  func (r *MetricData) CopyLink() *MetricData {
   496  	tags := CopyLink(r.Tags)
   497  
   498  	return &MetricData{
   499  		FetchResponse: pb.FetchResponse{
   500  			Name:                    r.Name,
   501  			PathExpression:          r.PathExpression,
   502  			ConsolidationFunc:       r.ConsolidationFunc,
   503  			StartTime:               r.StartTime,
   504  			StopTime:                r.StopTime,
   505  			StepTime:                r.StepTime,
   506  			XFilesFactor:            r.XFilesFactor,
   507  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   508  			Values:                  r.Values,
   509  			AppliedFunctions:        r.AppliedFunctions,
   510  			RequestStartTime:        r.RequestStartTime,
   511  			RequestStopTime:         r.RequestStopTime,
   512  		},
   513  		GraphOptions:      r.GraphOptions,
   514  		ValuesPerPoint:    r.ValuesPerPoint,
   515  		aggregatedValues:  r.aggregatedValues,
   516  		Tags:              tags,
   517  		AggregateFunction: r.AggregateFunction,
   518  	}
   519  }
   520  
   521  // CopyLinkTags returns the copy of MetricData, Values not copied and link from parent. Tags map set by rereference without copy (so, DON'T change them for prevent naming bugs)
   522  func (r *MetricData) CopyLinkTags() *MetricData {
   523  	return &MetricData{
   524  		FetchResponse: pb.FetchResponse{
   525  			Name:                    r.Name,
   526  			PathExpression:          r.PathExpression,
   527  			ConsolidationFunc:       r.ConsolidationFunc,
   528  			StartTime:               r.StartTime,
   529  			StopTime:                r.StopTime,
   530  			StepTime:                r.StepTime,
   531  			XFilesFactor:            r.XFilesFactor,
   532  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   533  			Values:                  r.Values,
   534  			AppliedFunctions:        r.AppliedFunctions,
   535  			RequestStartTime:        r.RequestStartTime,
   536  			RequestStopTime:         r.RequestStopTime,
   537  		},
   538  		GraphOptions:      r.GraphOptions,
   539  		ValuesPerPoint:    r.ValuesPerPoint,
   540  		aggregatedValues:  r.aggregatedValues,
   541  		Tags:              r.Tags,
   542  		AggregateFunction: r.AggregateFunction,
   543  	}
   544  }
   545  
   546  // CopyName returns the copy of MetricData, Values not copied and link from parent. If name set, Name and Name tag changed
   547  func (r *MetricData) CopyName(name string) *MetricData {
   548  	res := r.CopyLink()
   549  	res.Name = name
   550  	res.Tags["name"] = name
   551  
   552  	return res
   553  }
   554  
   555  // CopyNameWithDefault returns the copy of MetricData, Values not copied and link from parent. Name is changed, Tags will be reset.
   556  // If Name tag not set, it will be set with default value.
   557  // Use this function in aggregate function (like sumSeries)
   558  func (r *MetricData) CopyNameWithDefault(name, defaultName string) *MetricData {
   559  	if name == "" {
   560  		name = defaultName
   561  	}
   562  
   563  	tags := tags.ExtractTags(ExtractName(name))
   564  	if _, exist := tags["name"]; !exist {
   565  		tags["name"] = defaultName
   566  	}
   567  
   568  	return &MetricData{
   569  		FetchResponse: pb.FetchResponse{
   570  			Name:                    name,
   571  			PathExpression:          r.PathExpression,
   572  			ConsolidationFunc:       r.ConsolidationFunc,
   573  			StartTime:               r.StartTime,
   574  			StopTime:                r.StopTime,
   575  			StepTime:                r.StepTime,
   576  			XFilesFactor:            r.XFilesFactor,
   577  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   578  			Values:                  r.Values,
   579  			AppliedFunctions:        r.AppliedFunctions,
   580  			RequestStartTime:        r.RequestStartTime,
   581  			RequestStopTime:         r.RequestStopTime,
   582  		},
   583  		GraphOptions:      r.GraphOptions,
   584  		ValuesPerPoint:    r.ValuesPerPoint,
   585  		aggregatedValues:  r.aggregatedValues,
   586  		Tags:              tags,
   587  		AggregateFunction: r.AggregateFunction,
   588  	}
   589  }
   590  
   591  // CopyTag returns the copy of MetricData, Values not copied and link from parent. If name set, Name and Name tag changed, Tags will be reset.
   592  // WARNING: can provide inconsistence beetween name and tags, if incorectly used
   593  func (r *MetricData) CopyTag(name string, tags map[string]string) *MetricData {
   594  	if name == "" {
   595  		return r.CopyLink()
   596  	}
   597  
   598  	return &MetricData{
   599  		FetchResponse: pb.FetchResponse{
   600  			Name:                    name,
   601  			PathExpression:          r.PathExpression,
   602  			ConsolidationFunc:       r.ConsolidationFunc,
   603  			StartTime:               r.StartTime,
   604  			StopTime:                r.StopTime,
   605  			StepTime:                r.StepTime,
   606  			XFilesFactor:            r.XFilesFactor,
   607  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   608  			Values:                  r.Values,
   609  			AppliedFunctions:        r.AppliedFunctions,
   610  			RequestStartTime:        r.RequestStartTime,
   611  			RequestStopTime:         r.RequestStopTime,
   612  		},
   613  		GraphOptions:      r.GraphOptions,
   614  		ValuesPerPoint:    r.ValuesPerPoint,
   615  		aggregatedValues:  r.aggregatedValues,
   616  		Tags:              tags,
   617  		AggregateFunction: r.AggregateFunction,
   618  	}
   619  }
   620  
   621  // CopyNameArg returns the copy of MetricData, Values not copied and link from parent. Name is changed, tags extracted from seriesByTag args, if extractTags is true
   622  // For use in functions like aggregate
   623  func (r *MetricData) CopyNameArg(name, defaultName string, defaultTags map[string]string, extractTags bool) *MetricData {
   624  	if name == "" {
   625  		return r.CopyLink()
   626  	}
   627  
   628  	var tagsExtracted map[string]string
   629  	nameStiped := ExtractName(name)
   630  	if strings.HasPrefix(nameStiped, "seriesByTag(") {
   631  		if extractTags {
   632  			// from aggregation functions with seriesByTag
   633  			tagsExtracted = tags.ExtractSeriesByTags(nameStiped, defaultName)
   634  		} else {
   635  			tagsExtracted = defaultTags
   636  		}
   637  	} else {
   638  		tagsExtracted = tags.ExtractTags(nameStiped)
   639  	}
   640  
   641  	return &MetricData{
   642  		FetchResponse: pb.FetchResponse{
   643  			Name:                    name,
   644  			PathExpression:          r.PathExpression,
   645  			ConsolidationFunc:       r.ConsolidationFunc,
   646  			StartTime:               r.StartTime,
   647  			StopTime:                r.StopTime,
   648  			StepTime:                r.StepTime,
   649  			XFilesFactor:            r.XFilesFactor,
   650  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   651  			Values:                  r.Values,
   652  			AppliedFunctions:        r.AppliedFunctions,
   653  			RequestStartTime:        r.RequestStartTime,
   654  			RequestStopTime:         r.RequestStopTime,
   655  		},
   656  		GraphOptions:      r.GraphOptions,
   657  		ValuesPerPoint:    r.ValuesPerPoint,
   658  		aggregatedValues:  r.aggregatedValues,
   659  		Tags:              tagsExtracted,
   660  		AggregateFunction: r.AggregateFunction,
   661  	}
   662  }
   663  
   664  // CopyName returns the copy of MetricData, Values not copied and link from parent. If name set, Name and Name tag changed, Tags wil be reset
   665  func (r *MetricData) CopyNameWithVal(name string) *MetricData {
   666  	if name == "" {
   667  		return r.Copy(true)
   668  	}
   669  
   670  	values := make([]float64, len(r.Values))
   671  	copy(values, r.Values)
   672  
   673  	tags := tags.ExtractTags(ExtractName(name))
   674  
   675  	return &MetricData{
   676  		FetchResponse: pb.FetchResponse{
   677  			Name:                    name,
   678  			PathExpression:          r.PathExpression,
   679  			ConsolidationFunc:       r.ConsolidationFunc,
   680  			StartTime:               r.StartTime,
   681  			StopTime:                r.StopTime,
   682  			StepTime:                r.StepTime,
   683  			XFilesFactor:            r.XFilesFactor,
   684  			HighPrecisionTimestamps: r.HighPrecisionTimestamps,
   685  			Values:                  values,
   686  			AppliedFunctions:        r.AppliedFunctions,
   687  			RequestStartTime:        r.RequestStartTime,
   688  			RequestStopTime:         r.RequestStopTime,
   689  		},
   690  		GraphOptions:      r.GraphOptions,
   691  		ValuesPerPoint:    r.ValuesPerPoint,
   692  		aggregatedValues:  r.aggregatedValues,
   693  		Tags:              tags,
   694  		AggregateFunction: r.AggregateFunction,
   695  	}
   696  }
   697  
   698  // SetConsolidationFunc set ConsolidationFunc
   699  func (r *MetricData) SetConsolidationFunc(f string) *MetricData {
   700  	r.ConsolidationFunc = f
   701  	return r
   702  }
   703  
   704  // SetXFilesFactor set XFilesFactor
   705  func (r *MetricData) SetXFilesFactor(x float32) *MetricData {
   706  	r.XFilesFactor = x
   707  	return r
   708  }
   709  
   710  // AppendStopTime append to StopTime for simulate broken time series
   711  func (r *MetricData) AppendStopTime(step int64) *MetricData {
   712  	r.StopTime += step
   713  	return r
   714  }
   715  
   716  // FixStopTime fix broken StopTime (less than need for values)
   717  func (r *MetricData) FixStopTime() *MetricData {
   718  	stop := r.StartTime + int64(len(r.Values))*r.StepTime
   719  	if r.StopTime < stop {
   720  		r.StopTime = stop
   721  	}
   722  	return r
   723  }
   724  
   725  // SetNameTag set name tag
   726  func (r *MetricData) SetNameTag(name string) *MetricData {
   727  	r.Tags["name"] = name
   728  	return r
   729  }
   730  
   731  // FixNameTag for safe name tag for future use without metric.ExtractMetric
   732  func (r *MetricData) FixNameTag() *MetricData {
   733  	r.Tags["name"] = ExtractName(r.Tags["name"])
   734  	return r
   735  }
   736  
   737  // SetTag allow to set custom tag (for tests)
   738  func (r *MetricData) SetTag(key, value string) *MetricData {
   739  	r.Tags[key] = value
   740  	return r
   741  }
   742  
   743  // SetTag allow to set tags
   744  func (r *MetricData) SetTags(tags map[string]string) *MetricData {
   745  	r.Tags = tags
   746  	return r
   747  }
   748  
   749  // SetPathExpression set path expression
   750  func (r *MetricData) SetPathExpression(path string) *MetricData {
   751  	r.PathExpression = path
   752  	return r
   753  }
   754  
   755  // RecalcStopTime recalc StopTime with StartTime and Values length
   756  func (r *MetricData) RecalcStopTime() *MetricData {
   757  	stop := r.StartTime + int64(len(r.Values))*r.StepTime
   758  	if r.StopTime != stop {
   759  		r.StopTime = stop
   760  	}
   761  	return r
   762  }
   763  
   764  // CopyMetricDataSlice returns the slice of metrics that should be changed later.
   765  // It allows to avoid a changing of source data, e.g. by AlignMetrics
   766  func CopyMetricDataSlice(args []*MetricData) (newData []*MetricData) {
   767  	newData = make([]*MetricData, len(args))
   768  	for i, m := range args {
   769  		newData[i] = m.Copy(true)
   770  	}
   771  	return newData
   772  }
   773  
   774  // CopyMetricDataSliceLink returns the copies slice of metrics, Values not copied and link from parent.
   775  func CopyMetricDataSliceLink(args []*MetricData) (newData []*MetricData) {
   776  	newData = make([]*MetricData, len(args))
   777  	for i, m := range args {
   778  		newData[i] = m.CopyLink()
   779  	}
   780  	return newData
   781  }
   782  
   783  // CopyMetricDataSliceWithName returns the copies slice of metrics with name overwrite, Values not copied and link from parent. Tags will be reset
   784  func CopyMetricDataSliceWithName(args []*MetricData, name string) (newData []*MetricData) {
   785  	newData = make([]*MetricData, len(args))
   786  	for i, m := range args {
   787  		newData[i] = m.CopyName(name)
   788  	}
   789  	return newData
   790  }
   791  
   792  // CopyMetricDataSliceWithTags returns the copies slice of metrics with name overwrite, Values not copied and link from parent.
   793  func CopyMetricDataSliceWithTags(args []*MetricData, name string, tags map[string]string) (newData []*MetricData) {
   794  	newData = make([]*MetricData, len(args))
   795  	for i, m := range args {
   796  		newData[i] = m.CopyTag(name, tags)
   797  	}
   798  	return newData
   799  }
   800  
   801  // MakeMetricData creates new metrics data with given metric timeseries
   802  func MakeMetricData(name string, values []float64, step, start int64) *MetricData {
   803  	tags := tags.ExtractTags(ExtractName(name))
   804  	return makeMetricDataWithTags(name, values, step, start, tags).FixNameTag()
   805  }
   806  
   807  // MakeMetricDataWithTags creates new metrics data with given metric Time Series (with tags)
   808  func makeMetricDataWithTags(name string, values []float64, step, start int64, tags map[string]string) *MetricData {
   809  	stop := start + int64(len(values))*step
   810  
   811  	return &MetricData{
   812  		FetchResponse: pb.FetchResponse{
   813  			Name:      name,
   814  			Values:    values,
   815  			StartTime: start,
   816  			StepTime:  step,
   817  			StopTime:  stop,
   818  		},
   819  		Tags: tags,
   820  	}
   821  }