karmada/test/e2e/framework/metrics.go

194 lines
6.5 KiB
Go

/*
Copyright 2024 The Karmada Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"context"
"fmt"
"regexp"
"time"
"github.com/prometheus/common/model"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/component-base/metrics/testutil"
"k8s.io/klog/v2"
"github.com/karmada-io/karmada/pkg/util/names"
)
const (
karmadaNamespace = "karmada-system"
metricsBindPort = 8080
leaderPodMetric = "leader_election_master_status"
queryTimeout = 10 * time.Second
)
// following refers to https://github.com/kubernetes/kubernetes/blob/master/test/e2e/framework/metrics/metrics_grabber.go
// Grabber is used to grab metrics from karmada components
type Grabber struct {
hostKubeClient clientset.Interface
controllerManagerPods []string
schedulerPods []string
deschedulerPods []string
metricsAdapterPods []string
schedulerEstimatorPods []string
webhookPods []string
}
// NewMetricsGrabber creates a new metrics grabber
func NewMetricsGrabber(ctx context.Context, c clientset.Interface) (*Grabber, error) {
grabber := Grabber{hostKubeClient: c}
regKarmadaControllerManagerPods := regexp.MustCompile(names.KarmadaControllerManagerComponentName + "-.*")
regKarmadaSchedulerPods := regexp.MustCompile(names.KarmadaSchedulerComponentName + "-.*")
regKarmadaDeschedulerPods := regexp.MustCompile(names.KarmadaDeschedulerComponentName + "-.*")
regKarmadaMetricsAdapterPods := regexp.MustCompile(names.KarmadaMetricsAdapterComponentName + "-.*")
regKarmadaSchedulerEstimatorPods := regexp.MustCompile(names.KarmadaSchedulerEstimatorComponentName + "-" + ClusterNames()[0] + "-.*")
regKarmadaWebhookPods := regexp.MustCompile(names.KarmadaWebhookComponentName + "-.*")
podList, err := c.CoreV1().Pods(karmadaNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
if len(podList.Items) < 1 {
klog.Warningf("Can't find any pods in namespace %s to grab metrics from", karmadaNamespace)
}
for _, pod := range podList.Items {
if regKarmadaControllerManagerPods.MatchString(pod.Name) {
grabber.controllerManagerPods = append(grabber.controllerManagerPods, pod.Name)
continue
}
if regKarmadaDeschedulerPods.MatchString(pod.Name) {
grabber.deschedulerPods = append(grabber.deschedulerPods, pod.Name)
continue
}
if regKarmadaMetricsAdapterPods.MatchString(pod.Name) {
grabber.metricsAdapterPods = append(grabber.metricsAdapterPods, pod.Name)
continue
}
if regKarmadaSchedulerEstimatorPods.MatchString(pod.Name) {
grabber.schedulerEstimatorPods = append(grabber.schedulerEstimatorPods, pod.Name)
continue
}
if regKarmadaSchedulerPods.MatchString(pod.Name) {
grabber.schedulerPods = append(grabber.schedulerPods, pod.Name)
continue
}
if regKarmadaWebhookPods.MatchString(pod.Name) {
grabber.webhookPods = append(grabber.webhookPods, pod.Name)
}
}
return &grabber, nil
}
// GrabMetricsFromComponent fetch metrics from the leader of a specified Karmada component
func (g *Grabber) GrabMetricsFromComponent(ctx context.Context, component string) (map[string]testutil.Metrics, error) {
pods, fromLeader := make([]string, 0), false
switch component {
case names.KarmadaControllerManagerComponentName:
pods, fromLeader = g.controllerManagerPods, true
case names.KarmadaSchedulerComponentName:
pods, fromLeader = g.schedulerPods, true
case names.KarmadaDeschedulerComponentName:
pods, fromLeader = g.deschedulerPods, true
case names.KarmadaMetricsAdapterComponentName:
pods = g.metricsAdapterPods
case names.KarmadaSchedulerEstimatorComponentName:
pods = g.schedulerEstimatorPods
case names.KarmadaWebhookComponentName:
pods = g.webhookPods
}
return g.grabMetricsFromPod(ctx, component, pods, fromLeader)
}
// grabMetricsFromPod fetch metrics from the leader pod
func (g *Grabber) grabMetricsFromPod(ctx context.Context, component string, pods []string, fromLeader bool) (map[string]testutil.Metrics, error) {
var output string
var lastMetricsFetchErr error
result := make(map[string]testutil.Metrics)
for _, podName := range pods {
if metricsWaitErr := wait.PollUntilContextTimeout(ctx, time.Second, queryTimeout, true, func(ctx context.Context) (bool, error) {
output, lastMetricsFetchErr = GetMetricsFromPod(ctx, g.hostKubeClient, podName, karmadaNamespace, metricsBindPort)
return lastMetricsFetchErr == nil, nil
}); metricsWaitErr != nil {
klog.Errorf("error waiting for %s to expose metrics: %v; %v", podName, metricsWaitErr, lastMetricsFetchErr)
continue
}
podMetrics := testutil.Metrics{}
metricsParseErr := testutil.ParseMetrics(output, &podMetrics)
if metricsParseErr != nil {
klog.Errorf("failed to parse metrics for %s: %v", podName, metricsParseErr)
continue
}
// judge which pod is the leader pod
if fromLeader && !isLeaderPod(podMetrics[leaderPodMetric]) {
klog.Infof("skip fetch %s since it is not the leader pod", podName)
continue
}
result[podName] = podMetrics
klog.Infof("successfully grabbed metrics of %s", podName)
}
if len(result) == 0 {
return nil, fmt.Errorf("failed to fetch metrics from the pod of %s", component)
}
return result, nil
}
// GetMetricsFromPod retrieves metrics data.
func GetMetricsFromPod(ctx context.Context, client clientset.Interface, podName string, namespace string, port int) (string, error) {
rawOutput, err := client.CoreV1().RESTClient().Get().
Namespace(namespace).
Resource("pods").
SubResource("proxy").
Name(fmt.Sprintf("%s:%d", podName, port)).
Suffix("metrics").
Do(ctx).Raw()
if err != nil {
return "", err
}
return string(rawOutput), nil
}
func isLeaderPod(samples model.Samples) bool {
for _, sample := range samples {
if sample.Value > 0 {
return true
}
}
return false
}
// PrintMetricSample prints the metric sample
func PrintMetricSample(podName string, sample model.Samples) {
if sample.Len() == 0 {
return
}
if podName != "" {
klog.Infof("metrics from pod: %s", podName)
}
for _, s := range sample {
klog.Infof("metric: %v, value: %v, timestamp: %v", s.Metric, s.Value, s.Timestamp)
}
}