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 }