...

Source file src/edge-infra.dev/pkg/k8s/runtime/controller/metrics/metrics.go

Documentation: edge-infra.dev/pkg/k8s/runtime/controller/metrics

     1  package metrics
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"time"
     7  
     8  	"github.com/prometheus/client_golang/prometheus"
     9  	"k8s.io/apimachinery/pkg/runtime"
    10  	"k8s.io/client-go/tools/reference"
    11  	ctrl "sigs.k8s.io/controller-runtime"
    12  	ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
    13  
    14  	"edge-infra.dev/pkg/k8s/meta/status"
    15  	"edge-infra.dev/pkg/k8s/runtime/conditions"
    16  )
    17  
    18  // Metrics wraps the base Recorder to provide helper methods which automatically
    19  // get the object reference the metric is being recorded for and handles logging
    20  // of metric collection errors.
    21  type Metrics struct {
    22  	Scheme   *runtime.Scheme
    23  	Recorder *Recorder
    24  }
    25  
    26  // New instantiates a Metrics struct with a new Recorder based on the options.
    27  // The metrics are registered with the controller-runtime metric registry.
    28  func New(mgr ctrl.Manager, prefix string, options ...Option) Metrics {
    29  	rec := NewRecorder(prefix, options...)
    30  	// Register the collectors with the prometheus registry found in k8s controller-runtime metrics package.
    31  	ctrlmetrics.Registry.MustRegister(rec.Collectors()...)
    32  	return Metrics{
    33  		Scheme:   mgr.GetScheme(),
    34  		Recorder: rec,
    35  	}
    36  }
    37  
    38  func (m Metrics) RecordDuration(ctx context.Context, obj conditions.Getter, startTime time.Time) {
    39  	if m.Recorder == nil {
    40  		return
    41  	}
    42  
    43  	ref, err := reference.GetReference(m.Scheme, obj)
    44  	if err != nil {
    45  		ctrl.LoggerFrom(ctx).Error(
    46  			err, "unable to get object reference to record duration",
    47  		)
    48  		return
    49  	}
    50  	m.Recorder.RecordDuration(*ref, startTime)
    51  }
    52  
    53  // RecordSuspend records the suspension of the given obj based on the given suspend value.
    54  func (m Metrics) RecordSuspend(ctx context.Context, obj conditions.Getter, suspend bool) {
    55  	if m.Recorder == nil {
    56  		return
    57  	}
    58  
    59  	ref, err := reference.GetReference(m.Scheme, obj)
    60  	if err != nil {
    61  		ctrl.LoggerFrom(ctx).Error(
    62  			err, "unable to get object reference to record suspension",
    63  		)
    64  		return
    65  	}
    66  	if err := m.Recorder.RecordSuspend(*ref, suspend); err != nil {
    67  		ctrl.LoggerFrom(ctx).Error(
    68  			err, "unable to record suspension",
    69  		)
    70  		return
    71  	}
    72  }
    73  
    74  // RecordConditionsWithReason deletes all metrics based on the object's kind, name, and namespace
    75  // before recording the status of each active condition the object has.
    76  // The deletion behavior allows the recording function to record active conditions and its associated
    77  // values only, without keeping track of the explicit set of conditions and reasons the controller uses,
    78  // eliminating the need to set inactive condition-status-reason combinations.
    79  // NOTE: This function can only be used for controllers that pass `WithReason()` during setup.
    80  func (m Metrics) RecordConditionsWithReason(ctx context.Context, obj conditions.Getter) {
    81  	if m.Recorder == nil {
    82  		return
    83  	}
    84  
    85  	// Since the DeletePartialMatch happens within this function, gauge existence
    86  	// also needs to be checked.
    87  	if m.Recorder.reconcileConditionGaugeWithReason == nil {
    88  		ctrl.LoggerFrom(ctx).Error(
    89  			errors.New("gauge not set"), "reconcileConditionGaugeWithReason not set",
    90  		)
    91  		return
    92  	}
    93  
    94  	ref, err := reference.GetReference(m.Scheme, obj)
    95  	if err != nil {
    96  		ctrl.LoggerFrom(ctx).Error(
    97  			err, "unable to get object reference to record condition metric",
    98  		)
    99  		return
   100  	}
   101  
   102  	labels := prometheus.Labels{
   103  		"kind":      ref.Kind,
   104  		"name":      ref.Name,
   105  		"namespace": ref.Namespace,
   106  	}
   107  
   108  	// Delete metrics for this object to reset the observed status and conditions
   109  	m.Recorder.reconcileConditionGaugeWithReason.DeletePartialMatch(labels)
   110  
   111  	conds := conditions.GetConditions(obj)
   112  	for _, c := range conds {
   113  		if err := m.Recorder.RecordConditionWithReason(*ref, c, !obj.GetDeletionTimestamp().IsZero()); err != nil {
   114  			ctrl.LoggerFrom(ctx).Error(
   115  				err, "unable to record condition",
   116  			)
   117  			return
   118  		}
   119  	}
   120  }
   121  
   122  // RecordCondition records the status of the given conditionType for the given obj.
   123  func (m Metrics) RecordCondition(ctx context.Context, obj conditions.Getter, conditionType string) {
   124  	if m.Recorder == nil {
   125  		return
   126  	}
   127  
   128  	ref, err := reference.GetReference(m.Scheme, obj)
   129  	if err != nil {
   130  		ctrl.LoggerFrom(ctx).Error(
   131  			err, "unable to get object reference to record condition metric",
   132  		)
   133  	}
   134  	rc := conditions.Get(obj, conditionType)
   135  	if rc == nil {
   136  		rc = conditions.UnknownCondition(conditionType, "", "")
   137  	}
   138  	if err := m.Recorder.RecordCondition(*ref, *rc, !obj.GetDeletionTimestamp().IsZero()); err != nil {
   139  		ctrl.LoggerFrom(ctx).Error(
   140  			err, "unable to record condition",
   141  		)
   142  		return
   143  	}
   144  }
   145  
   146  // RecordReadiness records the status.ReadyCondition status for the given obj.
   147  func (m Metrics) RecordReadiness(ctx context.Context, obj conditions.Getter) {
   148  	m.RecordCondition(ctx, obj, status.ReadyCondition)
   149  }
   150  
   151  // RecordReconciling records the status.ReconcilingCondition status for the given obj.
   152  func (m Metrics) RecordReconciling(ctx context.Context, obj conditions.Getter) {
   153  	m.RecordCondition(ctx, obj, status.ReconcilingCondition)
   154  }
   155  
   156  // RecordStalled records the status.StalledCondition status for the given obj.
   157  func (m Metrics) RecordStalled(ctx context.Context, obj conditions.Getter) {
   158  	m.RecordCondition(ctx, obj, status.StalledCondition)
   159  }
   160  

View as plain text