k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/instrumentation/main.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bufio" 21 "encoding/json" 22 "flag" 23 "fmt" 24 "go/ast" 25 "go/parser" 26 "go/token" 27 "os" 28 "os/exec" 29 "path/filepath" 30 "sort" 31 "strconv" 32 "strings" 33 34 "gopkg.in/yaml.v2" 35 ) 36 37 const ( 38 kubeMetricImportPath = `"k8s.io/component-base/metrics"` 39 // Should equal to final directory name of kubeMetricImportPath 40 kubeMetricsDefaultImportName = "metrics" 41 ) 42 43 var ( 44 // env configs 45 GOOS string = findGOOS() 46 ALL_STABILITY_CLASSES bool 47 ) 48 49 func findGOOS() string { 50 cmd := exec.Command("go", "env", "GOOS") 51 out, err := cmd.CombinedOutput() 52 if err != nil { 53 panic(fmt.Sprintf("running `go env` failed: %v\n\n%s", err, string(out))) 54 } 55 if len(out) == 0 { 56 panic("empty result from `go env GOOS`") 57 } 58 return string(out) 59 } 60 61 func main() { 62 63 flag.BoolVar(&ALL_STABILITY_CLASSES, "allstabilityclasses", false, "use this flag to enable all stability classes") 64 flag.Parse() 65 if len(flag.Args()) < 1 { 66 fmt.Fprintf(os.Stderr, "USAGE: %s <DIR or FILE or '-'> [...]\n", os.Args[0]) 67 os.Exit(64) 68 } 69 stableMetricNames := map[string]struct{}{} 70 stableMetrics := []metric{} 71 errors := []error{} 72 73 addStdin := false 74 for _, arg := range flag.Args() { 75 if arg == "-" { 76 addStdin = true 77 continue 78 } 79 ms, es := searchPathForStableMetrics(arg) 80 for _, m := range ms { 81 if _, ok := stableMetricNames[m.Name]; !ok { 82 stableMetrics = append(stableMetrics, m) 83 } 84 stableMetricNames[m.Name] = struct{}{} 85 } 86 errors = append(errors, es...) 87 } 88 if addStdin { 89 scanner := bufio.NewScanner(os.Stdin) 90 scanner.Split(bufio.ScanLines) 91 for scanner.Scan() { 92 arg := scanner.Text() 93 ms, es := searchPathForStableMetrics(arg) 94 stableMetrics = append(stableMetrics, ms...) 95 errors = append(errors, es...) 96 } 97 } 98 99 for _, err := range errors { 100 fmt.Fprintf(os.Stderr, "%s\n", err) 101 } 102 if len(errors) != 0 { 103 os.Exit(1) 104 } 105 if len(stableMetrics) == 0 { 106 os.Exit(0) 107 } 108 for i, m := range stableMetrics { 109 if m.StabilityLevel == "" { 110 m.StabilityLevel = "ALPHA" 111 } 112 stableMetrics[i] = m 113 } 114 sort.Sort(byFQName(stableMetrics)) 115 data, err := yaml.Marshal(stableMetrics) 116 if err != nil { 117 fmt.Fprintf(os.Stderr, "%s\n", err) 118 os.Exit(1) 119 } 120 121 fmt.Print(string(data)) 122 } 123 124 func searchPathForStableMetrics(path string) ([]metric, []error) { 125 metrics := []metric{} 126 errors := []error{} 127 err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 128 if strings.HasPrefix(path, "vendor") { 129 return filepath.SkipDir 130 } 131 if !strings.HasSuffix(path, ".go") { 132 return nil 133 } 134 ms, es := searchFileForStableMetrics(path, nil) 135 errors = append(errors, es...) 136 metrics = append(metrics, ms...) 137 return nil 138 }) 139 if err != nil { 140 errors = append(errors, err) 141 } 142 return metrics, errors 143 } 144 145 // Pass either only filename of existing file or src including source code in any format and a filename that it comes from 146 func searchFileForStableMetrics(filename string, src interface{}) ([]metric, []error) { 147 fileset := token.NewFileSet() 148 tree, err := parser.ParseFile(fileset, filename, src, parser.AllErrors) 149 if err != nil { 150 return []metric{}, []error{err} 151 } 152 metricsImportName, err := getLocalNameOfImportedPackage(tree, kubeMetricImportPath, kubeMetricsDefaultImportName) 153 if err != nil { 154 return []metric{}, addFileInformationToErrors([]error{err}, fileset) 155 } 156 if metricsImportName == "" { 157 return []metric{}, []error{} 158 } 159 variables := globalVariableDeclarations(tree) 160 161 variables, err = importedGlobalVariableDeclaration(variables, tree.Imports) 162 if err != nil { 163 return []metric{}, addFileInformationToErrors([]error{err}, fileset) 164 } 165 166 stableMetricsFunctionCalls, errors := findStableMetricDeclaration(tree, metricsImportName) 167 metrics, es := decodeMetricCalls(stableMetricsFunctionCalls, metricsImportName, variables) 168 errors = append(errors, es...) 169 return metrics, addFileInformationToErrors(errors, fileset) 170 } 171 172 func getLocalNameOfImportedPackage(tree *ast.File, importPath, defaultImportName string) (string, error) { 173 var importName string 174 for _, im := range tree.Imports { 175 if im.Path.Value == importPath { 176 if im.Name == nil { 177 importName = defaultImportName 178 } else { 179 if im.Name.Name == "." { 180 return "", newDecodeErrorf(im, errImport) 181 } 182 importName = im.Name.Name 183 } 184 } 185 } 186 return importName, nil 187 } 188 189 func addFileInformationToErrors(es []error, fileset *token.FileSet) []error { 190 for i := range es { 191 if de, ok := es[i].(*decodeError); ok { 192 es[i] = de.errorWithFileInformation(fileset) 193 } 194 } 195 return es 196 } 197 198 func globalVariableDeclarations(tree *ast.File) map[string]ast.Expr { 199 consts := make(map[string]ast.Expr) 200 for _, d := range tree.Decls { 201 if gd, ok := d.(*ast.GenDecl); ok && (gd.Tok == token.CONST || gd.Tok == token.VAR) { 202 for _, spec := range gd.Specs { 203 if vspec, ok := spec.(*ast.ValueSpec); ok { 204 for _, name := range vspec.Names { 205 for _, value := range vspec.Values { 206 consts[name.Name] = value 207 } 208 } 209 } 210 } 211 } 212 } 213 return consts 214 } 215 216 func findPkgDir(pkg string) (string, error) { 217 // Use Go's module mechanism. 218 cmd := exec.Command("go", "list", "-find", "-json=Dir", pkg) 219 out, err := cmd.CombinedOutput() 220 if err != nil { 221 return "", fmt.Errorf("running `go list` failed: %w\n\n%s", err, string(out)) 222 } 223 result := struct { 224 Dir string 225 }{} 226 if err := json.Unmarshal(out, &result); err != nil { 227 return "", fmt.Errorf("json unmarshal of `go list` failed: %w", err) 228 } 229 if result.Dir != "" { 230 return result.Dir, nil 231 } 232 233 return "", fmt.Errorf("empty respose from `go list`") 234 } 235 236 func importedGlobalVariableDeclaration(localVariables map[string]ast.Expr, imports []*ast.ImportSpec) (map[string]ast.Expr, error) { 237 for _, im := range imports { 238 // get imported label 239 var importAlias string 240 if im.Name == nil { 241 pathSegments := strings.Split(im.Path.Value, "/") 242 importAlias = strings.Trim(pathSegments[len(pathSegments)-1], "\"") 243 } else { 244 importAlias = im.Name.String() 245 } 246 247 // find local path on disk for listed import 248 pkg, err := strconv.Unquote(im.Path.Value) 249 if err != nil { 250 return nil, fmt.Errorf("can't handle import '%s': %w", im.Path.Value, err) 251 } 252 importDirectory, err := findPkgDir(pkg) 253 if err != nil { 254 return nil, fmt.Errorf("can't find import '%s': %w", im.Path.Value, err) 255 } 256 257 files, err := os.ReadDir(importDirectory) 258 if err != nil { 259 return nil, fmt.Errorf("failed to read import directory %s: %w", importDirectory, err) 260 } 261 262 for _, file := range files { 263 if file.IsDir() { 264 // do not grab constants from subpackages 265 continue 266 } 267 268 if strings.Contains(file.Name(), "_test") { 269 // do not parse test files 270 continue 271 } 272 273 if !strings.HasSuffix(file.Name(), ".go") { 274 // not a go code file, do not attempt to parse 275 continue 276 } 277 278 fileset := token.NewFileSet() 279 tree, err := parser.ParseFile(fileset, strings.Join([]string{importDirectory, file.Name()}, string(os.PathSeparator)), nil, parser.AllErrors) 280 if err != nil { 281 return nil, fmt.Errorf("failed to parse path %s with error %w", im.Path.Value, err) 282 } 283 284 // pass parsed filepath into globalVariableDeclarations 285 variables := globalVariableDeclarations(tree) 286 287 // add returned map into supplied map and prepend import label to all keys 288 for k, v := range variables { 289 importK := strings.Join([]string{importAlias, k}, ".") 290 if _, ok := localVariables[importK]; !ok { 291 localVariables[importK] = v 292 } else { 293 // cross-platform file that gets included in the correct OS build via OS build tags 294 // use whatever matches GOOS 295 296 if strings.Contains(file.Name(), GOOS) { 297 // assume at some point we will find the correct OS version of this file 298 // if we are running on an OS that does not have an OS specific file for something then we will include a constant we shouldn't 299 // TODO: should we include/exclude based on the build tags? 300 localVariables[importK] = v 301 } 302 303 } 304 } 305 } 306 307 } 308 309 return localVariables, nil 310 }