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