| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | package test
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import (
 | 
					
						
							|  |  |  |  | 	"go/ast"
 | 
					
						
							|  |  |  |  | 	"go/parser"
 | 
					
						
							|  |  |  |  | 	"go/token"
 | 
					
						
							|  |  |  |  | 	"os"
 | 
					
						
							|  |  |  |  | 	"path/filepath"
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 	"strconv"
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 	"strings"
 | 
					
						
							|  |  |  |  | 	"testing"
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"github.com/coredns/coredns/plugin"
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 	"github.com/prometheus/client_golang/prometheus"
 | 
					
						
							|  |  |  |  | 	"github.com/prometheus/client_golang/prometheus/testutil/promlint"
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 	dto "github.com/prometheus/client_model/go"
 | 
					
						
							|  |  |  |  | )
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func TestMetricNaming(t *testing.T) {
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	walker := validMetricWalker{}
 | 
					
						
							|  |  |  |  | 	err := filepath.Walk("..", walker.walk)
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  |  | 		t.Fatal(err)
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if len(walker.Metrics) > 0 {
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 		l := promlint.NewWithMetricFamilies(walker.Metrics)
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 		problems, err := l.Lint()
 | 
					
						
							|  |  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  |  | 			t.Fatalf("Link found error: %s", err)
 | 
					
						
							|  |  |  |  | 		}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		if len(problems) > 0 {
 | 
					
						
							|  |  |  |  | 			t.Fatalf("A slice of Problems indicating any issues found in the metrics stream: %s", problems)
 | 
					
						
							|  |  |  |  | 		}
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | }
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | type validMetricWalker struct {
 | 
					
						
							|  |  |  |  | 	Metrics []*dto.MetricFamily
 | 
					
						
							|  |  |  |  | }
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func (w *validMetricWalker) walk(path string, info os.FileInfo, _ error) error {
 | 
					
						
							|  |  |  |  | 	// only for regular files, not starting with a . and those that are go files.
 | 
					
						
							|  |  |  |  | 	if !info.Mode().IsRegular() {
 | 
					
						
							|  |  |  |  | 		return nil
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	// Is it appropriate to compare the file name equals metrics.go directly?
 | 
					
						
							|  |  |  |  | 	if strings.HasPrefix(path, "../.") {
 | 
					
						
							|  |  |  |  | 		return nil
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	if strings.HasSuffix(path, "_test.go") {
 | 
					
						
							|  |  |  |  | 		return nil
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	if !strings.HasSuffix(path, ".go") {
 | 
					
						
							|  |  |  |  | 		return nil
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	fs := token.NewFileSet()
 | 
					
						
							|  |  |  |  | 	f, err := parser.ParseFile(fs, path, nil, parser.AllErrors)
 | 
					
						
							|  |  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  |  | 		return err
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	l := &metric{}
 | 
					
						
							|  |  |  |  | 	ast.Walk(l, f)
 | 
					
						
							|  |  |  |  | 	if l.Metric != nil {
 | 
					
						
							|  |  |  |  | 		w.Metrics = append(w.Metrics, l.Metric)
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	return nil
 | 
					
						
							|  |  |  |  | }
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | type metric struct {
 | 
					
						
							|  |  |  |  | 	Metric *dto.MetricFamily
 | 
					
						
							|  |  |  |  | }
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | func (l *metric) Visit(n ast.Node) ast.Visitor {
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 	if n == nil {
 | 
					
						
							|  |  |  |  | 		return nil
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	ce, ok := n.(*ast.CallExpr)
 | 
					
						
							|  |  |  |  | 	if !ok {
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	se, ok := ce.Fun.(*ast.SelectorExpr)
 | 
					
						
							|  |  |  |  | 	if !ok {
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	id, ok := se.X.(*ast.Ident)
 | 
					
						
							|  |  |  |  | 	if !ok {
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	if id.Name != "prometheus" { //prometheus
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	var metricsType dto.MetricType
 | 
					
						
							|  |  |  |  | 	switch se.Sel.Name {
 | 
					
						
							|  |  |  |  | 	case "NewCounterVec", "NewCounter":
 | 
					
						
							|  |  |  |  | 		metricsType = dto.MetricType_COUNTER
 | 
					
						
							|  |  |  |  | 	case "NewGaugeVec", "NewGauge":
 | 
					
						
							|  |  |  |  | 		metricsType = dto.MetricType_GAUGE
 | 
					
						
							|  |  |  |  | 	case "NewHistogramVec", "NewHistogram":
 | 
					
						
							|  |  |  |  | 		metricsType = dto.MetricType_HISTOGRAM
 | 
					
						
							|  |  |  |  | 	case "NewSummaryVec", "NewSummary":
 | 
					
						
							|  |  |  |  | 		metricsType = dto.MetricType_SUMMARY
 | 
					
						
							|  |  |  |  | 	default:
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	// Check first arg, that should have basic lit with capital
 | 
					
						
							|  |  |  |  | 	if len(ce.Args) < 1 {
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	bl, ok := ce.Args[0].(*ast.CompositeLit)
 | 
					
						
							|  |  |  |  | 	if !ok {
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// parse Namespace Subsystem Name Help
 | 
					
						
							|  |  |  |  | 	var subsystem, name, help string
 | 
					
						
							|  |  |  |  | 	for _, elt := range bl.Elts {
 | 
					
						
							|  |  |  |  | 		expr, ok := elt.(*ast.KeyValueExpr)
 | 
					
						
							|  |  |  |  | 		if !ok {
 | 
					
						
							|  |  |  |  | 			continue
 | 
					
						
							|  |  |  |  | 		}
 | 
					
						
							|  |  |  |  | 		object, ok := expr.Key.(*ast.Ident)
 | 
					
						
							|  |  |  |  | 		if !ok {
 | 
					
						
							|  |  |  |  | 			continue
 | 
					
						
							|  |  |  |  | 		}
 | 
					
						
							|  |  |  |  | 		value, ok := expr.Value.(*ast.BasicLit)
 | 
					
						
							|  |  |  |  | 		if !ok {
 | 
					
						
							|  |  |  |  | 			continue
 | 
					
						
							|  |  |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// remove quotes
 | 
					
						
							|  |  |  |  | 		stringLiteral, err := strconv.Unquote(value.Value)
 | 
					
						
							|  |  |  |  | 		if err != nil {
 | 
					
						
							|  |  |  |  | 			return l
 | 
					
						
							|  |  |  |  | 		}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 		switch object.Name {
 | 
					
						
							|  |  |  |  | 		case "Subsystem":
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 			subsystem = stringLiteral
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 		case "Name":
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 			name = stringLiteral
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 		case "Help":
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 			help = stringLiteral
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 		}
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// validate metrics field
 | 
					
						
							|  |  |  |  | 	if len(name) == 0 || len(help) == 0 {
 | 
					
						
							|  |  |  |  | 		return l
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-21 08:15:55 +00:00
										 |  |  |  | 	metricName := prometheus.BuildFQName(plugin.Namespace, subsystem, name)
 | 
					
						
							| 
									
										
										
										
											2020-03-31 14:07:36 +08:00
										 |  |  |  | 	l.Metric = &dto.MetricFamily{
 | 
					
						
							|  |  |  |  | 		Name: &metricName,
 | 
					
						
							|  |  |  |  | 		Help: &help,
 | 
					
						
							|  |  |  |  | 		Type: &metricsType,
 | 
					
						
							|  |  |  |  | 	}
 | 
					
						
							|  |  |  |  | 	return l
 | 
					
						
							|  |  |  |  | }
 |