github.com/Axway/agent-sdk@v1.1.101/pkg/traceability/sampling/sampling_test.go (about) 1 package sampling 2 3 import ( 4 "fmt" 5 "math" 6 "os" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/Axway/agent-sdk/pkg/agent" 12 "github.com/Axway/agent-sdk/pkg/config" 13 "github.com/elastic/beats/v7/libbeat/beat" 14 "github.com/elastic/beats/v7/libbeat/common" 15 "github.com/elastic/beats/v7/libbeat/publisher" 16 "github.com/stretchr/testify/assert" 17 ) 18 19 func TestSamplingConfig(t *testing.T) { 20 testCases := []struct { 21 name string 22 errExpected bool 23 apicDeployment string 24 qaOverride string 25 config Sampling 26 expectedConfig Sampling 27 }{ 28 { 29 name: "Default Config", 30 errExpected: false, 31 config: DefaultConfig(), 32 expectedConfig: Sampling{ 33 Percentage: 0, 34 }, 35 }, 36 { 37 name: "Good Custom Config", 38 errExpected: false, 39 config: Sampling{ 40 Percentage: 5, 41 }, 42 expectedConfig: Sampling{ 43 Percentage: 5, 44 }, 45 }, 46 { 47 name: "Bad Config Too Low", 48 errExpected: true, 49 config: Sampling{ 50 Percentage: -5, 51 }, 52 }, 53 { 54 name: "Bad Config Too High", 55 errExpected: true, 56 config: Sampling{ 57 Percentage: 150, 58 }, 59 }, 60 { 61 name: "QA Override for production", 62 errExpected: true, 63 qaOverride: "100", 64 apicDeployment: "prod-eu", 65 config: Sampling{ 66 Percentage: 150, 67 }, 68 }, 69 { 70 name: "QA Override for non-production", 71 errExpected: false, 72 qaOverride: "100", 73 apicDeployment: "preprod", 74 config: Sampling{ 75 Percentage: 150, 76 }, 77 expectedConfig: Sampling{ 78 Percentage: 100, 79 }, 80 }, 81 { 82 name: "Invalid QA Override for non-production", 83 errExpected: true, 84 qaOverride: "150", 85 apicDeployment: "preprod", 86 config: Sampling{ 87 Percentage: 150, 88 }, 89 expectedConfig: Sampling{ 90 Percentage: 1, 91 }, 92 }, 93 { 94 name: "Good Config, Report All Errors", 95 errExpected: false, 96 config: Sampling{ 97 Percentage: 10, 98 OnlyErrors: true, 99 }, 100 expectedConfig: Sampling{ 101 Percentage: 10, 102 }, 103 }, 104 } 105 106 for _, test := range testCases { 107 t.Run(test.name, func(t *testing.T) { 108 cfg := config.NewTestCentralConfig(config.TraceabilityAgent) 109 if test.apicDeployment != "" { 110 centralCfg := cfg.(*config.CentralConfiguration) 111 centralCfg.APICDeployment = test.apicDeployment 112 } 113 os.Setenv(qaSamplingPercentageEnvVar, test.qaOverride) 114 agent.Initialize(cfg) 115 116 err := SetupSampling(test.config, false) 117 if test.errExpected { 118 assert.NotNil(t, err, "Expected the config to fail") 119 } else { 120 assert.Nil(t, err, "Expected the config to pass") 121 assert.Equal(t, test.expectedConfig.Percentage, agentSamples.config.Percentage) 122 percentage, _ := GetGlobalSamplingPercentage() 123 assert.Equal(t, test.expectedConfig.Percentage, percentage) 124 } 125 }) 126 } 127 } 128 129 func TestShouldSample(t *testing.T) { 130 type transactionCount struct { 131 successCount int 132 errorCount int 133 } 134 testCases := []struct { 135 name string 136 apiTransactions map[string]transactionCount 137 expectedSampled int 138 config Sampling 139 subIDs map[string]string 140 }{ 141 { 142 name: "Maximum Transactions", 143 apiTransactions: map[string]transactionCount{ 144 "id1": {successCount: 1000}, 145 "id2": {successCount: 1000}, 146 }, 147 expectedSampled: 200, 148 config: Sampling{ 149 Percentage: 10, 150 PerAPI: false, 151 }, 152 }, 153 { 154 name: "Default config transactions", 155 apiTransactions: map[string]transactionCount{ 156 "id1": {successCount: 1000}, 157 "id2": {successCount: 1000}, 158 }, 159 expectedSampled: 0, 160 config: DefaultConfig(), 161 }, 162 { 163 name: "5% of Transactions when per api is disabled", 164 apiTransactions: map[string]transactionCount{ 165 "id1": {successCount: 50}, 166 "id2": {successCount: 50}, 167 "id3": {successCount: 50}, 168 "id4": {successCount: 50}, 169 }, // Total = 200 170 expectedSampled: 10, 171 config: Sampling{ 172 Percentage: 5, 173 PerAPI: false, 174 }, 175 }, 176 { 177 name: "10% of Transactions when per api is disabled", 178 apiTransactions: map[string]transactionCount{ 179 "id1": {successCount: 1000}, 180 "id2": {successCount: 1000}, 181 }, 182 expectedSampled: 200, 183 config: Sampling{ 184 Percentage: 10, 185 PerAPI: false, 186 }, 187 }, 188 { 189 name: "0.55% of Transactions when per api is disabled", 190 apiTransactions: map[string]transactionCount{ 191 "id1": {successCount: 10000}, 192 }, 193 expectedSampled: 55, 194 config: Sampling{ 195 Percentage: 0.55, 196 PerAPI: false, 197 }, 198 }, 199 { 200 name: "9.99% of Transactions when per api is disabled", 201 apiTransactions: map[string]transactionCount{ 202 "id1": {successCount: 10000}, 203 "id2": {successCount: 10000}, 204 "id3": {successCount: 10000}, 205 }, 206 expectedSampled: 30000 * 999 / 10000, 207 config: Sampling{ 208 Percentage: 9.99, 209 PerAPI: false, 210 }, 211 }, 212 { 213 name: "0.0006% of Transactions when per api is disabled", 214 apiTransactions: map[string]transactionCount{ 215 "id1": {successCount: 2000000}, 216 }, 217 expectedSampled: 12, 218 config: Sampling{ 219 Percentage: 0.0006, 220 PerAPI: false, 221 }, 222 }, 223 { 224 name: "1% of Transactions when per api is disabled", 225 apiTransactions: map[string]transactionCount{ 226 "id1": {successCount: 1000}, 227 "id2": {successCount: 1000}, 228 }, 229 expectedSampled: 20, 230 config: Sampling{ 231 Percentage: 1, 232 PerAPI: false, 233 }, 234 }, 235 { 236 name: "0% of Transactions when per api is disabled", 237 apiTransactions: map[string]transactionCount{ 238 "id1": {successCount: 1000}, 239 "id2": {successCount: 1000}, 240 }, 241 expectedSampled: 0, 242 config: Sampling{ 243 Percentage: 0, 244 PerAPI: false, 245 }, 246 }, 247 { 248 name: "5% per API of Transactions when per api is enabled", 249 apiTransactions: map[string]transactionCount{ 250 "id1": {successCount: 50}, // expect 50 251 "id2": {successCount: 50}, // expect 50 252 "id3": {successCount: 50}, // expect 50 253 "id4": {successCount: 50}, // expect 50 254 }, 255 expectedSampled: 20, 256 config: Sampling{ 257 Percentage: 5, 258 PerAPI: true, 259 }, 260 }, 261 { 262 name: "5% of subscription transactions when per api and per sub are enabled", 263 apiTransactions: map[string]transactionCount{ 264 "id1": {successCount: 50}, // expect 50 265 "id2": {successCount: 50}, // expect 50 266 "id3": {successCount: 50}, // expect 50 267 "id4": {successCount: 50}, // expect 50 268 }, 269 subIDs: map[string]string{ 270 "id1": "sub1", 271 "id2": "sub2", 272 "id3": "sub3", 273 "id4": "sub4", 274 }, 275 expectedSampled: 20, 276 config: Sampling{ 277 Percentage: 5, 278 PerAPI: true, 279 PerSub: true, 280 }, 281 }, 282 { 283 name: "5% of subscription transactions when per api is disabled and per sub is enabled", 284 apiTransactions: map[string]transactionCount{ 285 "id1": {successCount: 50}, // expect 50 286 "id2": {successCount: 50}, // expect 50 287 "id3": {successCount: 50}, // expect 50 288 "id4": {successCount: 50}, // expect 50 289 }, 290 subIDs: map[string]string{ 291 "id1": "sub1", 292 "id2": "sub2", 293 "id3": "sub3", 294 "id4": "sub4", 295 }, 296 expectedSampled: 20, 297 config: Sampling{ 298 Percentage: 5, 299 PerAPI: false, 300 PerSub: true, 301 }, 302 }, 303 { 304 name: "5% of per API transactions when per api and per sub are enabled, but no subID is found", 305 apiTransactions: map[string]transactionCount{ 306 "id1": {successCount: 50}, // expect 50 307 "id2": {successCount: 50}, // expect 50 308 "id3": {successCount: 50}, // expect 50 309 "id4": {successCount: 50}, // expect 50 310 }, 311 subIDs: map[string]string{}, 312 expectedSampled: 20, 313 config: Sampling{ 314 Percentage: 5, 315 PerAPI: true, 316 PerSub: true, 317 }, 318 }, 319 { 320 name: "only errors to be sampled", 321 apiTransactions: map[string]transactionCount{ 322 "id1": {successCount: 500, errorCount: 500}, 323 "id2": {successCount: 500, errorCount: 500}, 324 }, 325 expectedSampled: 100, 326 config: Sampling{ 327 Percentage: 10, 328 OnlyErrors: true, 329 }, 330 }, 331 { 332 name: "errors and success to be sampled", 333 apiTransactions: map[string]transactionCount{ 334 "id1": {successCount: 500, errorCount: 500}, 335 "id2": {successCount: 500, errorCount: 500}, 336 }, 337 expectedSampled: 200, 338 config: Sampling{ 339 Percentage: 10, 340 }, 341 }, 342 } 343 344 for _, test := range testCases { 345 t.Run(test.name, func(t *testing.T) { 346 waitGroup := sync.WaitGroup{} 347 sampleCounterLock := sync.Mutex{} 348 centralCfg := config.NewTestCentralConfig(config.TraceabilityAgent) 349 agent.Initialize(centralCfg) 350 351 err := SetupSampling(test.config, false) 352 assert.Nil(t, err) 353 354 sampled := 0 355 356 for apiID, numCalls := range test.apiTransactions { 357 waitGroup.Add(1) 358 359 var subID string 360 if test.subIDs != nil { 361 subID = test.subIDs[apiID] 362 } 363 364 go func(wg *sync.WaitGroup, id, subID string, calls transactionCount) { 365 defer wg.Done() 366 sampleFunc := func(id, subID string, status string) { 367 testDetails := TransactionDetails{ 368 Status: status, 369 APIID: id, 370 SubID: subID, 371 } 372 sample, err := ShouldSampleTransaction(testDetails) 373 if sample { 374 sampleCounterLock.Lock() 375 sampled++ 376 sampleCounterLock.Unlock() 377 } 378 assert.Nil(t, err) 379 } 380 for i := 0; i < calls.successCount; i++ { 381 sampleFunc(id, subID, "Success") 382 } 383 for i := 0; i < calls.errorCount; i++ { 384 sampleFunc(id, subID, "Failure") 385 } 386 }(&waitGroup, apiID, subID, numCalls) 387 } 388 389 waitGroup.Wait() 390 assert.Nil(t, err) 391 assert.Equal(t, test.expectedSampled, sampled) 392 }) 393 } 394 } 395 396 func createEvents(numberOfEvents int, samplePercent float64) []publisher.Event { 397 events := []publisher.Event{} 398 399 count := 0 400 sampled := 0 401 countMax := 100 * int(math.Pow(10, float64(numberOfDecimals(samplePercent)))) 402 limit := int(float64(countMax) * samplePercent / 100) 403 for i := 0; i < numberOfEvents; i++ { 404 var event publisher.Event 405 if count < limit { 406 sampled++ 407 event = createEvent(true) 408 } else { 409 event = createEvent(false) 410 } 411 events = append(events, event) 412 count++ 413 if count == countMax { 414 count = 0 415 } 416 } 417 418 return events 419 } 420 421 func createEvent(sampled bool) publisher.Event { 422 fieldsData := common.MapStr{ 423 "message": "message value", 424 } 425 meta := common.MapStr{} 426 if sampled { 427 meta.Put(SampleKey, true) 428 } 429 return publisher.Event{ 430 Content: beat.Event{ 431 Timestamp: time.Now(), 432 Meta: meta, 433 Private: nil, 434 Fields: fieldsData, 435 }, 436 } 437 } 438 439 func TestFilterEvents(t *testing.T) { 440 testCases := []struct { 441 name string 442 testEvents int 443 eventsExpected int 444 config Sampling 445 }{ 446 { 447 name: "10% of Events", 448 testEvents: 2000, 449 eventsExpected: 200, 450 config: Sampling{ 451 Percentage: 10, 452 }, 453 }, 454 { 455 name: "1% of Events", 456 testEvents: 2000, 457 eventsExpected: 20, 458 config: Sampling{ 459 Percentage: 1, 460 }, 461 }, 462 { 463 name: "0.1% of Events", 464 testEvents: 2000, 465 eventsExpected: 2, 466 config: Sampling{ 467 Percentage: 0.1, 468 }, 469 }, 470 { 471 name: "0% of Events", 472 testEvents: 2000, 473 eventsExpected: 0, 474 config: Sampling{ 475 Percentage: 0, 476 }, 477 }, 478 } 479 480 for _, test := range testCases { 481 t.Run(test.name, func(t *testing.T) { 482 centralCfg := config.NewTestCentralConfig(config.TraceabilityAgent) 483 agent.Initialize(centralCfg) 484 485 err := SetupSampling(test.config, false) 486 assert.Nil(t, err) 487 488 eventsInTest := createEvents(test.testEvents, test.config.Percentage) 489 filterEvents, err := FilterEvents(eventsInTest) 490 491 assert.Nil(t, err) 492 assert.Len(t, filterEvents, test.eventsExpected) 493 }) 494 } 495 } 496 497 func Test_SamplingPercentageDecimals(t *testing.T) { 498 testCases := []struct { 499 value float64 500 expectedNbOfDecimals int 501 }{ 502 { 503 value: 10.9654, 504 expectedNbOfDecimals: 4, 505 }, 506 { 507 value: 2.34567890, 508 expectedNbOfDecimals: 7, 509 }, 510 { 511 value: 0, 512 expectedNbOfDecimals: 0, 513 }, 514 { 515 value: 100, 516 expectedNbOfDecimals: 0, 517 }, 518 } 519 for _, test := range testCases { 520 t.Run(fmt.Sprintf("%f", test.value), func(t *testing.T) { 521 assert.Equal(t, numberOfDecimals(test.value), test.expectedNbOfDecimals) 522 }) 523 } 524 }