k8s.io/kubernetes@v1.29.3/pkg/controller/volume/attachdetach/metrics/metrics.go (about)

     1  /*
     2  Copyright 2018 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 metrics
    18  
    19  import (
    20  	"errors"
    21  	"sync"
    22  
    23  	"k8s.io/apimachinery/pkg/labels"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	corelisters "k8s.io/client-go/listers/core/v1"
    26  	"k8s.io/component-base/metrics"
    27  	"k8s.io/component-base/metrics/legacyregistry"
    28  	"k8s.io/klog/v2"
    29  	"k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache"
    30  	"k8s.io/kubernetes/pkg/controller/volume/attachdetach/util"
    31  	"k8s.io/kubernetes/pkg/volume"
    32  	"k8s.io/kubernetes/pkg/volume/csimigration"
    33  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    34  )
    35  
    36  const pluginNameNotAvailable = "N/A"
    37  
    38  const (
    39  	// Force detach reason is timeout
    40  	ForceDetachReasonTimeout = "timeout"
    41  	// Force detach reason is the node has an out-of-service taint
    42  	ForceDetachReasonOutOfService = "out-of-service"
    43  	attachDetachController        = "attach_detach_controller"
    44  )
    45  
    46  var (
    47  	inUseVolumeMetricDesc = metrics.NewDesc(
    48  		metrics.BuildFQName("", "storage_count", "attachable_volumes_in_use"),
    49  		"Measure number of volumes in use",
    50  		[]string{"node", "volume_plugin"}, nil,
    51  		metrics.ALPHA, "")
    52  
    53  	totalVolumesMetricDesc = metrics.NewDesc(
    54  		metrics.BuildFQName("", "attachdetach_controller", "total_volumes"),
    55  		"Number of volumes in A/D Controller",
    56  		[]string{"plugin_name", "state"}, nil,
    57  		metrics.ALPHA, "")
    58  
    59  	ForceDetachMetricCounter = metrics.NewCounterVec(
    60  		&metrics.CounterOpts{
    61  			Subsystem:      attachDetachController,
    62  			Name:           "attachdetach_controller_forced_detaches",
    63  			Help:           "Number of times the A/D Controller performed a forced detach",
    64  			StabilityLevel: metrics.ALPHA,
    65  		},
    66  		[]string{"reason"},
    67  	)
    68  )
    69  var registerMetrics sync.Once
    70  
    71  // Register registers metrics in A/D Controller.
    72  func Register(pvcLister corelisters.PersistentVolumeClaimLister,
    73  	pvLister corelisters.PersistentVolumeLister,
    74  	podLister corelisters.PodLister,
    75  	asw cache.ActualStateOfWorld,
    76  	dsw cache.DesiredStateOfWorld,
    77  	pluginMgr *volume.VolumePluginMgr,
    78  	csiMigratedPluginManager csimigration.PluginManager,
    79  	intreeToCSITranslator csimigration.InTreeToCSITranslator) {
    80  	registerMetrics.Do(func() {
    81  		legacyregistry.CustomMustRegister(newAttachDetachStateCollector(pvcLister,
    82  			podLister,
    83  			pvLister,
    84  			asw,
    85  			dsw,
    86  			pluginMgr,
    87  			csiMigratedPluginManager,
    88  			intreeToCSITranslator))
    89  		legacyregistry.MustRegister(ForceDetachMetricCounter)
    90  	})
    91  }
    92  
    93  type attachDetachStateCollector struct {
    94  	metrics.BaseStableCollector
    95  
    96  	pvcLister                corelisters.PersistentVolumeClaimLister
    97  	podLister                corelisters.PodLister
    98  	pvLister                 corelisters.PersistentVolumeLister
    99  	asw                      cache.ActualStateOfWorld
   100  	dsw                      cache.DesiredStateOfWorld
   101  	volumePluginMgr          *volume.VolumePluginMgr
   102  	csiMigratedPluginManager csimigration.PluginManager
   103  	intreeToCSITranslator    csimigration.InTreeToCSITranslator
   104  }
   105  
   106  // volumeCount is a map of maps used as a counter, e.g.:
   107  //
   108  //	node 172.168.1.100.ec2.internal has 10 EBS and 3 glusterfs PVC in use:
   109  //	{"172.168.1.100.ec2.internal": {"aws-ebs": 10, "glusterfs": 3}}
   110  //	state actual_state_of_world contains a total of 10 EBS volumes:
   111  //	{"actual_state_of_world": {"aws-ebs": 10}}
   112  type volumeCount map[string]map[string]int64
   113  
   114  func (v volumeCount) add(typeKey, counterKey string) {
   115  	count, ok := v[typeKey]
   116  	if !ok {
   117  		count = map[string]int64{}
   118  	}
   119  	count[counterKey]++
   120  	v[typeKey] = count
   121  }
   122  
   123  func newAttachDetachStateCollector(
   124  	pvcLister corelisters.PersistentVolumeClaimLister,
   125  	podLister corelisters.PodLister,
   126  	pvLister corelisters.PersistentVolumeLister,
   127  	asw cache.ActualStateOfWorld,
   128  	dsw cache.DesiredStateOfWorld,
   129  	pluginMgr *volume.VolumePluginMgr,
   130  	csiMigratedPluginManager csimigration.PluginManager,
   131  	intreeToCSITranslator csimigration.InTreeToCSITranslator) *attachDetachStateCollector {
   132  	return &attachDetachStateCollector{pvcLister: pvcLister, podLister: podLister, pvLister: pvLister, asw: asw, dsw: dsw, volumePluginMgr: pluginMgr, csiMigratedPluginManager: csiMigratedPluginManager, intreeToCSITranslator: intreeToCSITranslator}
   133  }
   134  
   135  // Check if our collector implements necessary collector interface
   136  var _ metrics.StableCollector = &attachDetachStateCollector{}
   137  
   138  func (collector *attachDetachStateCollector) DescribeWithStability(ch chan<- *metrics.Desc) {
   139  	ch <- inUseVolumeMetricDesc
   140  	ch <- totalVolumesMetricDesc
   141  }
   142  
   143  func (collector *attachDetachStateCollector) CollectWithStability(ch chan<- metrics.Metric) {
   144  	nodeVolumeMap := collector.getVolumeInUseCount(klog.TODO())
   145  	for nodeName, pluginCount := range nodeVolumeMap {
   146  		for pluginName, count := range pluginCount {
   147  			ch <- metrics.NewLazyConstMetric(inUseVolumeMetricDesc,
   148  				metrics.GaugeValue,
   149  				float64(count),
   150  				string(nodeName),
   151  				pluginName)
   152  		}
   153  	}
   154  
   155  	stateVolumeMap := collector.getTotalVolumesCount()
   156  	for stateName, pluginCount := range stateVolumeMap {
   157  		for pluginName, count := range pluginCount {
   158  			ch <- metrics.NewLazyConstMetric(totalVolumesMetricDesc,
   159  				metrics.GaugeValue,
   160  				float64(count),
   161  				pluginName,
   162  				string(stateName))
   163  		}
   164  	}
   165  }
   166  
   167  func (collector *attachDetachStateCollector) getVolumeInUseCount(logger klog.Logger) volumeCount {
   168  	pods, err := collector.podLister.List(labels.Everything())
   169  	if err != nil {
   170  		logger.Error(errors.New("Error getting pod list"), "Get pod list failed")
   171  		return nil
   172  	}
   173  
   174  	nodeVolumeMap := make(volumeCount)
   175  	for _, pod := range pods {
   176  		if len(pod.Spec.Volumes) <= 0 {
   177  			continue
   178  		}
   179  
   180  		if pod.Spec.NodeName == "" {
   181  			continue
   182  		}
   183  		for _, podVolume := range pod.Spec.Volumes {
   184  			volumeSpec, err := util.CreateVolumeSpec(logger, podVolume, pod, types.NodeName(pod.Spec.NodeName), collector.volumePluginMgr, collector.pvcLister, collector.pvLister, collector.csiMigratedPluginManager, collector.intreeToCSITranslator)
   185  			if err != nil {
   186  				continue
   187  			}
   188  			volumePlugin, err := collector.volumePluginMgr.FindPluginBySpec(volumeSpec)
   189  			if err != nil {
   190  				continue
   191  			}
   192  			pluginName := volumeutil.GetFullQualifiedPluginNameForVolume(volumePlugin.GetPluginName(), volumeSpec)
   193  			nodeVolumeMap.add(pod.Spec.NodeName, pluginName)
   194  		}
   195  	}
   196  	return nodeVolumeMap
   197  }
   198  
   199  func (collector *attachDetachStateCollector) getTotalVolumesCount() volumeCount {
   200  	stateVolumeMap := make(volumeCount)
   201  	for _, v := range collector.dsw.GetVolumesToAttach() {
   202  		if plugin, err := collector.volumePluginMgr.FindPluginBySpec(v.VolumeSpec); err == nil {
   203  			pluginName := pluginNameNotAvailable
   204  			if plugin != nil {
   205  				pluginName = volumeutil.GetFullQualifiedPluginNameForVolume(plugin.GetPluginName(), v.VolumeSpec)
   206  			}
   207  			stateVolumeMap.add("desired_state_of_world", pluginName)
   208  		}
   209  	}
   210  	for _, v := range collector.asw.GetAttachedVolumes() {
   211  		if plugin, err := collector.volumePluginMgr.FindPluginBySpec(v.VolumeSpec); err == nil {
   212  			pluginName := pluginNameNotAvailable
   213  			if plugin != nil {
   214  				pluginName = volumeutil.GetFullQualifiedPluginNameForVolume(plugin.GetPluginName(), v.VolumeSpec)
   215  			}
   216  			stateVolumeMap.add("actual_state_of_world", pluginName)
   217  		}
   218  	}
   219  	return stateVolumeMap
   220  }
   221  
   222  // RecordForcedDetachMetric register a forced detach metric.
   223  func RecordForcedDetachMetric(forceDetachReason string) {
   224  	ForceDetachMetricCounter.WithLabelValues(forceDetachReason).Inc()
   225  }