diff --git a/internal/store/pod.go b/internal/store/pod.go index 09a83d171b..e04f63f09c 100644 --- a/internal/store/pod.go +++ b/internal/store/pod.go @@ -882,11 +882,12 @@ func createPodInitContainerStatusReadyFamilyGenerator() generator.FamilyGenerato } func createPodInitContainerStatusRestartsTotalFamilyGenerator() generator.FamilyGenerator { - return *generator.NewFamilyGeneratorWithStability( + return *generator.NewFamilyGeneratorWithStabilityV2( "kube_pod_init_container_status_restarts_total", "The number of restarts for the init container.", metric.Counter, basemetrics.STABLE, "", + []string{"container"}, wrapPodFunc(func(p *v1.Pod) *metric.Family { ms := make([]*metric.Metric, len(p.Status.InitContainerStatuses)) diff --git a/pkg/metric_generator/generator.go b/pkg/metric_generator/generator.go index 2a2e30221f..70f9e46f71 100644 --- a/pkg/metric_generator/generator.go +++ b/pkg/metric_generator/generator.go @@ -39,6 +39,24 @@ type FamilyGenerator struct { GenerateFunc func(obj interface{}) *metric.Family } +// NewFamilyGeneratorWithStabilityV2 creates new FamilyGenerator instances with metric +// stabilityLevel and explicit labels. These explicit labels are used for verifying stable metrics. +func NewFamilyGeneratorWithStabilityV2(name string, help string, metricType metric.Type, stabilityLevel basemetrics.StabilityLevel, deprecatedVersion string, labels []string, generateFunc func(obj interface{}) *metric.Family) *FamilyGenerator { + f := &FamilyGenerator{ + Name: name, + Type: metricType, + Help: help, + OptIn: false, + StabilityLevel: stabilityLevel, + DeprecatedVersion: deprecatedVersion, + GenerateFunc: WrapLabels(generateFunc, labels), + } + if deprecatedVersion != "" { + f.Help = fmt.Sprintf("(Deprecated since %s) %s", deprecatedVersion, help) + } + return f +} + // NewFamilyGeneratorWithStability creates new FamilyGenerator instances with metric // stabilityLevel. func NewFamilyGeneratorWithStability(name string, help string, metricType metric.Type, stabilityLevel basemetrics.StabilityLevel, deprecatedVersion string, generateFunc func(obj interface{}) *metric.Family) *FamilyGenerator { @@ -57,6 +75,19 @@ func NewFamilyGeneratorWithStability(name string, help string, metricType metric return f } +func WrapLabels(generateFunc func(obj interface{}) *metric.Family, labels []string) func(obj interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + + metricFamily := generateFunc(obj) + + for _, m := range metricFamily.Metrics { + m.LabelKeys = append(labels, m.LabelKeys...) + } + + return metricFamily + } +} + // NewOptInFamilyGenerator creates new FamilyGenerator instances for opt-in metric families. func NewOptInFamilyGenerator(name string, help string, metricType metric.Type, stabilityLevel basemetrics.StabilityLevel, deprecatedVersion string, generateFunc func(obj interface{}) *metric.Family) *FamilyGenerator { f := NewFamilyGeneratorWithStability(name, help, metricType, stabilityLevel, diff --git a/tests/stablemetrics/decode_metric.go b/tests/stablemetrics/decode_metric.go index 1668511491..f323a815e9 100644 --- a/tests/stablemetrics/decode_metric.go +++ b/tests/stablemetrics/decode_metric.go @@ -17,13 +17,9 @@ limitations under the License. package main import ( - "fmt" "go/ast" "go/token" - "sort" - "strconv" "strings" - "time" "k8s.io/component-base/metrics" ) @@ -56,108 +52,51 @@ type metricDecoder struct { func (c *metricDecoder) decodeNewMetricCall(fc *ast.CallExpr) (*metric, error) { var m metric var err error - se, ok := fc.Fun.(*ast.SelectorExpr) - if !ok { - // account for timing ratio histogram functions - switch v := fc.Fun.(type) { - case *ast.Ident: - if v.Name == "NewTimingRatioHistogramVec" { - m, err = c.decodeMetricVecForTimingRatioHistogram(fc) - m.Type = timingRatioHistogram - return &m, err - } - } - return nil, newDecodeErrorf(fc, errNotDirectCall) - } - functionName := se.Sel.String() - functionImport, ok := se.X.(*ast.Ident) + + _, ok := fc.Fun.(*ast.SelectorExpr) if !ok { return nil, newDecodeErrorf(fc, errNotDirectCall) } - if functionImport.String() != c.kubeMetricsImportName { - return nil, nil - } - switch functionName { - case "NewCounter", "NewGauge", "NewHistogram", "NewSummary", "NewTimingHistogram", "NewGaugeFunc": - m, err = c.decodeMetric(fc) - case "NewCounterVec", "NewGaugeVec", "NewHistogramVec", "NewSummaryVec", "NewTimingHistogramVec": - m, err = c.decodeMetricVec(fc) - case "Labels", "HandlerOpts", "HandlerFor", "HandlerWithReset": - return nil, nil - case "NewDesc": - m, err = c.decodeDesc(fc) - 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 "NewDesc": - return customType - case "NewCounter", "NewCounterVec": - return counterMetricType - case "NewGauge", "NewGaugeVec", "NewGaugeFunc": - return gaugeMetricType - case "NewHistogram", "NewHistogramVec": - return histogramMetricType - case "NewSummary", "NewSummaryVec": - return summaryMetricType - case "NewTimingHistogram", "NewTimingHistogramVec", "NewTimingRatioHistogramVec": - return timingRatioHistogram - default: - panic("getMetricType expects correct function name") - } -} - -func (c *metricDecoder) decodeMetric(call *ast.CallExpr) (metric, error) { - if len(call.Args) > 2 { - return metric{}, newDecodeErrorf(call, errInvalidNewMetricCall) - } - return c.decodeOpts(call.Args[0]) + m, err = c.decodeDesc(fc) + return &m, err } func (c *metricDecoder) decodeDesc(ce *ast.CallExpr) (metric, error) { m := &metric{} + name, err := c.decodeString(ce.Args[0]) if err != nil { return *m, newDecodeErrorf(ce, errorDecodingString) } m.Name = *name + help, err := c.decodeString(ce.Args[1]) if err != nil { return *m, newDecodeErrorf(ce, errorDecodingString) } m.Help = *help - labels, err := c.decodeLabels(ce.Args[2]) - if err != nil { - return *m, newDecodeErrorf(ce, errorDecodingLabels) - } - m.Labels = labels - cLabels, err := c.decodeConstLabels(ce.Args[3]) + + metricType, err := decodeStabilityLevel(ce.Args[2], "metric") if err != nil { - return *m, newDecodeErrorf(ce, "can't decode const labels") + return *m, newDecodeErrorf(ce, errorDecodingString) } - m.ConstLabels = cLabels - sl, err := decodeStabilityLevel(ce.Args[4], c.kubeMetricsImportName) + m.Type = string(*metricType) + + sl, err := decodeStabilityLevel(ce.Args[3], "basemetrics") if err != nil { return *m, newDecodeErrorf(ce, "can't decode stability level") } + if sl != nil { m.StabilityLevel = string(*sl) } - deprecatedVersion, err := c.decodeString(ce.Args[5]) + + labels, err := c.decodeLabels(ce.Args[5]) if err != nil { - return *m, newDecodeErrorf(ce, errorDecodingString) - } - if deprecatedVersion != nil { - m.DeprecatedVersion = *deprecatedVersion + return *m, newDecodeErrorf(ce, errorDecodingLabels) } + m.Labels = labels + return *m, nil } @@ -169,121 +108,10 @@ func (c *metricDecoder) decodeString(expr ast.Expr) (*string, error) { return nil, err } return &value, nil - case *ast.CallExpr: - firstArg, secondArg, thirdArg, err := c.decodeBuildFQNameArguments(e) - if err != nil { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - se, ok := e.Fun.(*ast.SelectorExpr) - if ok { - functionName := se.Sel.Name - switch functionName { - case "BuildFQName": - n := metrics.BuildFQName(firstArg, secondArg, thirdArg) - return &n, nil - } - } - case *ast.Ident: - variableExpr, found := c.variables[e.Name] - if !found { - return nil, newDecodeErrorf(expr, errBadVariableAttribute) - } - bl, ok := variableExpr.(*ast.BasicLit) - if !ok { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - value, err := stringValue(bl) - if err != nil { - return nil, err - } - return &value, nil - case *ast.SelectorExpr: - s, ok := e.X.(*ast.Ident) - if !ok { - return nil, newDecodeErrorf(expr, errExprNotIdent, e.X) - } - variableExpr, found := c.variables[strings.Join([]string{s.Name, e.Sel.Name}, ".")] - if !found { - return nil, newDecodeErrorf(expr, errBadImportedVariableAttribute) - } - bl, ok := variableExpr.(*ast.BasicLit) - if !ok { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - value, err := stringValue(bl) - if err != nil { - return nil, err - } - return &value, nil - case *ast.BinaryExpr: - var binaryExpr *ast.BinaryExpr - binaryExpr = e - var okay bool - var value string - okay = true - - for okay { - yV, okay := binaryExpr.Y.(*ast.BasicLit) - if !okay { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - yVal, err := stringValue(yV) - if err != nil { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - value = fmt.Sprintf("%s%s", yVal, value) - x, okay := binaryExpr.X.(*ast.BinaryExpr) - if !okay { - // should be basicLit - xV, okay := binaryExpr.X.(*ast.BasicLit) - if !okay { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - xVal, err := stringValue(xV) - if err != nil { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - value = fmt.Sprintf("%s%s", xVal, value) - break - } - binaryExpr = x - } - return &value, nil } return nil, newDecodeErrorf(expr, errorDecodingString) } -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 := c.decodeLabels(call.Args[1]) - if err != nil { - return m, err - } - sort.Strings(labels) - m.Labels = labels - return m, nil -} - -func (c *metricDecoder) decodeMetricVecForTimingRatioHistogram(call *ast.CallExpr) (metric, error) { - m, err := c.decodeOpts(call.Args[0]) - if err != nil { - return m, err - } - labels, err := c.decodeLabelsFromArray(call.Args[1:]) - if err != nil { - return m, err - } - sort.Strings(labels) - m.Labels = labels - return m, nil -} - func (c *metricDecoder) decodeLabelsFromArray(exprs []ast.Expr) ([]string, error) { retval := []string{} for _, e := range exprs { @@ -319,99 +147,6 @@ func (c *metricDecoder) decodeLabels(expr ast.Expr) ([]string, error) { return c.decodeLabelsFromArray(cl.Elts) } -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", "DeprecatedVersion": - var value string - var err error - s, err := c.decodeString(kv.Value) - if err != nil { - return m, newDecodeErrorf(expr, err.Error()) - } - value = *s - switch key { - case "Namespace": - m.Namespace = value - case "Subsystem": - m.Subsystem = value - case "Name": - m.Name = value - case "DeprecatedVersion": - m.DeprecatedVersion = value - case "Help": - m.Help = value - } - case "Buckets": - buckets, err := c.decodeBuckets(kv.Value) - if err != nil { - return m, err - } - sort.Float64s(buckets) - m.Buckets = buckets - case "StabilityLevel": - level, err := decodeStabilityLevel(kv.Value, c.kubeMetricsImportName) - if err != nil { - return m, err - } - m.StabilityLevel = string(*level) - case "ConstLabels": - labels, err := c.decodeConstLabels(kv.Value) - if err != nil { - return m, err - } - m.ConstLabels = labels - case "AgeBuckets", "BufCap": - uintVal, err := c.decodeUint32(kv.Value) - if err != nil { - print(key) - return m, err - } - if key == "AgeBuckets" { - m.AgeBuckets = uintVal - } - if key == "BufCap" { - m.BufCap = uintVal - } - - case "Objectives": - obj, err := c.decodeObjectives(kv.Value) - if err != nil { - print(key) - return m, err - } - m.Objectives = obj - case "MaxAge": - int64Val, err := c.decodeInt64(kv.Value) - if err != nil { - return m, err - } - m.MaxAge = int64Val - default: - return m, newDecodeErrorf(expr, errFieldNotSupported, key) - } - } - return m, nil -} - func stringValue(bl *ast.BasicLit) (string, error) { if bl.Kind != token.STRING { return "", newDecodeErrorf(bl, errNonStringAttribute) @@ -419,367 +154,6 @@ func stringValue(bl *ast.BasicLit) (string, error) { return strings.Trim(bl.Value, `"`), nil } -func (c *metricDecoder) decodeBuckets(expr ast.Expr) ([]float64, error) { - switch v := expr.(type) { - case *ast.Ident: - variableExpr, found := c.variables[v.Name] - if !found { - return nil, newDecodeErrorf(v, "couldn't find variable for bucket") - } - switch v2 := variableExpr.(type) { - case *ast.CompositeLit: - return decodeListOfFloats(v2, v2.Elts) - case *ast.CallExpr: - float64s, err2, done := c.decodeBucketFunctionCall(v2) - if done { - return float64s, err2 - } - default: - return nil, newDecodeErrorf(v, errorFindingVariableForBuckets) - } - - case *ast.CompositeLit: - return decodeListOfFloats(v, v.Elts) - case *ast.SelectorExpr: - variableName := v.Sel.String() - importName, ok := v.X.(*ast.Ident) - if ok && importName.String() == c.kubeMetricsImportName && variableName == "DefBuckets" { - return metrics.DefBuckets, nil - } - case *ast.CallExpr: - float64s, err2, done := c.decodeBucketFunctionCall(v) - if done { - return float64s, err2 - } - } - return nil, newDecodeErrorf(expr, errBuckets) -} - -func (c *metricDecoder) decodeBucketFunctionCall(v *ast.CallExpr) ([]float64, error, bool) { - se, ok := v.Fun.(*ast.SelectorExpr) - if !ok { - // support merged - if ai, ok := v.Fun.(*ast.Ident); ok && ai.Name == "merge" { - merged := []float64{} - for _, arg := range v.Args { - v2, ok := arg.(*ast.CallExpr) - if !ok { - return nil, newDecodeErrorf(v2, errBuckets), true - } - se, ok = v2.Fun.(*ast.SelectorExpr) - if ok { - functionName := se.Sel.String() - functionImport, ok := se.X.(*ast.Ident) - if !ok { - return nil, newDecodeErrorf(v, errBuckets), true - } - if functionImport.String() != c.kubeMetricsImportName { - return nil, newDecodeErrorf(v, errBuckets), true - } - firstArg, secondArg, thirdArg, err := decodeBucketArguments(v2) - if err != nil { - return nil, newDecodeErrorf(v, errBuckets), true - } - switch functionName { - case "LinearBuckets": - merged = append(merged, metrics.LinearBuckets(firstArg, secondArg, thirdArg)...) - case "ExponentialBuckets": - merged = append(merged, metrics.ExponentialBuckets(firstArg, secondArg, thirdArg)...) - case "ExponentialBucketsRange": - merged = append(merged, metrics.ExponentialBuckets(firstArg, secondArg, thirdArg)...) - // merged = append(merged, metrics.ExponentialBucketsRange(firstArg, secondArg, thirdArg)...) - } - } - } - return merged, nil, true - } - return nil, newDecodeErrorf(v, errBuckets), true - } - functionName := se.Sel.String() - functionImport, ok := se.X.(*ast.Ident) - if !ok { - return nil, newDecodeErrorf(v, errBuckets), true - } - if functionImport.String() != c.kubeMetricsImportName { - return nil, newDecodeErrorf(v, errBuckets), true - } - switch functionName { - case "LinearBuckets": - firstArg, secondArg, thirdArg, err := decodeBucketArguments(v) - if err != nil { - return nil, err, true - } - return metrics.LinearBuckets(firstArg, secondArg, thirdArg), nil, true - case "ExponentialBuckets": - firstArg, secondArg, thirdArg, err := decodeBucketArguments(v) - if err != nil { - return nil, err, true - } - return metrics.ExponentialBuckets(firstArg, secondArg, thirdArg), nil, true - case "ExponentialBucketsRange": - firstArg, secondArg, thirdArg, err := decodeBucketArguments(v) - if err != nil { - return nil, err, true - } - return metrics.ExponentialBuckets(firstArg, secondArg, thirdArg), nil, true - // return metrics.ExponentialBucketsRange(firstArg, secondArg, thirdArg), nil, true - case "MergeBuckets": - merged := []float64{} - for _, arg := range v.Args { - switch argExpr := arg.(type) { - case *ast.CompositeLit: - fs, err := decodeListOfFloats(argExpr, argExpr.Elts) - if err != nil { - return nil, newDecodeErrorf(v, errBuckets), true - } - merged = append(merged, fs...) - case *ast.CallExpr: - se, ok = argExpr.Fun.(*ast.SelectorExpr) - if ok { - functionName := se.Sel.String() - functionImport, ok := se.X.(*ast.Ident) - if !ok { - return nil, newDecodeErrorf(v, errBuckets), true - } - if functionImport.String() != c.kubeMetricsImportName { - return nil, newDecodeErrorf(v, errBuckets), true - } - firstArg, secondArg, thirdArg, err := decodeBucketArguments(argExpr) - if err != nil { - return nil, newDecodeErrorf(v, errBuckets), true - } - switch functionName { - case "LinearBuckets": - merged = append(merged, metrics.LinearBuckets(firstArg, secondArg, thirdArg)...) - case "ExponentialBuckets": - merged = append(merged, metrics.LinearBuckets(firstArg, secondArg, thirdArg)...) - } - } - } - } - return merged, nil, true - } - return nil, nil, false -} - -func (c *metricDecoder) decodeObjectives(expr ast.Expr) (map[float64]float64, error) { - switch v := expr.(type) { - case *ast.CompositeLit: - return decodeFloatMap(v.Elts) - case *ast.Ident: - variableExpr, found := c.variables[v.Name] - if !found { - return nil, newDecodeErrorf(expr, errBadVariableAttribute) - } - return decodeFloatMap(variableExpr.(*ast.CompositeLit).Elts) - } - return nil, newDecodeErrorf(expr, errObjectives) -} - -func (c *metricDecoder) decodeUint32(expr ast.Expr) (uint32, error) { - switch v := expr.(type) { - case *ast.BasicLit: - if v.Kind != token.FLOAT && v.Kind != token.INT { - print(v.Kind) - } - value, err := strconv.ParseUint(v.Value, 10, 32) - if err != nil { - return 0, err - } - return uint32(value), nil - case *ast.SelectorExpr: - variableName := v.Sel.String() - importName, ok := v.X.(*ast.Ident) - if ok && importName.String() == c.kubeMetricsImportName { - if variableName == "DefAgeBuckets" { - // hardcode this for now - return metrics.DefAgeBuckets, nil - } - if variableName == "DefBufCap" { - // hardcode this for now - return metrics.DefBufCap, nil - } - } - case *ast.CallExpr: - _, ok := v.Fun.(*ast.SelectorExpr) - if !ok { - return 0, newDecodeErrorf(v, errDecodeUint32) - } - return 0, nil - } - return 0, newDecodeErrorf(expr, errDecodeUint32) -} - -func (c *metricDecoder) decodeInt64(expr ast.Expr) (int64, error) { - switch v := expr.(type) { - case *ast.BasicLit: - if v.Kind != token.FLOAT && v.Kind != token.INT { - print(v.Kind) - } - - value, err := strconv.ParseInt(v.Value, 10, 64) - if err != nil { - return 0, err - } - return value, nil - case *ast.SelectorExpr: - variableName := v.Sel.String() - importName, ok := v.X.(*ast.Ident) - if ok && importName.String() == c.kubeMetricsImportName { - if variableName == "DefMaxAge" { - // hardcode this for now. This is a duration, but we'll output it as - // an int64 representing nanoseconds. - return int64(metrics.DefMaxAge), nil - } - } - case *ast.Ident: - variableExpr, found := c.variables[v.Name] - if found { - be, ok := variableExpr.(*ast.BinaryExpr) - if ok { - i, err2, done := c.extractTimeExpression(be) - if done { - return i, err2 - } - } - } - case *ast.CallExpr: - _, ok := v.Fun.(*ast.SelectorExpr) - if !ok { - return 0, newDecodeErrorf(v, errDecodeInt64) - } - return 0, nil - case *ast.BinaryExpr: - i, err2, done := c.extractTimeExpression(v) - if done { - return i, err2 - } - } - return 0, newDecodeErrorf(expr, errDecodeInt64) -} - -func (c *metricDecoder) extractTimeExpression(v *ast.BinaryExpr) (int64, error, bool) { - x := v.X.(*ast.BasicLit) - if x.Kind != token.FLOAT && x.Kind != token.INT { - print(x.Kind) - } - - xValue, err := strconv.ParseInt(x.Value, 10, 64) - if err != nil { - return 0, err, true - } - - switch y := v.Y.(type) { - case *ast.SelectorExpr: - variableName := y.Sel.String() - importName, ok := y.X.(*ast.Ident) - if ok && importName.String() == "time" { - if variableName == "Hour" { - return xValue * int64(time.Hour), nil, true - } - if variableName == "Minute" { - return xValue * int64(time.Minute), nil, true - } - if variableName == "Second" { - return xValue * int64(time.Second), nil, true - } - } - } - return 0, nil, false -} - -func decodeFloatMap(exprs []ast.Expr) (map[float64]float64, error) { - buckets := map[float64]float64{} - for _, elt := range exprs { - bl, ok := elt.(*ast.KeyValueExpr) - if !ok { - return nil, newDecodeErrorf(bl, errObjectives) - } - keyExpr, ok := bl.Key.(*ast.BasicLit) - if !ok { - return nil, newDecodeErrorf(bl, errObjectives) - } - valueExpr, ok := bl.Value.(*ast.BasicLit) - if !ok { - return nil, newDecodeErrorf(bl, errObjectives) - } - valueForKey, err := strconv.ParseFloat(keyExpr.Value, 64) - if err != nil { - return nil, newDecodeErrorf(bl, errObjectives) - } - valueForValue, err := strconv.ParseFloat(valueExpr.Value, 64) - if err != nil { - return nil, newDecodeErrorf(bl, errObjectives) - } - buckets[valueForKey] = valueForValue - } - return buckets, nil -} - -func decodeListOfFloats(expr ast.Expr, exprs []ast.Expr) ([]float64, error) { - buckets := make([]float64, len(exprs)) - for i, elt := range exprs { - bl, ok := elt.(*ast.BasicLit) - if !ok { - return nil, newDecodeErrorf(expr, 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 decodeBucketArguments(fc *ast.CallExpr) (float64, float64, int, error) { - if len(fc.Args) != 3 { - return 0, 0, 0, newDecodeErrorf(fc, errBuckets) - } - strArgs := make([]string, len(fc.Args)) - for i, elt := range fc.Args { - bl, ok := elt.(*ast.BasicLit) - if !ok { - return 0, 0, 0, newDecodeErrorf(bl, errBuckets) - } - if bl.Kind != token.FLOAT && bl.Kind != token.INT { - return 0, 0, 0, newDecodeErrorf(bl, errBuckets) - } - strArgs[i] = bl.Value - } - firstArg, err := strconv.ParseFloat(strArgs[0], 64) - if err != nil { - return 0, 0, 0, newDecodeErrorf(fc.Args[0], errBuckets) - } - secondArg, err := strconv.ParseFloat(strArgs[1], 64) - if err != nil { - return 0, 0, 0, newDecodeErrorf(fc.Args[1], errBuckets) - } - thirdArg, err := strconv.ParseInt(strArgs[2], 10, 64) - if err != nil { - return 0, 0, 0, newDecodeErrorf(fc.Args[2], errBuckets) - } - - return firstArg, secondArg, int(thirdArg), nil -} -func (c *metricDecoder) decodeBuildFQNameArguments(fc *ast.CallExpr) (string, string, string, error) { - if len(fc.Args) != 3 { - return "", "", "", newDecodeErrorf(fc, "can't decode fq name args") - } - strArgs := make([]string, len(fc.Args)) - for i, elt := range fc.Args { - s, err := c.decodeString(elt) - if err != nil || s == nil { - return "", "", "", newDecodeErrorf(fc, err.Error()) - } - strArgs[i] = *s - } - return strArgs[0], strArgs[1], strArgs[2], nil -} - func decodeStabilityLevel(expr ast.Expr, metricsFrameworkImportName string) (*metrics.StabilityLevel, error) { se, ok := expr.(*ast.SelectorExpr) if !ok { @@ -796,43 +170,3 @@ func decodeStabilityLevel(expr ast.Expr, metricsFrameworkImportName string) (*me stability := metrics.StabilityLevel(se.Sel.Name) return &stability, nil } - -func (c *metricDecoder) decodeConstLabels(expr ast.Expr) (map[string]string, error) { - retval := map[string]string{} - switch v := expr.(type) { - case *ast.CompositeLit: - for _, e2 := range v.Elts { - kv := e2.(*ast.KeyValueExpr) - key := "" - switch k := kv.Key.(type) { - - case *ast.Ident: - variableExpr, found := c.variables[k.Name] - if !found { - return nil, newDecodeErrorf(expr, errBadVariableAttribute) - } - bl, ok := variableExpr.(*ast.BasicLit) - if !ok { - return nil, newDecodeErrorf(expr, errNonStringAttribute) - } - k2, err := stringValue(bl) - if err != nil { - return nil, err - } - key = k2 - case *ast.BasicLit: - k2, err := stringValue(k) - if err != nil { - return nil, err - } - key = k2 - } - val, err := stringValue(kv.Value.(*ast.BasicLit)) - if err != nil { - return nil, err - } - retval[key] = val - } - } - return retval, nil -} diff --git a/tests/stablemetrics/find_stable_metric.go b/tests/stablemetrics/find_stable_metric.go index a5975ecbb6..dc060efcc8 100644 --- a/tests/stablemetrics/find_stable_metric.go +++ b/tests/stablemetrics/find_stable_metric.go @@ -17,21 +17,9 @@ limitations under the License. package main import ( - "fmt" "go/ast" - - "k8s.io/component-base/metrics" ) -var metricsOptionStructuresNames = []string{ - "KubeOpts", - "CounterOpts", - "GaugeOpts", - "HistogramOpts", - "SummaryOpts", - "TimingHistogramOpts", -} - func findStableMetricDeclaration(tree ast.Node, metricsImportName string) ([]*ast.CallExpr, []error) { v := stableMetricFinder{ metricsImportName: metricsImportName, @@ -52,75 +40,15 @@ type stableMetricFinder struct { var _ ast.Visitor = (*stableMetricFinder)(nil) -func contains(v metrics.StabilityLevel, a []metrics.StabilityLevel) bool { - for _, i := range a { - if i == v { - return true - } - } - return false -} - func (f *stableMetricFinder) Visit(node ast.Node) (w ast.Visitor) { switch opts := node.(type) { case *ast.CallExpr: + f.currentFunctionCall = opts if se, ok := opts.Fun.(*ast.SelectorExpr); ok { - if se.Sel.Name == "NewDesc" { - sl, _ := decodeStabilityLevel(opts.Args[4], f.metricsImportName) - if sl != nil { - classes := []metrics.StabilityLevel{metrics.STABLE, metrics.BETA} - if ALL_STABILITY_CLASSES { - classes = append(classes, metrics.ALPHA) - } - switch { - case contains(*sl, classes): - f.stableMetricsFunctionCalls = append(f.stableMetricsFunctionCalls, opts) - f.currentFunctionCall = nil - default: - return nil - } - } - } else { - f.currentFunctionCall = opts + if se.Sel.Name == "NewFamilyGeneratorWithStabilityV2" { + f.stableMetricsFunctionCalls = append(f.stableMetricsFunctionCalls, opts) + f.currentFunctionCall = nil } - - } else { - 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 - } - classes := []metrics.StabilityLevel{metrics.STABLE, metrics.BETA} - if ALL_STABILITY_CLASSES { - classes = append(classes, metrics.ALPHA) - } - switch { - case contains(*stabilityLevel, classes): - if f.currentFunctionCall == nil { - f.errors = append(f.errors, newDecodeErrorf(opts, errNotDirectCall)) - return nil - } - f.stableMetricsFunctionCalls = append(f.stableMetricsFunctionCalls, f.currentFunctionCall) - f.currentFunctionCall = nil - default: - return nil } default: if f.currentFunctionCall == nil || node == nil || node.Pos() < f.currentFunctionCall.Rparen { @@ -130,30 +58,3 @@ func (f *stableMetricFinder) Visit(node ast.Node) (w ast.Visitor) { } 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 -} diff --git a/tests/stablemetrics/main.go b/tests/stablemetrics/main.go index 58c716ff00..1dae042c66 100644 --- a/tests/stablemetrics/main.go +++ b/tests/stablemetrics/main.go @@ -146,14 +146,8 @@ func searchFileForStableMetrics(filename string, src interface{}) ([]metric, []e return []metric{}, []error{} } variables := globalVariableDeclarations(tree) - - variables, err = importedGlobalVariableDeclaration(variables, tree.Imports) - if err != nil { - return []metric{}, addFileInformationToErrors([]error{err}, fileset) - } - - // fmt.Println("## tree", tree) stableMetricsFunctionCalls, errors := findStableMetricDeclaration(tree, metricsImportName) + metrics, es := decodeMetricCalls(stableMetricsFunctionCalls, metricsImportName, variables) errors = append(errors, es...) return metrics, addFileInformationToErrors(errors, fileset) @@ -202,107 +196,3 @@ func globalVariableDeclarations(tree *ast.File) map[string]ast.Expr { } return consts } - -func localImportPath(importExpr string) (string, error) { - // parse directory path - var pathPrefix string - if strings.Contains(importExpr, kubeURLRoot) { - // search k/k local checkout - pathPrefix = KUBE_ROOT - importExpr = strings.Replace(importExpr, kubeURLRoot, "", 1) - } else if strings.Contains(importExpr, "k8s.io/klog/v2") || strings.Contains(importExpr, "k8s.io/util") { - pathPrefix = strings.Join([]string{KUBE_ROOT, "vendor"}, string(os.PathSeparator)) - } else if strings.Contains(importExpr, "k8s.io/") { - // search k/k/staging local checkout - pathPrefix = strings.Join([]string{KUBE_ROOT, "staging", "src"}, string(os.PathSeparator)) - } else if strings.Contains(importExpr, ".") { - // not stdlib -> prefix with GOMODCACHE - // pathPrefix = strings.Join([]string{KUBE_ROOT, "vendor"}, string(os.PathSeparator)) - - // this requires implementing SIV, skip for now - return "", fmt.Errorf("unable to handle general, non STL imports for metric analysis. import path: %s", importExpr) - } else { - // stdlib -> prefix with GOROOT - pathPrefix = strings.Join([]string{GOROOT, "src"}, string(os.PathSeparator)) - } // ToDo: support non go mod - - crossPlatformImportExpr := strings.Replace(importExpr, "/", string(os.PathSeparator), -1) - importDirectory := strings.Join([]string{pathPrefix, strings.Trim(crossPlatformImportExpr, "\"")}, string(os.PathSeparator)) - - return importDirectory, nil -} - -func importedGlobalVariableDeclaration(localVariables map[string]ast.Expr, imports []*ast.ImportSpec) (map[string]ast.Expr, error) { - for _, im := range imports { - // get imported label - var importAlias string - if im.Name == nil { - pathSegments := strings.Split(im.Path.Value, "/") - importAlias = strings.Trim(pathSegments[len(pathSegments)-1], "\"") - } else { - importAlias = im.Name.String() - } - - // find local path on disk for listed import - importDirectory, err := localImportPath(im.Path.Value) - if err != nil { - // uncomment the below log line if you want to start using non k8s/non stl libs for resolving const/var in metric definitions - // fmt.Fprint(os.Stderr, err.Error() + "\n") - continue - } - - files, err := os.ReadDir(importDirectory) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to read import path directory %s with error %s, skipping\n", importDirectory, err) - continue - } - - for _, file := range files { - if file.IsDir() { - // do not grab constants from subpackages - continue - } - - if strings.Contains(file.Name(), "_test") { - // do not parse test files - continue - } - - if !strings.HasSuffix(file.Name(), ".go") { - // not a go code file, do not attempt to parse - continue - } - - fileset := token.NewFileSet() - tree, err := parser.ParseFile(fileset, strings.Join([]string{importDirectory, file.Name()}, string(os.PathSeparator)), nil, parser.AllErrors) - if err != nil { - return nil, fmt.Errorf("failed to parse path %s with error %w", im.Path.Value, err) - } - - // pass parsed filepath into globalVariableDeclarations - variables := globalVariableDeclarations(tree) - - // add returned map into supplied map and prepend import label to all keys - for k, v := range variables { - importK := strings.Join([]string{importAlias, k}, ".") - if _, ok := localVariables[importK]; !ok { - localVariables[importK] = v - } else { - // cross-platform file that gets included in the correct OS build via OS build tags - // use whatever matches GOOS - - if strings.Contains(file.Name(), GOOS) { - // assume at some point we will find the correct OS version of this file - // if we are running on an OS that does not have an OS specific file for something then we will include a constant we shouldn't - // TODO: should we include/exclude based on the build tags? - localVariables[importK] = v - } - - } - } - } - - } - - return localVariables, nil -} diff --git a/tests/stablemetrics/metric.go b/tests/stablemetrics/metric.go index 061596bcae..411a91e70c 100644 --- a/tests/stablemetrics/metric.go +++ b/tests/stablemetrics/metric.go @@ -20,15 +20,6 @@ import ( "k8s.io/component-base/metrics" ) -const ( - counterMetricType = "Counter" - gaugeMetricType = "Gauge" - histogramMetricType = "Histogram" - summaryMetricType = "Summary" - timingRatioHistogram = "TimingRatioHistogram" - customType = "Custom" -) - type metric struct { Name string `yaml:"name" json:"name"` Subsystem string `yaml:"subsystem,omitempty" json:"subsystem,omitempty"`