github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/protocol/encoder/prometheus/encoder_prometheus_test.go (about)

     1  // Copyright 2024 iLogtail Authors
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package prometheus
    16  
    17  import (
    18  	"errors"
    19  	"strconv"
    20  	"testing"
    21  
    22  	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
    23  	pb "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
    24  	"github.com/prometheus/common/model"
    25  	"github.com/stretchr/testify/assert"
    26  
    27  	"github.com/alibaba/ilogtail/pkg/models"
    28  )
    29  
    30  // 场景:性能测试,确定性能基线(UT粒度)
    31  // 因子:所有 Event type 均为 models.EventTypeMetric
    32  // 因子:所有 PipelineEvent interface 的实现均为 *models.Metric
    33  // 预期:EncodeV2 和 EncodeBatchV2 性能相当(实现上 EncodeBatchV2 会循环调用 EncodeV2)
    34  // Benchmark结果(每次在具体数值上可能会有差异,但数量级相同):
    35  // goos: darwin
    36  // goarch: arm64
    37  // pkg: github.com/alibaba/ilogtail/pkg/protocol/encoder/prometheus
    38  // BenchmarkV2Encode
    39  // BenchmarkV2Encode/EncodeV2
    40  // BenchmarkV2Encode/EncodeV2-12         	     685	   1655657 ns/op
    41  // BenchmarkV2Encode/BatchEncodeV2
    42  // BenchmarkV2Encode/BatchEncodeV2-12    	     716	   1639491 ns/op
    43  // PASS
    44  func BenchmarkV2Encode(b *testing.B) {
    45  	// given
    46  	p := NewPromEncoder(19)
    47  	groupEventsSlice := genNormalPipelineGroupEventsSlice(100)
    48  	want := append([]*models.PipelineGroupEvents(nil), groupEventsSlice...)
    49  
    50  	b.Run("EncodeV2", func(b *testing.B) {
    51  		b.ResetTimer()
    52  
    53  		for i := 0; i < b.N; i++ {
    54  			for _, groupEvents := range groupEventsSlice {
    55  				p.EncodeV2(groupEvents)
    56  			}
    57  		}
    58  	})
    59  	assert.Equal(b, want, groupEventsSlice)
    60  
    61  	b.Run("BatchEncodeV2", func(b *testing.B) {
    62  		b.ResetTimer()
    63  
    64  		for i := 0; i < b.N; i++ {
    65  			p.EncodeBatchV2(groupEventsSlice)
    66  		}
    67  	})
    68  	assert.Equal(b, want, groupEventsSlice)
    69  }
    70  
    71  // 场景:V2 Encode接口功能测试
    72  // 说明:EncodeBatchV2 内部会调用 EncodeV2,所以也同时测试了 EncodeV2 的正常逻辑的功能
    73  // 因子:所有 Event type 均为 models.EventTypeMetric
    74  // 因子:所有 PipelineEvent interface 的实现均为「正常的*models.Metric」(而不是new(models.Metric)),
    75  // 具体区别在于正常的*models.Metric,其中的Tags、Value等都不是nil(如果为nil,会触发序列化的异常逻辑)
    76  // 预期:V2 Encode逻辑正常(正常流程都能正确处理),返回的error类型为nil,[][]byte不为nil
    77  func TestV2Encode_ShouldReturnNoError_GivenNormalDataOfPipelineGroupEvents(t *testing.T) {
    78  	// given
    79  	groupEventsSlice1 := genNormalPipelineGroupEventsSlice(100)
    80  	groupEventsSlice2 := genNormalPipelineGroupEventsSlice(100)
    81  	p := NewPromEncoder(19)
    82  
    83  	// when
    84  	// then
    85  	data1, err1 := p.EncodeBatchV2(groupEventsSlice1)
    86  	assert.NoError(t, err1)
    87  	data2, err2 := p.EncodeBatchV2(groupEventsSlice2)
    88  	assert.NoError(t, err2)
    89  
    90  	assert.Equal(t, len(data2), len(data1))
    91  }
    92  
    93  // 场景:V2 Encode接口功能测试(异常数据,非全nil或0值)
    94  // 说明:尽管 EncodeBatchV2 内部会调用 EncodeV2,但异常情况可能是 EncodeBatchV2 侧的防御,
    95  // 所以还需要测试异常情况下 EncodeV2 的功能
    96  // 因子:并非所有 Event type 均为 models.EventTypeMetric(e.g. 还可能是 models.EventTypeLogging 等)
    97  // 因子:PipelineEvent interface 的实现,部分是「正常的*models.Metric」,部分为 nil,部分为new(models.Metric),
    98  // 部分为其它(e.g. *models.Log 等)
    99  // 预期:Encode逻辑正常(异常流程也能正确处理),返回的error类型不为nil,[][]byte为nil
   100  func TestV2Encode_ShouldReturnError_GivenAbNormalDataOfPipelineGroupEvents(t *testing.T) {
   101  	// given
   102  	groupEventsSlice1 := genPipelineGroupEventsSliceIncludingAbnormalData(100)
   103  	groupEventsSlice2 := genPipelineGroupEventsSliceIncludingAbnormalData(100)
   104  	assert.Equal(t, len(groupEventsSlice1), len(groupEventsSlice2))
   105  	p := NewPromEncoder(19)
   106  
   107  	// when
   108  	// then
   109  	t.Run("Test EncodeV2 with abnormal data input", func(t *testing.T) {
   110  		for i, groupEvents := range groupEventsSlice1 {
   111  			data1, err1 := p.EncodeV2(groupEvents)
   112  			data2, err2 := p.EncodeV2(groupEventsSlice2[i])
   113  			if err1 != nil {
   114  				assert.Error(t, err2)
   115  				assert.Equal(t, err1, err2)
   116  			} else {
   117  				assert.NoError(t, err2)
   118  				assert.Equal(t, len(data2), len(data1))
   119  			}
   120  		}
   121  	})
   122  
   123  	t.Run("Test EncodeBatchV2 with abnormal data input", func(t *testing.T) {
   124  		data1, err1 := p.EncodeBatchV2(groupEventsSlice1)
   125  		assert.NoError(t, err1)
   126  		data2, err2 := p.EncodeBatchV2(groupEventsSlice2)
   127  		assert.NoError(t, err2)
   128  
   129  		assert.Equal(t, len(data2), len(data1))
   130  	})
   131  }
   132  
   133  // 场景:V2 Encode接口功能测试(异常数据,全nil或0值)
   134  // 说明:尽管 EncodeBatchV2 内部会调用 EncodeV2,但异常情况可能是 EncodeBatchV2 侧的防御,
   135  // 所以还需要测试异常情况下 EncodeV2 的功能
   136  // 因子:所有 *models.PipelineGroupEvents 及 []*models.PipelineGroupEvents 底层为 nil 或者 长度为0的切片
   137  // 预期:Encode逻辑正常(异常流程也能正确处理),返回的error类型不为nil,[][]byte为nil
   138  func TestV2Encode_ShouldReturnError_GivenNilOrZeroDataOfPipelineGroupEvents(t *testing.T) {
   139  	// given
   140  	p := NewPromEncoder(19)
   141  	nilOrZeroGroupEventsSlices := []*models.PipelineGroupEvents{
   142  		nil,
   143  		{}, // same as {Events: nil},
   144  		{Events: make([]models.PipelineEvent, 0)},
   145  	}
   146  	nilOrZeroGroupEventsSlicesEx := [][]*models.PipelineGroupEvents{
   147  		nil,
   148  		{}, // same as {nil}
   149  		{{Events: nil}},
   150  		nilOrZeroGroupEventsSlices,
   151  	}
   152  
   153  	// when
   154  	// then
   155  	t.Run("Test EncodeV2 with nil or zero data input", func(t *testing.T) {
   156  		for _, input := range nilOrZeroGroupEventsSlices {
   157  			data, err := p.EncodeV2(input)
   158  			assert.Error(t, err)
   159  			assert.Nil(t, data)
   160  		}
   161  	})
   162  
   163  	t.Run("Test EncodeBatchV2 with nil or zero data input", func(t *testing.T) {
   164  		for _, input := range nilOrZeroGroupEventsSlicesEx {
   165  			data, err := p.EncodeBatchV2(input)
   166  			assert.Error(t, err)
   167  			assert.Nil(t, data)
   168  		}
   169  	})
   170  }
   171  
   172  // 场景:V2 Encode接口功能测试
   173  // 说明:EncoderBatchV2 内部会调用 EncoderV2,所以也同时测试了 EncoderV2 的功能
   174  // 因子:所有 Event type 均为 models.EventTypeMetric
   175  // 因子:所有 PipelineEvent interface 的实现均为 *models.Metric
   176  // 因子:每个 metric event 生成的 *models.Metric.Tags 中的 tag 仅一个
   177  // (确保 Encode 时 range map不用考虑 range go原生map每次顺序随机,从而导致2次Encode相同数据后得到的结果不同)
   178  // PS:如果不这么做,就要对map做转化,先变成 range 保序的 slice,再 Encode;
   179  // 但测试的功能点是Encode,所以采用上述方法绕过range go原生map每次顺序随机的特点,完成功能测试
   180  // 预期:Encode逻辑正常(正常流程都能正确处理),返回的error类型为nil,[][]byte不为nil,且两次Encode后返回的数据相同
   181  func TestEncoderBatchV2_ShouldReturnNoErrorAndEqualData_GivenNormalDataOfDataOfPipelineGroupEventsWithSingleTag(t *testing.T) {
   182  	// given
   183  	groupEventsSlice1 := genPipelineGroupEventsSliceSingleTag(100)
   184  	groupEventsSlice2 := genPipelineGroupEventsSliceSingleTag(100)
   185  	p := NewPromEncoder(19)
   186  
   187  	// when
   188  	// then
   189  	data1, err1 := p.EncodeBatchV2(groupEventsSlice1)
   190  	assert.NoError(t, err1)
   191  	data2, err2 := p.EncodeBatchV2(groupEventsSlice2)
   192  	assert.NoError(t, err2)
   193  
   194  	assert.Equal(t, data2, data1)
   195  }
   196  
   197  // 场景:V2 Encode接口功能测试
   198  // 说明:EncoderBatchV2 内部会调用 EncoderV2,所以也同时测试了 EncoderV2 的功能
   199  // 因子:所有 Event type 均为 models.EventTypeMetric
   200  // 因子:所有 PipelineEvent interface 的实现均为 *models.Metric
   201  // 因子:每个 metric event 生成的 *models.Metric.Tags 中的 tag 仅一个
   202  // (确保 Encode 时 range map不用考虑 range go原生map每次顺序随机,从而导致2次Encode相同数据后得到的结果不同)
   203  // PS:如果不这么做,就要对map做转化,先变成 range 保序的 slice,再 Encode;
   204  // 但测试的功能点是Encode,所以采用上述方法绕过range go原生map每次顺序随机的特点,完成功能测试
   205  // 因子:对Encode后的数据进行Decode
   206  // 因子:「构造的用例数据」的长度(len([]*models.PipelineGroupEvents))未超过 series limit
   207  // 预期:「构造的用例数据」和「对用例数据先Encode再Decode后的数据」相等
   208  func TestEncoderBatchV2_ShouldDecodeSuccess_GivenNormalDataOfDataOfPipelineGroupEventsWithSingleTagNotExceedingSeriesLimit(t *testing.T) {
   209  	// given
   210  	seriesLimit := 19
   211  	n := seriesLimit
   212  	wantGroupEventsSlice := genPipelineGroupEventsSliceSingleTag(n)
   213  	p := NewPromEncoder(seriesLimit)
   214  	data, err := p.EncodeBatchV2(wantGroupEventsSlice)
   215  	assert.NoError(t, err)
   216  
   217  	// when
   218  	// then
   219  	gotGroupEventsSlice, err := DecodeBatchV2(data)
   220  	assert.NoError(t, err)
   221  	assert.Equal(t, wantGroupEventsSlice, gotGroupEventsSlice)
   222  }
   223  
   224  // 场景:V2 Encode接口功能测试
   225  // 说明:EncoderBatchV2 内部会调用 EncoderV2,所以也同时测试了 EncoderV2 的功能
   226  // 因子:所有 Event type 均为 models.EventTypeMetric
   227  // 因子:所有 PipelineEvent interface 的实现均为 *models.Metric
   228  // 因子:每个 metric event 生成的 *models.Metric.Tags 中的 tag 仅一个
   229  // (确保 Encode 时 range map不用考虑 range go原生map每次顺序随机,从而导致2次Encode相同数据后得到的结果不同)
   230  // PS:如果不这么做,就要对map做转化,先变成 range 保序的 slice,再 Encode;
   231  // 但测试的功能点是Encode,所以采用上述方法绕过range go原生map每次顺序随机的特点,完成功能测试
   232  // 因子:对Encode后的数据进行Decode
   233  // 因子:「构造的用例数据」的长度(len([]*models.PipelineGroupEvents))超过 series limit
   234  // 预期:「构造的用例数据」的长度小于「对用例数据先Encode再Decode后的数据」的长度,且用 expectedLen 计算后的长度相等
   235  // PS:expectedLen 的计算方法,其实是和 genPipelineGroupEventsSlice 生成用例及根据series limit确定encode批次
   236  // 的逻辑相关,和 Encode 本身的逻辑无关
   237  func TestEncoderBatchV2_ShouldDecodeSuccess_GivenNormalDataOfDataOfPipelineGroupEventsWithSingleTagExceedingSeriesLimit(t *testing.T) {
   238  	// given
   239  	seriesLimit := 19
   240  	n := 100
   241  	wantGroupEventsSlice := genPipelineGroupEventsSliceSingleTag(n)
   242  	assert.Equal(t, n, len(wantGroupEventsSlice))
   243  	p := NewPromEncoder(seriesLimit)
   244  	data, err := p.EncodeBatchV2(wantGroupEventsSlice)
   245  	assert.NoError(t, err)
   246  	expectedLen := func(limit, length int) int {
   247  		// make sure limit > 0 && length > 0
   248  		if limit <= 0 || length <= 0 {
   249  			return -1
   250  		}
   251  
   252  		mod := length % limit
   253  		mul := length / limit
   254  
   255  		res := 0
   256  		for i := 0; i <= mul; i++ {
   257  			res += i * limit
   258  		}
   259  		res += (mul + 1) * mod
   260  
   261  		return res
   262  	}
   263  
   264  	// when
   265  	gotGroupEventsSlice, err := DecodeBatchV2(data)
   266  	assert.NoError(t, err)
   267  
   268  	// then
   269  	assert.Equal(t, expectedLen(seriesLimit, n), len(gotGroupEventsSlice))
   270  }
   271  
   272  func genNormalPipelineGroupEventsSlice(n int) []*models.PipelineGroupEvents {
   273  	return genPipelineGroupEventsSlice(n, genPipelineEvent)
   274  }
   275  
   276  func genPipelineGroupEventsSliceIncludingAbnormalData(n int) []*models.PipelineGroupEvents {
   277  	return genPipelineGroupEventsSlice(n, genPipelineEventIncludingAbnormalData)
   278  }
   279  
   280  func genPipelineGroupEventsSliceSingleTag(n int) []*models.PipelineGroupEvents {
   281  	return genPipelineGroupEventsSlice(n, genPipelineEventSingleTag)
   282  }
   283  
   284  func genPipelineGroupEventsSlice(n int, genPipelineEventFn func(int) []models.PipelineEvent) []*models.PipelineGroupEvents {
   285  	res := make([]*models.PipelineGroupEvents, 0, n)
   286  	for i := 1; i <= n; i++ {
   287  		res = append(res, &models.PipelineGroupEvents{
   288  			Group:  models.NewGroup(models.NewMetadata(), models.NewTags()),
   289  			Events: genPipelineEventFn(i),
   290  		})
   291  	}
   292  
   293  	return res
   294  }
   295  
   296  func genPipelineEvent(n int) []models.PipelineEvent {
   297  	res := make([]models.PipelineEvent, 0, n)
   298  	for i := 1; i <= n; i++ {
   299  		res = append(res, genMetric(i))
   300  	}
   301  
   302  	return res
   303  }
   304  
   305  func genMetric(n int) *models.Metric {
   306  	i := strconv.Itoa(n)
   307  	tags := models.NewKeyValues[string]()
   308  	tags.AddAll(map[string]string{
   309  		// range map will out of order
   310  		"a" + i: "A" + i,
   311  		"b" + i: "B" + i,
   312  		"c" + i: "C" + i,
   313  		"d" + i: "D" + i,
   314  	})
   315  
   316  	return &models.Metric{
   317  		Timestamp: 11111111 * uint64(n),
   318  		Tags:      tags,
   319  		Value:     &models.MetricSingleValue{Value: 1.1 * float64(n)},
   320  	}
   321  }
   322  
   323  func genPipelineEventIncludingAbnormalData(n int) []models.PipelineEvent {
   324  	res := make([]models.PipelineEvent, 0, n)
   325  	for i := 1; i <= n; i++ {
   326  		if i&1 == 0 { // i is even number
   327  			// normal data
   328  			res = append(res, genMetric(i))
   329  			continue
   330  		}
   331  
   332  		// i is odd number
   333  		// abnormal data
   334  		if i%3 == 0 {
   335  			// abnormal data: nil data
   336  			res = append(res, nil)
   337  			continue
   338  		}
   339  
   340  		if i%5 == 0 {
   341  			// abnormal data: zero data
   342  			// PS:
   343  			// 1. 这里只是从边界情况考虑,构造了这种异常值
   344  			// 但实际场景中,不会直接 new(models.Metric) 或者 &models.Metric{} 这样创建 zero data,
   345  			// 一般都是用 models.NewMetric|NewSingleValueMetric|NewMultiValuesMetric 等 构造函数(工厂模式)来创建,
   346  			// 上述构造函数位置:ilogtail/pkg/models/factory.go
   347  			// 2. 此外,也可以给 *models.Metric 的 GetTag 方法增加下 *models.Metric.Tag 为 nil 时的保护
   348  			// (参考其 GetValue 方法的实现),文件位置:ilogtail/pkg/models/metric.go
   349  			res = append(res, new(models.Metric))
   350  			continue
   351  		}
   352  
   353  		// abnormal data: other event type not models.EventTypeMetric
   354  		res = append(res, new(models.Log))
   355  	}
   356  
   357  	return res
   358  }
   359  
   360  func genPipelineEventSingleTag(n int) []models.PipelineEvent {
   361  	res := make([]models.PipelineEvent, 0, n)
   362  	for i := 1; i <= n; i++ {
   363  		res = append(res, genMetricSingleTag(i))
   364  	}
   365  
   366  	return res
   367  }
   368  
   369  func genMetricSingleTag(n int) *models.Metric {
   370  	metricName := "test_metric"
   371  	i := strconv.Itoa(n)
   372  	tags := models.NewTagsWithMap(map[string]string{
   373  		// only single tag
   374  		// keep range in order
   375  		"x" + i: "X" + i,
   376  	})
   377  
   378  	dataPoint := pb.Sample{Timestamp: 11111111 * int64(n), Value: 1.1 * float64(n)}
   379  
   380  	return models.NewSingleValueMetric(
   381  		metricName, // value of key "__name__" in prometheus
   382  		models.MetricTypeGauge,
   383  		tags,
   384  		model.Time(dataPoint.Timestamp).Time().UnixNano(),
   385  		dataPoint.Value,
   386  	)
   387  }
   388  
   389  func DecodeBatchV2(data [][]byte) ([]*models.PipelineGroupEvents, error) {
   390  	if len(data) == 0 {
   391  		return nil, errors.New("no data to decode")
   392  	}
   393  
   394  	var res []*models.PipelineGroupEvents
   395  
   396  	meta, commonTags := models.NewMetadata(), models.NewTags()
   397  	for _, d := range data {
   398  		groupEvents, err := convertPromRequestToPipelineGroupEvents(d, meta, commonTags)
   399  		if err != nil {
   400  			continue
   401  		}
   402  
   403  		res = append(res, groupEvents)
   404  	}
   405  
   406  	return res, nil
   407  }
   408  
   409  func convertPromRequestToPipelineGroupEvents(data []byte, metaInfo models.Metadata, commonTags models.Tags) (*models.PipelineGroupEvents, error) {
   410  	wr, err := unmarshalBatchTimeseriesData(data)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  
   415  	groupEvent := &models.PipelineGroupEvents{
   416  		Group: models.NewGroup(metaInfo, commonTags),
   417  	}
   418  
   419  	for _, ts := range wr.Timeseries {
   420  		var metricName string
   421  		tags := models.NewTags()
   422  		for _, label := range ts.Labels {
   423  			if label.Name == metricNameKey {
   424  				metricName = label.Value
   425  				continue
   426  			}
   427  			tags.Add(label.Name, label.Value)
   428  		}
   429  
   430  		for _, dataPoint := range ts.Samples {
   431  			metric := models.NewSingleValueMetric(
   432  				metricName,
   433  				models.MetricTypeGauge,
   434  				tags,
   435  				// Decode (during input_prometheus stage) makes timestamp
   436  				// with unix milliseconds into unix nanoseconds,
   437  				// e.g. "model.Time(milliseconds).Time().UnixNano()".
   438  				model.Time(dataPoint.Timestamp).Time().UnixNano(),
   439  				dataPoint.Value,
   440  			)
   441  			groupEvent.Events = append(groupEvent.Events, metric)
   442  		}
   443  	}
   444  
   445  	return groupEvent, nil
   446  }
   447  
   448  func unmarshalBatchTimeseriesData(data []byte) (*pb.WriteRequest, error) {
   449  	wr := new(prompb.WriteRequest)
   450  	if err := wr.Unmarshal(data); err != nil {
   451  		return nil, err
   452  	}
   453  
   454  	return convertPrompbToVictoriaMetricspb(wr)
   455  }
   456  
   457  func convertPrompbToVictoriaMetricspb(wr *prompb.WriteRequest) (*pb.WriteRequest, error) {
   458  	if wr == nil || len(wr.Timeseries) == 0 {
   459  		return nil, errors.New("nil *prompb.WriteRequest")
   460  	}
   461  
   462  	res := &pb.WriteRequest{
   463  		Timeseries: make([]pb.TimeSeries, 0, len(wr.Timeseries)),
   464  	}
   465  	for _, tss := range wr.Timeseries {
   466  		res.Timeseries = append(res.Timeseries, pb.TimeSeries{
   467  			Labels:  convertToVMLabels(tss.Labels),
   468  			Samples: convertToVMSamples(tss.Samples),
   469  		})
   470  	}
   471  
   472  	return res, nil
   473  }
   474  
   475  func convertToVMLabels(labels []prompb.Label) []pb.Label {
   476  	if len(labels) == 0 {
   477  		return nil
   478  	}
   479  
   480  	res := make([]pb.Label, 0, len(labels))
   481  	for _, label := range labels {
   482  		res = append(res, pb.Label{
   483  			Name:  string(label.Name),
   484  			Value: string(label.Value),
   485  		})
   486  	}
   487  
   488  	return res
   489  }
   490  
   491  func convertToVMSamples(samples []prompb.Sample) []pb.Sample {
   492  	if len(samples) == 0 {
   493  		return nil
   494  	}
   495  
   496  	res := make([]pb.Sample, 0, len(samples))
   497  	for _, sample := range samples {
   498  		res = append(res, pb.Sample{
   499  			Value:     sample.Value,
   500  			Timestamp: sample.Timestamp,
   501  		})
   502  	}
   503  
   504  	return res
   505  }