main.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /*
  2. Copyright 2019 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. import (
  15. "bufio"
  16. "flag"
  17. "fmt"
  18. "go/ast"
  19. "go/parser"
  20. "go/token"
  21. "os"
  22. "path/filepath"
  23. "sort"
  24. "strings"
  25. "gopkg.in/yaml.v2"
  26. )
  27. const (
  28. kubeMetricImportPath = `"k8s.io/component-base/metrics"`
  29. // Should equal to final directory name of kubeMetricImportPath
  30. kubeMetricsDefaultImportName = "metrics"
  31. )
  32. func main() {
  33. flag.Parse()
  34. if len(flag.Args()) < 1 {
  35. fmt.Fprintf(os.Stderr, "USAGE: %s <DIR or FILE or '-'> [...]\n", os.Args[0])
  36. os.Exit(64)
  37. }
  38. stableMetrics := []metric{}
  39. errors := []error{}
  40. addStdin := false
  41. for _, arg := range flag.Args() {
  42. if arg == "-" {
  43. addStdin = true
  44. continue
  45. }
  46. ms, es := searchPathForStableMetrics(arg)
  47. stableMetrics = append(stableMetrics, ms...)
  48. errors = append(errors, es...)
  49. }
  50. if addStdin {
  51. scanner := bufio.NewScanner(os.Stdin)
  52. scanner.Split(bufio.ScanLines)
  53. for scanner.Scan() {
  54. arg := scanner.Text()
  55. ms, es := searchPathForStableMetrics(arg)
  56. stableMetrics = append(stableMetrics, ms...)
  57. errors = append(errors, es...)
  58. }
  59. }
  60. for _, err := range errors {
  61. fmt.Fprintf(os.Stderr, "%s\n", err)
  62. }
  63. if len(errors) != 0 {
  64. os.Exit(1)
  65. }
  66. sort.Sort(byFQName(stableMetrics))
  67. data, err := yaml.Marshal(stableMetrics)
  68. if err != nil {
  69. fmt.Fprintf(os.Stderr, "%s\n", err)
  70. os.Exit(1)
  71. }
  72. fmt.Print(string(data))
  73. }
  74. func searchPathForStableMetrics(path string) ([]metric, []error) {
  75. metrics := []metric{}
  76. errors := []error{}
  77. err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
  78. if strings.HasPrefix(path, "vendor") {
  79. return filepath.SkipDir
  80. }
  81. if !strings.HasSuffix(path, ".go") {
  82. return nil
  83. }
  84. ms, es := searchFileForStableMetrics(path, nil)
  85. errors = append(errors, es...)
  86. metrics = append(metrics, ms...)
  87. return nil
  88. })
  89. if err != nil {
  90. errors = append(errors, err)
  91. }
  92. return metrics, errors
  93. }
  94. // Pass either only filename of existing file or src including source code in any format and a filename that it comes from
  95. func searchFileForStableMetrics(filename string, src interface{}) ([]metric, []error) {
  96. fileset := token.NewFileSet()
  97. tree, err := parser.ParseFile(fileset, filename, src, parser.AllErrors)
  98. if err != nil {
  99. return []metric{}, []error{err}
  100. }
  101. metricsImportName, err := getLocalNameOfImportedPackage(tree, kubeMetricImportPath, kubeMetricsDefaultImportName)
  102. if err != nil {
  103. return []metric{}, addFileInformationToErrors([]error{err}, fileset)
  104. }
  105. if metricsImportName == "" {
  106. return []metric{}, []error{}
  107. }
  108. variables := globalVariableDeclarations(tree)
  109. stableMetricsFunctionCalls, errors := findStableMetricDeclaration(tree, metricsImportName)
  110. metrics, es := decodeMetricCalls(stableMetricsFunctionCalls, metricsImportName, variables)
  111. errors = append(errors, es...)
  112. return metrics, addFileInformationToErrors(errors, fileset)
  113. }
  114. func getLocalNameOfImportedPackage(tree *ast.File, importPath, defaultImportName string) (string, error) {
  115. var importName string
  116. for _, im := range tree.Imports {
  117. if im.Path.Value == importPath {
  118. if im.Name == nil {
  119. importName = defaultImportName
  120. } else {
  121. if im.Name.Name == "." {
  122. return "", newDecodeErrorf(im, errImport)
  123. }
  124. importName = im.Name.Name
  125. }
  126. }
  127. }
  128. return importName, nil
  129. }
  130. func addFileInformationToErrors(es []error, fileset *token.FileSet) []error {
  131. for i := range es {
  132. if de, ok := es[i].(*decodeError); ok {
  133. es[i] = de.errorWithFileInformation(fileset)
  134. }
  135. }
  136. return es
  137. }
  138. func globalVariableDeclarations(tree *ast.File) map[string]ast.Expr {
  139. consts := make(map[string]ast.Expr)
  140. for _, d := range tree.Decls {
  141. if gd, ok := d.(*ast.GenDecl); ok && (gd.Tok == token.CONST || gd.Tok == token.VAR) {
  142. for _, spec := range gd.Specs {
  143. if vspec, ok := spec.(*ast.ValueSpec); ok {
  144. for _, name := range vspec.Names {
  145. for _, value := range vspec.Values {
  146. consts[name.Name] = value
  147. }
  148. }
  149. }
  150. }
  151. }
  152. }
  153. return consts
  154. }