github.com/Axway/agent-sdk@v1.1.101/pkg/traceability/sampling/globalsampling.go (about) 1 package sampling 2 3 import ( 4 "math" 5 "os" 6 "strconv" 7 "strings" 8 "sync" 9 10 "github.com/Axway/agent-sdk/pkg/agent" 11 "github.com/Axway/agent-sdk/pkg/util/log" 12 "github.com/elastic/beats/v7/libbeat/publisher" 13 "github.com/shopspring/decimal" 14 ) 15 16 // Global Agent samples 17 var agentSamples *sample 18 19 const ( 20 qaSamplingPercentageEnvVar = "QA_TRACEABILITY_SAMPLING_PERCENTAGE" 21 ) 22 23 // Sampling - configures the sampling of events the agent sends to Amplify 24 type Sampling struct { 25 Percentage float64 `config:"percentage"` 26 PerAPI bool `config:"per_api"` 27 PerSub bool `config:"per_subscription"` 28 OnlyErrors bool `config:"onlyErrors" yaml:"onlyErrors"` 29 countMax int 30 shouldSampleMax int 31 } 32 33 // DefaultConfig - returns a default sampling config where all transactions are sent 34 func DefaultConfig() Sampling { 35 return Sampling{ 36 Percentage: defaultSamplingRate, 37 PerAPI: true, 38 PerSub: true, 39 OnlyErrors: false, 40 countMax: countMax, 41 shouldSampleMax: defaultSamplingRate, 42 } 43 } 44 45 // GetGlobalSamplingPercentage - 46 func GetGlobalSamplingPercentage() (float64, error) { 47 return agentSamples.config.Percentage, nil 48 } 49 50 // GetGlobalSampling - 51 func GetGlobalSampling() Sampling { 52 return agentSamples.config 53 } 54 55 func getSamplingPercentageConfig(percentage float64) (float64, error) { 56 maxAllowedSampling := float64(maximumSamplingRate) 57 if !strings.HasPrefix(agent.GetCentralConfig().GetAPICDeployment(), "prod") { 58 if val := os.Getenv(qaSamplingPercentageEnvVar); val != "" { 59 if qaSamplingPercentage, err := strconv.ParseFloat(val, 64); err == nil { 60 log.Tracef("Using %s (%s) rather than the default (%d) for non-production", qaSamplingPercentageEnvVar, val, defaultSamplingRate) 61 percentage = qaSamplingPercentage 62 maxAllowedSampling = 100 63 } else { 64 log.Tracef("Could not use %s (%s) it is not a proper sampling percentage", qaSamplingPercentageEnvVar, val) 65 } 66 } 67 } 68 69 // Validate the config to make sure it is not out of bounds 70 if percentage < 0 || percentage > maxAllowedSampling { 71 return defaultSamplingRate, ErrSamplingCfg.FormatError(maximumSamplingRate, defaultSamplingRate) 72 } 73 74 return percentage, nil 75 } 76 77 // SetupSampling - set up the global sampling for use by traceability 78 func SetupSampling(cfg Sampling, offlineMode bool) error { 79 var err error 80 81 if offlineMode { 82 cfg = Sampling{ 83 Percentage: 0, 84 PerAPI: false, 85 PerSub: false, 86 OnlyErrors: false, 87 } 88 } else { 89 cfg.Percentage, err = getSamplingPercentageConfig(cfg.Percentage) 90 cfg.countMax = int(100 * math.Pow(10, float64(numberOfDecimals(cfg.Percentage)))) 91 cfg.shouldSampleMax = int(float64(cfg.countMax) * cfg.Percentage / 100) 92 } 93 94 agentSamples = &sample{ 95 config: cfg, 96 currentCounts: make(map[string]int), 97 counterLock: sync.Mutex{}, 98 } 99 100 if err != nil { 101 return err 102 } 103 return nil 104 } 105 106 // ShouldSampleTransaction - receives the transaction details and returns true to sample it false to not 107 func ShouldSampleTransaction(details TransactionDetails) (bool, error) { 108 if agentSamples == nil { 109 return false, ErrGlobalSamplingCfg 110 } 111 return agentSamples.ShouldSampleTransaction(details), nil 112 } 113 114 // FilterEvents - returns an array of events that are part of the sample 115 func FilterEvents(events []publisher.Event) ([]publisher.Event, error) { 116 if agentSamples == nil { 117 return events, ErrGlobalSamplingCfg 118 } 119 return agentSamples.FilterEvents(events), nil 120 } 121 122 func numberOfDecimals(v float64) int { 123 dec := decimal.NewFromFloat(v) 124 x := dec.Exponent() 125 // Exponent returns positive values if number is a multiple of 10 126 if x > 0 { 127 return 0 128 } 129 // and negative if it contains non-zero decimals 130 return int(x) * (-1) 131 }