Skip to content

Commit

Permalink
optimize parsing config values and layer bucketing
Browse files Browse the repository at this point in the history
  • Loading branch information
kenny-statsig committed Mar 25, 2024
1 parent 5af41ee commit d9929e3
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 40 deletions.
8 changes: 2 additions & 6 deletions client_initialize_response.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package statsig

import (
"encoding/json"
"fmt"
"strings"
)
Expand Down Expand Up @@ -116,12 +115,9 @@ func getClientInitializeResponse(
*result.ExplicitParameters = spec.ExplicitParameters
layerName, _ := store.getExperimentLayer(spec.Name)
layer, exists := store.getLayerConfig(layerName)
var defaultValue map[string]interface{}
defaultValue := make(map[string]interface{})
if exists {
err := json.Unmarshal(layer.DefaultValue, &defaultValue)
if err != nil {
defaultValue = make(map[string]interface{})
}
mergeMaps(defaultValue, layer.DefaultValueJSON)
mergeMaps(defaultValue, result.Value)
result.Value = defaultValue
}
Expand Down
19 changes: 7 additions & 12 deletions evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package statsig
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"reflect"
Expand Down Expand Up @@ -214,10 +213,7 @@ func (e *evaluator) eval(user User, spec configSpec, depth int) *evalResult {
evalDetails := e.createEvaluationDetails(reason)
isDynamicConfig := strings.ToLower(spec.Type) == dynamicConfigType
if isDynamicConfig {
err := json.Unmarshal(spec.DefaultValue, &configValue)
if err != nil {
configValue = make(map[string]interface{})
}
configValue = spec.DefaultValueJSON
}

var exposures = make([]map[string]string, 0)
Expand All @@ -239,12 +235,7 @@ func (e *evaluator) eval(user User, spec configSpec, depth int) *evalResult {
pass := evalPassPercent(user, rule, spec)
if isDynamicConfig {
if pass {
var ruleConfigValue map[string]interface{}
err := json.Unmarshal(rule.ReturnValue, &ruleConfigValue)
if err != nil {
ruleConfigValue = make(map[string]interface{})
}
configValue = ruleConfigValue
configValue = rule.ReturnValueJSON
}
result := &evalResult{
Pass: pass,
Expand Down Expand Up @@ -426,7 +417,11 @@ func (e *evaluator) evalCondition(user User, cond configCondition, depth int) *e
// array operations
case "any":
pass = arrayAny(cond.TargetValue, value, func(x, y interface{}) bool {
return compareStrings(x, y, true, func(s1, s2 string) bool { return s1 == s2 })
if condType == "user_bucket" {
return compareNumbers(x, y, func(s1, s2 float64) bool { return s1 == s2 })
} else {
return compareStrings(x, y, true, func(s1, s2 string) bool { return s1 == s2 })
}
})
case "none":
pass = !arrayAny(cond.TargetValue, value, func(x, y interface{}) bool {
Expand Down
67 changes: 45 additions & 22 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import (
)

type configSpec struct {
Name string `json:"name"`
Type string `json:"type"`
Salt string `json:"salt"`
Enabled bool `json:"enabled"`
Rules []configRule `json:"rules"`
DefaultValue json.RawMessage `json:"defaultValue"`
IDType string `json:"idType"`
ExplicitParameters []string `json:"explicitParameters"`
Entity string `json:"entity"`
IsActive *bool `json:"isActive,omitempty"`
HasSharedParams *bool `json:"hasSharedParams,omitempty"`
TargetAppIDs []string `json:"targetAppIDs,omitempty"`
Name string `json:"name"`
Type string `json:"type"`
Salt string `json:"salt"`
Enabled bool `json:"enabled"`
Rules []configRule `json:"rules"`
DefaultValue json.RawMessage `json:"defaultValue"`
DefaultValueJSON map[string]interface{} `json:"-"`
DefaultValueBool *bool `json:"-"`
IDType string `json:"idType"`
ExplicitParameters []string `json:"explicitParameters"`
Entity string `json:"entity"`
IsActive *bool `json:"isActive,omitempty"`
HasSharedParams *bool `json:"hasSharedParams,omitempty"`
TargetAppIDs []string `json:"targetAppIDs,omitempty"`
}

func (c configSpec) hasTargetAppID(appId string) bool {
Expand All @@ -39,16 +41,18 @@ func (c configSpec) hasTargetAppID(appId string) bool {
}

type configRule struct {
Name string `json:"name"`
ID string `json:"id"`
GroupName string `json:"groupName,omitempty"`
Salt string `json:"salt"`
PassPercentage float64 `json:"passPercentage"`
Conditions []configCondition `json:"conditions"`
ReturnValue json.RawMessage `json:"returnValue"`
IDType string `json:"idType"`
ConfigDelegate string `json:"configDelegate"`
IsExperimentGroup *bool `json:"isExperimentGroup,omitempty"`
Name string `json:"name"`
ID string `json:"id"`
GroupName string `json:"groupName,omitempty"`
Salt string `json:"salt"`
PassPercentage float64 `json:"passPercentage"`
Conditions []configCondition `json:"conditions"`
ReturnValue json.RawMessage `json:"returnValue"`
ReturnValueJSON map[string]interface{} `json:"-"`
ReturnValueBool *bool `json:"-"`
IDType string `json:"idType"`
ConfigDelegate string `json:"configDelegate"`
IsExperimentGroup *bool `json:"isExperimentGroup,omitempty"`
}

type configCondition struct {
Expand Down Expand Up @@ -331,6 +335,23 @@ func (s *store) processConfigSpecs(configSpecs interface{}, diagnosticsMarker *m
return parsed, updated
}

func (s *store) parseJSONValuesFromSpec(spec *configSpec) {
var defaultValue map[string]interface{}
err := json.Unmarshal(spec.DefaultValue, &defaultValue)
if err != nil {
defaultValue = make(map[string]interface{})
}
spec.DefaultValueJSON = defaultValue
for i, rule := range spec.Rules {
var ruleValue map[string]interface{}
err := json.Unmarshal(rule.ReturnValue, &ruleValue)
if err != nil {
ruleValue = make(map[string]interface{})
}
spec.Rules[i].ReturnValueJSON = ruleValue
}
}

// Returns a tuple of booleans indicating 1. parsed, 2. updated
func (s *store) setConfigSpecs(specs downloadConfigSpecResponse) (bool, bool) {
s.diagnostics.initDiagnostics.updateSamplingRates(specs.DiagnosticsSampleRates)
Expand All @@ -349,11 +370,13 @@ func (s *store) setConfigSpecs(specs downloadConfigSpecResponse) (bool, bool) {

newConfigs := make(map[string]configSpec)
for _, config := range specs.DynamicConfigs {
s.parseJSONValuesFromSpec(&config)
newConfigs[config.Name] = config
}

newLayers := make(map[string]configSpec)
for _, layer := range specs.LayerConfigs {
s.parseJSONValuesFromSpec(&layer)
newLayers[layer.Name] = layer
}

Expand Down

0 comments on commit d9929e3

Please sign in to comment.