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

     1  package helpers
     2  
     3  import (
     4  	"math"
     5  	"regexp"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/go-graphite/carbonapi/zipper/protocols/prometheus/types"
    11  )
    12  
    13  // ConvertGraphiteTargetToPromQL - converts graphite target string to PromQL friendly format
    14  func ConvertGraphiteTargetToPromQL(query string) string {
    15  	var sb strings.Builder
    16  
    17  	for {
    18  		n := strings.IndexAny(query, "*[{")
    19  		if n < 0 {
    20  			sb.WriteString(regexp.QuoteMeta(query))
    21  			return sb.String()
    22  		}
    23  
    24  		sb.WriteString(regexp.QuoteMeta(query[:n]))
    25  		ch := query[n]
    26  		query = query[n+1:]
    27  
    28  		switch ch {
    29  		case '*':
    30  			if query == "" {
    31  				// needed to support find requests when asterisk is the last character and dots should be included
    32  				sb.WriteString(".*")
    33  				break
    34  			}
    35  
    36  			sb.WriteString("[^.]*?")
    37  
    38  		case '[':
    39  			n = strings.Index(query, "]")
    40  			if n < 0 {
    41  				sb.WriteString(regexp.QuoteMeta("[" + query))
    42  				return sb.String()
    43  			}
    44  			sb.WriteString("[" + query[:n+1])
    45  			query = query[n+1:]
    46  
    47  		case '{':
    48  			n = strings.Index(query, "}")
    49  			if n < 0 {
    50  				sb.WriteString(regexp.QuoteMeta("{" + query))
    51  				return sb.String()
    52  			}
    53  			alts := strings.Split(query[:n], ",")
    54  			query = query[n+1:]
    55  			for i := range alts {
    56  				alts[i] = regexp.QuoteMeta(alts[i])
    57  			}
    58  			sb.WriteString("(" + strings.Join(alts, "|") + ")")
    59  		}
    60  	}
    61  }
    62  
    63  // AlignValues inserts math.NaN() in place of gaps in data from Prometheus
    64  func AlignValues(startTime, stopTime, step int64, promValues []types.Value) []float64 {
    65  	var (
    66  		promValuesCtr = 0
    67  		resValues     = make([]float64, (stopTime-startTime)/step+1)
    68  	)
    69  
    70  	for i := range resValues {
    71  		nextTimestamp := float64(startTime + int64(i)*step)
    72  
    73  		if promValuesCtr < len(promValues) && promValues[promValuesCtr].Timestamp == nextTimestamp {
    74  			resValues[i] = promValues[promValuesCtr].Value
    75  			promValuesCtr++
    76  			continue
    77  		}
    78  
    79  		resValues[i] = math.NaN()
    80  	}
    81  
    82  	return resValues
    83  }
    84  
    85  // AdjustStep adjusts step keeping in mind default/configurable limit of maximum points per query
    86  // Steps sequence is aligned with Grafana. Step progresses in the following order:
    87  // minimal configured step if not default => 20 => 30 => 60 => 120 => 300 => 600 => 900 => 1200 => 1800 => 3600 => 7200 => 10800 => 21600 => 43200 => 86400
    88  func AdjustStep(start, stop, maxPointsPerQuery, minStep int64, forceMinStepInterval time.Duration) int64 {
    89  
    90  	interval := float64(stop - start)
    91  
    92  	if forceMinStepInterval.Seconds() > interval {
    93  		return minStep
    94  	}
    95  
    96  	safeStep := int64(math.Ceil(interval / float64(maxPointsPerQuery)))
    97  
    98  	step := minStep
    99  	if safeStep > minStep {
   100  		step = safeStep
   101  	}
   102  
   103  	switch {
   104  	case step <= minStep:
   105  		return minStep // minimal configured step
   106  	case step <= 20:
   107  		return 20 // 20s
   108  	case step <= 30:
   109  		return 30 // 30s
   110  	case step <= 60:
   111  		return 60 // 1m
   112  	case step <= 120:
   113  		return 120 // 2m
   114  	case step <= 300:
   115  		return 300 // 5m
   116  	case step <= 600:
   117  		return 600 // 10m
   118  	case step <= 900:
   119  		return 900 // 15m
   120  	case step <= 1200:
   121  		return 1200 // 20m
   122  	case step <= 1800:
   123  		return 1800 // 30m
   124  	case step <= 3600:
   125  		return 3600 // 1h
   126  	case step <= 7200:
   127  		return 7200 // 2h
   128  	case step <= 10800:
   129  		return 10800 // 3h
   130  	case step <= 21600:
   131  		return 21600 // 6h
   132  	case step <= 43200:
   133  		return 43200 // 12h
   134  	default:
   135  		return 86400 // 24h
   136  	}
   137  }
   138  
   139  // PromethizeTagValue - accept 'Tag=value' or 'Tag=~value' string and return sanitized version of it
   140  func PromethizeTagValue(tagValue string) (string, types.Tag) {
   141  	// Handle = and =~
   142  	var (
   143  		t       types.Tag
   144  		tagName string
   145  		idx     = strings.Index(tagValue, "=")
   146  	)
   147  
   148  	if idx < 0 {
   149  		return tagName, t
   150  	}
   151  
   152  	if idx > 0 && tagValue[idx-1] == '!' {
   153  		t.OP = "!"
   154  		tagName = tagValue[:idx-1]
   155  	} else {
   156  		tagName = tagValue[:idx]
   157  	}
   158  
   159  	switch {
   160  	case idx+1 == len(tagValue): // != or = with empty value
   161  		t.OP += "="
   162  	case tagValue[idx+1] == '~':
   163  		if len(t.OP) > 0 { // !=~
   164  			t.OP += "~"
   165  		} else { // =~
   166  			t.OP = "=~"
   167  		}
   168  
   169  		if idx+2 < len(tagValue) { // check is not empty value
   170  			t.TagValue = tagValue[idx+2:]
   171  		}
   172  	default: // != or = with value
   173  		t.OP += "="
   174  		t.TagValue = tagValue[idx+1:]
   175  	}
   176  
   177  	return tagName, t
   178  }
   179  
   180  // SplitTagValues - For given tag-value list converts it to more usable map[string]Tag, where string is TagName
   181  func SplitTagValues(query string) map[string]types.Tag {
   182  	tags := strings.Split(query, ",")
   183  	result := make(map[string]types.Tag)
   184  	for _, tvString := range tags {
   185  		tvString = strings.TrimSpace(tvString)
   186  		name, tag := PromethizeTagValue(tvString[1 : len(tvString)-1])
   187  		result[name] = tag
   188  	}
   189  	return result
   190  }
   191  
   192  // PromMetricToGraphite converts prometheus metric name to a format expected by graphite
   193  func PromMetricToGraphite(metric map[string]string) string {
   194  	var res strings.Builder
   195  
   196  	res.WriteString(metric["__name__"])
   197  	delete(metric, "__name__")
   198  
   199  	keys := make([]string, 0, len(metric))
   200  	for k := range metric {
   201  		keys = append(keys, k)
   202  	}
   203  
   204  	sort.Strings(keys)
   205  
   206  	for _, k := range keys {
   207  		res.WriteString(";" + k + "=" + metric[k])
   208  	}
   209  
   210  	return res.String()
   211  }
   212  
   213  // SeriesByTagToPromQL converts graphite SeriesByTag to PromQL
   214  // will return step if __step__ is passed
   215  func SeriesByTagToPromQL(step, target string) (string, string) {
   216  	firstTag := true
   217  	var queryBuilder strings.Builder
   218  	tagsString := target[len("seriesByTag(") : len(target)-1]
   219  	tvs := SplitTagValues(tagsString)
   220  	// It's ok to have empty "__name__"
   221  	if v, ok := tvs["__name__"]; ok {
   222  		if v.OP == "=" {
   223  			queryBuilder.WriteString(v.TagValue)
   224  		} else {
   225  			firstTag = false
   226  			queryBuilder.WriteByte('{')
   227  			queryBuilder.WriteString("__name__" + v.OP + "\"" + v.TagValue + "\"")
   228  		}
   229  
   230  		delete(tvs, "__name__")
   231  	}
   232  	for tagName, t := range tvs {
   233  		if tagName == "__step__" {
   234  			step = t.TagValue
   235  			continue
   236  		}
   237  		if firstTag {
   238  			firstTag = false
   239  			queryBuilder.WriteByte('{')
   240  			queryBuilder.WriteString(tagName + t.OP + "\"" + t.TagValue + "\"")
   241  		} else {
   242  			queryBuilder.WriteString(", " + tagName + t.OP + "\"" + t.TagValue + "\"")
   243  		}
   244  	}
   245  	if !firstTag {
   246  		queryBuilder.WriteByte('}')
   247  	}
   248  	return step, queryBuilder.String()
   249  }