github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/server/rawtcp/server_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package rawtcp 22 23 import ( 24 "errors" 25 "net" 26 "sync" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/aggregator/aggregator" 31 "github.com/m3db/m3/src/aggregator/aggregator/capture" 32 "github.com/m3db/m3/src/metrics/aggregation" 33 "github.com/m3db/m3/src/metrics/encoding" 34 "github.com/m3db/m3/src/metrics/encoding/protobuf" 35 "github.com/m3db/m3/src/metrics/metadata" 36 "github.com/m3db/m3/src/metrics/metric" 37 "github.com/m3db/m3/src/metrics/metric/aggregated" 38 "github.com/m3db/m3/src/metrics/metric/unaggregated" 39 "github.com/m3db/m3/src/metrics/pipeline" 40 "github.com/m3db/m3/src/metrics/pipeline/applied" 41 "github.com/m3db/m3/src/metrics/policy" 42 "github.com/m3db/m3/src/x/clock" 43 "github.com/m3db/m3/src/x/instrument" 44 "github.com/m3db/m3/src/x/retry" 45 xserver "github.com/m3db/m3/src/x/server" 46 xtest "github.com/m3db/m3/src/x/test" 47 xtime "github.com/m3db/m3/src/x/time" 48 49 "github.com/golang/mock/gomock" 50 "github.com/google/go-cmp/cmp" 51 "github.com/google/go-cmp/cmp/cmpopts" 52 "github.com/stretchr/testify/require" 53 "go.uber.org/zap" 54 "go.uber.org/zap/zapcore" 55 "go.uber.org/zap/zaptest/observer" 56 ) 57 58 const ( 59 testListenAddress = "127.0.0.1:0" 60 ) 61 62 var ( 63 testNowNanos = time.Now().UnixNano() 64 testCounter = unaggregated.MetricUnion{ 65 Type: metric.CounterType, 66 ID: []byte("testCounter"), 67 CounterVal: 123, 68 } 69 testBatchTimer = unaggregated.MetricUnion{ 70 Type: metric.TimerType, 71 ID: []byte("testBatchTimer"), 72 BatchTimerVal: []float64{1.0, 2.0, 3.0}, 73 } 74 testGauge = unaggregated.MetricUnion{ 75 Type: metric.GaugeType, 76 ID: []byte("testGauge"), 77 GaugeVal: 456.780, 78 } 79 testTimed = aggregated.Metric{ 80 Type: metric.CounterType, 81 ID: []byte("testTimed"), 82 TimeNanos: 12345, 83 Value: -13, 84 } 85 testForwarded = aggregated.ForwardedMetric{ 86 Type: metric.CounterType, 87 ID: []byte("testForwarded"), 88 TimeNanos: 12345, 89 Values: []float64{908, -13}, 90 } 91 testPassthrough = aggregated.Metric{ 92 Type: metric.CounterType, 93 ID: []byte("testPassthrough"), 94 TimeNanos: 12345, 95 Value: -13, 96 } 97 testDefaultPoliciesList = policy.DefaultPoliciesList 98 testCustomPoliciesList = policy.PoliciesList{ 99 policy.NewStagedPolicies( 100 testNowNanos, 101 false, 102 []policy.Policy{ 103 policy.NewPolicy(policy.NewStoragePolicy(10*time.Second, xtime.Second, 6*time.Hour), aggregation.MustCompressTypes(aggregation.Min)), 104 policy.NewPolicy(policy.NewStoragePolicy(time.Minute, xtime.Minute, 2*24*time.Hour), aggregation.DefaultID), 105 policy.NewPolicy(policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 30*24*time.Hour), aggregation.DefaultID), 106 }, 107 ), 108 } 109 testDefaultMetadatas = metadata.DefaultStagedMetadatas 110 testCustomMetadatas = metadata.StagedMetadatas{ 111 { 112 CutoverNanos: testNowNanos, 113 Tombstoned: false, 114 Metadata: metadata.Metadata{ 115 Pipelines: []metadata.PipelineMetadata{ 116 { 117 AggregationID: aggregation.MustCompressTypes(aggregation.Min), 118 StoragePolicies: []policy.StoragePolicy{ 119 policy.NewStoragePolicy(10*time.Second, xtime.Second, 6*time.Hour), 120 }, 121 }, 122 { 123 AggregationID: aggregation.DefaultID, 124 StoragePolicies: []policy.StoragePolicy{ 125 policy.NewStoragePolicy(time.Minute, xtime.Minute, 2*24*time.Hour), 126 policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 30*24*time.Hour), 127 }, 128 }, 129 }, 130 }, 131 }, 132 } 133 testTimedMetadata = metadata.TimedMetadata{ 134 AggregationID: aggregation.DefaultID, 135 StoragePolicy: policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour), 136 } 137 testForwardMetadata = metadata.ForwardMetadata{ 138 AggregationID: aggregation.DefaultID, 139 StoragePolicy: policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour), 140 Pipeline: applied.NewPipeline([]applied.OpUnion{ 141 { 142 Type: pipeline.RollupOpType, 143 Rollup: applied.RollupOp{ 144 ID: []byte("foo"), 145 AggregationID: aggregation.MustCompressTypes(aggregation.Count), 146 }, 147 }, 148 }), 149 SourceID: 1234, 150 NumForwardedTimes: 3, 151 } 152 testPassthroughStoragePolicy = policy.NewStoragePolicy(time.Minute, xtime.Minute, 12*time.Hour) 153 testBatchTimerWithPoliciesList = unaggregated.BatchTimerWithPoliciesList{ 154 BatchTimer: testBatchTimer.BatchTimer(), 155 PoliciesList: testCustomPoliciesList, 156 } 157 testGaugeWithPoliciesList = unaggregated.GaugeWithPoliciesList{ 158 Gauge: testGauge.Gauge(), 159 PoliciesList: testDefaultPoliciesList, 160 } 161 testCounterWithMetadatas = unaggregated.CounterWithMetadatas{ 162 Counter: testCounter.Counter(), 163 StagedMetadatas: testDefaultMetadatas, 164 } 165 testBatchTimerWithMetadatas = unaggregated.BatchTimerWithMetadatas{ 166 BatchTimer: testBatchTimer.BatchTimer(), 167 StagedMetadatas: testCustomMetadatas, 168 } 169 testGaugeWithMetadatas = unaggregated.GaugeWithMetadatas{ 170 Gauge: testGauge.Gauge(), 171 StagedMetadatas: testDefaultMetadatas, 172 } 173 testTimedMetricWithMetadata = aggregated.TimedMetricWithMetadata{ 174 Metric: testTimed, 175 TimedMetadata: testTimedMetadata, 176 } 177 testTimedMetricWithMetadatas = aggregated.TimedMetricWithMetadatas{ 178 Metric: testTimed, 179 StagedMetadatas: testDefaultMetadatas, 180 } 181 testForwardedMetricWithMetadata = aggregated.ForwardedMetricWithMetadata{ 182 ForwardedMetric: testForwarded, 183 ForwardMetadata: testForwardMetadata, 184 } 185 testPassthroughMetricWithMetadata = aggregated.PassthroughMetricWithMetadata{ 186 Metric: testPassthrough, 187 StoragePolicy: testPassthroughStoragePolicy, 188 } 189 testCmpOpts = []cmp.Option{ 190 cmpopts.EquateEmpty(), 191 cmp.AllowUnexported(policy.StoragePolicy{}), 192 } 193 ) 194 195 func TestRawTCPServerHandleUnaggregatedProtobufEncoding(t *testing.T) { 196 agg := capture.NewAggregator() 197 h := NewHandler(agg, testServerOptions()) 198 199 var ( 200 numClients = 9 201 wgClient sync.WaitGroup 202 expectedResult capture.SnapshotResult 203 ) 204 205 listener, err := net.Listen("tcp", testListenAddress) 206 require.NoError(t, err) 207 208 s := xserver.NewServer(testListenAddress, h, xserver.NewOptions()) 209 // Start server. 210 require.NoError(t, s.Serve(listener)) 211 212 // Now establish multiple connections and send data to the server. 213 var expectedTotalMetrics int 214 for i := 0; i < numClients; i++ { 215 wgClient.Add(1) 216 217 // Add test metrics to expected result. 218 expectedResult.CountersWithMetadatas = append(expectedResult.CountersWithMetadatas, testCounterWithMetadatas) 219 expectedResult.BatchTimersWithMetadatas = append(expectedResult.BatchTimersWithMetadatas, testBatchTimerWithMetadatas) 220 expectedResult.GaugesWithMetadatas = append(expectedResult.GaugesWithMetadatas, testGaugeWithMetadatas) 221 expectedResult.TimedMetricWithMetadata = append(expectedResult.TimedMetricWithMetadata, testTimedMetricWithMetadata) 222 expectedResult.PassthroughMetricWithMetadata = append(expectedResult.PassthroughMetricWithMetadata, testPassthroughMetricWithMetadata) 223 expectedResult.ForwardedMetricsWithMetadata = append(expectedResult.ForwardedMetricsWithMetadata, testForwardedMetricWithMetadata) 224 expectedTotalMetrics += 5 225 226 go func() { 227 defer wgClient.Done() 228 229 conn, err := net.Dial("tcp", listener.Addr().String()) 230 require.NoError(t, err) 231 232 encoder := protobuf.NewUnaggregatedEncoder(protobuf.NewUnaggregatedOptions()) 233 require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{ 234 Type: encoding.CounterWithMetadatasType, 235 CounterWithMetadatas: testCounterWithMetadatas, 236 })) 237 require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{ 238 Type: encoding.BatchTimerWithMetadatasType, 239 BatchTimerWithMetadatas: testBatchTimerWithMetadatas, 240 })) 241 require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{ 242 Type: encoding.GaugeWithMetadatasType, 243 GaugeWithMetadatas: testGaugeWithMetadatas, 244 })) 245 require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{ 246 Type: encoding.TimedMetricWithMetadataType, 247 TimedMetricWithMetadata: testTimedMetricWithMetadata, 248 })) 249 require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{ 250 Type: encoding.PassthroughMetricWithMetadataType, 251 PassthroughMetricWithMetadata: testPassthroughMetricWithMetadata, 252 })) 253 require.NoError(t, encoder.EncodeMessage(encoding.UnaggregatedMessageUnion{ 254 Type: encoding.ForwardedMetricWithMetadataType, 255 ForwardedMetricWithMetadata: testForwardedMetricWithMetadata, 256 })) 257 258 _, err = conn.Write(encoder.Relinquish().Bytes()) 259 require.NoError(t, err) 260 }() 261 } 262 263 // Wait for all metrics to be processed. 264 wgClient.Wait() 265 for agg.NumMetricsAdded() < expectedTotalMetrics { 266 time.Sleep(50 * time.Millisecond) 267 } 268 269 // Close the server. 270 s.Close() 271 272 // Assert the snapshot match expectations. 273 snapshot := agg.Snapshot() 274 require.True(t, cmp.Equal(expectedResult, snapshot, testCmpOpts...), expectedResult, snapshot) 275 } 276 277 func TestHandle_Errors(t *testing.T) { 278 cases := []struct { 279 name string 280 msg encoding.UnaggregatedMessageUnion 281 logMsg string 282 }{ 283 { 284 name: "timed with staged metadata", 285 msg: encoding.UnaggregatedMessageUnion{ 286 Type: encoding.TimedMetricWithMetadatasType, 287 TimedMetricWithMetadatas: testTimedMetricWithMetadatas, 288 }, 289 logMsg: "error adding timed metric", 290 }, 291 { 292 name: "timed with metadata", 293 msg: encoding.UnaggregatedMessageUnion{ 294 Type: encoding.TimedMetricWithMetadataType, 295 TimedMetricWithMetadata: testTimedMetricWithMetadata, 296 }, 297 logMsg: "error adding timed metric", 298 }, 299 { 300 name: "gauge untimed", 301 msg: encoding.UnaggregatedMessageUnion{ 302 Type: encoding.GaugeWithMetadatasType, 303 GaugeWithMetadatas: testGaugeWithMetadatas, 304 }, 305 logMsg: "error adding untimed metric", 306 }, 307 { 308 name: "counter untimed", 309 msg: encoding.UnaggregatedMessageUnion{ 310 Type: encoding.CounterWithMetadatasType, 311 CounterWithMetadatas: testCounterWithMetadatas, 312 }, 313 logMsg: "error adding untimed metric", 314 }, 315 { 316 name: "timer untimed", 317 msg: encoding.UnaggregatedMessageUnion{ 318 Type: encoding.BatchTimerWithMetadatasType, 319 BatchTimerWithMetadatas: testBatchTimerWithMetadatas, 320 }, 321 logMsg: "error adding untimed metric", 322 }, 323 { 324 name: "forward", 325 msg: encoding.UnaggregatedMessageUnion{ 326 Type: encoding.ForwardedMetricWithMetadataType, 327 ForwardedMetricWithMetadata: testForwardedMetricWithMetadata, 328 }, 329 logMsg: "error adding forwarded metric", 330 }, 331 { 332 name: "passthrough", 333 msg: encoding.UnaggregatedMessageUnion{ 334 Type: encoding.PassthroughMetricWithMetadataType, 335 PassthroughMetricWithMetadata: testPassthroughMetricWithMetadata, 336 }, 337 logMsg: "error adding passthrough metric", 338 }, 339 } 340 341 ctrl := xtest.NewController(t) 342 defer ctrl.Finish() 343 agg := aggregator.NewMockAggregator(ctrl) 344 345 aggErr := errors.New("boom") 346 agg.EXPECT().AddTimedWithStagedMetadatas(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes() 347 agg.EXPECT().AddUntimed(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes() 348 agg.EXPECT().AddForwarded(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes() 349 agg.EXPECT().AddTimed(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes() 350 agg.EXPECT().AddPassthrough(gomock.Any(), gomock.Any()).Return(aggErr).AnyTimes() 351 352 for _, tc := range cases { 353 tc := tc 354 t.Run(tc.name, func(t *testing.T) { 355 core, recorded := observer.New(zapcore.InfoLevel) 356 listener, err := net.Listen("tcp", testListenAddress) 357 require.NoError(t, err) 358 359 h := NewHandler(agg, testServerOptions().SetInstrumentOptions(instrument.NewOptions(). 360 SetLogger(zap.New(core)))) 361 s := xserver.NewServer(testListenAddress, h, xserver.NewOptions()) 362 // Start server. 363 require.NoError(t, s.Serve(listener)) 364 365 conn, err := net.Dial("tcp", listener.Addr().String()) 366 encoder := protobuf.NewUnaggregatedEncoder(protobuf.NewUnaggregatedOptions()) 367 require.NoError(t, err) 368 369 require.NoError(t, encoder.EncodeMessage(tc.msg)) 370 371 _, err = conn.Write(encoder.Relinquish().Bytes()) 372 require.NoError(t, err) 373 res := clock.WaitUntil(func() bool { 374 return len(recorded.FilterMessage(tc.logMsg).All()) == 1 375 }, time.Second*5) 376 if !res { 377 require.Fail(t, 378 "failed to find expected log message", 379 "expected=%v logs=%v", tc.logMsg, recorded.All()) 380 } 381 }) 382 } 383 } 384 385 func testServerOptions() Options { 386 opts := NewOptions() 387 instrumentOpts := opts.InstrumentOptions().SetReportInterval(time.Second) 388 serverOpts := xserver.NewOptions().SetRetryOptions(retry.NewOptions().SetMaxRetries(2)) 389 return opts.SetInstrumentOptions(instrumentOpts).SetServerOptions(serverOpts) 390 }