github.com/grafana/pyroscope@v1.18.0/pkg/model/time_series_merger_test.go (about)

     1  package model
     2  
     3  import (
     4  	"testing"
     5  
     6  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
     7  	"github.com/grafana/pyroscope/pkg/testhelper"
     8  )
     9  
    10  func Test_SeriesMerger(t *testing.T) {
    11  	for _, tc := range []struct {
    12  		name string
    13  		in   [][]*typesv1.Series
    14  		out  []*typesv1.Series
    15  	}{
    16  		{
    17  			name: "empty",
    18  			in:   [][]*typesv1.Series{},
    19  			out:  []*typesv1.Series(nil),
    20  		},
    21  		{
    22  			name: "merge two series",
    23  			in: [][]*typesv1.Series{
    24  				{
    25  					{Labels: LabelsFromStrings("foor", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}}},
    26  				},
    27  				{
    28  					{Labels: LabelsFromStrings("foor", "bar"), Points: []*typesv1.Point{{Timestamp: 2, Value: 2}}},
    29  				},
    30  			},
    31  			out: []*typesv1.Series{
    32  				{Labels: LabelsFromStrings("foor", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}}},
    33  			},
    34  		},
    35  		{
    36  			name: "merge multiple series",
    37  			in: [][]*typesv1.Series{
    38  				{
    39  					{Labels: LabelsFromStrings("foor", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}}},
    40  					{Labels: LabelsFromStrings("foor", "buzz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}}},
    41  				},
    42  				{
    43  					{Labels: LabelsFromStrings("foor", "bar"), Points: []*typesv1.Point{{Timestamp: 2, Value: 2}}},
    44  					{Labels: LabelsFromStrings("foor", "buzz"), Points: []*typesv1.Point{{Timestamp: 3, Value: 3}}},
    45  				},
    46  			},
    47  			out: []*typesv1.Series{
    48  				{Labels: LabelsFromStrings("foor", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}}},
    49  				{Labels: LabelsFromStrings("foor", "buzz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 3, Value: 3}}},
    50  			},
    51  		},
    52  	} {
    53  		t.Run(tc.name, func(t *testing.T) {
    54  			testhelper.EqualProto(t, tc.out, MergeSeries(nil, tc.in...))
    55  		})
    56  	}
    57  }
    58  
    59  func Test_SeriesMerger_Annotations(t *testing.T) {
    60  	for _, tc := range []struct {
    61  		name string
    62  		in   [][]*typesv1.Series
    63  		out  []*typesv1.Series
    64  	}{
    65  		{
    66  			name: "merge two distinct annotations",
    67  			in: [][]*typesv1.Series{
    68  				{
    69  					{
    70  						Labels: LabelsFromStrings("foo", "bar"),
    71  						Points: []*typesv1.Point{
    72  							{
    73  								Timestamp: 1,
    74  								Value:     1,
    75  								Annotations: []*typesv1.ProfileAnnotation{
    76  									{Key: "key1", Value: "value1"},
    77  								},
    78  							},
    79  						},
    80  					},
    81  				},
    82  				{
    83  					{
    84  						Labels: LabelsFromStrings("foo", "bar"),
    85  						Points: []*typesv1.Point{
    86  							{
    87  								Timestamp: 1,
    88  								Value:     2,
    89  								Annotations: []*typesv1.ProfileAnnotation{
    90  									{Key: "key1", Value: "value2"},
    91  								},
    92  							},
    93  						},
    94  					},
    95  				},
    96  			},
    97  			out: []*typesv1.Series{
    98  				{
    99  					Labels: LabelsFromStrings("foo", "bar"),
   100  					Points: []*typesv1.Point{
   101  						{
   102  							Timestamp: 1,
   103  							Value:     3,
   104  							Annotations: []*typesv1.ProfileAnnotation{
   105  								{Key: "key1", Value: "value1"},
   106  								{Key: "key1", Value: "value2"},
   107  							},
   108  						},
   109  					},
   110  				},
   111  			},
   112  		},
   113  		{
   114  			name: "merge duplicate annotations",
   115  			in: [][]*typesv1.Series{
   116  				{
   117  					{
   118  						Labels: LabelsFromStrings("foo", "bar"),
   119  						Points: []*typesv1.Point{
   120  							{
   121  								Timestamp: 1,
   122  								Value:     1,
   123  								Annotations: []*typesv1.ProfileAnnotation{
   124  									{Key: "key1", Value: "value1"},
   125  									{Key: "key2", Value: "value2"},
   126  								},
   127  							},
   128  						},
   129  					},
   130  				},
   131  				{
   132  					{
   133  						Labels: LabelsFromStrings("foo", "bar"),
   134  						Points: []*typesv1.Point{
   135  							{
   136  								Timestamp: 1,
   137  								Value:     2,
   138  								Annotations: []*typesv1.ProfileAnnotation{
   139  									{Key: "key1", Value: "value1"},
   140  									{Key: "key3", Value: "value3"},
   141  								},
   142  							},
   143  						},
   144  					},
   145  				},
   146  			},
   147  			out: []*typesv1.Series{
   148  				{
   149  					Labels: LabelsFromStrings("foo", "bar"),
   150  					Points: []*typesv1.Point{
   151  						{
   152  							Timestamp: 1,
   153  							Value:     3,
   154  							Annotations: []*typesv1.ProfileAnnotation{
   155  								{Key: "key1", Value: "value1"},
   156  								{Key: "key2", Value: "value2"},
   157  								{Key: "key3", Value: "value3"},
   158  							},
   159  						},
   160  					},
   161  				},
   162  			},
   163  		},
   164  		{
   165  			name: "merge all duplicate annotations",
   166  			in: [][]*typesv1.Series{
   167  				{
   168  					{
   169  						Labels: LabelsFromStrings("foo", "bar"),
   170  						Points: []*typesv1.Point{
   171  							{
   172  								Timestamp: 1,
   173  								Value:     1,
   174  								Annotations: []*typesv1.ProfileAnnotation{
   175  									{Key: "key1", Value: "value1"},
   176  									{Key: "key2", Value: "value2"},
   177  								},
   178  							},
   179  						},
   180  					},
   181  				},
   182  				{
   183  					{
   184  						Labels: LabelsFromStrings("foo", "bar"),
   185  						Points: []*typesv1.Point{
   186  							{
   187  								Timestamp: 1,
   188  								Value:     2,
   189  								Annotations: []*typesv1.ProfileAnnotation{
   190  									{Key: "key1", Value: "value1"},
   191  									{Key: "key2", Value: "value2"},
   192  								},
   193  							},
   194  						},
   195  					},
   196  				},
   197  			},
   198  			out: []*typesv1.Series{
   199  				{
   200  					Labels: LabelsFromStrings("foo", "bar"),
   201  					Points: []*typesv1.Point{
   202  						{
   203  							Timestamp: 1,
   204  							Value:     3,
   205  							Annotations: []*typesv1.ProfileAnnotation{
   206  								{Key: "key1", Value: "value1"},
   207  								{Key: "key2", Value: "value2"},
   208  							},
   209  						},
   210  					},
   211  				},
   212  			},
   213  		},
   214  		{
   215  			name: "annotations sorted by key then value",
   216  			in: [][]*typesv1.Series{
   217  				{
   218  					{
   219  						Labels: LabelsFromStrings("foo", "bar"),
   220  						Points: []*typesv1.Point{
   221  							{
   222  								Timestamp: 1,
   223  								Value:     1,
   224  								Annotations: []*typesv1.ProfileAnnotation{
   225  									{Key: "z", Value: "last"},
   226  									{Key: "a", Value: "first"},
   227  								},
   228  							},
   229  						},
   230  					},
   231  				},
   232  				{
   233  					{
   234  						Labels: LabelsFromStrings("foo", "bar"),
   235  						Points: []*typesv1.Point{
   236  							{
   237  								Timestamp: 1,
   238  								Value:     2,
   239  								Annotations: []*typesv1.ProfileAnnotation{
   240  									{Key: "m", Value: "middle"},
   241  								},
   242  							},
   243  						},
   244  					},
   245  				},
   246  			},
   247  			out: []*typesv1.Series{
   248  				{
   249  					Labels: LabelsFromStrings("foo", "bar"),
   250  					Points: []*typesv1.Point{
   251  						{
   252  							Timestamp: 1,
   253  							Value:     3,
   254  							Annotations: []*typesv1.ProfileAnnotation{
   255  								{Key: "a", Value: "first"},
   256  								{Key: "m", Value: "middle"},
   257  								{Key: "z", Value: "last"},
   258  							},
   259  						},
   260  					},
   261  				},
   262  			},
   263  		},
   264  		{
   265  			name: "empty annotations on one side",
   266  			in: [][]*typesv1.Series{
   267  				{
   268  					{
   269  						Labels: LabelsFromStrings("foo", "bar"),
   270  						Points: []*typesv1.Point{
   271  							{
   272  								Timestamp:   1,
   273  								Value:       1,
   274  								Annotations: []*typesv1.ProfileAnnotation{},
   275  							},
   276  						},
   277  					},
   278  				},
   279  				{
   280  					{
   281  						Labels: LabelsFromStrings("foo", "bar"),
   282  						Points: []*typesv1.Point{
   283  							{
   284  								Timestamp: 1,
   285  								Value:     2,
   286  								Annotations: []*typesv1.ProfileAnnotation{
   287  									{Key: "key1", Value: "value1"},
   288  								},
   289  							},
   290  						},
   291  					},
   292  				},
   293  			},
   294  			out: []*typesv1.Series{
   295  				{
   296  					Labels: LabelsFromStrings("foo", "bar"),
   297  					Points: []*typesv1.Point{
   298  						{
   299  							Timestamp: 1,
   300  							Value:     3,
   301  							Annotations: []*typesv1.ProfileAnnotation{
   302  								{Key: "key1", Value: "value1"},
   303  							},
   304  						},
   305  					},
   306  				},
   307  			},
   308  		},
   309  	} {
   310  		t.Run(tc.name, func(t *testing.T) {
   311  			testhelper.EqualProto(t, tc.out, MergeSeries(nil, tc.in...))
   312  		})
   313  	}
   314  }
   315  
   316  func Test_SeriesMerger_Overlap_Sum(t *testing.T) {
   317  	for _, tc := range []struct {
   318  		name string
   319  		in   [][]*typesv1.Series
   320  		out  []*typesv1.Series
   321  	}{
   322  		{
   323  			name: "merge deduplicate overlapping series",
   324  			in: [][]*typesv1.Series{
   325  				{
   326  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}},
   327  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   328  				},
   329  				{
   330  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   331  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}},
   332  				},
   333  			},
   334  			out: []*typesv1.Series{
   335  				{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 1}}},
   336  				{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 1}}},
   337  			},
   338  		},
   339  	} {
   340  		t.Run(tc.name, func(t *testing.T) {
   341  			testhelper.EqualProto(t, tc.out, MergeSeries(nil, tc.in...))
   342  		})
   343  	}
   344  }
   345  
   346  func Test_SeriesMerger_Top(t *testing.T) {
   347  	for _, tc := range []struct {
   348  		name string
   349  		in   [][]*typesv1.Series
   350  		out  []*typesv1.Series
   351  		top  int
   352  	}{
   353  		{
   354  			name: "top == len",
   355  			in: [][]*typesv1.Series{
   356  				{
   357  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}},
   358  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   359  				},
   360  				{
   361  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   362  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   363  				},
   364  			},
   365  			top: 2,
   366  			out: []*typesv1.Series{
   367  				{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 2}}},
   368  				{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 1}}},
   369  			},
   370  		},
   371  		{
   372  			name: "top < len",
   373  			in: [][]*typesv1.Series{
   374  				{
   375  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}},
   376  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   377  				},
   378  				{
   379  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   380  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   381  				},
   382  			},
   383  			top: 1,
   384  			out: []*typesv1.Series{
   385  				{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 2}}},
   386  			},
   387  		},
   388  		{
   389  			name: "top > len",
   390  			in: [][]*typesv1.Series{
   391  				{
   392  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}},
   393  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   394  				},
   395  				{
   396  					{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   397  					{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   398  				},
   399  			},
   400  			top: 3,
   401  			out: []*typesv1.Series{
   402  				{Labels: LabelsFromStrings("foo", "baz"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 2}}},
   403  				{Labels: LabelsFromStrings("foo", "bar"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 1}}},
   404  			},
   405  		},
   406  		{
   407  			name: "order",
   408  			in: [][]*typesv1.Series{
   409  				{
   410  					{Labels: LabelsFromStrings("foo", "d"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   411  					{Labels: LabelsFromStrings("foo", "e"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}},
   412  					{Labels: LabelsFromStrings("foo", "c"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   413  					{Labels: LabelsFromStrings("foo", "a"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   414  					{Labels: LabelsFromStrings("foo", "b"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 3}}},
   415  				},
   416  			},
   417  			top: 4,
   418  			out: []*typesv1.Series{
   419  				{Labels: LabelsFromStrings("foo", "b"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 3}}},
   420  				{Labels: LabelsFromStrings("foo", "a"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   421  				{Labels: LabelsFromStrings("foo", "c"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   422  				{Labels: LabelsFromStrings("foo", "d"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   423  			},
   424  		},
   425  		{
   426  			name: "k == 0",
   427  			in: [][]*typesv1.Series{
   428  				{
   429  					{Labels: LabelsFromStrings("foo", "d"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   430  					{Labels: LabelsFromStrings("foo", "c"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   431  					{Labels: LabelsFromStrings("foo", "a"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   432  					{Labels: LabelsFromStrings("foo", "b"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 3}}},
   433  				},
   434  			},
   435  			top: 0,
   436  			out: []*typesv1.Series{
   437  				{Labels: LabelsFromStrings("foo", "b"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 3}}},
   438  				{Labels: LabelsFromStrings("foo", "a"), Points: []*typesv1.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 2}}},
   439  				{Labels: LabelsFromStrings("foo", "c"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   440  				{Labels: LabelsFromStrings("foo", "d"), Points: []*typesv1.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 1}}},
   441  			},
   442  		},
   443  	} {
   444  		t.Run(tc.name, func(t *testing.T) {
   445  			m := NewTimeSeriesMerger(true)
   446  			for _, s := range tc.in {
   447  				m.MergeTimeSeries(s)
   448  			}
   449  			testhelper.EqualProto(t, tc.out, m.Top(tc.top))
   450  		})
   451  	}
   452  }
   453  
   454  func Test_SeriesMerger_WithExemplars(t *testing.T) {
   455  	for _, tc := range []struct {
   456  		name string
   457  		in   [][]*typesv1.Series
   458  		out  []*typesv1.Series
   459  	}{
   460  		{
   461  			name: "merge keeps highest value exemplar per profile ID",
   462  			in: [][]*typesv1.Series{
   463  				{
   464  					{
   465  						Labels: LabelsFromStrings("foo", "bar"),
   466  						Points: []*typesv1.Point{
   467  							{
   468  								Timestamp: 1,
   469  								Value:     10,
   470  								Exemplars: []*typesv1.Exemplar{
   471  									{ProfileId: "prof-1", Value: 100, Timestamp: 1},
   472  								},
   473  							},
   474  						},
   475  					},
   476  				},
   477  				{
   478  					{
   479  						Labels: LabelsFromStrings("foo", "bar"),
   480  						Points: []*typesv1.Point{
   481  							{
   482  								Timestamp: 1,
   483  								Value:     20,
   484  								Exemplars: []*typesv1.Exemplar{
   485  									{ProfileId: "prof-1", Value: 500, Timestamp: 1},
   486  									{ProfileId: "prof-2", Value: 200, Timestamp: 1},
   487  								},
   488  							},
   489  						},
   490  					},
   491  				},
   492  			},
   493  			out: []*typesv1.Series{
   494  				{
   495  					Labels: LabelsFromStrings("foo", "bar"),
   496  					Points: []*typesv1.Point{
   497  						{
   498  							Timestamp: 1,
   499  							Value:     30,
   500  							Exemplars: []*typesv1.Exemplar{
   501  								{ProfileId: "prof-1", Value: 500, Timestamp: 1},
   502  								{ProfileId: "prof-2", Value: 200, Timestamp: 1},
   503  							},
   504  						},
   505  					},
   506  				},
   507  			},
   508  		},
   509  		{
   510  			name: "merge preserves exemplar labels",
   511  			in: [][]*typesv1.Series{
   512  				{
   513  					{
   514  						Labels: LabelsFromStrings("service_name", "api"),
   515  						Points: []*typesv1.Point{
   516  							{
   517  								Timestamp: 1000,
   518  								Value:     100,
   519  								Exemplars: []*typesv1.Exemplar{
   520  									{
   521  										ProfileId: "prof-1",
   522  										Value:     100,
   523  										Timestamp: 1000,
   524  										Labels:    []*typesv1.LabelPair{{Name: "pod", Value: "pod-123"}},
   525  									},
   526  								},
   527  							},
   528  						},
   529  					},
   530  				},
   531  			},
   532  			out: []*typesv1.Series{
   533  				{
   534  					Labels: LabelsFromStrings("service_name", "api"),
   535  					Points: []*typesv1.Point{
   536  						{
   537  							Timestamp: 1000,
   538  							Value:     100,
   539  							Exemplars: []*typesv1.Exemplar{
   540  								{
   541  									ProfileId: "prof-1",
   542  									Value:     100,
   543  									Timestamp: 1000,
   544  									Labels:    []*typesv1.LabelPair{{Name: "pod", Value: "pod-123"}},
   545  								},
   546  							},
   547  						},
   548  					},
   549  				},
   550  			},
   551  		},
   552  	} {
   553  		t.Run(tc.name, func(t *testing.T) {
   554  			testhelper.EqualProto(t, tc.out, MergeSeries(nil, tc.in...))
   555  		})
   556  	}
   557  }