github.com/kubewharf/katalyst-core@v0.5.3/pkg/custom-metric/mock/mock_collector.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 mock
    18  
    19  import (
    20  	"context"
    21  	"hash/fnv"
    22  	"math/rand"
    23  	"strconv"
    24  	"sync"
    25  	"time"
    26  
    27  	"go.uber.org/atomic"
    28  	v1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	"k8s.io/client-go/util/workqueue"
    31  
    32  	katalystbase "github.com/kubewharf/katalyst-core/cmd/base"
    33  	"github.com/kubewharf/katalyst-core/pkg/config/metric"
    34  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/collector"
    35  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/store"
    36  	"github.com/kubewharf/katalyst-core/pkg/custom-metric/store/data"
    37  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    38  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    39  )
    40  
    41  const MetricCollectorNameMock = "mock-collector"
    42  
    43  const (
    44  	metricNamePromCollectorSyncCosts = "kcmas_collector_sync_costs"
    45  
    46  	metricNamePromCollectorStoreReqCount = "kcmas_collector_store_req_cnt"
    47  	metricNamePromCollectorStoreLatency  = "kcmas_collector_store_latency"
    48  )
    49  
    50  // MockCollector produces mock data for test.
    51  type MockCollector struct {
    52  	ctx               context.Context
    53  	collectConf       *metric.CollectorConfiguration
    54  	genericConf       *metric.GenericMetricConfiguration
    55  	podList           []*v1.Pod
    56  	metricNames       []string
    57  	metricBuffer      map[int]*metricBucket
    58  	bufferBucketSize  int
    59  	generateBatchSize int
    60  
    61  	metricStore store.MetricStore
    62  	emitter     metrics.MetricEmitter
    63  }
    64  
    65  func NewMockCollector(ctx context.Context, baseCtx *katalystbase.GenericContext, genericConf *metric.GenericMetricConfiguration,
    66  	collectConf *metric.CollectorConfiguration, mockConf *metric.MockConfiguration, metricStore store.MetricStore,
    67  ) (collector.MetricCollector, error) {
    68  	bufferBucketSize := 2000
    69  	metricBuffer := make(map[int]*metricBucket, bufferBucketSize)
    70  	for i := 0; i < bufferBucketSize; i++ {
    71  		bucketID := i
    72  		metricBuffer[bucketID] = newMetricBucket(bucketID)
    73  	}
    74  
    75  	return &MockCollector{
    76  		ctx:         ctx,
    77  		collectConf: collectConf,
    78  		genericConf: genericConf,
    79  		podList:     GenerateMockPods(mockConf.NamespaceCount, mockConf.WorkloadCount, mockConf.PodCount),
    80  		metricNames: []string{
    81  			"pod_memory_rss",
    82  			"pod_memory_usage",
    83  			"pod_cpu_load_1min",
    84  			"pod_cpu_usage",
    85  		},
    86  		metricBuffer:      metricBuffer,
    87  		bufferBucketSize:  bufferBucketSize,
    88  		generateBatchSize: 10,
    89  		metricStore:       metricStore,
    90  		emitter:           baseCtx.EmitterPool.GetDefaultMetricsEmitter().WithTags("mock_collector"),
    91  	}, nil
    92  }
    93  
    94  func (m *MockCollector) Name() string { return MetricCollectorNameMock }
    95  
    96  func (m *MockCollector) Start() error {
    97  	go wait.Until(m.sync, m.collectConf.SyncInterval, m.ctx.Done())
    98  
    99  	for i := 0; i < m.generateBatchSize; i++ {
   100  		batchID := i
   101  		generate := func() {
   102  			m.generateData(batchID)
   103  		}
   104  		time.Sleep(1 * time.Second)
   105  		go wait.Until(generate, 10*time.Second, m.ctx.Done())
   106  	}
   107  	return nil
   108  }
   109  
   110  func (m *MockCollector) Stop() error {
   111  	return nil
   112  }
   113  
   114  func (m *MockCollector) sync() {
   115  	syncStart := time.Now()
   116  	defer func() {
   117  		costs := time.Since(syncStart)
   118  		general.Infof("mock collector handled with total %v requests, cost %s", len(m.metricBuffer), costs.String())
   119  		_ = m.emitter.StoreInt64(metricNamePromCollectorSyncCosts, costs.Microseconds(), metrics.MetricTypeNameRaw)
   120  	}()
   121  
   122  	if len(m.metricBuffer) == 0 {
   123  		return
   124  	}
   125  
   126  	var (
   127  		successReqs = atomic.NewInt64(0)
   128  		failedReqs  = atomic.NewInt64(0)
   129  	)
   130  
   131  	upload := func(bucketID int) {
   132  		storeStart := time.Now()
   133  		defer func() {
   134  			_ = m.emitter.StoreInt64(metricNamePromCollectorStoreLatency, time.Since(storeStart).Microseconds(), metrics.MetricTypeNameRaw)
   135  		}()
   136  
   137  		if m.metricBuffer[bucketID] == nil || m.metricBuffer[bucketID].size() == 0 {
   138  			return
   139  		}
   140  
   141  		metricList := m.metricBuffer[bucketID].extractMetrics()
   142  
   143  		if err := m.metricStore.InsertMetric(metricList); err != nil {
   144  			failedReqs.Inc()
   145  			return
   146  		}
   147  
   148  		successReqs.Inc()
   149  		m.metricBuffer[bucketID].clear()
   150  	}
   151  
   152  	workqueue.ParallelizeUntil(m.ctx, 64, len(m.metricBuffer), upload)
   153  	general.Infof("mock collector handle %v succeeded requests, %v failed requests", successReqs.Load(), failedReqs.Load())
   154  	_ = m.emitter.StoreInt64(metricNamePromCollectorStoreReqCount, successReqs.Load(), metrics.MetricTypeNameCount, []metrics.MetricTag{
   155  		{Key: "type", Val: "succeeded"},
   156  	}...)
   157  	_ = m.emitter.StoreInt64(metricNamePromCollectorStoreReqCount, failedReqs.Load(), metrics.MetricTypeNameCount, []metrics.MetricTag{
   158  		{Key: "type", Val: "failed"},
   159  	}...)
   160  }
   161  
   162  func formatPodName(pod *v1.Pod) string {
   163  	return pod.Namespace + ":" + pod.Name
   164  }
   165  
   166  func (m *MockCollector) generateData(batchID int) {
   167  	general.Infof("start generate mock data for batch %v", batchID)
   168  
   169  	start := time.Now()
   170  	defer func() {
   171  		general.Infof("generate mock data for batch %v costs:%v", batchID, time.Now().Sub(start))
   172  	}()
   173  
   174  	for _, pod := range m.podList {
   175  		now := time.Now()
   176  		hash := fnv.New64()
   177  		namespacedName := formatPodName(pod)
   178  		hash.Write([]byte(namespacedName))
   179  		bucketID := int(hash.Sum64() % uint64(m.bufferBucketSize))
   180  
   181  		if bucketID%m.generateBatchSize != batchID {
   182  			continue
   183  		}
   184  
   185  		for _, metricName := range m.metricNames {
   186  			m.metricBuffer[bucketID].insert(pod, metricName, rand.Float64(), now.UnixMilli())
   187  		}
   188  	}
   189  }
   190  
   191  // metricBucket is a map key by namespaced pod name
   192  type metricBucket struct {
   193  	id     int
   194  	bucket map[string]*data.MetricSeries
   195  	sync.Mutex
   196  }
   197  
   198  func newMetricBucket(id int) *metricBucket {
   199  	return &metricBucket{
   200  		id:     id,
   201  		bucket: make(map[string]*data.MetricSeries),
   202  	}
   203  }
   204  
   205  func (m *metricBucket) insert(pod *v1.Pod, metricName string, value float64, timestamp int64) {
   206  	m.Lock()
   207  	defer m.Unlock()
   208  
   209  	namespacedName := formatPodName(pod)
   210  	objectMetricKey := namespacedName + ":" + metricName
   211  	ms, msExists := m.bucket[objectMetricKey]
   212  	if !msExists {
   213  		ms = &data.MetricSeries{
   214  			Name: metricName,
   215  			Labels: map[string]string{
   216  				string(data.CustomMetricLabelKeyNamespace):                    pod.Namespace,
   217  				string(data.CustomMetricLabelKeyObject):                       "pods",
   218  				string(data.CustomMetricLabelKeyObjectName):                   pod.Name,
   219  				string(data.CustomMetricLabelSelectorPrefixKey + "node_name"): strconv.Itoa(m.id),
   220  			},
   221  			Series: []*data.MetricData{},
   222  		}
   223  		m.bucket[objectMetricKey] = ms
   224  	}
   225  
   226  	ms.Series = append(ms.Series, &data.MetricData{
   227  		Data:      value,
   228  		Timestamp: timestamp,
   229  	})
   230  }
   231  
   232  func (m *metricBucket) clear() {
   233  	m.Lock()
   234  	defer m.Unlock()
   235  
   236  	m.bucket = make(map[string]*data.MetricSeries)
   237  }
   238  
   239  func (m *metricBucket) size() int {
   240  	return len(m.bucket)
   241  }
   242  
   243  func (m *metricBucket) extractMetrics() []*data.MetricSeries {
   244  	m.Lock()
   245  	defer m.Unlock()
   246  
   247  	metricList := make([]*data.MetricSeries, 0, len(m.bucket))
   248  	for key := range m.bucket {
   249  		metricList = append(metricList, m.bucket[key])
   250  	}
   251  	return metricList
   252  }