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

     1  package tags
     2  
     3  import (
     4  	"strings"
     5  	"unicode/utf8"
     6  )
     7  
     8  type ParseStep int8
     9  
    10  const (
    11  	WantTag ParseStep = iota
    12  	WantCmp
    13  	WantDelim
    14  )
    15  
    16  // . (dot) is a special symbol in regexp, so if user use =~ instead of =, name with dots a transformed (and also can produce unwanted results)
    17  // use \. for consistent results
    18  // `.*` prepended to regexp without ^ at start
    19  // `.*` appended to regexp without $ at end
    20  func sanitizeRegex(s string) string {
    21  	var result strings.Builder
    22  	result.Grow(len(s) + 20)
    23  
    24  	if strings.HasPrefix(s, "^") {
    25  		s = s[1:]
    26  	} else {
    27  		result.WriteString("__*")
    28  	}
    29  	var closedRegex bool
    30  	if strings.HasSuffix(s, "$") {
    31  		s = s[0 : len(s)-1]
    32  		closedRegex = true
    33  	}
    34  	// cleanup regexp
    35  	s = strings.TrimRight(s, ".*")
    36  
    37  	var prev rune
    38  	for _, c := range s {
    39  		switch c {
    40  		case '.':
    41  			if prev != '\\' {
    42  				result.WriteString("__")
    43  			} else {
    44  				result.WriteRune(c)
    45  			}
    46  		default:
    47  			result.WriteRune(c)
    48  		}
    49  		prev = c
    50  	}
    51  
    52  	if !closedRegex {
    53  		result.WriteString("__*")
    54  	}
    55  	return result.String()
    56  }
    57  
    58  // parse seriesByTag args
    59  func ExtractSeriesByTags(s, defaultName string) map[string]string {
    60  	if s == "" || len(s) < 13 {
    61  		return nil
    62  	}
    63  	s = s[12:]
    64  
    65  	tags := make(map[string]string)
    66  
    67  	startTag := 0
    68  	startVal := 0
    69  	step := WantTag
    70  	var (
    71  		i, w  int
    72  		c     rune
    73  		delim rune
    74  	)
    75  LOOP:
    76  	for i < len(s) {
    77  		c, w = utf8.DecodeRuneInString(s[i:])
    78  		switch c {
    79  		case ',':
    80  			if step == WantDelim {
    81  				step = WantTag
    82  			}
    83  			i++
    84  		case ')':
    85  			if step == WantTag || step == WantDelim {
    86  				break LOOP
    87  			}
    88  			i++
    89  		case '\'', '"':
    90  			if step == WantTag {
    91  				// new segment found
    92  				step = WantCmp
    93  				delim = c
    94  				startTag = i + 1
    95  			} else {
    96  				step = WantDelim
    97  			}
    98  			i++
    99  		case '=', '!', '~':
   100  			var add bool
   101  			var isRegex bool
   102  			if step == WantCmp {
   103  				tag := s[startTag:i]
   104  				if tag == "__name__" {
   105  					tag = "name"
   106  				}
   107  				p := s[i:]
   108  				if strings.HasPrefix(p, "!=~") {
   109  					isRegex = true
   110  					i += 3
   111  				} else if strings.HasPrefix(p, "!=") {
   112  					i += 2
   113  				} else if strings.HasPrefix(p, "=~") {
   114  					isRegex = true
   115  					add = true
   116  					i += 2
   117  				} else if strings.HasPrefix(p, "=") {
   118  					add = true
   119  					i++
   120  				} else {
   121  					i += w
   122  					// broken comparator, skip
   123  					continue
   124  				}
   125  				startVal = i
   126  				end := strings.IndexRune(s[startVal:], delim)
   127  				if add && tag != "" && end > 0 {
   128  					var v string
   129  					if isRegex {
   130  						v = sanitizeRegex(s[startVal : startVal+end])
   131  					} else {
   132  						v = s[startVal : startVal+end]
   133  					}
   134  					tags[tag] = v
   135  				}
   136  				step = WantDelim
   137  				i = startVal + end + 1
   138  			} else {
   139  				i += w
   140  			}
   141  		default:
   142  			i += w
   143  		}
   144  	}
   145  
   146  	if _, exist := tags["name"]; !exist {
   147  		tags["name"] = defaultName
   148  	}
   149  
   150  	return tags
   151  }
   152  
   153  // ExtractTags extracts all graphite-style tags out of metric name
   154  // E.x. cpu.usage_idle;cpu=cpu-total;host=test => {"name": "cpu.usage_idle", "cpu": "cpu-total", "host": "test"}
   155  // There are some differences between how we handle tags and how graphite-web can do that. In our case it is possible
   156  // to have empty value as it doesn't make sense to skip tag in that case but can be potentially useful
   157  // Also we do not fail on invalid cases, but rather than silently skipping broken tags as some backends might accept
   158  // invalid tag and store it and one of the purposes of carbonapi is to keep working even if backends gives us slightly
   159  // broken replies.
   160  func ExtractTags(s string) map[string]string {
   161  	result := make(map[string]string)
   162  	idx := strings.IndexRune(s, ';')
   163  	if idx < 0 {
   164  		result["name"] = s
   165  		return result
   166  	}
   167  
   168  	result["name"] = s[:idx]
   169  
   170  	newS := s[idx+1:]
   171  	for {
   172  		idx := strings.IndexRune(newS, ';')
   173  		if idx < 0 {
   174  			firstEqualSignIdx := strings.IndexRune(newS, '=')
   175  			// tag starts with `=` sign or have zero length
   176  			if newS == "" || firstEqualSignIdx == 0 {
   177  				break
   178  			}
   179  			// tag doesn't have = sign at all
   180  			if firstEqualSignIdx == -1 {
   181  				result[newS] = ""
   182  				break
   183  			}
   184  
   185  			result[newS[:firstEqualSignIdx]] = newS[firstEqualSignIdx+1:]
   186  			break
   187  		}
   188  
   189  		firstEqualSignIdx := strings.IndexRune(newS[:idx], '=')
   190  		// Got an empty tag or tag starts with `=`. That is totally broken, so skipping that
   191  		if idx == 0 || firstEqualSignIdx == 0 {
   192  			newS = newS[idx+1:]
   193  			continue
   194  		}
   195  
   196  		// Tag doesn't have value
   197  		if firstEqualSignIdx == -1 {
   198  			result[newS[:idx]] = ""
   199  			newS = newS[idx+1:]
   200  			continue
   201  		}
   202  
   203  		result[newS[:firstEqualSignIdx]] = newS[firstEqualSignIdx+1 : idx]
   204  		newS = newS[idx+1:]
   205  	}
   206  
   207  	return result
   208  }