github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logproto/compat_test.go (about) 1 package logproto 2 3 import ( 4 "encoding/json" 5 stdlibjson "encoding/json" 6 "fmt" 7 "math" 8 "testing" 9 "unsafe" 10 11 jsoniter "github.com/json-iterator/go" 12 "github.com/prometheus/prometheus/model/labels" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 // This test verifies that jsoninter uses our custom method for marshalling. 18 // We do that by using "test sample" recognized by marshal function when in testing mode. 19 func TestJsoniterMarshalForSample(t *testing.T) { 20 testMarshalling(t, jsoniter.Marshal, "test sample") 21 } 22 23 func TestStdlibJsonMarshalForSample(t *testing.T) { 24 testMarshalling(t, stdlibjson.Marshal, "json: error calling MarshalJSON for type logproto.LegacySample: test sample") 25 } 26 27 func testMarshalling(t *testing.T, marshalFn func(v interface{}) ([]byte, error), expectedError string) { 28 isTesting = true 29 defer func() { isTesting = false }() 30 31 out, err := marshalFn(LegacySample{Value: 12345, TimestampMs: 98765}) 32 require.NoError(t, err) 33 require.Equal(t, `[98.765,"12345"]`, string(out)) 34 35 _, err = marshalFn(LegacySample{Value: math.NaN(), TimestampMs: 0}) 36 require.EqualError(t, err, expectedError) 37 38 // If not testing, we get normal output. 39 isTesting = false 40 out, err = marshalFn(LegacySample{Value: math.NaN(), TimestampMs: 0}) 41 require.NoError(t, err) 42 require.Equal(t, `[0,"NaN"]`, string(out)) 43 } 44 45 // This test verifies that jsoninter uses our custom method for unmarshalling Sample. 46 // As with Marshal, we rely on testing mode and special value that reports error. 47 func TestJsoniterUnmarshalForSample(t *testing.T) { 48 testUnmarshalling(t, jsoniter.Unmarshal, "test sample") 49 } 50 51 func TestStdlibJsonUnmarshalForSample(t *testing.T) { 52 testUnmarshalling(t, json.Unmarshal, "test sample") 53 } 54 55 func testUnmarshalling(t *testing.T, unmarshalFn func(data []byte, v interface{}) error, expectedError string) { 56 isTesting = true 57 defer func() { isTesting = false }() 58 59 sample := LegacySample{} 60 61 err := unmarshalFn([]byte(`[98.765,"12345"]`), &sample) 62 require.NoError(t, err) 63 require.Equal(t, LegacySample{Value: 12345, TimestampMs: 98765}, sample) 64 65 err = unmarshalFn([]byte(`[0.0,"NaN"]`), &sample) 66 require.EqualError(t, err, expectedError) 67 68 isTesting = false 69 err = unmarshalFn([]byte(`[0.0,"NaN"]`), &sample) 70 require.NoError(t, err) 71 require.Equal(t, int64(0), sample.TimestampMs) 72 require.True(t, math.IsNaN(sample.Value)) 73 } 74 75 func TestFromLabelAdaptersToLabels(t *testing.T) { 76 input := []LabelAdapter{{Name: "hello", Value: "world"}} 77 expected := labels.Labels{labels.Label{Name: "hello", Value: "world"}} 78 actual := FromLabelAdaptersToLabels(input) 79 80 assert.Equal(t, expected, actual) 81 82 // All strings must NOT be copied. 83 assert.Equal(t, uintptr(unsafe.Pointer(&input[0].Name)), uintptr(unsafe.Pointer(&actual[0].Name))) 84 assert.Equal(t, uintptr(unsafe.Pointer(&input[0].Value)), uintptr(unsafe.Pointer(&actual[0].Value))) 85 } 86 87 func TestFromLabelAdaptersToLabelsWithCopy(t *testing.T) { 88 input := []LabelAdapter{{Name: "hello", Value: "world"}} 89 expected := labels.Labels{labels.Label{Name: "hello", Value: "world"}} 90 actual := FromLabelAdaptersToLabelsWithCopy(input) 91 92 assert.Equal(t, expected, actual) 93 94 // All strings must be copied. 95 assert.NotEqual(t, uintptr(unsafe.Pointer(&input[0].Name)), uintptr(unsafe.Pointer(&actual[0].Name))) 96 assert.NotEqual(t, uintptr(unsafe.Pointer(&input[0].Value)), uintptr(unsafe.Pointer(&actual[0].Value))) 97 } 98 99 func BenchmarkFromLabelAdaptersToLabelsWithCopy(b *testing.B) { 100 input := []LabelAdapter{ 101 {Name: "hello", Value: "world"}, 102 {Name: "some label", Value: "and its value"}, 103 {Name: "long long long long long label name", Value: "perhaps even longer label value, but who's counting anyway?"}} 104 105 for i := 0; i < b.N; i++ { 106 FromLabelAdaptersToLabelsWithCopy(input) 107 } 108 } 109 110 func TestLegacySampleCompatibilityMarshalling(t *testing.T) { 111 ts := int64(1232132123) 112 val := 12345.12345 113 legacySample := LegacySample{Value: val, TimestampMs: ts} 114 got, err := json.Marshal(legacySample) 115 require.NoError(t, err) 116 117 legacyExpected := fmt.Sprintf("[%d.%d,\"%.5f\"]", ts/1000, 123, val) 118 require.Equal(t, []byte(legacyExpected), got) 119 120 // proving that `logproto.Sample` marshal things differently than `logproto.LegacySample`: 121 incompatibleSample := Sample{Value: val, Timestamp: ts} 122 gotIncompatibleSample, err := json.Marshal(incompatibleSample) 123 require.NoError(t, err) 124 require.NotEqual(t, []byte(legacyExpected), gotIncompatibleSample) 125 } 126 127 func TestLegacySampleCompatibilityUnmarshalling(t *testing.T) { 128 serializedSample := "[123123.123,\"12345.12345\"]" 129 var legacySample LegacySample 130 err := json.Unmarshal([]byte(serializedSample), &legacySample) 131 require.NoError(t, err) 132 expectedLegacySample := LegacySample{Value: 12345.12345, TimestampMs: 123123123} 133 require.EqualValues(t, expectedLegacySample, legacySample) 134 135 // proving that `logproto.Sample` unmarshal things differently than `logproto.LegacySample`: 136 incompatibleSample := Sample{Value: 12345.12345, Timestamp: 123123123} 137 require.NotEqualValues(t, expectedLegacySample, incompatibleSample) 138 } 139 140 func TestLegacyLabelPairCompatibilityMarshalling(t *testing.T) { 141 legacyLabelPair := LegacyLabelPair{Name: []byte("labelname"), Value: []byte("labelvalue")} 142 got, err := json.Marshal(legacyLabelPair) 143 require.NoError(t, err) 144 145 expectedStr := `{"name":"bGFiZWxuYW1l","value":"bGFiZWx2YWx1ZQ=="}` 146 require.Equal(t, []byte(expectedStr), got) 147 148 // proving that `logproto.LegacyLabelPair` marshal things differently than `logproto.LabelPair`: 149 incompatibleLabelPair := LabelPair{Name: "labelname", Value: "labelvalue"} 150 gotIncompatible, err := json.Marshal(incompatibleLabelPair) 151 require.NoError(t, err) 152 require.NotEqual(t, []byte(expectedStr), gotIncompatible) 153 } 154 155 func TestLegacyLabelPairCompatibilityUnmarshalling(t *testing.T) { 156 serializedLabelPair := `{"name":"bGFiZWxuYW1l","value":"bGFiZWx2YWx1ZQ=="}` 157 var legacyLabelPair LegacyLabelPair 158 err := json.Unmarshal([]byte(serializedLabelPair), &legacyLabelPair) 159 require.NoError(t, err) 160 expectedLabelPair := LegacyLabelPair{Name: []byte("labelname"), Value: []byte("labelvalue")} 161 require.EqualValues(t, expectedLabelPair, legacyLabelPair) 162 163 // proving that `logproto.LegacyLabelPair` unmarshal things differently than `logproto.LabelPair`: 164 var incompatibleLabelPair LabelPair 165 err = json.Unmarshal([]byte(serializedLabelPair), &incompatibleLabelPair) 166 require.NoError(t, err) 167 require.NotEqualValues(t, expectedLabelPair, incompatibleLabelPair) 168 } 169 170 func TestMergeLabelResponses(t *testing.T) { 171 for _, tc := range []struct { 172 desc string 173 responses []*LabelResponse 174 expected []*LabelResponse 175 err error 176 }{ 177 { 178 desc: "merge two label responses", 179 responses: []*LabelResponse{ 180 {Values: []string{"test"}}, 181 {Values: []string{"test2"}}, 182 }, 183 expected: []*LabelResponse{ 184 {Values: []string{"test", "test2"}}, 185 }, 186 }, 187 { 188 desc: "merge three label responses", 189 responses: []*LabelResponse{ 190 {Values: []string{"test"}}, 191 {Values: []string{"test2"}}, 192 {Values: []string{"test3"}}, 193 }, 194 expected: []*LabelResponse{ 195 {Values: []string{"test", "test2", "test3"}}, 196 }, 197 }, 198 { 199 desc: "merge three label responses with one non-unique", 200 responses: []*LabelResponse{ 201 {Values: []string{"test"}}, 202 {Values: []string{"test"}}, 203 {Values: []string{"test2"}}, 204 {Values: []string{"test3"}}, 205 }, 206 expected: []*LabelResponse{ 207 {Values: []string{"test", "test2", "test3"}}, 208 }, 209 }, 210 { 211 desc: "merge one and expect one", 212 responses: []*LabelResponse{ 213 {Values: []string{"test"}}, 214 }, 215 expected: []*LabelResponse{ 216 {Values: []string{"test"}}, 217 }, 218 }, 219 { 220 desc: "merge empty and expect empty", 221 responses: []*LabelResponse{}, 222 expected: []*LabelResponse{}, 223 }, 224 } { 225 t.Run(tc.desc, func(t *testing.T) { 226 merged, err := MergeLabelResponses(tc.responses) 227 if err != nil { 228 require.Equal(t, tc.err, err) 229 } else if len(tc.expected) == 0 { 230 require.Empty(t, merged) 231 } else { 232 require.ElementsMatch(t, tc.expected[0].Values, merged.Values) 233 } 234 }) 235 } 236 } 237 238 func TestMergeSeriesResponses(t *testing.T) { 239 mockSeriesResponse := func(series []map[string]string) *SeriesResponse { 240 resp := &SeriesResponse{} 241 for _, s := range series { 242 resp.Series = append(resp.Series, SeriesIdentifier{ 243 Labels: s, 244 }) 245 } 246 return resp 247 } 248 249 for _, tc := range []struct { 250 desc string 251 responses []*SeriesResponse 252 expected []*SeriesResponse 253 err error 254 }{ 255 { 256 desc: "merge one series response and expect one", 257 responses: []*SeriesResponse{ 258 {Series: []SeriesIdentifier{{Labels: map[string]string{"test": "test"}}}}, 259 }, 260 expected: []*SeriesResponse{ 261 mockSeriesResponse([]map[string]string{{"test": "test"}}), 262 }, 263 }, 264 { 265 desc: "merge two series responses", 266 responses: []*SeriesResponse{ 267 {Series: []SeriesIdentifier{{Labels: map[string]string{"test": "test"}}}}, 268 {Series: []SeriesIdentifier{{Labels: map[string]string{"test2": "test2"}}}}, 269 }, 270 expected: []*SeriesResponse{ 271 mockSeriesResponse([]map[string]string{{"test": "test"}, {"test2": "test2"}}), 272 }, 273 }, 274 { 275 desc: "merge three series responses", 276 responses: []*SeriesResponse{ 277 {Series: []SeriesIdentifier{{Labels: map[string]string{"test": "test"}}}}, 278 {Series: []SeriesIdentifier{{Labels: map[string]string{"test2": "test2"}}}}, 279 {Series: []SeriesIdentifier{{Labels: map[string]string{"test3": "test3"}}}}, 280 }, 281 expected: []*SeriesResponse{ 282 mockSeriesResponse([]map[string]string{{"test": "test"}, {"test2": "test2"}, {"test3": "test3"}}), 283 }, 284 }, 285 { 286 desc: "merge empty and expect empty", 287 responses: []*SeriesResponse{}, 288 expected: []*SeriesResponse{}, 289 }, 290 } { 291 t.Run(tc.desc, func(t *testing.T) { 292 merged, err := MergeSeriesResponses(tc.responses) 293 if err != nil { 294 require.Equal(t, tc.err, err) 295 } else if len(tc.expected) == 0 { 296 require.Empty(t, merged) 297 } else { 298 require.ElementsMatch(t, tc.expected[0].Series, merged.Series) 299 } 300 }) 301 } 302 } 303 304 func benchmarkMergeLabelResponses(b *testing.B, responses []*LabelResponse) { 305 b.ReportAllocs() 306 for n := 0; n < b.N; n++ { 307 MergeLabelResponses(responses) //nolint:errcheck 308 } 309 } 310 311 func benchmarkMergeSeriesResponses(b *testing.B, responses []*SeriesResponse) { 312 b.ReportAllocs() 313 for n := 0; n < b.N; n++ { 314 MergeSeriesResponses(responses) //nolint:errcheck 315 } 316 } 317 318 func BenchmarkMergeALabelResponse(b *testing.B) { 319 response := []*LabelResponse{{Values: []string{"test"}}} 320 benchmarkMergeLabelResponses(b, response) 321 } 322 323 func BenchmarkMergeASeriesResponse(b *testing.B) { 324 response := []*SeriesResponse{{Series: []SeriesIdentifier{{Labels: map[string]string{"test": "test"}}}}} 325 benchmarkMergeSeriesResponses(b, response) 326 } 327 328 func BenchmarkMergeSomeLabelResponses(b *testing.B) { 329 responses := []*LabelResponse{ 330 {Values: []string{"test"}}, 331 {Values: []string{"test2"}}, 332 {Values: []string{"test3"}}, 333 } 334 benchmarkMergeLabelResponses(b, responses) 335 } 336 337 func BenchmarkMergeSomeSeriesResponses(b *testing.B) { 338 responses := []*SeriesResponse{ 339 {Series: []SeriesIdentifier{{Labels: map[string]string{"test": "test"}}}}, 340 {Series: []SeriesIdentifier{{Labels: map[string]string{"test2": "test2"}}}}, 341 {Series: []SeriesIdentifier{{Labels: map[string]string{"test3": "test3"}}}}, 342 } 343 benchmarkMergeSeriesResponses(b, responses) 344 } 345 346 func BenchmarkMergeManyLabelResponses(b *testing.B) { 347 responses := []*LabelResponse{} 348 for i := 0; i < 20; i++ { 349 responses = append(responses, &LabelResponse{Values: []string{fmt.Sprintf("test%d", i)}}) 350 } 351 benchmarkMergeLabelResponses(b, responses) 352 } 353 354 func BenchmarkMergeManySeriesResponses(b *testing.B) { 355 responses := []*SeriesResponse{} 356 for i := 0; i < 20; i++ { 357 test := fmt.Sprintf("test%d", i) 358 responses = append(responses, &SeriesResponse{Series: []SeriesIdentifier{{Labels: map[string]string{test: test}}}}) 359 } 360 benchmarkMergeSeriesResponses(b, responses) 361 }