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 }