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  }