github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/promutil/implement_test.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package promutil 15 16 import ( 17 "testing" 18 19 engineModel "github.com/pingcap/tiflow/engine/model" 20 "github.com/pingcap/tiflow/engine/pkg/tenant" 21 "github.com/prometheus/client_golang/prometheus" 22 dto "github.com/prometheus/client_model/go" 23 "github.com/stretchr/testify/require" 24 ) 25 26 func TestWrapCounterOpts(t *testing.T) { 27 t.Parallel() 28 29 cases := []struct { 30 prefix string 31 constLabels prometheus.Labels 32 inputOpts *prometheus.CounterOpts 33 outputOpts *prometheus.CounterOpts 34 }{ 35 { 36 prefix: "", 37 inputOpts: &prometheus.CounterOpts{ 38 Name: "test", 39 }, 40 outputOpts: &prometheus.CounterOpts{ 41 Name: "test", 42 }, 43 }, 44 { 45 prefix: "DM", 46 inputOpts: &prometheus.CounterOpts{ 47 Namespace: "ns", 48 Name: "test", 49 }, 50 outputOpts: &prometheus.CounterOpts{ 51 Namespace: "DM_ns", 52 Name: "test", 53 }, 54 }, 55 { 56 constLabels: prometheus.Labels{ 57 "k2": "v2", 58 }, 59 inputOpts: &prometheus.CounterOpts{ 60 ConstLabels: prometheus.Labels{ 61 "k0": "v0", 62 "k1": "v1", 63 }, 64 }, 65 outputOpts: &prometheus.CounterOpts{ 66 ConstLabels: prometheus.Labels{ 67 "k0": "v0", 68 "k1": "v1", 69 "k2": "v2", 70 }, 71 }, 72 }, 73 { 74 constLabels: prometheus.Labels{ 75 "k2": "v2", 76 }, 77 inputOpts: &prometheus.CounterOpts{}, 78 outputOpts: &prometheus.CounterOpts{ 79 ConstLabels: prometheus.Labels{ 80 "k2": "v2", 81 }, 82 }, 83 }, 84 } 85 86 for _, c := range cases { 87 output := wrapCounterOpts(c.prefix, c.constLabels, c.inputOpts) 88 require.Equal(t, c.outputOpts, output) 89 } 90 } 91 92 func TestWrapCounterOptsLableDuplicate(t *testing.T) { 93 t.Parallel() 94 95 defer func() { 96 err := recover() 97 require.NotNil(t, err) 98 require.Regexp(t, "duplicate label name", err.(string)) 99 }() 100 101 constLabels := prometheus.Labels{ 102 "k0": "v0", 103 } 104 inputOpts := &prometheus.CounterOpts{ 105 ConstLabels: prometheus.Labels{ 106 "k0": "v0", 107 "k1": "v1", 108 }, 109 } 110 _ = wrapCounterOpts("", constLabels, inputOpts) 111 // unreachable 112 require.True(t, false) 113 } 114 115 func TestNewCounter(t *testing.T) { 116 t.Parallel() 117 118 reg := NewRegistry() 119 require.NotNil(t, reg) 120 121 tent := tenant.NewProjectInfo( 122 "user0", 123 "project0", 124 ) 125 tenantID := tent.TenantID() 126 projectID := tent.ProjectID() 127 labelKey := "k0" 128 labelValue := "v0" 129 jobType := engineModel.JobTypeDM 130 jobID := "job0" 131 jobKey := constLabelJobKey 132 projectKey := constLabelProjectKey 133 tenantKey := constLabelTenantKey 134 135 factory := NewFactory4MasterImpl( 136 reg, 137 tent, 138 jobType.String(), 139 jobID, 140 ) 141 counter := factory.NewCounter(prometheus.CounterOpts{ 142 Namespace: "dm", 143 Subsystem: "syncer", 144 Name: "http_request", 145 ConstLabels: prometheus.Labels{ 146 labelKey: labelValue, // user defined const labels 147 }, 148 }) 149 counter.Inc() 150 counter.Add(float64(10)) 151 var ( 152 out dto.Metric 153 t3 = float64(11) 154 ) 155 156 require.Nil(t, counter.Write(&out)) 157 compareMetric(t, &dto.Metric{ 158 Label: []*dto.LabelPair{ 159 // all const labels 160 { 161 Name: &jobKey, 162 Value: &jobID, 163 }, 164 { 165 Name: &labelKey, 166 Value: &labelValue, 167 }, 168 { 169 Name: &projectKey, 170 Value: &projectID, 171 }, 172 { 173 Name: &tenantKey, 174 Value: &tenantID, 175 }, 176 }, 177 Counter: &dto.Counter{ 178 Value: &t3, 179 }, 180 }, 181 &out, 182 ) 183 184 // different jobID of the same project, but with same metric 185 jobID = "job1" 186 factory = NewFactory4MasterImpl( 187 reg, 188 tent, 189 jobType.String(), 190 jobID, 191 ) 192 counter = factory.NewCounter(prometheus.CounterOpts{ 193 Namespace: "dm", 194 Subsystem: "syncer", 195 Name: "http_request", 196 ConstLabels: prometheus.Labels{ 197 labelKey: labelValue, // user defined const labels 198 }, 199 }) 200 201 // different project but with same metric 202 tent = tenant.NewProjectInfo(tent.TenantID(), "project1") 203 factory = NewFactory4MasterImpl( 204 reg, 205 tent, 206 jobType.String(), 207 jobID, 208 ) 209 counter = factory.NewCounter(prometheus.CounterOpts{ 210 Namespace: "dm", 211 Subsystem: "syncer", 212 Name: "http_request", 213 ConstLabels: prometheus.Labels{ 214 labelKey: labelValue, // user defined const labels 215 }, 216 }) 217 218 // JobMaster and Worker of the same job type can't has same 219 // metric name 220 workerID := "worker0" 221 factory = NewFactory4WorkerImpl( 222 reg, 223 tent, 224 jobType.String(), 225 jobID, 226 workerID, 227 ) 228 counter = factory.NewCounter(prometheus.CounterOpts{ 229 Namespace: "dm", 230 Subsystem: "worker", 231 Name: "http_request", 232 ConstLabels: prometheus.Labels{ 233 labelKey: labelValue, // user defined const labels 234 }, 235 }) 236 237 // different workerID of the same job, but with same metric 238 workerID = "worker1" 239 factory = NewFactory4WorkerImpl( 240 reg, 241 tent, 242 jobType.String(), 243 jobID, 244 workerID, 245 ) 246 counter = factory.NewCounter(prometheus.CounterOpts{ 247 Namespace: "dm", 248 Subsystem: "worker", 249 Name: "http_request", 250 ConstLabels: prometheus.Labels{ 251 labelKey: labelValue, // user defined const labels 252 }, 253 }) 254 255 // framework with same metric 256 factory = NewFactory4FrameworkImpl( 257 reg, 258 ) 259 counter = factory.NewCounter(prometheus.CounterOpts{ 260 Namespace: "dm", 261 Subsystem: "worker", 262 Name: "http_request", 263 ConstLabels: prometheus.Labels{ 264 labelKey: labelValue, // user defined const labels 265 }, 266 }) 267 } 268 269 // const label conflict with inner const labels 270 func TestNewCounterFailConstLabelConflict(t *testing.T) { 271 t.Parallel() 272 273 defer func() { 274 err := recover() 275 require.NotNil(t, err) 276 require.Regexp(t, "duplicate label name", err.(string)) 277 }() 278 279 reg := NewRegistry() 280 require.NotNil(t, reg) 281 282 factory := NewFactory4MasterImpl( 283 reg, 284 tenant.NewProjectInfo( 285 "user0", 286 "proj0", 287 ), 288 engineModel.JobTypeDM.String(), 289 "job0", 290 ) 291 _ = factory.NewCounter(prometheus.CounterOpts{ 292 Namespace: "dm", 293 Subsystem: "worker", 294 Name: "http_request", 295 ConstLabels: prometheus.Labels{ 296 constLabelJobKey: "job0", // conflict with inner const labels 297 }, 298 }) 299 } 300 301 func TestNewCounterVec(t *testing.T) { 302 t.Parallel() 303 304 reg := NewRegistry() 305 require.NotNil(t, reg) 306 307 factory := NewFactory4MasterImpl( 308 reg, 309 tenant.NewProjectInfo( 310 "user0", 311 "proj0", 312 ), 313 engineModel.JobTypeDM.String(), 314 "job0", 315 ) 316 counterVec := factory.NewCounterVec(prometheus.CounterOpts{ 317 Namespace: "dm", 318 Subsystem: "worker", 319 Name: "http_request", 320 ConstLabels: prometheus.Labels{ 321 "k1": "v1", 322 }, 323 }, 324 []string{"k2", "k3", "k4"}, 325 ) 326 327 counter, err := counterVec.GetMetricWithLabelValues([]string{"v1", "v2", "v3"}...) 328 require.NoError(t, err) 329 counter.Inc() 330 331 // unmatch label values count 332 _, err = counterVec.GetMetricWithLabelValues([]string{"v1", "v2"}...) 333 require.Error(t, err) 334 335 counter, err = counterVec.GetMetricWith(prometheus.Labels{ 336 "k2": "v2", "k3": "v3", "k4": "v4", 337 }) 338 require.NoError(t, err) 339 counter.Inc() 340 341 // unmatch label values count 342 counter, err = counterVec.GetMetricWith(prometheus.Labels{ 343 "k3": "v3", "k4": "v4", 344 }) 345 require.Error(t, err) 346 347 require.True(t, counterVec.DeleteLabelValues([]string{"v1", "v2", "v3"}...)) 348 require.False(t, counterVec.DeleteLabelValues([]string{"v1", "v2"}...)) 349 require.False(t, counterVec.DeleteLabelValues([]string{"v1", "v2", "v4"}...)) 350 351 require.True(t, counterVec.Delete(prometheus.Labels{ 352 "k2": "v2", "k3": "v3", "k4": "v4", 353 })) 354 require.False(t, counterVec.Delete(prometheus.Labels{ 355 "k3": "v3", "k4": "v4", 356 })) 357 require.False(t, counterVec.Delete(prometheus.Labels{ 358 "k2": "v3", "k3": "v3", "k4": "v4", 359 })) 360 361 curryCounterVec, err := counterVec.CurryWith(prometheus.Labels{ 362 "k2": "v2", 363 }) 364 require.NoError(t, err) 365 counter, err = curryCounterVec.GetMetricWith(prometheus.Labels{ 366 "k3": "v3", "k4": "v4", 367 }) 368 require.NoError(t, err) 369 counter.Add(1) 370 371 // unmatch label values count after curry 372 _, err = curryCounterVec.GetMetricWith(prometheus.Labels{ 373 "k2": "v2", "k3": "v3", "k4": "v4", 374 }) 375 require.Error(t, err) 376 } 377 378 func compareMetric(t *testing.T, expected *dto.Metric, actual *dto.Metric) { 379 // compare label pairs 380 require.Equal(t, len(expected.Label), len(actual.Label)) 381 for i, label := range expected.Label { 382 require.Equal(t, label.Name, actual.Label[i].Name) 383 require.Equal(t, label.Value, actual.Label[i].Value) 384 } 385 386 if expected.Counter != nil { 387 compareCounter(t, expected.Counter, actual.Counter) 388 } else if expected.Gauge != nil { 389 compareCounter(t, expected.Counter, actual.Counter) 390 } else if expected.Histogram != nil { 391 // TODO 392 } else { 393 require.Fail(t, "unexpected metric type") 394 } 395 } 396 397 func compareCounter(t *testing.T, expected *dto.Counter, actual *dto.Counter) { 398 require.NotNil(t, expected) 399 require.NotNil(t, actual) 400 require.Equal(t, expected.Value, actual.Value) 401 if expected.Exemplar == nil { 402 require.Nil(t, actual.Exemplar) 403 } else { 404 require.Equal(t, len(expected.Exemplar.Label), len(actual.Exemplar.Label)) 405 for i, label := range expected.Exemplar.Label { 406 require.Equal(t, label.Name, actual.Exemplar.Label[i].Name) 407 require.Equal(t, label.Value, actual.Exemplar.Label[i].Value) 408 } 409 } 410 }