...

Source file src/k8s.io/kubernetes/test/instrumentation/main.go

Documentation: k8s.io/kubernetes/test/instrumentation

     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  }
   311  

View as plain text