github.com/oam-dev/kubevela@v1.9.11/references/common/metrics.go (about) 1 /* 2 Copyright 2022 The KubeVela 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 common 18 19 import ( 20 "context" 21 "math" 22 "strconv" 23 24 pkgmulticluster "github.com/kubevela/pkg/multicluster" 25 v1 "k8s.io/api/core/v1" 26 "k8s.io/apimachinery/pkg/api/resource" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/client-go/rest" 31 "k8s.io/metrics/pkg/apis/metrics/v1beta1" 32 metricsclientset "k8s.io/metrics/pkg/client/clientset/versioned" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 35 appv1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 36 "github.com/oam-dev/kubevela/pkg/multicluster" 37 "github.com/oam-dev/kubevela/pkg/velaql/providers/query" 38 ) 39 40 const ( 41 // MetricsNA is the value of metrics when it is not available 42 MetricsNA = "N/A" 43 ) 44 45 // PodMetricStatus is the status of pod metrics 46 type PodMetricStatus struct { 47 Spec MetricsLR 48 Usage MetricsUsage 49 } 50 51 // ApplicationMetrics is the metrics of application 52 type ApplicationMetrics struct { 53 Metrics *ApplicationMetricsStatus 54 ResourceNum *ApplicationResourceNum 55 } 56 57 // ApplicationMetricsStatus is the status of application metrics 58 type ApplicationMetricsStatus struct { 59 CPUUsage int64 60 CPURequest int64 61 CPULimit int64 62 MemoryUsage int64 63 MemoryRequest int64 64 MemoryLimit int64 65 Storage int64 66 } 67 68 // ApplicationResourceNum is the resource number of application 69 type ApplicationResourceNum struct { 70 Node int 71 Cluster int 72 Subresource int 73 Pod int 74 Container int 75 } 76 77 // MetricsLR is the metric of resource requests and limits 78 type MetricsLR struct { 79 Rcpu, Rmem int64 80 Lcpu, Lmem int64 81 } 82 83 // MetricsUsage is the metric of resource usage 84 type MetricsUsage struct { 85 CPU, Mem, Storage int64 86 } 87 88 func podUsage(metrics *v1beta1.PodMetrics) (*resource.Quantity, *resource.Quantity) { 89 cpu, mem := new(resource.Quantity), new(resource.Quantity) 90 for _, co := range metrics.Containers { 91 usage := co.Usage 92 93 if len(usage) == 0 { 94 continue 95 } 96 if usage.Cpu() != nil { 97 cpu.Add(*usage.Cpu()) 98 } 99 if co.Usage.Memory() != nil { 100 mem.Add(*usage.Memory()) 101 } 102 } 103 return cpu, mem 104 } 105 106 func podLimits(spec v1.PodSpec) (*resource.Quantity, *resource.Quantity) { 107 cpu, mem := new(resource.Quantity), new(resource.Quantity) 108 for _, co := range spec.Containers { 109 limits := co.Resources.Limits 110 if len(limits) == 0 { 111 continue 112 } 113 if limits.Cpu() != nil { 114 cpu.Add(*limits.Cpu()) 115 } 116 if limits.Memory() != nil { 117 mem.Add(*limits.Memory()) 118 } 119 } 120 return cpu, mem 121 } 122 123 func podRequests(spec v1.PodSpec) (*resource.Quantity, *resource.Quantity) { 124 cpu, mem := new(resource.Quantity), new(resource.Quantity) 125 for _, co := range spec.Containers { 126 req := co.Resources.Requests 127 if len(req) == 0 { 128 continue 129 } 130 if req.Cpu() != nil { 131 cpu.Add(*req.Cpu()) 132 } 133 if req.Memory() != nil { 134 mem.Add(*req.Memory()) 135 } 136 } 137 return cpu, mem 138 } 139 140 // ToPercentage computes percentage as string otherwise n/aa. 141 func ToPercentage(v1, v2 int64) int { 142 if v2 == 0 { 143 return 0 144 } 145 return int(math.Floor((float64(v1) / float64(v2)) * 100)) 146 } 147 148 // ToPercentageStr computes percentage, but if v2 is 0, it will return NAValue instead of 0. 149 func ToPercentageStr(v1, v2 int64) string { 150 if v2 == 0 { 151 return MetricsNA 152 } 153 return strconv.Itoa(ToPercentage(v1, v2)) + "%" 154 } 155 156 // GetPodMetrics get pod metrics object 157 func GetPodMetrics(conf *rest.Config, podName, namespace, cluster string) (*v1beta1.PodMetrics, error) { 158 ctx := multicluster.ContextWithClusterName(context.Background(), cluster) 159 conf.Wrap(pkgmulticluster.NewTransportWrapper()) 160 metricsClient := metricsclientset.NewForConfigOrDie(conf) 161 m, err := metricsClient.MetricsV1beta1().PodMetricses(namespace).Get(ctx, podName, metav1.GetOptions{}) 162 if err != nil { 163 return nil, err 164 } 165 return m, nil 166 } 167 168 // GetPodResourceSpecAndUsage return the usage metrics of a pod and specified metric including requests and limits metrics 169 func GetPodResourceSpecAndUsage(c client.Client, pod *v1.Pod, metrics *v1beta1.PodMetrics) (MetricsLR, MetricsUsage) { 170 var metricsSpec MetricsLR 171 var metricsUsage MetricsUsage 172 rcpu, rmem := podRequests(pod.Spec) 173 lcpu, lmem := podLimits(pod.Spec) 174 metricsSpec.Rcpu, metricsSpec.Lcpu, metricsSpec.Rmem, metricsSpec.Lmem = rcpu.MilliValue(), lcpu.MilliValue(), rmem.Value(), lmem.Value() 175 176 if metrics != nil { 177 ccpu, cmem := podUsage(metrics) 178 metricsUsage.CPU, metricsUsage.Mem = ccpu.MilliValue(), cmem.Value() 179 } 180 181 storage := int64(0) 182 storages := GetPodStorage(c, pod) 183 for _, s := range storages { 184 storage += s.Status.Capacity.Storage().Value() 185 } 186 metricsUsage.Storage = storage 187 return metricsSpec, metricsUsage 188 } 189 190 // GetPodStorage get pod storage 191 func GetPodStorage(client client.Client, pod *v1.Pod) (storages []v1.PersistentVolumeClaim) { 192 for _, v := range pod.Spec.Volumes { 193 if v.PersistentVolumeClaim != nil { 194 storage := v1.PersistentVolumeClaim{} 195 err := client.Get(context.Background(), types.NamespacedName{Name: v.PersistentVolumeClaim.ClaimName, Namespace: pod.Namespace}, &storage) 196 if err != nil { 197 continue 198 } 199 storages = append(storages, storage) 200 } 201 } 202 return 203 } 204 205 // ListApplicationResource list application resource 206 func ListApplicationResource(c client.Client, name, namespace string) ([]query.Resource, error) { 207 opt := query.Option{ 208 Name: name, 209 Namespace: namespace, 210 Filter: query.FilterOption{}, 211 } 212 collector := query.NewAppCollector(c, opt) 213 appResList, err := collector.CollectResourceFromApp(context.Background()) 214 if err != nil { 215 return []query.Resource{}, err 216 } 217 return appResList, err 218 } 219 220 // GetPodOfManagedResource get pod of managed resource 221 func GetPodOfManagedResource(c client.Client, app *appv1beta1.Application, components string) []*v1.Pod { 222 pods := make([]*v1.Pod, 0) 223 opt := query.Option{ 224 Name: app.Name, 225 Namespace: app.Namespace, 226 Filter: query.FilterOption{ 227 Components: []string{components}, 228 APIVersion: "v1", 229 Kind: "Pod", 230 }, 231 WithTree: true, 232 } 233 objects, err := CollectApplicationResource(context.Background(), c, opt) 234 if err != nil { 235 return pods 236 } 237 for _, object := range objects { 238 pod := &v1.Pod{} 239 err = runtime.DefaultUnstructuredConverter.FromUnstructured(object.UnstructuredContent(), pod) 240 if err != nil { 241 continue 242 } 243 pods = append(pods, pod) 244 } 245 return pods 246 } 247 248 // GetPodMetricsStatus get pod metrics 249 func GetPodMetricsStatus(c client.Client, conf *rest.Config, pod *v1.Pod, cluster string) (*PodMetricStatus, error) { 250 metricsStatus := &PodMetricStatus{} 251 podMetrics, err := GetPodMetrics(conf, pod.Name, pod.Namespace, cluster) 252 if err != nil { 253 return nil, err 254 } 255 spec, usage := GetPodResourceSpecAndUsage(c, pod, podMetrics) 256 metricsStatus.Spec = spec 257 metricsStatus.Usage = usage 258 259 return metricsStatus, nil 260 } 261 262 // GetApplicationMetrics get application metrics 263 func GetApplicationMetrics(c client.Client, conf *rest.Config, app *appv1beta1.Application) (*ApplicationMetrics, error) { 264 appResList, err := ListApplicationResource(c, app.Name, app.Namespace) 265 if err != nil { 266 return nil, err 267 } 268 clusters := make(map[string]struct{}) 269 nodes := make(map[string]struct{}) 270 podNum := 0 271 containerNum := 0 272 podMetricsArray := make([]*PodMetricStatus, 0) 273 274 for _, managedResource := range appResList { 275 clusters[managedResource.Cluster] = struct{}{} 276 pods := GetPodOfManagedResource(c, app, managedResource.Object.GetName()) 277 podNum += len(pods) 278 for _, pod := range pods { 279 nodes[pod.Spec.NodeName] = struct{}{} 280 containerNum += len(pod.Spec.Containers) 281 status, err := GetPodMetricsStatus(c, conf, pod, managedResource.Cluster) 282 if err != nil { 283 continue 284 } 285 podMetricsArray = append(podMetricsArray, status) 286 } 287 } 288 289 appResource := &ApplicationResourceNum{ 290 Cluster: len(clusters), 291 Node: len(nodes), 292 Subresource: len(appResList), 293 Pod: podNum, 294 Container: containerNum, 295 } 296 appMetrics := &ApplicationMetricsStatus{} 297 for _, metrics := range podMetricsArray { 298 appMetrics.CPUUsage += metrics.Usage.CPU 299 appMetrics.CPULimit += metrics.Spec.Lcpu 300 appMetrics.CPURequest += metrics.Spec.Rcpu 301 appMetrics.MemoryUsage += metrics.Usage.Mem 302 appMetrics.MemoryLimit += metrics.Spec.Lmem 303 appMetrics.MemoryRequest += metrics.Spec.Rmem 304 appMetrics.Storage += metrics.Usage.Storage 305 } 306 return &ApplicationMetrics{ 307 Metrics: appMetrics, 308 ResourceNum: appResource, 309 }, nil 310 }