mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -04:00 
			
		
		
		
	* introduce metric naming test Signed-off-by: zounengren <zounengren@cmss.chinamobile.com> * Update metrics.go Signed-off-by: zounengren <zounengren@cmss.chinamobile.com>
		
			
				
	
	
		
			173 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			173 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package test
 | ||
| 
 | ||
| import (
 | ||
| 	"bytes"
 | ||
| 	"go/ast"
 | ||
| 	"go/parser"
 | ||
| 	"go/token"
 | ||
| 	"os"
 | ||
| 	"path/filepath"
 | ||
| 	"strings"
 | ||
| 	"testing"
 | ||
| 
 | ||
| 	"github.com/coredns/coredns/plugin"
 | ||
| 
 | ||
| 	dto "github.com/prometheus/client_model/go"
 | ||
| 	"github.com/prometheus/common/expfmt"
 | ||
| 	"github.com/prometheus/prometheus/util/promlint"
 | ||
| )
 | ||
| 
 | ||
| func TestMetricNaming(t *testing.T) {
 | ||
| 
 | ||
| 	walker := validMetricWalker{}
 | ||
| 	err := filepath.Walk("..", walker.walk)
 | ||
| 
 | ||
| 	if err != nil {
 | ||
| 		t.Fatal(err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if len(walker.Metrics) > 0 {
 | ||
| 
 | ||
| 		buf := &bytes.Buffer{}
 | ||
| 		encoder := expfmt.NewEncoder(buf, expfmt.FmtText)
 | ||
| 		for _, mf := range walker.Metrics {
 | ||
| 			if err := encoder.Encode(mf); err != nil {
 | ||
| 				t.Fatalf("Encoding and sending metric family: %s", err)
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		l := promlint.New(buf)
 | ||
| 		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
 | ||
| }
 | ||
| 
 | ||
| func (l metric) Visit(n ast.Node) ast.Visitor {
 | ||
| 	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
 | ||
| 		}
 | ||
| 		switch object.Name {
 | ||
| 		case "Subsystem":
 | ||
| 			subsystem = value.Value
 | ||
| 		case "Name":
 | ||
| 			name = value.Value
 | ||
| 		case "Help":
 | ||
| 			help = value.Value
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// validate metrics field
 | ||
| 	if len(name) == 0 || len(help) == 0 {
 | ||
| 		return l
 | ||
| 	}
 | ||
| 
 | ||
| 	var metricName string
 | ||
| 	if len(subsystem) > 0 {
 | ||
| 		metricName = strings.Join([]string{plugin.Namespace, subsystem, name}, "_")
 | ||
| 	} else {
 | ||
| 		metricName = strings.Join([]string{plugin.Namespace, name}, "_")
 | ||
| 	}
 | ||
| 	l.Metric = &dto.MetricFamily{
 | ||
| 		Name: &metricName,
 | ||
| 		Help: &help,
 | ||
| 		Type: &metricsType,
 | ||
| 	}
 | ||
| 	return l
 | ||
| }
 |