github.com/go-graphite/carbonapi@v0.17.0/zipper/protocols/victoriametrics/feature_set.go (about) 1 package victoriametrics 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "net/url" 8 "strconv" 9 "strings" 10 "time" 11 12 "go.uber.org/zap" 13 ) 14 15 type vmSupportedFeatures struct { 16 vmVersion string 17 versionParsed [3]int64 18 SupportGraphiteFindAPI bool 19 SupportGraphiteTagsAPI bool 20 GraphiteTagsAPIRequiresDedupe bool 21 SupportOptimizedGraphiteFetch bool 22 } 23 24 // Example: v1.46.0 25 func versionToFeatureSet(logger *zap.Logger, version string) *vmSupportedFeatures { 26 logger = logger.With(zap.String("function", "versionToFeatureSet")) 27 res := &vmSupportedFeatures{ 28 vmVersion: version, 29 } 30 31 verSplitted := strings.Split(version, ".") 32 if len(verSplitted) < 3 { 33 logger.Warn("failed to parse version string", 34 zap.Strings("version_array", verSplitted), 35 zap.Error(fmt.Errorf("expected at least 3 components")), 36 ) 37 return res 38 } 39 if verSplitted[0] != "v1" { 40 return res 41 } 42 res.versionParsed[0] = 1 43 44 v2, err := strconv.ParseInt(verSplitted[1], 10, 64) 45 if err != nil { 46 logger.Warn("failed to parse version string", 47 zap.Strings("version_array", verSplitted), 48 zap.Error(err), 49 ) 50 return res 51 } 52 res.versionParsed[1] = v2 53 54 ver3Raw := strings.Split(verSplitted[2], "-")[0] 55 v3, err := strconv.ParseInt(ver3Raw, 10, 64) 56 if err != nil { 57 logger.Warn("failed to parse version string", 58 zap.Strings("version_array", verSplitted), 59 zap.Error(err), 60 ) 61 return res 62 } 63 res.versionParsed[2] = v3 64 65 if v2 >= 42 { 66 res.SupportGraphiteFindAPI = true 67 } 68 69 if v2 >= 47 { 70 res.SupportGraphiteTagsAPI = true 71 res.GraphiteTagsAPIRequiresDedupe = true 72 } 73 74 if v2 >= 50 { 75 res.GraphiteTagsAPIRequiresDedupe = false 76 } 77 78 if v2 > 53 || (v2 == 53 && v3 >= 1) { 79 res.SupportOptimizedGraphiteFetch = true 80 } 81 82 return res 83 } 84 85 func parseVMVersion(in []byte, fallbackVersion string) string { 86 /* 87 Logic: 88 Step1: 89 Input: 90 process_start_time_seconds 1607269249 91 process_virtual_memory_bytes 3207864320 92 vm_app_version{version="victoria-metrics-20201109-230848-tags-v1.46.0-0-g41813eb8", short_version="v1.46.0"} 1 93 vm_allowed_memory_bytes 20197038489 94 vm_app_start_timestamp 1607269249 95 96 Output: 97 {version="victoria-metrics-20201109-230848-tags-v1.46.0-0-g41813eb8", short_version="v1.46.0"} 1 98 vm_allowed_memory_bytes 20197038489 99 vm_app_start_timestamp 1607269249 100 101 Step2: 102 Input: 103 {version="victoria-metrics-20201109-230848-tags-v1.46.0-0-g41813eb8", short_version="v1.46.0"} 1 104 vm_allowed_memory_bytes 20197038489 105 vm_app_start_timestamp 1607269249 106 107 Output: 108 v1.46.0"} 1 109 vm_allowed_memory_bytes 20197038489 110 vm_app_start_timestamp 1607269249 111 Step3: 112 Input: 113 v1.46.0"} 1 114 vm_allowed_memory_bytes 20197038489 115 vm_app_start_timestamp 1607269249 116 117 Output: 118 v1.46.0 119 */ 120 tokens := [][]byte{ 121 []byte("vm_app_version"), 122 []byte("short_version=\""), 123 []byte("\""), 124 } 125 126 idx := 0 127 for i := range tokens { 128 l := 0 129 if i != 0 { 130 l = len(tokens[i-1]) 131 } 132 in = in[idx+l:] 133 idx = bytes.Index(in, tokens[i]) 134 if idx == -1 { 135 return fallbackVersion 136 } 137 } 138 139 in = in[:idx] 140 if len(in) == 0 { 141 return fallbackVersion 142 } 143 144 return string(in) 145 } 146 147 func (c *VictoriaMetricsGroup) updateFeatureSet(ctx context.Context) { 148 logger := c.logger.With(zap.String("function", "updateFeatureSet")) 149 rewrite, _ := url.Parse("http://127.0.0.1/metrics") 150 var minFeatureSet *vmSupportedFeatures 151 152 res, queryErr := c.httpQuery.DoQueryToAll(ctx, logger, rewrite.RequestURI(), nil) 153 if queryErr != nil { 154 logger.Debug("got some errors while getting capabilities", 155 zap.Error(queryErr), 156 ) 157 } 158 if len(res) == 0 { 159 return 160 } 161 162 for i := range res { 163 if res[i] == nil || res[i].Response == nil { 164 continue 165 } 166 version := parseVMVersion(res[i].Response, c.fallbackVersion) 167 featureSet := versionToFeatureSet(logger, version) 168 if minFeatureSet == nil { 169 minFeatureSet = featureSet 170 continue 171 } 172 173 if minFeatureSet.versionParsed[0] > featureSet.versionParsed[0] || 174 minFeatureSet.versionParsed[1] > featureSet.versionParsed[1] || 175 minFeatureSet.versionParsed[2] > featureSet.versionParsed[2] { 176 minFeatureSet = featureSet 177 } 178 } 179 180 logger.Debug("got feature set", 181 zap.Any("featureset", minFeatureSet), 182 ) 183 184 if minFeatureSet == nil { 185 minFeatureSet = versionToFeatureSet(logger, "c.fallbackVersion") 186 } 187 188 c.featureSet.Store(minFeatureSet) 189 } 190 191 func (c *VictoriaMetricsGroup) probeVMVersion(ctx context.Context) { 192 ticker := time.NewTicker(c.probeVersionInterval) 193 194 for { 195 select { 196 case <-ctx.Done(): 197 return 198 case <-ticker.C: 199 c.updateFeatureSet(ctx) 200 } 201 } 202 }