diff --git a/test/instrumentation/decode_metric.go b/test/instrumentation/decode_metric.go index 81bb878c4b3..2100e3cd6b3 100644 --- a/test/instrumentation/decode_metric.go +++ b/test/instrumentation/decode_metric.go @@ -24,6 +24,7 @@ import ( "sort" "strconv" "strings" + "time" "k8s.io/component-base/metrics" ) @@ -70,7 +71,7 @@ func (c *metricDecoder) decodeNewMetricCall(fc *ast.CallExpr) (*metric, error) { return nil, nil } switch functionName { - case "NewCounter", "NewGauge", "NewHistogram", "NewSummary", "NewTimingHistogram": + case "NewCounter", "NewGauge", "NewHistogram", "NewSummary", "NewTimingHistogram", "NewGaugeFunc": m, err = c.decodeMetric(fc) case "NewCounterVec", "NewGaugeVec", "NewHistogramVec", "NewSummaryVec", "NewTimingHistogramVec": m, err = c.decodeMetricVec(fc) @@ -90,7 +91,7 @@ func getMetricType(functionName string) string { switch functionName { case "NewCounter", "NewCounterVec": return counterMetricType - case "NewGauge", "NewGaugeVec": + case "NewGauge", "NewGaugeVec", "NewGaugeFunc": return gaugeMetricType case "NewHistogram", "NewHistogramVec": return histogramMetricType @@ -104,7 +105,7 @@ func getMetricType(functionName string) string { } func (c *metricDecoder) decodeMetric(call *ast.CallExpr) (metric, error) { - if len(call.Args) != 1 { + if len(call.Args) > 2 { return metric{}, newDecodeErrorf(call, errInvalidNewMetricCall) } return c.decodeOpts(call.Args[0]) @@ -326,7 +327,6 @@ func (c *metricDecoder) decodeOpts(expr ast.Expr) (metric, error) { case "MaxAge": int64Val, err := c.decodeInt64(kv.Value) if err != nil { - print(key) return m, err } m.MaxAge = int64Val @@ -462,16 +462,64 @@ func (c *metricDecoder) decodeInt64(expr ast.Expr) (int64, error) { return 1000 * 1000 * 1000 * 60 * 10, 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 { diff --git a/test/instrumentation/find_stable_metric.go b/test/instrumentation/find_stable_metric.go index 03605017946..41d3b23a610 100644 --- a/test/instrumentation/find_stable_metric.go +++ b/test/instrumentation/find_stable_metric.go @@ -29,6 +29,7 @@ var metricsOptionStructuresNames = []string{ "GaugeOpts", "HistogramOpts", "SummaryOpts", + "TimingHistogramOpts", } func findStableMetricDeclaration(tree ast.Node, metricsImportName string) ([]*ast.CallExpr, []error) { @@ -98,7 +99,7 @@ func (f *stableMetricFinder) Visit(node ast.Node) (w ast.Visitor) { func isMetricOps(name string) bool { var found = false for _, optsName := range metricsOptionStructuresNames { - if name != optsName { + if name == optsName { found = true break } diff --git a/test/instrumentation/testdata/pkg/kubelet/metrics/metrics.go b/test/instrumentation/testdata/pkg/kubelet/metrics/metrics.go index 45bbe077938..5a8181c6ab5 100644 --- a/test/instrumentation/testdata/pkg/kubelet/metrics/metrics.go +++ b/test/instrumentation/testdata/pkg/kubelet/metrics/metrics.go @@ -77,6 +77,8 @@ var ( defObjectives = map[float64]float64{0.5: 0.5, 0.75: 0.75} testBuckets = []float64{0, 0.5, 1.0} testLabels = []string{"a", "b", "c"} + maxAge = 2 * time.Minute + // NodeName is a Gauge that tracks the ode's name. The count is always 1. NodeName = metrics.NewGaugeVec( &metrics.GaugeOpts{ @@ -109,6 +111,29 @@ var ( }, testLabels, ) + // PodWorkerDuration is a Histogram that tracks the duration (in seconds) in takes to sync a single pod. + // Broken down by the operation type. + SummaryMaxAge = metrics.NewSummary( + &metrics.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: "max_age", + Help: "Duration in seconds to sync a single pod. Broken down by operation type: create, update, or sync", + StabilityLevel: metrics.BETA, + MaxAge: 2 * time.Hour, + }, + ) + + // PodWorkerDuration is a Histogram that tracks the duration (in seconds) in takes to sync a single pod. + // Broken down by the operation type. + SummaryMaxAgeConst = metrics.NewSummary( + &metrics.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: "max_age_const", + Help: "Duration in seconds to sync a single pod. Broken down by operation type: create, update, or sync", + StabilityLevel: metrics.BETA, + MaxAge: maxAge, + }, + ) // PodStartDuration is a Histogram that tracks the duration (in seconds) it takes for a single pod to go from pending to running. PodStartDuration = metrics.NewHistogram( &metrics.HistogramOpts{ @@ -481,6 +506,26 @@ func Register(collectors ...metrics.StableCollector) { for _, collector := range collectors { legacyregistry.CustomMustRegister(collector) } + legacyregistry.RawMustRegister(metrics.NewGaugeFunc( + &metrics.GaugeOpts{ + Subsystem: "kubelet", + Name: "certificate_manager_client_ttl_seconds", + Help: "Gauge of the TTL (time-to-live) of the Kubelet's client certificate. " + + "The value is in seconds until certificate expiry (negative if already expired). " + + "If client certificate is invalid or unused, the value will be +INF.", + StabilityLevel: metrics.BETA, + }, + func() float64 { + return 0 + }, + )) + _ = metrics.Labels{ + "probe_type": "1", + "container": "2", + "pod": "podName", + "namespace": "space", + "pod_uid": "123", + } }) } diff --git a/test/instrumentation/testdata/test-stable-metrics-list.yaml b/test/instrumentation/testdata/test-stable-metrics-list.yaml index 4fbe7ee07c1..d6cc66b59aa 100644 --- a/test/instrumentation/testdata/test-stable-metrics-list.yaml +++ b/test/instrumentation/testdata/test-stable-metrics-list.yaml @@ -1,3 +1,10 @@ +- name: certificate_manager_client_ttl_seconds + subsystem: kubelet + help: Gauge of the TTL (time-to-live) of the Kubelet's client certificate. The value + is in seconds until certificate expiry (negative if already expired). If client + certificate is invalid or unused, the value will be +INF. + type: Gauge + stabilityLevel: BETA - name: device_plugin_alloc_duration_seconds subsystem: kubelet help: Duration in seconds to serve a device plugin Allocation request. Broken down @@ -18,6 +25,20 @@ - 2.5 - 5 - 10 +- name: max_age + subsystem: kubelet + help: 'Duration in seconds to sync a single pod. Broken down by operation type: + create, update, or sync' + type: Summary + stabilityLevel: BETA + maxAge: 7200000000000 +- name: max_age_const + subsystem: kubelet + help: 'Duration in seconds to sync a single pod. Broken down by operation type: + create, update, or sync' + type: Summary + stabilityLevel: BETA + maxAge: 120000000000 - name: multiline subsystem: kubelet help: Cumulative number of pod preemptions by preemption resource asdf asdf asdf