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  }