github.com/kubewharf/katalyst-core@v0.5.3/pkg/custom-metric/store/data/internal/internal.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 internal
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/montanaflynn/stats"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  
    29  	apimetric "github.com/kubewharf/katalyst-api/pkg/metric"
    30  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/store/data/types"
    31  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    32  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    33  )
    34  
    35  var aggregateFuncMap = map[string]aggregateFunc{
    36  	apimetric.AggregateFunctionMax:    maxAgg,
    37  	apimetric.AggregateFunctionMin:    minAgg,
    38  	apimetric.AggregateFunctionAvg:    avgAgg,
    39  	apimetric.AggregateFunctionP99:    p99Agg,
    40  	apimetric.AggregateFunctionP95:    p95Agg,
    41  	apimetric.AggregateFunctionP90:    p90Agg,
    42  	apimetric.AggregateFunctionLatest: latestAgg,
    43  }
    44  
    45  type aggregateFunc func(items []*types.SeriesItem) (float64, error)
    46  
    47  func buildAggregatedIdentity(items []*types.SeriesItem) (types.AggregatedIdentity, error) {
    48  	latestTime := items[0].Timestamp
    49  	oldestTime := items[0].Timestamp
    50  
    51  	for i := range items {
    52  		latestTime = general.MaxInt64(latestTime, items[i].Timestamp)
    53  		oldestTime = general.MinInt64(oldestTime, items[i].Timestamp)
    54  	}
    55  
    56  	identity := types.AggregatedIdentity{
    57  		Count:         int64(len(items)),
    58  		Timestamp:     latestTime,
    59  		WindowSeconds: (latestTime - oldestTime) / time.Second.Milliseconds(),
    60  	}
    61  
    62  	return identity, nil
    63  }
    64  
    65  func maxAgg(items []*types.SeriesItem) (float64, error) {
    66  	if len(items) == 0 {
    67  		return -1, fmt.Errorf("empty sequence for max aggregate")
    68  	}
    69  	max := items[0].Value
    70  	for i := range items {
    71  		max = general.MaxFloat64(max, items[i].Value)
    72  	}
    73  
    74  	return max, nil
    75  }
    76  
    77  func minAgg(items []*types.SeriesItem) (float64, error) {
    78  	if len(items) == 0 {
    79  		return -1, fmt.Errorf("empty sequence for min aggregate")
    80  	}
    81  	max := items[0].Value
    82  	for i := range items {
    83  		max = general.MinFloat64(max, items[i].Value)
    84  	}
    85  
    86  	return max, nil
    87  }
    88  
    89  func avgAgg(items []*types.SeriesItem) (float64, error) {
    90  	if len(items) == 0 {
    91  		return -1, fmt.Errorf("empty sequence for avg aggregate")
    92  	}
    93  	var sum float64 = 0
    94  	for i := range items {
    95  		sum += items[i].Value
    96  	}
    97  
    98  	return sum / float64(len(items)), nil
    99  }
   100  
   101  func p99Agg(items []*types.SeriesItem) (float64, error) {
   102  	if len(items) == 0 {
   103  		return -1, fmt.Errorf("empty sequence for p99 aggregate")
   104  	}
   105  
   106  	var statsData stats.Float64Data
   107  	for i := range items {
   108  		statsData = append(statsData, items[i].Value)
   109  	}
   110  
   111  	if p99, err := statsData.Percentile(99); err != nil {
   112  		return -1, fmt.Errorf("failed to get stats p99: %v", err)
   113  	} else {
   114  		return p99, nil
   115  	}
   116  }
   117  
   118  func p90Agg(items []*types.SeriesItem) (float64, error) {
   119  	if len(items) == 0 {
   120  		return -1, fmt.Errorf("empty sequence for p90 aggregate")
   121  	}
   122  
   123  	var statsData stats.Float64Data
   124  	for i := range items {
   125  		statsData = append(statsData, items[i].Value)
   126  	}
   127  
   128  	if p90, err := statsData.Percentile(90); err != nil {
   129  		return -1, fmt.Errorf("failed to get stats p90: %v", err)
   130  	} else {
   131  		return p90, nil
   132  	}
   133  }
   134  
   135  func p95Agg(items []*types.SeriesItem) (float64, error) {
   136  	if len(items) == 0 {
   137  		return -1, fmt.Errorf("empty sequence for p95 aggregate")
   138  	}
   139  
   140  	var statsData stats.Float64Data
   141  	for i := range items {
   142  		statsData = append(statsData, items[i].Value)
   143  	}
   144  
   145  	if p95, err := statsData.Percentile(95); err != nil {
   146  		return -1, fmt.Errorf("failed to get stats p95: %v", err)
   147  	} else {
   148  		return p95, nil
   149  	}
   150  }
   151  
   152  func latestAgg(items []*types.SeriesItem) (float64, error) {
   153  	if len(items) == 0 {
   154  		return -1, fmt.Errorf("empty sequence for latest aggregate")
   155  	}
   156  
   157  	latestItem := items[0]
   158  	for i := range items {
   159  		if latestItem.Timestamp < items[i].Timestamp {
   160  			latestItem = items[i]
   161  		}
   162  	}
   163  
   164  	return latestItem.Value, nil
   165  }
   166  
   167  // labeledMetricImp is used as an internal version of metricItem for only one combination of labels.
   168  type labeledMetricImp struct {
   169  	types.BasicMetric
   170  
   171  	timestampSets map[int64]interface{}
   172  	seriesMetric  *types.SeriesMetric
   173  }
   174  
   175  func newLabeledMetricImp(b types.BasicMetric) *labeledMetricImp {
   176  	return &labeledMetricImp{
   177  		BasicMetric:   b,
   178  		timestampSets: make(map[int64]interface{}),
   179  		seriesMetric:  types.NewSeriesMetric(),
   180  	}
   181  }
   182  
   183  func (l *labeledMetricImp) DeepCopy() *labeledMetricImp {
   184  	return &labeledMetricImp{
   185  		BasicMetric:   l.BasicMetric.DeepCopy(),
   186  		timestampSets: l.timestampSets,
   187  		seriesMetric:  l.seriesMetric.DeepCopy().(*types.SeriesMetric),
   188  	}
   189  }
   190  
   191  func (l *labeledMetricImp) gc(expiredTimestamp int64) {
   192  	var ordered []*types.SeriesItem
   193  	for _, m := range l.seriesMetric.Values {
   194  		if m.Timestamp > expiredTimestamp {
   195  			ordered = append(ordered, m)
   196  		} else {
   197  			delete(l.timestampSets, m.Timestamp)
   198  		}
   199  	}
   200  	l.seriesMetric.Values = ordered
   201  }
   202  
   203  func (l *labeledMetricImp) len() int {
   204  	return l.seriesMetric.Len()
   205  }
   206  
   207  // MetricImp is used as an internal version of metricItem.
   208  type MetricImp struct {
   209  	// those unified fields is only kept for one replica in MetricImp,
   210  	// and we will try to copy into types.Metric when corresponding functions are called.
   211  	types.MetricMetaImp
   212  	types.ObjectMetaImp
   213  
   214  	sync.RWMutex
   215  
   216  	// Timestamp will be used as a unique key to avoid
   217  	// duplicated metric to be written more than once
   218  	expiredTime int64
   219  
   220  	labeledMetricStore map[string]*labeledMetricImp
   221  	aggregatedMetric   map[string]*types.AggregatedMetric
   222  }
   223  
   224  func NewInternalMetric(m types.MetricMetaImp, o types.ObjectMetaImp) *MetricImp {
   225  	return &MetricImp{
   226  		MetricMetaImp:      m,
   227  		ObjectMetaImp:      o,
   228  		labeledMetricStore: make(map[string]*labeledMetricImp),
   229  		aggregatedMetric:   make(map[string]*types.AggregatedMetric),
   230  	}
   231  }
   232  
   233  func (a *MetricImp) GetSeriesItems(metricSelector labels.Selector, latest bool) ([]*types.SeriesMetric, bool) {
   234  	a.RLock()
   235  	defer a.RUnlock()
   236  
   237  	if len(a.labeledMetricStore) == 0 {
   238  		return nil, false
   239  	}
   240  
   241  	var latestTimestamp int64 = 0
   242  	result := make([]*types.SeriesMetric, 0, len(a.labeledMetricStore))
   243  	for k := range a.labeledMetricStore {
   244  		if metricSelector != nil && !metricSelector.Matches(labels.Set(a.labeledMetricStore[k].Labels)) {
   245  			continue
   246  		}
   247  
   248  		res := a.labeledMetricStore[k].seriesMetric.DeepCopy().(*types.SeriesMetric)
   249  		res.MetricMetaImp = a.MetricMetaImp.DeepCopy()
   250  		res.ObjectMetaImp = a.ObjectMetaImp.DeepCopy()
   251  		res.BasicMetric = a.labeledMetricStore[k].BasicMetric
   252  		if latest {
   253  			latestItem := res.Values[res.Len()-1]
   254  			if latestItem.Timestamp <= latestTimestamp {
   255  				continue
   256  			}
   257  
   258  			latestTimestamp = latestItem.Timestamp
   259  			res.Values = []*types.SeriesItem{latestItem}
   260  		}
   261  		result = append(result, res)
   262  	}
   263  
   264  	// only one latest metric
   265  	if latest && len(result) > 1 {
   266  		latestMetric := result[0]
   267  		for i := range result {
   268  			if latestMetric.Values[0].Timestamp < result[i].Values[0].Timestamp {
   269  				latestMetric = result[i]
   270  			}
   271  		}
   272  		result = []*types.SeriesMetric{latestMetric}
   273  	}
   274  
   275  	return result, true
   276  }
   277  
   278  // parseMetricSelector will parse the metricSelector and return the groupByTags and selector.
   279  func (a *MetricImp) parseMetricSelector(metricSelector labels.Selector) (groupLabelKeys sets.String, selector labels.Selector) {
   280  	if metricSelector == nil {
   281  		return sets.NewString(), labels.Everything()
   282  	}
   283  
   284  	requirements, selectable := metricSelector.Requirements()
   285  	if !selectable {
   286  		return sets.NewString(), metricSelector
   287  	}
   288  
   289  	selector = labels.Everything()
   290  	for i := range requirements {
   291  		if requirements[i].Key() == apimetric.MetricSelectorKeyGroupBy {
   292  			groupLabelKeys = requirements[i].Values()
   293  		} else {
   294  			selector = selector.Add(requirements[i])
   295  		}
   296  	}
   297  
   298  	return groupLabelKeys, selector
   299  }
   300  
   301  func (a *MetricImp) GetAggregatedItems(metricSelector labels.Selector, agg string) ([]types.Metric, bool) {
   302  	a.RLock()
   303  	defer a.RUnlock()
   304  
   305  	groupLabelKeys, selector := a.parseMetricSelector(metricSelector)
   306  	// just retrieve the pre-aggregated value if there is no filter or group by demands.
   307  	if (selector == nil || selector.Empty()) && groupLabelKeys.Len() == 0 {
   308  		v, ok := a.aggregatedMetric[agg]
   309  		if !ok {
   310  			return nil, false
   311  		}
   312  		res := v.DeepCopy().(*types.AggregatedMetric)
   313  		res.MetricMetaImp = types.AggregatorMetricMetaImp(a.MetricMetaImp, agg)
   314  		res.ObjectMetaImp = a.ObjectMetaImp.DeepCopy()
   315  		// don't set metric labels for aggregated metric
   316  		return []types.Metric{res}, true
   317  	}
   318  
   319  	// realtime aggregate for items selected by selector and group by label keys.
   320  	aggFunc, ok := aggregateFuncMap[agg]
   321  	if !ok {
   322  		general.Errorf("unsupported aggregate function:%v", agg)
   323  		return nil, false
   324  	}
   325  
   326  	// filter metrics
   327  	matchedMetrics := make([]*labeledMetricImp, 0)
   328  	for k := range a.labeledMetricStore {
   329  		ms := a.labeledMetricStore[k]
   330  		if selector.Matches(labels.Set(ms.Labels)) {
   331  			matchedMetrics = append(matchedMetrics, ms)
   332  		}
   333  	}
   334  
   335  	// group metrics
   336  	groupMap := make(map[string][]*types.SeriesItem)
   337  	if groupLabelKeys.Len() == 0 {
   338  		for i := range matchedMetrics {
   339  			groupMap[""] = append(groupMap[""], matchedMetrics[i].seriesMetric.Values...)
   340  		}
   341  	} else {
   342  		groupMap = groupMetrics(matchedMetrics, groupLabelKeys)
   343  	}
   344  
   345  	var results []types.Metric
   346  	for labelGroup, seriesMetric := range groupMap {
   347  		var (
   348  			aggregatedValue float64
   349  			identity        types.AggregatedIdentity
   350  			metricLabel     labels.Set
   351  			err             error
   352  		)
   353  		if aggregatedValue, err = aggFunc(seriesMetric); err != nil {
   354  			general.Errorf("aggregate for %v/%v metric %v with metric selector %v failed, err:%v",
   355  				a.GetObjectNamespace(), a.GetObjectName(), a.MetricMetaImp.Name, metricSelector, err)
   356  			return nil, false
   357  		}
   358  
   359  		if identity, err = buildAggregatedIdentity(seriesMetric); err != nil {
   360  			general.Errorf("get aggregated identity for %v/%v metric %v with metric selector %v failed, err:%v",
   361  				a.GetObjectNamespace(), a.GetObjectName(), a.MetricMetaImp.Name, metricSelector, err)
   362  			return nil, false
   363  		}
   364  
   365  		metricLabel, err = labels.ConvertSelectorToLabelsMap(labelGroup)
   366  		if err != nil {
   367  			general.Errorf("get aggregated identity for %v/%v metric %v with metric selector %v failed, err:%v",
   368  				a.GetObjectNamespace(), a.GetObjectName(), a.MetricMetaImp.Name, metricSelector, err)
   369  			return nil, false
   370  		}
   371  
   372  		m := types.NewAggregatedInternalMetric(aggregatedValue, identity)
   373  		m.MetricMetaImp = types.AggregatorMetricMetaImp(a.MetricMetaImp, agg)
   374  		m.ObjectMetaImp = a.ObjectMetaImp.DeepCopy()
   375  		m.Labels = metricLabel
   376  		results = append(results, m)
   377  	}
   378  
   379  	return results, true
   380  }
   381  
   382  func groupMetrics(metrics []*labeledMetricImp, groupLabelKeys sets.String) map[string][]*types.SeriesItem {
   383  	// group the metrics by label combinations which are in groupLabelKeys.
   384  	results := make(map[string][]*types.SeriesItem)
   385  	for i := range metrics {
   386  		groupLabels := labels.Set{}
   387  		for k, v := range metrics[i].Labels {
   388  			if groupLabelKeys.Has(k) {
   389  				groupLabels[k] = v
   390  			}
   391  		}
   392  		// drop the metrics which doesn't contain any group label.
   393  		if len(groupLabels) == 0 {
   394  			continue
   395  		}
   396  		results[groupLabels.String()] = append(results[groupLabels.String()], metrics[i].seriesMetric.Values...)
   397  	}
   398  
   399  	return results
   400  }
   401  
   402  func (a *MetricImp) AddSeriesMetric(is *types.SeriesMetric) []*types.SeriesItem {
   403  	a.Lock()
   404  	defer a.Unlock()
   405  
   406  	var res []*types.SeriesItem
   407  
   408  	labelsString := is.BasicMetric.String()
   409  	if _, ok := a.labeledMetricStore[labelsString]; !ok {
   410  		a.labeledMetricStore[labelsString] = newLabeledMetricImp(is.BasicMetric)
   411  	}
   412  
   413  	ms := a.labeledMetricStore[labelsString]
   414  
   415  	for _, v := range is.Values {
   416  		// timestamp must be none-empty and valid
   417  		if v.Timestamp == 0 || v.Timestamp < a.expiredTime {
   418  			continue
   419  		}
   420  
   421  		// timestamp must be unique
   422  		if _, ok := ms.timestampSets[v.Timestamp]; ok {
   423  			continue
   424  		}
   425  		ms.timestampSets[v.Timestamp] = struct{}{}
   426  
   427  		// always make the Value list as ordered
   428  		i := sort.Search(len(ms.seriesMetric.Values), func(i int) bool {
   429  			return v.Timestamp < ms.seriesMetric.Values[i].Timestamp
   430  		})
   431  
   432  		ms.seriesMetric.Values = append(ms.seriesMetric.Values, &types.SeriesItem{})
   433  		copy(ms.seriesMetric.Values[i+1:], ms.seriesMetric.Values[i:])
   434  		ms.seriesMetric.Values[i] = &types.SeriesItem{
   435  			Value:     v.Value,
   436  			Timestamp: v.Timestamp,
   437  		}
   438  
   439  		res = append(res, v)
   440  	}
   441  
   442  	return res
   443  }
   444  
   445  func (a *MetricImp) MergeAggregatedMetric(as *types.AggregatedMetric) {
   446  	a.Lock()
   447  	defer a.Unlock()
   448  
   449  	_, aggName := types.ParseAggregator(as.GetName())
   450  	if _, ok := a.aggregatedMetric[aggName]; !ok {
   451  		a.aggregatedMetric[aggName] = as
   452  	}
   453  }
   454  
   455  // aggregate calculate the aggregated metric based on snapshot of current store.
   456  func (a *MetricImp) aggregate() {
   457  	a.aggregatedMetric = make(map[string]*types.AggregatedMetric)
   458  	if len(a.labeledMetricStore) <= 0 {
   459  		return
   460  	}
   461  
   462  	allItems := make([]*types.SeriesItem, 0)
   463  	for _, ms := range a.labeledMetricStore {
   464  		allItems = append(allItems, ms.seriesMetric.Values...)
   465  	}
   466  
   467  	var err error
   468  	var identity types.AggregatedIdentity
   469  	var sum float64
   470  	var statsData stats.Float64Data
   471  	max := allItems[0].Value
   472  	min := allItems[0].Value
   473  	latestTime := allItems[0].Timestamp
   474  	oldestTime := allItems[0].Timestamp
   475  	latestItem := allItems[0]
   476  
   477  	if identity, err = buildAggregatedIdentity(allItems); err != nil {
   478  		general.Errorf("failed to get aggregated identity,err:%v", err)
   479  		return
   480  	}
   481  
   482  	for i := range allItems {
   483  		item := allItems[i]
   484  		sum += item.Value
   485  		max = general.MaxFloat64(max, item.Value)
   486  		min = general.MinFloat64(min, item.Value)
   487  		statsData = append(statsData, item.Value)
   488  		latestTime = general.MaxInt64(latestTime, item.Timestamp)
   489  		oldestTime = general.MinInt64(oldestTime, item.Timestamp)
   490  		if latestItem.Timestamp < item.Timestamp {
   491  			latestItem = item
   492  		}
   493  	}
   494  
   495  	a.aggregatedMetric[apimetric.AggregateFunctionMax] = types.NewAggregatedInternalMetric(max, identity)
   496  	a.aggregatedMetric[apimetric.AggregateFunctionMin] = types.NewAggregatedInternalMetric(min, identity)
   497  	a.aggregatedMetric[apimetric.AggregateFunctionAvg] = types.NewAggregatedInternalMetric(sum/float64(len(allItems)), identity)
   498  	a.aggregatedMetric[apimetric.AggregateFunctionLatest] = types.NewAggregatedInternalMetric(latestItem.Value, identity)
   499  
   500  	if p99, err := statsData.Percentile(99); err != nil {
   501  		general.Errorf("failed to get stats p99: %v", err)
   502  	} else {
   503  		a.aggregatedMetric[apimetric.AggregateFunctionP99] = types.NewAggregatedInternalMetric(p99, identity)
   504  	}
   505  
   506  	if p90, err := statsData.Percentile(90); err != nil {
   507  		general.Errorf("failed to get stats p90: %v", err)
   508  	} else {
   509  		a.aggregatedMetric[apimetric.AggregateFunctionP90] = types.NewAggregatedInternalMetric(p90, identity)
   510  	}
   511  }
   512  
   513  // AggregateMetric calculate the aggregated metric based on snapshot of current store
   514  func (a *MetricImp) AggregateMetric() {
   515  	a.Lock()
   516  	defer a.Unlock()
   517  
   518  	a.aggregate()
   519  }
   520  
   521  func (a *MetricImp) GC(expiredTimestamp int64) {
   522  	a.Lock()
   523  	defer a.Unlock()
   524  
   525  	a.expiredTime = expiredTimestamp
   526  
   527  	for k, l := range a.labeledMetricStore {
   528  		l.gc(expiredTimestamp)
   529  		if l.len() == 0 {
   530  			delete(a.labeledMetricStore, k)
   531  		}
   532  	}
   533  
   534  	a.aggregate()
   535  }
   536  
   537  func (a *MetricImp) Empty() bool {
   538  	a.RLock()
   539  	defer a.RUnlock()
   540  
   541  	return len(a.labeledMetricStore) == 0
   542  }
   543  
   544  func (a *MetricImp) Len() int {
   545  	a.RLock()
   546  	defer a.RUnlock()
   547  
   548  	count := 0
   549  	for k := range a.labeledMetricStore {
   550  		count += a.labeledMetricStore[k].len()
   551  	}
   552  	return count
   553  }
   554  
   555  func (a *MetricImp) GenerateTags() []metrics.MetricTag {
   556  	return []metrics.MetricTag{
   557  		{Key: "metric_name", Val: a.GetName()},
   558  		{Key: "object_name", Val: a.GetObjectName()},
   559  	}
   560  }
   561  
   562  // GetLatestTimestamp returns the latest metric timestamp in milliseconds.
   563  func (a *MetricImp) GetLatestTimestamp() int64 {
   564  	a.RLock()
   565  	defer a.RUnlock()
   566  
   567  	var latestTimestamp int64 = -1
   568  
   569  	for s := range a.labeledMetricStore {
   570  		seriesMetric := a.labeledMetricStore[s].seriesMetric
   571  		if latestTimestamp < seriesMetric.Values[seriesMetric.Len()-1].Timestamp {
   572  			latestTimestamp = seriesMetric.Values[seriesMetric.Len()-1].Timestamp
   573  		}
   574  	}
   575  
   576  	return latestTimestamp
   577  }