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 }