mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 19:01:49 +00:00
Implement stable metric validation and verification
Based on KEP: https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190605-metrics-validation-and-verification.md Add //test/instrumentation:stable_metric_test that compares metrics in source code to those available in "test/instrumentation/testdata/stable-metrics-list.yaml".
This commit is contained in:
parent
a4c5a57800
commit
a5377a684c
@ -1,10 +1,21 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["main.go"],
|
srcs = [
|
||||||
|
"decode_metric.go",
|
||||||
|
"error.go",
|
||||||
|
"find_stable_metric.go",
|
||||||
|
"main.go",
|
||||||
|
"metric.go",
|
||||||
|
],
|
||||||
importpath = "k8s.io/kubernetes/test/instrumentation",
|
importpath = "k8s.io/kubernetes/test/instrumentation",
|
||||||
visibility = ["//visibility:private"],
|
visibility = ["//visibility:private"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/component-base/metrics:go_default_library",
|
||||||
|
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||||
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_binary(
|
go_binary(
|
||||||
@ -26,3 +37,30 @@ filegroup(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
genrule(
|
||||||
|
name = "list_stable_metrics",
|
||||||
|
srcs = [
|
||||||
|
"//:all-srcs",
|
||||||
|
],
|
||||||
|
outs = ["stable-metrics-list.yaml"],
|
||||||
|
cmd = "./$(locations :instrumentation) $(locations //:all-srcs) > $@",
|
||||||
|
message = "Listing all stable metrics.",
|
||||||
|
tools = [":instrumentation"],
|
||||||
|
)
|
||||||
|
|
||||||
|
sh_test(
|
||||||
|
name = "verify_stable_metric",
|
||||||
|
srcs = ["verify-stable-metrics.sh"],
|
||||||
|
data = [
|
||||||
|
"testdata/stable-metrics-list.yaml",
|
||||||
|
":list_stable_metrics",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["main_test.go"],
|
||||||
|
data = glob(["testdata/**"]),
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
)
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
This is a WIP directory for ensuring stability rules around kubernetes metrics.
|
This directory contains the regression test for controlling the list of stable metrics
|
||||||
|
|
||||||
Design [Metrics validation and verification](https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190605-metrics-validation-and-verification.md)
|
If you add or remove a stable metric, this test will fail and you will need
|
||||||
|
to update the golden list of tests stored in `testdata/`. Changes to that file
|
||||||
|
require review by sig-instrumentation.
|
||||||
|
|
||||||
|
To update the list, run
|
||||||
|
|
||||||
|
```console
|
||||||
|
./update-stable-metrics.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the changed file to your PR, then send for review.
|
||||||
|
239
test/instrumentation/decode_metric.go
Normal file
239
test/instrumentation/decode_metric.go
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/component-base/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decodeMetricCalls(fs []*ast.CallExpr, metricsImportName string) ([]metric, []error) {
|
||||||
|
finder := metricDecoder{
|
||||||
|
metricsImportName: metricsImportName,
|
||||||
|
}
|
||||||
|
ms := make([]metric, 0, len(fs))
|
||||||
|
errors := []error{}
|
||||||
|
for _, f := range fs {
|
||||||
|
m, err := finder.decodeNewMetricCall(f)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ms = append(ms, m)
|
||||||
|
}
|
||||||
|
return ms, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
type metricDecoder struct {
|
||||||
|
metricsImportName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *metricDecoder) decodeNewMetricCall(fc *ast.CallExpr) (metric, error) {
|
||||||
|
var m metric
|
||||||
|
var err error
|
||||||
|
se, ok := fc.Fun.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return m, newDecodeErrorf(fc, errNotDirectCall)
|
||||||
|
}
|
||||||
|
functionName := se.Sel.String()
|
||||||
|
functionImport, ok := se.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return m, newDecodeErrorf(fc, errNotDirectCall)
|
||||||
|
}
|
||||||
|
if functionImport.String() != c.metricsImportName {
|
||||||
|
return m, newDecodeErrorf(fc, errNotDirectCall)
|
||||||
|
}
|
||||||
|
switch functionName {
|
||||||
|
case "NewCounter", "NewGauge", "NewHistogram":
|
||||||
|
m, err = c.decodeMetric(fc)
|
||||||
|
case "NewCounterVec", "NewGaugeVec", "NewHistogramVec":
|
||||||
|
m, err = c.decodeMetricVec(fc)
|
||||||
|
case "NewSummary", "NewSummaryVec":
|
||||||
|
return m, newDecodeErrorf(fc, errStableSummary)
|
||||||
|
default:
|
||||||
|
return m, newDecodeErrorf(fc, errNotDirectCall)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
m.Type = getMetricType(functionName)
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetricType(functionName string) string {
|
||||||
|
switch functionName {
|
||||||
|
case "NewCounter", "NewCounterVec":
|
||||||
|
return counterMetricType
|
||||||
|
case "NewGauge", "NewGaugeVec":
|
||||||
|
return gaugeMetricType
|
||||||
|
case "NewHistogram", "NewHistogramVec":
|
||||||
|
return histogramMetricType
|
||||||
|
default:
|
||||||
|
panic("getMetricType expects correct function name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *metricDecoder) decodeMetric(call *ast.CallExpr) (metric, error) {
|
||||||
|
if len(call.Args) != 1 {
|
||||||
|
return metric{}, newDecodeErrorf(call, errInvalidNewMetricCall)
|
||||||
|
}
|
||||||
|
return c.decodeOpts(call.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *metricDecoder) decodeMetricVec(call *ast.CallExpr) (metric, error) {
|
||||||
|
if len(call.Args) != 2 {
|
||||||
|
return metric{}, newDecodeErrorf(call, errInvalidNewMetricCall)
|
||||||
|
}
|
||||||
|
m, err := c.decodeOpts(call.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
labels, err := decodeLabels(call.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
sort.Strings(labels)
|
||||||
|
m.Labels = labels
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeLabels(expr ast.Expr) ([]string, error) {
|
||||||
|
cl, ok := expr.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(expr, errInvalidNewMetricCall)
|
||||||
|
}
|
||||||
|
labels := make([]string, len(cl.Elts))
|
||||||
|
for i, el := range cl.Elts {
|
||||||
|
bl, ok := el.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(bl, errLabels)
|
||||||
|
}
|
||||||
|
if bl.Kind != token.STRING {
|
||||||
|
return nil, newDecodeErrorf(bl, errLabels)
|
||||||
|
}
|
||||||
|
labels[i] = strings.Trim(bl.Value, `"`)
|
||||||
|
}
|
||||||
|
return labels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *metricDecoder) decodeOpts(expr ast.Expr) (metric, error) {
|
||||||
|
m := metric{
|
||||||
|
Labels: []string{},
|
||||||
|
}
|
||||||
|
ue, ok := expr.(*ast.UnaryExpr)
|
||||||
|
if !ok {
|
||||||
|
return m, newDecodeErrorf(expr, errInvalidNewMetricCall)
|
||||||
|
}
|
||||||
|
cl, ok := ue.X.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
return m, newDecodeErrorf(expr, errInvalidNewMetricCall)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expr := range cl.Elts {
|
||||||
|
kv, ok := expr.(*ast.KeyValueExpr)
|
||||||
|
if !ok {
|
||||||
|
return m, newDecodeErrorf(expr, errPositionalArguments)
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%v", kv.Key)
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "Namespace", "Subsystem", "Name", "Help":
|
||||||
|
k, ok := kv.Value.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return m, newDecodeErrorf(expr, errNonStringAttribute)
|
||||||
|
}
|
||||||
|
if k.Kind != token.STRING {
|
||||||
|
return m, newDecodeErrorf(expr, errNonStringAttribute)
|
||||||
|
}
|
||||||
|
value := strings.Trim(k.Value, `"`)
|
||||||
|
switch key {
|
||||||
|
case "Namespace":
|
||||||
|
m.Namespace = value
|
||||||
|
case "Subsystem":
|
||||||
|
m.Subsystem = value
|
||||||
|
case "Name":
|
||||||
|
m.Name = value
|
||||||
|
case "Help":
|
||||||
|
m.Help = value
|
||||||
|
}
|
||||||
|
case "Buckets":
|
||||||
|
buckets, err := decodeBuckets(kv)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
sort.Float64s(buckets)
|
||||||
|
m.Buckets = buckets
|
||||||
|
case "StabilityLevel":
|
||||||
|
level, err := decodeStabilityLevel(kv.Value, c.metricsImportName)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
m.StabilityLevel = string(*level)
|
||||||
|
default:
|
||||||
|
return m, newDecodeErrorf(expr, errFieldNotSupported, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBuckets(kv *ast.KeyValueExpr) ([]float64, error) {
|
||||||
|
cl, ok := kv.Value.(*ast.CompositeLit)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(kv, errBuckets)
|
||||||
|
}
|
||||||
|
buckets := make([]float64, len(cl.Elts))
|
||||||
|
for i, elt := range cl.Elts {
|
||||||
|
bl, ok := elt.(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(bl, errBuckets)
|
||||||
|
}
|
||||||
|
if bl.Kind != token.FLOAT && bl.Kind != token.INT {
|
||||||
|
return nil, newDecodeErrorf(bl, errBuckets)
|
||||||
|
}
|
||||||
|
value, err := strconv.ParseFloat(bl.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buckets[i] = value
|
||||||
|
}
|
||||||
|
return buckets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeStabilityLevel(expr ast.Expr, metricsFrameworkImportName string) (*metrics.StabilityLevel, error) {
|
||||||
|
se, ok := expr.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(expr, errStabilityLevel)
|
||||||
|
}
|
||||||
|
s, ok := se.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(expr, errStabilityLevel)
|
||||||
|
}
|
||||||
|
if s.String() != metricsFrameworkImportName {
|
||||||
|
return nil, newDecodeErrorf(expr, errStabilityLevel)
|
||||||
|
}
|
||||||
|
if se.Sel.Name != "ALPHA" && se.Sel.Name != "STABLE" {
|
||||||
|
return nil, newDecodeErrorf(expr, errStabilityLevel)
|
||||||
|
}
|
||||||
|
stability := metrics.StabilityLevel(se.Sel.Name)
|
||||||
|
return &stability, nil
|
||||||
|
}
|
59
test/instrumentation/error.go
Normal file
59
test/instrumentation/error.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errNotDirectCall = "Opts for STABLE metric was not directly passed to new metric function"
|
||||||
|
errPositionalArguments = "Positional arguments are not supported"
|
||||||
|
errStabilityLevel = "StabilityLevel should be passed STABLE, ALPHA or removed"
|
||||||
|
errStableSummary = "Stable summary metric is not supported"
|
||||||
|
errInvalidNewMetricCall = "Invalid new metric call, please ensure code compiles"
|
||||||
|
errNonStringAttribute = "Non string attribute it not supported"
|
||||||
|
errFieldNotSupported = "Field %s is not supported"
|
||||||
|
errBuckets = "Buckets were not set to list of floats"
|
||||||
|
errLabels = "Labels were not set to list of strings"
|
||||||
|
errImport = `Importing through "." metrics framework is not supported`
|
||||||
|
)
|
||||||
|
|
||||||
|
type decodeError struct {
|
||||||
|
msg string
|
||||||
|
pos token.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDecodeErrorf(node ast.Node, format string, a ...interface{}) *decodeError {
|
||||||
|
return &decodeError{
|
||||||
|
msg: fmt.Sprintf(format, a...),
|
||||||
|
pos: node.Pos(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = (*decodeError)(nil)
|
||||||
|
|
||||||
|
func (e decodeError) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e decodeError) errorWithFileInformation(fileset *token.FileSet) error {
|
||||||
|
position := fileset.Position(e.pos)
|
||||||
|
return fmt.Errorf("%s:%d:%d: %s", position.Filename, position.Line, position.Column, e.msg)
|
||||||
|
}
|
123
test/instrumentation/find_stable_metric.go
Normal file
123
test/instrumentation/find_stable_metric.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"k8s.io/component-base/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
var metricsOptionStructuresNames = []string{
|
||||||
|
"KubeOpts",
|
||||||
|
"CounterOpts",
|
||||||
|
"GaugeOpts",
|
||||||
|
"HistogramOpts",
|
||||||
|
"SummaryOpts",
|
||||||
|
}
|
||||||
|
|
||||||
|
func findStableMetricDeclaration(tree ast.Node, metricsImportName string) ([]*ast.CallExpr, []error) {
|
||||||
|
v := stableMetricFinder{
|
||||||
|
metricsImportName: metricsImportName,
|
||||||
|
stableMetricsFunctionCalls: []*ast.CallExpr{},
|
||||||
|
errors: []error{},
|
||||||
|
}
|
||||||
|
ast.Walk(&v, tree)
|
||||||
|
return v.stableMetricsFunctionCalls, v.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements visitor pattern for ast.Node that collects all stable metric expressions
|
||||||
|
type stableMetricFinder struct {
|
||||||
|
metricsImportName string
|
||||||
|
currentFunctionCall *ast.CallExpr
|
||||||
|
stableMetricsFunctionCalls []*ast.CallExpr
|
||||||
|
errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ast.Visitor = (*stableMetricFinder)(nil)
|
||||||
|
|
||||||
|
func (f *stableMetricFinder) Visit(node ast.Node) (w ast.Visitor) {
|
||||||
|
switch opts := node.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
f.currentFunctionCall = opts
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
se, ok := opts.Type.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
if !isMetricOps(se.Sel.Name) {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
id, ok := se.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
if id.Name != f.metricsImportName {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
stabilityLevel, err := getStabilityLevel(opts, f.metricsImportName)
|
||||||
|
if err != nil {
|
||||||
|
f.errors = append(f.errors, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch *stabilityLevel {
|
||||||
|
case metrics.STABLE:
|
||||||
|
if f.currentFunctionCall == nil {
|
||||||
|
f.errors = append(f.errors, newDecodeErrorf(opts, errNotDirectCall))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f.stableMetricsFunctionCalls = append(f.stableMetricsFunctionCalls, f.currentFunctionCall)
|
||||||
|
f.currentFunctionCall = nil
|
||||||
|
case metrics.ALPHA:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if f.currentFunctionCall == nil || node == nil || node.Pos() < f.currentFunctionCall.Rparen {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
f.currentFunctionCall = nil
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMetricOps(name string) bool {
|
||||||
|
var found = false
|
||||||
|
for _, optsName := range metricsOptionStructuresNames {
|
||||||
|
if name != optsName {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStabilityLevel(opts *ast.CompositeLit, metricsFrameworkImportName string) (*metrics.StabilityLevel, error) {
|
||||||
|
for _, expr := range opts.Elts {
|
||||||
|
kv, ok := expr.(*ast.KeyValueExpr)
|
||||||
|
if !ok {
|
||||||
|
return nil, newDecodeErrorf(expr, errPositionalArguments)
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%v", kv.Key)
|
||||||
|
if key != "StabilityLevel" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return decodeStabilityLevel(kv.Value, metricsFrameworkImportName)
|
||||||
|
}
|
||||||
|
stability := metrics.ALPHA
|
||||||
|
return &stability, nil
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2017 The Kubernetes Authors.
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,120 @@ limitations under the License.
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
metricFrameworkPath = `"k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"`
|
||||||
|
// Should equal to final directory name of metricFrameworkPath
|
||||||
|
defaultFrameworkImportName = "metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "USAGE: %s <DIR or FILE> [...]\n", os.Args[0])
|
||||||
|
os.Exit(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
stableMetrics := []metric{}
|
||||||
|
errors := []error{}
|
||||||
|
|
||||||
|
for _, arg := range flag.Args() {
|
||||||
|
ms, es := searchPathForStableMetrics(arg)
|
||||||
|
stableMetrics = append(stableMetrics, ms...)
|
||||||
|
errors = append(errors, es...)
|
||||||
|
}
|
||||||
|
for _, err := range errors {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
}
|
||||||
|
if len(errors) != 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
sort.Sort(byFQName(stableMetrics))
|
||||||
|
data, err := yaml.Marshal(stableMetrics)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Print(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchPathForStableMetrics(path string) ([]metric, []error) {
|
||||||
|
ms := []metric{}
|
||||||
|
errors := []error{}
|
||||||
|
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if strings.HasPrefix(path, "vendor") {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(path, ".go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ms, es := searchFileForStableMetrics(path, nil)
|
||||||
|
errors = append(errors, es...)
|
||||||
|
ms = append(ms, ms...)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
return ms, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass either only filename of existing file or src including source code in any format and a filename that it comes from
|
||||||
|
func searchFileForStableMetrics(filename string, src interface{}) ([]metric, []error) {
|
||||||
|
fileset := token.NewFileSet()
|
||||||
|
tree, err := parser.ParseFile(fileset, filename, src, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
return []metric{}, []error{err}
|
||||||
|
}
|
||||||
|
metricsImportName, err := getMetricsFrameworkImportName(tree)
|
||||||
|
if err != nil {
|
||||||
|
return []metric{}, addFileInformationToErrors([]error{err}, fileset)
|
||||||
|
}
|
||||||
|
if metricsImportName == "" {
|
||||||
|
return []metric{}, []error{}
|
||||||
|
}
|
||||||
|
|
||||||
|
stableMetricsFunctionCalls, errors := findStableMetricDeclaration(tree, metricsImportName)
|
||||||
|
metrics, es := decodeMetricCalls(stableMetricsFunctionCalls, metricsImportName)
|
||||||
|
errors = append(errors, es...)
|
||||||
|
return metrics, addFileInformationToErrors(errors, fileset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMetricsFrameworkImportName(tree *ast.File) (string, error) {
|
||||||
|
var importName string
|
||||||
|
for _, im := range tree.Imports {
|
||||||
|
if im.Path.Value == metricFrameworkPath {
|
||||||
|
if im.Name == nil {
|
||||||
|
importName = defaultFrameworkImportName
|
||||||
|
} else {
|
||||||
|
if im.Name.Name == "." {
|
||||||
|
return "", newDecodeErrorf(im, errImport)
|
||||||
|
}
|
||||||
|
importName = im.Name.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return importName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFileInformationToErrors(es []error, fileset *token.FileSet) []error {
|
||||||
|
for i := range es {
|
||||||
|
if de, ok := es[i].(*decodeError); ok {
|
||||||
|
es[i] = de.errorWithFileInformation(fileset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return es
|
||||||
}
|
}
|
||||||
|
493
test/instrumentation/main_test.go
Normal file
493
test/instrumentation/main_test.go
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const fakeFilename = "testdata/metric.go"
|
||||||
|
|
||||||
|
func TestSkipMetrics(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
testName string
|
||||||
|
src string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "Skip alpha metric with local variable",
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var name = "metric"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: name,
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Skip alpha metric created via function call",
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
func getName() string {
|
||||||
|
return "metric"
|
||||||
|
}
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: getName(),
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Skip metric without stability set",
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: "metric",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Skip functions of similar signature (not imported from framework path) with import rename",
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import metrics "k8s.io/fake/path"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Skip functions of similar signature (not imported from framework path)",
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/fake/path/metrics"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Skip . package import of non metric framework",
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import . "k8s.io/fake/path"
|
||||||
|
var _ = NewCounter(
|
||||||
|
&CounterOpts{
|
||||||
|
StabilityLevel: STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
} {
|
||||||
|
t.Run(test.testName, func(t *testing.T) {
|
||||||
|
metrics, errors := searchFileForStableMetrics(fakeFilename, test.src)
|
||||||
|
if len(metrics) != 0 {
|
||||||
|
t.Errorf("Didn't expect any stable metrics found, got: %d", len(metrics))
|
||||||
|
}
|
||||||
|
if len(errors) != 0 {
|
||||||
|
t.Errorf("Didn't expect any errors found, got: %s", errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStableMetric(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
testName string
|
||||||
|
src string
|
||||||
|
metric metric
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "Counter",
|
||||||
|
metric: metric{
|
||||||
|
Name: "metric",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Help: "help",
|
||||||
|
Type: counterMetricType,
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: "metric",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Help: "help",
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "CounterVec",
|
||||||
|
metric: metric{
|
||||||
|
Name: "metric",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Labels: []string{"label-1"},
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Help: "help",
|
||||||
|
Type: counterMetricType,
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounterVec(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: "metric",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "help",
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"label-1"},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Gauge",
|
||||||
|
metric: metric{
|
||||||
|
Name: "gauge",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Help: "help",
|
||||||
|
Type: gaugeMetricType,
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewGauge(
|
||||||
|
&metrics.GaugeOpts{
|
||||||
|
Name: "gauge",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "help",
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "GaugeVec",
|
||||||
|
metric: metric{
|
||||||
|
Name: "gauge",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Help: "help",
|
||||||
|
Type: gaugeMetricType,
|
||||||
|
Labels: []string{"label-1", "label-2"},
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewGaugeVec(
|
||||||
|
&metrics.GaugeOpts{
|
||||||
|
Name: "gauge",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
Help: "help",
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
[]string{"label-2", "label-1"},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Histogram",
|
||||||
|
metric: metric{
|
||||||
|
Name: "histogram",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Buckets: []float64{0.001, 0.01, 0.1, 1, 10, 100},
|
||||||
|
Help: "help",
|
||||||
|
Type: histogramMetricType,
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewHistogram(
|
||||||
|
&metrics.HistogramOpts{
|
||||||
|
Name: "histogram",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
Help: "help",
|
||||||
|
Buckets: []float64{0.001, 0.01, 0.1, 1, 10, 100},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "HistogramVec",
|
||||||
|
metric: metric{
|
||||||
|
Name: "histogram",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Buckets: []float64{0.001, 0.01, 0.1, 1, 10, 100},
|
||||||
|
Help: "help",
|
||||||
|
Type: histogramMetricType,
|
||||||
|
Labels: []string{"label-1", "label-2"},
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewHistogramVec(
|
||||||
|
&metrics.HistogramOpts{
|
||||||
|
Name: "histogram",
|
||||||
|
Namespace: "namespace",
|
||||||
|
Subsystem: "subsystem",
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
Help: "help",
|
||||||
|
Buckets: []float64{0.001, 0.01, 0.1, 1, 10, 100},
|
||||||
|
},
|
||||||
|
[]string{"label-2", "label-1"},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Custom import",
|
||||||
|
metric: metric{
|
||||||
|
Name: "metric",
|
||||||
|
StabilityLevel: "STABLE",
|
||||||
|
Type: counterMetricType,
|
||||||
|
},
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import custom "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = custom.NewCounter(
|
||||||
|
&custom.CounterOpts{
|
||||||
|
Name: "metric",
|
||||||
|
StabilityLevel: custom.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
} {
|
||||||
|
t.Run(test.testName, func(t *testing.T) {
|
||||||
|
metrics, errors := searchFileForStableMetrics(fakeFilename, test.src)
|
||||||
|
if len(errors) != 0 {
|
||||||
|
t.Errorf("Unexpected errors: %s", errors)
|
||||||
|
}
|
||||||
|
if len(metrics) != 1 {
|
||||||
|
t.Fatalf("Unexpected number of metrics: got %d, want 1", len(metrics))
|
||||||
|
}
|
||||||
|
if test.metric.Labels == nil {
|
||||||
|
test.metric.Labels = []string{}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(metrics[0], test.metric) {
|
||||||
|
t.Errorf("metric:\ngot %v\nwant %v", metrics[0], test.metric)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncorrectStableMetricDeclarations(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
testName string
|
||||||
|
src string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "Fail on stable summary metric (Summary is DEPRECATED)",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:4:9: Stable summary metric is not supported"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewSummary(
|
||||||
|
&metrics.SummaryOpts{
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Fail on stable metric with attribute set to variable",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:7:4: Non string attribute it not supported"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
const name = "metric"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: name,
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Fail on stable metric with attribute set to local function return",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:9:4: Non string attribute it not supported"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
func getName() string {
|
||||||
|
return "metric"
|
||||||
|
}
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: getName(),
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Fail on stable metric with attribute set to imported function return",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:7:4: Non string attribute it not supported"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
import "k8s.io/kubernetes/utils"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Name: utils.getMetricName(),
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "Fail on metric with stability set to function return",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:9:20: StabilityLevel should be passed STABLE, ALPHA or removed"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
func getMetricStability() metrics.StabilityLevel {
|
||||||
|
return metrics.STABLE
|
||||||
|
}
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: getMetricsStability(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error for passing stability as string",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:6:20: StabilityLevel should be passed STABLE, ALPHA or removed"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: "stable",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error for passing stability as unknown const",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:6:20: StabilityLevel should be passed STABLE, ALPHA or removed"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: metrics.UNKNOWN,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error for passing stability as variable",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:7:20: StabilityLevel should be passed STABLE, ALPHA or removed"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var stable = metrics.STABLE
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: stable,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error for stable metric created via function call",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:6:10: Opts for STABLE metric was not directly passed to new metric function"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounter(getStableCounterOpts())
|
||||||
|
func getStableCounterOpts() *metrics.CounterOpts {
|
||||||
|
return &metrics.CounterOpts{
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error . package import of metric framework",
|
||||||
|
err: fmt.Errorf(`testdata/metric.go:3:8: Importing through "." metrics framework is not supported`),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import . "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = NewCounter(
|
||||||
|
&CounterOpts{
|
||||||
|
StabilityLevel: STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error stable metric opts passed to local function",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:4:9: Opts for STABLE metric was not directly passed to new metric function"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = RegisterMetric(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error stable metric opts passed to imported function",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:4:9: Opts for STABLE metric was not directly passed to new metric function"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = test.RegisterMetric(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
StabilityLevel: metrics.STABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
{
|
||||||
|
testName: "error stable metric opts passed to imported function",
|
||||||
|
err: fmt.Errorf("testdata/metric.go:6:4: Positional arguments are not supported"),
|
||||||
|
src: `
|
||||||
|
package test
|
||||||
|
import "k8s.io/kubernetes/staging/src/k8s.io/component-base/metrics"
|
||||||
|
var _ = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
"counter",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
`},
|
||||||
|
} {
|
||||||
|
t.Run(test.testName, func(t *testing.T) {
|
||||||
|
_, errors := searchFileForStableMetrics(fakeFilename, test.src)
|
||||||
|
if len(errors) != 1 {
|
||||||
|
t.Fatalf("Unexpected number of errors, got %d, want 1", len(errors))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(errors[0], test.err) {
|
||||||
|
t.Errorf("error:\ngot %v\nwant %v", errors[0], test.err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
53
test/instrumentation/metric.go
Normal file
53
test/instrumentation/metric.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
counterMetricType = "Counter"
|
||||||
|
gaugeMetricType = "Gauge"
|
||||||
|
histogramMetricType = "Histogram"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metric struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Subsystem string `yaml:"subsystem,omitempty"`
|
||||||
|
Namespace string `yaml:"namespace,omitempty"`
|
||||||
|
Help string `yaml:"help,omitempty"`
|
||||||
|
Type string `yaml:"type,omitempty"`
|
||||||
|
DeprecatedVersion string `yaml:"deprecatedVersion,omitempty"`
|
||||||
|
StabilityLevel string `yaml:"stabilityLevel,omitempty"`
|
||||||
|
Labels []string `yaml:"labels,omitempty"`
|
||||||
|
Buckets []float64 `yaml:"buckets,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m metric) buildFQName() string {
|
||||||
|
return prometheus.BuildFQName(m.Namespace, m.Subsystem, m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type byFQName []metric
|
||||||
|
|
||||||
|
func (ms byFQName) Len() int { return len(ms) }
|
||||||
|
func (ms byFQName) Less(i, j int) bool {
|
||||||
|
return ms[i].buildFQName() < ms[j].buildFQName()
|
||||||
|
}
|
||||||
|
func (ms byFQName) Swap(i, j int) {
|
||||||
|
ms[i], ms[j] = ms[j], ms[i]
|
||||||
|
}
|
1
test/instrumentation/testdata/stable-metrics-list.yaml
vendored
Normal file
1
test/instrumentation/testdata/stable-metrics-list.yaml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
24
test/instrumentation/update-stable-metrics.sh
Executable file
24
test/instrumentation/update-stable-metrics.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/../..
|
||||||
|
|
||||||
|
bazel build //test/instrumentation:list_stable_metrics
|
||||||
|
cp "$KUBE_ROOT/bazel-genfiles/test/instrumentation/stable-metrics-list.yaml" "$KUBE_ROOT/test/instrumentation/testdata/stable-metrics-list.yaml"
|
35
test/instrumentation/verify-stable-metrics.sh
Executable file
35
test/instrumentation/verify-stable-metrics.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2019 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/../..
|
||||||
|
|
||||||
|
# detect if run from bazel
|
||||||
|
if [ -z "${TEST_BINARY}" ]; then
|
||||||
|
bazel build //test/instrumentation:list_stable_metrics
|
||||||
|
OUTPUT_FILE="$KUBE_ROOT/bazel-genfiles/test/instrumentation/stable-metrics-list.yaml"
|
||||||
|
else
|
||||||
|
OUTPUT_FILE="$KUBE_ROOT/test/instrumentation/stable-metrics-list.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if diff -u "$KUBE_ROOT/test/instrumentation/testdata/stable-metrics-list.yaml" "$OUTPUT_FILE"; then
|
||||||
|
echo PASS
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo 'Diffs in stable metrics detected, please run "test/instrumentation/update-stable-metrics.sh"'
|
||||||
|
exit 1
|
Loading…
Reference in New Issue
Block a user