github.com/kubewharf/katalyst-core@v0.5.3/pkg/custom-metric/store/local/local_store.go (about)

     1  /*
     2  Copyright 2022 The Katalyst 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 local
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/selection"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/klog/v2"
    34  
    35  	katalystbase "github.com/kubewharf/katalyst-core/cmd/base"
    36  	metricconf "github.com/kubewharf/katalyst-core/pkg/config/metric"
    37  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/store"
    38  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/store/data"
    39  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/store/data/types"
    40  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    41  )
    42  
    43  const (
    44  	MetricStoreNameLocalMemory = "local-memory-store"
    45  
    46  	labelIndexEmpty string = "empty"
    47  
    48  	MetricNameInsertFailed = "kcmas_local_store_insert_failed"
    49  )
    50  
    51  // getLabelIndexFunc is a function to get a index function that indexes based on object's label[labelName]
    52  var getLabelIndexFunc = func(labelName string) func(interface{}) ([]string, error) {
    53  	return func(obj interface{}) ([]string, error) {
    54  		objectMeta, err := meta.Accessor(obj)
    55  		if err != nil {
    56  			return []string{""}, fmt.Errorf("object has no meta: %v", err)
    57  		}
    58  		objectLabels := objectMeta.GetLabels()
    59  		if objectLabels == nil {
    60  			return []string{labelIndexEmpty}, nil
    61  		}
    62  		value, ok := objectLabels[labelName]
    63  		if !ok {
    64  			return []string{labelIndexEmpty}, nil
    65  		}
    66  		return []string{value}, nil
    67  	}
    68  }
    69  
    70  // LocalMemoryMetricStore implements MetricStore with single-node versioned
    71  // in-memory storage, and it will be used as a default implementation, especially
    72  // when the amount of internalMetric or the size of cluster is small.
    73  type LocalMemoryMetricStore struct {
    74  	ctx         context.Context
    75  	storeConf   *metricconf.StoreConfiguration
    76  	genericConf *metricconf.GenericMetricConfiguration
    77  	emitter     metrics.MetricEmitter
    78  
    79  	// validMetricObject is used to map kubernetes objects to gvk and informer
    80  	validMetricObject map[string]schema.GroupVersionResource
    81  	objectLister      map[string]cache.GenericLister
    82  	objectInformer    map[string]cache.SharedIndexInformer
    83  	indexLabelKeys    []string
    84  
    85  	syncedFunc  []cache.InformerSynced
    86  	syncSuccess bool
    87  
    88  	cache *data.CachedMetric
    89  }
    90  
    91  var _ store.MetricStore = &LocalMemoryMetricStore{}
    92  
    93  func NewLocalMemoryMetricStore(ctx context.Context, baseCtx *katalystbase.GenericContext,
    94  	genericConf *metricconf.GenericMetricConfiguration, storeConf *metricconf.StoreConfiguration,
    95  ) (store.MetricStore, error) {
    96  	metricsEmitter := baseCtx.EmitterPool.GetDefaultMetricsEmitter()
    97  	if metricsEmitter == nil {
    98  		metricsEmitter = metrics.DummyMetrics{}
    99  	}
   100  
   101  	l := &LocalMemoryMetricStore{
   102  		ctx:               ctx,
   103  		genericConf:       genericConf,
   104  		storeConf:         storeConf,
   105  		cache:             data.NewCachedMetric(metricsEmitter, data.ObjectMetricStoreTypeBucket),
   106  		validMetricObject: data.GetSupportedMetricObject(),
   107  		objectLister:      make(map[string]cache.GenericLister),
   108  		objectInformer:    make(map[string]cache.SharedIndexInformer),
   109  		indexLabelKeys:    storeConf.IndexLabelKeys,
   110  		emitter:           baseCtx.EmitterPool.GetDefaultMetricsEmitter().WithTags("local_store"),
   111  	}
   112  
   113  	for r, gvrSchema := range l.validMetricObject {
   114  		wf := baseCtx.MetaInformerFactory.ForResource(gvrSchema)
   115  		l.objectLister[r] = wf.Lister()
   116  		l.objectInformer[r] = wf.Informer()
   117  		if len(storeConf.IndexLabelKeys) > 0 {
   118  			for i := range storeConf.IndexLabelKeys {
   119  				key := storeConf.IndexLabelKeys[i]
   120  				if _, ok := wf.Informer().GetIndexer().GetIndexers()[key]; !ok {
   121  					if err := wf.Informer().AddIndexers(cache.Indexers{key: getLabelIndexFunc(key)}); err != nil {
   122  						klog.Errorf("create label indexer failed, indexName: %v, indexKey: %v, err: %v", storeConf.IndexLabelKeys, storeConf.IndexLabelKeys, err)
   123  						return nil, err
   124  					}
   125  				}
   126  			}
   127  		}
   128  		l.syncedFunc = append(l.syncedFunc, wf.Informer().HasSynced)
   129  	}
   130  
   131  	return l, nil
   132  }
   133  
   134  func (l *LocalMemoryMetricStore) Name() string { return MetricStoreNameLocalMemory }
   135  
   136  func (l *LocalMemoryMetricStore) Start() error {
   137  	klog.Info("starting local memory store")
   138  	if !cache.WaitForCacheSync(l.ctx.Done(), l.syncedFunc...) {
   139  		return fmt.Errorf("unable to sync caches for %s", MetricStoreNameLocalMemory)
   140  	}
   141  	klog.Info("started local memory store")
   142  	l.syncSuccess = true
   143  
   144  	go wait.Until(l.gc, l.storeConf.GCPeriod, l.ctx.Done())
   145  	go wait.Until(l.monitor, time.Minute*3, l.ctx.Done())
   146  	go wait.Until(l.purge, l.storeConf.PurgePeriod, l.ctx.Done())
   147  	return nil
   148  }
   149  
   150  func (l *LocalMemoryMetricStore) Stop() error {
   151  	return nil
   152  }
   153  
   154  func (l *LocalMemoryMetricStore) InsertMetric(seriesList []*data.MetricSeries) error {
   155  	begin := time.Now()
   156  	defer func() {
   157  		klog.V(5).Infof("[LocalMemoryMetricStore] InsertMetric costs %s", time.Since(begin).String())
   158  	}()
   159  
   160  	for _, series := range seriesList {
   161  		begin := time.Now()
   162  		seriesData, ok := l.parseMetricSeries(series)
   163  		if !ok {
   164  			continue
   165  		}
   166  
   167  		err := l.cache.AddSeriesMetric(seriesData)
   168  		if err != nil {
   169  			klog.Infof("Insert Metric failed, metricName: %v,objectName:%v,len:%v,", seriesData.GetName(), seriesData.GetObjectName(), seriesData.Len())
   170  			_ = l.emitter.StoreInt64(MetricNameInsertFailed, 1, metrics.MetricTypeNameCount,
   171  				metrics.MetricTag{Key: "metric_name", Val: seriesData.GetName()},
   172  				metrics.MetricTag{Key: "object_kind", Val: seriesData.GetObjectKind()},
   173  			)
   174  			return err
   175  		}
   176  		klog.V(6).Infof("LocalMemoryMetricStore] insert with %v, costs %s", seriesData.String(), time.Since(begin).String())
   177  	}
   178  	return nil
   179  }
   180  
   181  func (l *LocalMemoryMetricStore) getObjectMetaByIndex(gr *schema.GroupResource, objSelector labels.Selector) (bool, []types.ObjectMetaImp, error) {
   182  	hitIndex := false
   183  	matchedObjectMeta := make([]types.ObjectMetaImp, 0)
   184  
   185  	if objSelector == nil {
   186  		return false, matchedObjectMeta, nil
   187  	}
   188  
   189  	requirements, _ := objSelector.Requirements()
   190  	for _, requirement := range requirements {
   191  		if hitIndex {
   192  			break
   193  		}
   194  
   195  		for i := range l.indexLabelKeys {
   196  			key := l.indexLabelKeys[i]
   197  			if requirement.Key() == key {
   198  				switch requirement.Operator() {
   199  				case selection.Equals, selection.DoubleEquals, selection.In:
   200  					hitIndex = true
   201  					for indexValue := range requirement.Values() {
   202  						objects, err := l.objectInformer[gr.String()].GetIndexer().ByIndex(key, indexValue)
   203  						if err != nil {
   204  							return false, matchedObjectMeta, fmt.Errorf("get object by index failed,err:%v", err)
   205  						}
   206  						for i := range objects {
   207  							obj := objects[i]
   208  							metadata, ok := obj.(*v1.PartialObjectMetadata)
   209  							if !ok {
   210  								return false, matchedObjectMeta, fmt.Errorf("%#v failed to transform into PartialObjectMetadata", obj)
   211  							}
   212  
   213  							matchedObjectMeta = append(matchedObjectMeta, types.ObjectMetaImp{
   214  								ObjectNamespace: metadata.Namespace,
   215  								ObjectName:      metadata.Name,
   216  							})
   217  						}
   218  					}
   219  				}
   220  			}
   221  			if hitIndex {
   222  				return hitIndex, matchedObjectMeta, nil
   223  			}
   224  		}
   225  	}
   226  
   227  	return hitIndex, matchedObjectMeta, nil
   228  }
   229  
   230  func (l *LocalMemoryMetricStore) GetMetric(_ context.Context, namespace, metricName, objName string, gr *schema.GroupResource,
   231  	objSelector, metricSelector labels.Selector, latest bool,
   232  ) ([]types.Metric, error) {
   233  	begin := time.Now()
   234  	defer func() {
   235  		klog.V(5).Infof("[LocalMemoryMetricStore] GetMetric costs %s", time.Since(begin).String())
   236  	}()
   237  
   238  	var (
   239  		res               []types.Metric
   240  		metricList        []types.Metric
   241  		err               error
   242  		hitIndex          bool
   243  		matchedObjectMeta []types.ObjectMetaImp
   244  	)
   245  
   246  	// always try to get by metric-name if nominated, otherwise list all internal metrics
   247  	if metricName != "" && metricName != "*" {
   248  		if objName != "" && objName != "*" {
   249  			metricList, _, err = l.cache.GetMetric(namespace, metricName, objName, nil, false, gr, metricSelector, latest)
   250  		} else {
   251  			hitIndex, matchedObjectMeta, err = l.getObjectMetaByIndex(gr, objSelector)
   252  			if err != nil {
   253  				return metricList, err
   254  			}
   255  
   256  			if hitIndex {
   257  				metricList, _, err = l.cache.GetMetric(namespace, metricName, objName, matchedObjectMeta, true, gr, metricSelector, latest)
   258  			} else {
   259  				metricList, _, err = l.cache.GetMetric(namespace, metricName, objName, nil, false, gr, metricSelector, latest)
   260  			}
   261  		}
   262  	} else {
   263  		metricList = l.cache.GetAllMetricsInNamespace(namespace)
   264  	}
   265  
   266  	if err != nil {
   267  		return metricList, err
   268  	}
   269  
   270  	for _, metricItem := range metricList {
   271  		if objName != "" {
   272  			if valid, err := l.checkInternalMetricMatchedWithObject(metricItem, gr, namespace, objName); err != nil {
   273  				klog.Errorf("check %+v object %v err %v", metricItem.GetName(), objName, err)
   274  			} else if !valid {
   275  				klog.V(6).Infof("%v invalid object", metricItem.String())
   276  				continue
   277  			}
   278  		}
   279  
   280  		if objSelector != nil {
   281  			if valid, err := l.checkInternalMetricMatchedWithObjectList(metricItem, gr, namespace, objSelector); err != nil {
   282  				klog.Errorf("check %+v object selector %v err %v", metricItem.GetName(), objSelector, err)
   283  			} else if !valid {
   284  				klog.V(6).Infof("%v invalid objectSelector", metricItem.String())
   285  				continue
   286  			}
   287  		}
   288  
   289  		res = append(res, metricItem)
   290  	}
   291  	return res, nil
   292  }
   293  
   294  func (l *LocalMemoryMetricStore) ListMetricMeta(_ context.Context, withObject bool) ([]types.MetricMeta, error) {
   295  	begin := time.Now()
   296  	defer func() {
   297  		klog.V(5).Infof("[LocalMemoryMetricStore] ListMetricMeta costs %s", time.Since(begin).String())
   298  	}()
   299  
   300  	return l.cache.ListAllMetricMeta(withObject), nil
   301  }
   302  
   303  // gc is used to clean those custom metric internalMetric that has been out-of-date
   304  func (l *LocalMemoryMetricStore) gc() {
   305  	begin := time.Now()
   306  	defer func() {
   307  		klog.Infof("[LocalMemoryMetricStore] gc costs %s", time.Since(begin).String())
   308  	}()
   309  
   310  	expiredTime := begin.Add(-1 * l.genericConf.OutOfDataPeriod)
   311  	l.cache.GC(expiredTime)
   312  }
   313  
   314  func (l *LocalMemoryMetricStore) purge() {
   315  	begin := time.Now()
   316  	defer func() {
   317  		klog.Infof("[LocalMemoryMetricStore] purge costs %s", time.Since(begin).String())
   318  	}()
   319  
   320  	l.cache.Purge()
   321  }
   322  
   323  func (l *LocalMemoryMetricStore) monitor() {
   324  	begin := time.Now()
   325  	defer func() {
   326  		klog.Infof("[LocalMemoryMetricStore] monitor costs %s", time.Since(begin).String())
   327  	}()
   328  
   329  	names := l.cache.ListAllMetricNames()
   330  	klog.Infof("currently with %v metric: %v", len(names), names)
   331  }
   332  
   333  // parseMetricSeries parses the given data.MetricSeries into internalMetric
   334  func (l *LocalMemoryMetricStore) parseMetricSeries(series *data.MetricSeries) (types.Metric, bool) {
   335  	// skip already out-of-dated metric contents
   336  	expiredTime := time.Now().Add(-1 * l.genericConf.OutOfDataPeriod).UnixMilli()
   337  
   338  	res := types.NewSeriesMetric()
   339  
   340  	metricMeta := types.MetricMetaImp{Name: series.Name}
   341  	objectMeta := types.ObjectMetaImp{}
   342  	basicLabel := make(map[string]string)
   343  	for key, value := range series.Labels {
   344  		switch data.CustomMetricLabelKey(key) {
   345  		case data.CustomMetricLabelKeyNamespace:
   346  			metricMeta.Namespaced = true
   347  			objectMeta.ObjectNamespace = value
   348  		case data.CustomMetricLabelKeyObject:
   349  			metricMeta.ObjectKind = value
   350  		case data.CustomMetricLabelKeyObjectName:
   351  			objectMeta.ObjectName = value
   352  		default:
   353  			if strings.HasPrefix(key, fmt.Sprintf("%v", data.CustomMetricLabelSelectorPrefixKey)) {
   354  				basicLabel[strings.TrimPrefix(key, fmt.Sprintf("%v", data.CustomMetricLabelSelectorPrefixKey))] = value
   355  			}
   356  		}
   357  	}
   358  	res.MetricMetaImp = metricMeta
   359  	res.ObjectMetaImp = objectMeta
   360  	res.BasicMetric = types.BasicMetric{Labels: basicLabel}
   361  
   362  	if res.GetObjectKind() != "" {
   363  		if res.GetObjectName() == "" {
   364  			return nil, false
   365  		}
   366  
   367  		_, err := l.getObject(res.GetObjectKind(), res.GetObjectNamespace(), res.GetObjectName())
   368  		if err != nil {
   369  			klog.Errorf("invalid objects %v %v/%v: %v", res.GetObjectKind(), res.GetObjectNamespace(), res.GetObjectName(), err)
   370  			return nil, false
   371  		}
   372  	}
   373  
   374  	for _, m := range series.Series {
   375  		if m.Timestamp < expiredTime {
   376  			continue
   377  		}
   378  		res.AddMetric(&types.SeriesItem{Value: m.Data, Timestamp: m.Timestamp})
   379  	}
   380  	return res, true
   381  }
   382  
   383  // checkInternalMetricMatchedWithObject checks if the internal matches with kubernetes object
   384  // the kubernetes object should be obtained by namespace/name
   385  // if not, return an error to represent the unmatched reasons
   386  func (l *LocalMemoryMetricStore) checkInternalMetricMatchedWithObject(internal types.Metric,
   387  	gr *schema.GroupResource, namespace, name string,
   388  ) (bool, error) {
   389  	if gr != nil && gr.String() != internal.GetObjectKind() {
   390  		klog.V(5).Infof("gvr %+v not match with objects %v", gr, internal.GetObjectKind())
   391  		return false, nil
   392  	}
   393  
   394  	if internal.GetObjectName() != name {
   395  		klog.V(5).Infof("%v namespace %v not match objectName %v", internal.GetName(), namespace, name)
   396  		return false, nil
   397  	}
   398  
   399  	_, err := l.getObject(internal.GetObjectKind(), namespace, name)
   400  	if err != nil {
   401  		return false, err
   402  	}
   403  
   404  	return true, nil
   405  }
   406  
   407  // checkInternalMetricMatchedWithObject checks if the internal matches with kubernetes object
   408  // the kubernetes object should be obtained by label selector
   409  // if not, return an error to represent the unmatched reasons
   410  func (l *LocalMemoryMetricStore) checkInternalMetricMatchedWithObjectList(internal types.Metric,
   411  	gr *schema.GroupResource, namespace string, selector labels.Selector,
   412  ) (bool, error) {
   413  	if gr != nil && gr.String() != internal.GetObjectKind() {
   414  		klog.V(5).Infof("gvr %+v not match with objects %v", gr, internal.GetObjectKind())
   415  		return false, nil
   416  	}
   417  
   418  	obj, err := l.getObject(internal.GetObjectKind(), namespace, internal.GetObjectName())
   419  	if err != nil {
   420  		klog.V(5).Infof("get object %v/%v kind %s failed, %v", namespace, internal.GetName(), internal.GetObjectKind(), err)
   421  		return false, nil
   422  	}
   423  
   424  	workload, ok := obj.(*v1.PartialObjectMetadata)
   425  	if !ok {
   426  		return false, fmt.Errorf("%#v failed to transform into PartialObjectMetadata", obj)
   427  	}
   428  
   429  	if !selector.Matches(labels.Set(workload.GetLabels())) {
   430  		klog.V(5).Infof("%v selector %v not match label %v", internal.GetName(), selector, workload.GetLabels())
   431  		return false, nil
   432  	}
   433  
   434  	return true, nil
   435  }
   436  
   437  func (l *LocalMemoryMetricStore) getObject(gvr, namespace, name string) (runtime.Object, error) {
   438  	if name == "" {
   439  		return nil, fmt.Errorf("name should not be empty")
   440  	}
   441  
   442  	lister, ok := l.objectLister[gvr]
   443  	if !ok {
   444  		return nil, fmt.Errorf("unsupported obejct: %v", gvr)
   445  	}
   446  
   447  	if namespace != "" {
   448  		return lister.ByNamespace(namespace).Get(name)
   449  	}
   450  	return lister.Get(name)
   451  }
   452  
   453  func (l *LocalMemoryMetricStore) getObjectList(gvr, namespace string, selector labels.Selector) ([]runtime.Object, error) {
   454  	lister, ok := l.objectLister[gvr]
   455  	if !ok {
   456  		return nil, fmt.Errorf("unsupported obejct: %v", gvr)
   457  	}
   458  
   459  	if namespace != "" {
   460  		return lister.ByNamespace(namespace).List(selector)
   461  	}
   462  	return lister.List(selector)
   463  }