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 }