github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/metrics/metrics.go (about)

     1  package metrics
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/prometheus/client_golang/prometheus"
     8  	"google.golang.org/grpc/connectivity"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"k8s.io/apimachinery/pkg/labels"
    11  
    12  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    13  	v1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
    14  )
    15  
    16  const (
    17  	NameLabel      = "name"
    18  	InstalledLabel = "installed"
    19  	NamespaceLabel = "namespace"
    20  	ChannelLabel   = "channel"
    21  	VersionLabel   = "version"
    22  	PhaseLabel     = "phase"
    23  	ReasonLabel    = "reason"
    24  	PackageLabel   = "package"
    25  	Outcome        = "outcome"
    26  	Succeeded      = "succeeded"
    27  	Failed         = "failed"
    28  	ApprovalLabel  = "approval"
    29  	WarningLabel   = "warning"
    30  	GVKLabel       = "gvk"
    31  )
    32  
    33  type MetricsProvider interface {
    34  	HandleMetrics() error
    35  }
    36  
    37  type metricsCSV struct {
    38  	lister v1alpha1.ClusterServiceVersionLister
    39  }
    40  
    41  func NewMetricsCSV(lister v1alpha1.ClusterServiceVersionLister) MetricsProvider {
    42  	return &metricsCSV{lister}
    43  }
    44  
    45  func (m *metricsCSV) HandleMetrics() error {
    46  	cList, err := m.lister.List(labels.Everything())
    47  	if err != nil {
    48  		return err
    49  	}
    50  	csvCount.Set(float64(len(cList)))
    51  	return nil
    52  }
    53  
    54  type metricsInstallPlan struct {
    55  	lister v1alpha1.InstallPlanLister
    56  }
    57  
    58  func NewMetricsInstallPlan(lister v1alpha1.InstallPlanLister) MetricsProvider {
    59  	return &metricsInstallPlan{lister}
    60  }
    61  
    62  func (m *metricsInstallPlan) HandleMetrics() error {
    63  	cList, err := m.lister.InstallPlans(metav1.NamespaceAll).List(labels.Everything())
    64  	if err != nil {
    65  		return err
    66  	}
    67  	installPlanCount.Set(float64(len(cList)))
    68  	return nil
    69  }
    70  
    71  type metricsSubscription struct {
    72  	lister v1alpha1.SubscriptionLister
    73  }
    74  
    75  func NewMetricsSubscription(lister v1alpha1.SubscriptionLister) MetricsProvider {
    76  	return &metricsSubscription{lister}
    77  }
    78  
    79  func (m *metricsSubscription) HandleMetrics() error {
    80  	cList, err := m.lister.Subscriptions(metav1.NamespaceAll).List(labels.Everything())
    81  	if err != nil {
    82  		return err
    83  	}
    84  	subscriptionCount.Set(float64(len(cList)))
    85  	return nil
    86  }
    87  
    88  type metricsCatalogSource struct {
    89  	lister v1alpha1.CatalogSourceLister
    90  }
    91  
    92  func NewMetricsCatalogSource(lister v1alpha1.CatalogSourceLister) MetricsProvider {
    93  	return &metricsCatalogSource{lister}
    94  }
    95  
    96  func (m *metricsCatalogSource) HandleMetrics() error {
    97  	cList, err := m.lister.CatalogSources(metav1.NamespaceAll).List(labels.Everything())
    98  	if err != nil {
    99  		return err
   100  	}
   101  	catalogSourceCount.Set(float64(len(cList)))
   102  	return nil
   103  }
   104  
   105  type MetricsNil struct{}
   106  
   107  func NewMetricsNil() MetricsProvider {
   108  	return &MetricsNil{}
   109  }
   110  
   111  func (*MetricsNil) HandleMetrics() error {
   112  	return nil
   113  }
   114  
   115  // To add new metrics:
   116  // 1. Register new metrics in Register() below.
   117  // 2. Add appropriate metric updates in HandleMetrics (or elsewhere instead).
   118  var (
   119  	csvCount = prometheus.NewGauge(
   120  		prometheus.GaugeOpts{
   121  			Name: "csv_count",
   122  			Help: "Number of CSVs successfully registered",
   123  		},
   124  	)
   125  
   126  	installPlanCount = prometheus.NewGauge(
   127  		prometheus.GaugeOpts{
   128  			Name: "install_plan_count",
   129  			Help: "Number of install plans",
   130  		},
   131  	)
   132  
   133  	subscriptionCount = prometheus.NewGauge(
   134  		prometheus.GaugeOpts{
   135  			Name: "subscription_count",
   136  			Help: "Number of subscriptions",
   137  		},
   138  	)
   139  
   140  	catalogSourceCount = prometheus.NewGauge(
   141  		prometheus.GaugeOpts{
   142  			Name: "catalog_source_count",
   143  			Help: "Number of catalog sources",
   144  		},
   145  	)
   146  
   147  	catalogSourceReady = prometheus.NewGaugeVec(
   148  		prometheus.GaugeOpts{
   149  			Name: "catalogsource_ready",
   150  			Help: "State of a CatalogSource. 1 indicates that the CatalogSource is in a READY state. 0 indicates CatalogSource is in a Non READY state.",
   151  		},
   152  		[]string{NamespaceLabel, NameLabel},
   153  	)
   154  
   155  	// exported since it's not handled by HandleMetrics
   156  	CSVUpgradeCount = prometheus.NewCounter(
   157  		prometheus.CounterOpts{
   158  			Name: "csv_upgrade_count",
   159  			Help: "Monotonic count of CSV upgrades",
   160  		},
   161  	)
   162  
   163  	SubscriptionSyncCount = prometheus.NewCounterVec(
   164  		prometheus.CounterOpts{
   165  			Name: "subscription_sync_total",
   166  			Help: "Monotonic count of subscription syncs",
   167  		},
   168  		[]string{NameLabel, InstalledLabel, ChannelLabel, PackageLabel, ApprovalLabel},
   169  	)
   170  
   171  	csvSucceeded = prometheus.NewGaugeVec(
   172  		prometheus.GaugeOpts{
   173  			Name: "csv_succeeded",
   174  			Help: "Successful CSV install",
   175  		},
   176  		[]string{NamespaceLabel, NameLabel, VersionLabel},
   177  	)
   178  
   179  	csvAbnormal = prometheus.NewGaugeVec(
   180  		prometheus.GaugeOpts{
   181  			Name: "csv_abnormal",
   182  			Help: "CSV is not installed",
   183  		},
   184  		[]string{NamespaceLabel, NameLabel, VersionLabel, PhaseLabel, ReasonLabel},
   185  	)
   186  
   187  	dependencyResolutionSummary = prometheus.NewSummaryVec(
   188  		prometheus.SummaryOpts{
   189  			Name:       "olm_resolution_duration_seconds",
   190  			Help:       "The duration of a dependency resolution attempt",
   191  			Objectives: map[float64]float64{0.95: 0.05, 0.9: 0.01, 0.99: 0.001},
   192  		},
   193  		[]string{Outcome},
   194  	)
   195  
   196  	installPlanWarningCount = prometheus.NewCounter(
   197  		prometheus.CounterOpts{
   198  			Name: "installplan_warnings_total",
   199  			Help: "monotonic count of resources that generated warnings when applied as part of an InstallPlan (for example, due to deprecation)",
   200  		},
   201  	)
   202  
   203  	subscriptionSyncCounters = newSubscriptionSyncCounter()
   204  )
   205  
   206  // subscriptionSyncCounter keeps a record of the Prometheus counters emitted by
   207  // Subscription objects. The key of a record is the Subscription name, while the value
   208  // is struct containing label values used in the counter. Read and Write access are
   209  // protected by mutex.
   210  type subscriptionSyncCounter struct {
   211  	counters     map[string]subscriptionSyncLabelValues
   212  	countersLock sync.RWMutex
   213  }
   214  
   215  func newSubscriptionSyncCounter() subscriptionSyncCounter {
   216  	return subscriptionSyncCounter{
   217  		counters: make(map[string]subscriptionSyncLabelValues),
   218  	}
   219  }
   220  
   221  func (s *subscriptionSyncCounter) setValues(key string, val subscriptionSyncLabelValues) {
   222  	s.countersLock.Lock()
   223  	defer s.countersLock.Unlock()
   224  	s.counters[key] = val
   225  }
   226  
   227  func (s *subscriptionSyncCounter) readValues(key string) (subscriptionSyncLabelValues, bool) {
   228  	s.countersLock.RLock()
   229  	defer s.countersLock.RUnlock()
   230  	val, ok := s.counters[key]
   231  	return val, ok
   232  }
   233  
   234  type subscriptionSyncLabelValues struct {
   235  	installedCSV     string
   236  	pkg              string
   237  	channel          string
   238  	approvalStrategy string
   239  }
   240  
   241  func RegisterOLM() {
   242  	prometheus.MustRegister(csvCount)
   243  	prometheus.MustRegister(csvSucceeded)
   244  	prometheus.MustRegister(csvAbnormal)
   245  	prometheus.MustRegister(CSVUpgradeCount)
   246  }
   247  
   248  func RegisterCatalog() {
   249  	prometheus.MustRegister(installPlanCount)
   250  	prometheus.MustRegister(subscriptionCount)
   251  	prometheus.MustRegister(catalogSourceCount)
   252  	prometheus.MustRegister(catalogSourceReady)
   253  	prometheus.MustRegister(SubscriptionSyncCount)
   254  	prometheus.MustRegister(dependencyResolutionSummary)
   255  	prometheus.MustRegister(installPlanWarningCount)
   256  }
   257  
   258  func CounterForSubscription(name, installedCSV, channelName, packageName, planApprovalStrategy string) prometheus.Counter {
   259  	return SubscriptionSyncCount.WithLabelValues(name, installedCSV, channelName, packageName, planApprovalStrategy)
   260  }
   261  
   262  func RegisterCatalogSourceState(name, namespace string, state connectivity.State) {
   263  	switch state {
   264  	case connectivity.Ready:
   265  		catalogSourceReady.WithLabelValues(namespace, name).Set(1)
   266  	default:
   267  		catalogSourceReady.WithLabelValues(namespace, name).Set(0)
   268  	}
   269  }
   270  
   271  func DeleteCatalogSourceStateMetric(name, namespace string) {
   272  	catalogSourceReady.DeleteLabelValues(namespace, name)
   273  }
   274  
   275  func DeleteCSVMetric(oldCSV *operatorsv1alpha1.ClusterServiceVersion) {
   276  	// Delete the old CSV metrics
   277  	csvAbnormal.DeleteLabelValues(oldCSV.Namespace, oldCSV.Name, oldCSV.Spec.Version.String(), string(oldCSV.Status.Phase), string(oldCSV.Status.Reason))
   278  	csvSucceeded.DeleteLabelValues(oldCSV.Namespace, oldCSV.Name, oldCSV.Spec.Version.String())
   279  }
   280  
   281  func EmitCSVMetric(oldCSV *operatorsv1alpha1.ClusterServiceVersion, newCSV *operatorsv1alpha1.ClusterServiceVersion) {
   282  	if oldCSV == nil || newCSV == nil {
   283  		return
   284  	}
   285  
   286  	// Don't update the metric for copies
   287  	if newCSV.Status.Reason == operatorsv1alpha1.CSVReasonCopied {
   288  		return
   289  	}
   290  
   291  	// Delete the old CSV metrics
   292  	csvAbnormal.DeleteLabelValues(oldCSV.Namespace, oldCSV.Name, oldCSV.Spec.Version.String(), string(oldCSV.Status.Phase), string(oldCSV.Status.Reason))
   293  
   294  	// Get the phase of the new CSV
   295  	newCSVPhase := string(newCSV.Status.Phase)
   296  	csvSucceededGauge := csvSucceeded.WithLabelValues(newCSV.Namespace, newCSV.Name, newCSV.Spec.Version.String())
   297  	if newCSVPhase == string(operatorsv1alpha1.CSVPhaseSucceeded) {
   298  		csvSucceededGauge.Set(1)
   299  	} else {
   300  		csvSucceededGauge.Set(0)
   301  		csvAbnormal.WithLabelValues(newCSV.Namespace, newCSV.Name, newCSV.Spec.Version.String(), string(newCSV.Status.Phase), string(newCSV.Status.Reason)).Set(1)
   302  	}
   303  }
   304  
   305  func EmitSubMetric(sub *operatorsv1alpha1.Subscription) {
   306  	if sub.Spec == nil {
   307  		return
   308  	}
   309  
   310  	SubscriptionSyncCount.WithLabelValues(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package, string(sub.Spec.InstallPlanApproval)).Inc()
   311  	if _, present := subscriptionSyncCounters.readValues(sub.GetName()); !present {
   312  		subscriptionSyncCounters.setValues(sub.GetName(), subscriptionSyncLabelValues{
   313  			installedCSV:     sub.Status.InstalledCSV,
   314  			pkg:              sub.Spec.Package,
   315  			channel:          sub.Spec.Channel,
   316  			approvalStrategy: string(sub.Spec.InstallPlanApproval),
   317  		})
   318  	}
   319  }
   320  
   321  func DeleteSubsMetric(sub *operatorsv1alpha1.Subscription) {
   322  	if sub.Spec == nil {
   323  		return
   324  	}
   325  	SubscriptionSyncCount.DeleteLabelValues(sub.GetName(), sub.Status.InstalledCSV, sub.Spec.Channel, sub.Spec.Package, string(sub.Spec.InstallPlanApproval))
   326  }
   327  
   328  func UpdateSubsSyncCounterStorage(sub *operatorsv1alpha1.Subscription) {
   329  	if sub.Spec == nil {
   330  		return
   331  	}
   332  	counterValues, _ := subscriptionSyncCounters.readValues(sub.GetName())
   333  	approvalStrategy := string(sub.Spec.InstallPlanApproval)
   334  
   335  	if sub.Spec.Channel != counterValues.channel ||
   336  		sub.Spec.Package != counterValues.pkg ||
   337  		sub.Status.InstalledCSV != counterValues.installedCSV ||
   338  		approvalStrategy != counterValues.approvalStrategy {
   339  		// Delete metric will label values of old Subscription first
   340  		SubscriptionSyncCount.DeleteLabelValues(sub.GetName(), counterValues.installedCSV, counterValues.channel, counterValues.pkg, counterValues.approvalStrategy)
   341  
   342  		counterValues.installedCSV = sub.Status.InstalledCSV
   343  		counterValues.pkg = sub.Spec.Package
   344  		counterValues.channel = sub.Spec.Channel
   345  		counterValues.approvalStrategy = approvalStrategy
   346  
   347  		subscriptionSyncCounters.setValues(sub.GetName(), counterValues)
   348  	}
   349  }
   350  
   351  func RegisterDependencyResolutionSuccess(duration time.Duration) {
   352  	dependencyResolutionSummary.WithLabelValues(Succeeded).Observe(duration.Seconds())
   353  }
   354  
   355  func RegisterDependencyResolutionFailure(duration time.Duration) {
   356  	dependencyResolutionSummary.WithLabelValues(Failed).Observe(duration.Seconds())
   357  }
   358  
   359  func EmitInstallPlanWarning() {
   360  	installPlanWarningCount.Inc()
   361  }