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  }