...

Source file src/k8s.io/component-base/metrics/testutil/promlint.go

Documentation: k8s.io/component-base/metrics/testutil

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package testutil
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"strings"
    23  
    24  	"github.com/prometheus/client_golang/prometheus/testutil/promlint"
    25  )
    26  
    27  // exceptionMetrics is an exception list of metrics which violates promlint rules.
    28  //
    29  // The original entries come from the existing metrics when we introduce promlint.
    30  // We setup this list for allow and not fail on the current violations.
    31  // Generally speaking, you need to fix the problem for a new metric rather than add it into the list.
    32  var exceptionMetrics = []string{
    33  	// k8s.io/apiserver/pkg/server/egressselector
    34  	"apiserver_egress_dialer_dial_failure_count", // counter metrics should have "_total" suffix
    35  
    36  	// k8s.io/apiserver/pkg/server/healthz
    37  	"apiserver_request_total", // label names should be written in 'snake_case' not 'camelCase'
    38  
    39  	// k8s.io/apiserver/pkg/endpoints/filters
    40  	"authenticated_user_requests", // counter metrics should have "_total" suffix
    41  	"authentication_attempts",     // counter metrics should have "_total" suffix
    42  
    43  	// kube-apiserver
    44  	"aggregator_openapi_v2_regeneration_count",
    45  	"apiserver_admission_step_admission_duration_seconds_summary",
    46  	"apiserver_current_inflight_requests",
    47  	"apiserver_longrunning_gauge",
    48  	"get_token_count",
    49  	"get_token_fail_count",
    50  	"ssh_tunnel_open_count",
    51  	"ssh_tunnel_open_fail_count",
    52  
    53  	// kube-controller-manager
    54  	"attachdetach_controller_forced_detaches",
    55  	"authenticated_user_requests",
    56  	"authentication_attempts",
    57  	"get_token_count",
    58  	"get_token_fail_count",
    59  	"node_collector_evictions_number",
    60  }
    61  
    62  // A Problem is an issue detected by a Linter.
    63  type Problem promlint.Problem
    64  
    65  func (p *Problem) String() string {
    66  	return fmt.Sprintf("%s:%s", p.Metric, p.Text)
    67  }
    68  
    69  // A Linter is a Prometheus metrics linter.  It identifies issues with metric
    70  // names, types, and metadata, and reports them to the caller.
    71  type Linter struct {
    72  	promLinter *promlint.Linter
    73  }
    74  
    75  // Lint performs a linting pass, returning a slice of Problems indicating any
    76  // issues found in the metrics stream.  The slice is sorted by metric name
    77  // and issue description.
    78  func (l *Linter) Lint() ([]Problem, error) {
    79  	promProblems, err := l.promLinter.Lint()
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	// Ignore problems those in exception list
    85  	problems := make([]Problem, 0, len(promProblems))
    86  	for i := range promProblems {
    87  		if !l.shouldIgnore(promProblems[i].Metric) {
    88  			problems = append(problems, Problem(promProblems[i]))
    89  		}
    90  	}
    91  
    92  	return problems, nil
    93  }
    94  
    95  // shouldIgnore returns true if metric in the exception list, otherwise returns false.
    96  func (l *Linter) shouldIgnore(metricName string) bool {
    97  	for i := range exceptionMetrics {
    98  		if metricName == exceptionMetrics[i] {
    99  			return true
   100  		}
   101  	}
   102  
   103  	return false
   104  }
   105  
   106  // NewPromLinter creates a new Linter that reads an input stream of Prometheus metrics.
   107  // Only the text exposition format is supported.
   108  func NewPromLinter(r io.Reader) *Linter {
   109  	return &Linter{
   110  		promLinter: promlint.New(r),
   111  	}
   112  }
   113  
   114  func mergeProblems(problems []Problem) string {
   115  	var problemsMsg []string
   116  
   117  	for index := range problems {
   118  		problemsMsg = append(problemsMsg, problems[index].String())
   119  	}
   120  
   121  	return strings.Join(problemsMsg, ",")
   122  }
   123  
   124  // shouldIgnore returns true if metric in the exception list, otherwise returns false.
   125  func shouldIgnore(metricName string) bool {
   126  	for i := range exceptionMetrics {
   127  		if metricName == exceptionMetrics[i] {
   128  			return true
   129  		}
   130  	}
   131  
   132  	return false
   133  }
   134  
   135  // getLintError will ignore the metrics in exception list and converts lint problem to error.
   136  func getLintError(problems []promlint.Problem) error {
   137  	var filteredProblems []Problem
   138  	for _, problem := range problems {
   139  		if shouldIgnore(problem.Metric) {
   140  			continue
   141  		}
   142  
   143  		filteredProblems = append(filteredProblems, Problem(problem))
   144  	}
   145  
   146  	if len(filteredProblems) == 0 {
   147  		return nil
   148  	}
   149  
   150  	return fmt.Errorf("lint error: %s", mergeProblems(filteredProblems))
   151  }
   152  

View as plain text