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 }