experiments/client.go (181 lines of code) (raw):

package experiments import ( "crypto/md5" "errors" "fmt" "hash/fnv" "strconv" "time" "github.com/aliyun/aliyun-pairec-config-go-sdk/v2/api" "github.com/aliyun/aliyun-pairec-config-go-sdk/v2/common" "github.com/aliyun/aliyun-pairec-config-go-sdk/v2/model" ) type ClientOption func(c *ExperimentClient) func WithLogger(l Logger) ClientOption { return func(e *ExperimentClient) { e.Logger = l } } func WithErrorLogger(l Logger) ClientOption { return func(e *ExperimentClient) { e.ErrorLogger = l } } func WithDomain(domian string) ClientOption { return func(e *ExperimentClient) { e.APIClient.SetDomain(domian) } } type ExperimentClient struct { // Environment control the sdk shoud get which environment data . // Valid value is daily, prepub,product Environment string // APIClient invoke api to connect to pairecservice open api APIClient *api.APIClient // SceneMap map[string]*model.Scene // sceneParamData map of parameters of scene name sceneParamData map[string]model.SceneParams // sceneFlowCtrlPlanData map of flow ctrl plan of scene name productSceneTrafficControlTaskData map[string][]model.TrafficControlTask // prepubSceneFlowCtrlPlanData map of flow ctrl plan of scene name (prepub env) prepubSceneTrafficControlTaskData map[string][]model.TrafficControlTask // Logger specifies a logger used to report internal changes within the writer Logger Logger // ErrorLogger is the logger to report errors ErrorLogger Logger } func NewExperimentClient(instanceId, regionId, accessKeyId, accessKeySecret, environment string, opts ...ClientOption) (*ExperimentClient, error) { client := ExperimentClient{ Environment: environment, SceneMap: make(map[string]*model.Scene, 0), } var err error client.APIClient, err = api.NewAPIClient(instanceId, regionId, accessKeyId, accessKeySecret) if err != nil { return nil, err } for _, opt := range opts { opt(&client) } if err := client.Validate(); err != nil { return nil, err } client.LoadExperimentData() client.LoadSceneParamsData() go client.loopLoadExperimentData() go client.loopLoadSceneParamsData() client.LoadSceneTrafficControlTasksData() go client.loopLoadSceneFlowCtrlPlansData() return &client, nil } // Validate check the ExperimentClient value func (e *ExperimentClient) Validate() error { if e.Environment == "" { return errors.New("environment is empty") } if err := common.CheckEnvironmentValue(e.Environment); err != nil { return err } return nil } // MatchExperiment specifies to find match experiment by the ExperimentContext // If not find the scene return error or return ExperimentResult func (e *ExperimentClient) MatchExperiment(sceneName string, experimentContext *model.ExperimentContext) *model.ExperimentResult { sceneData := e.SceneMap scene, exist := sceneData[sceneName] if !exist { e.logError(fmt.Errorf("scene:%s, not found the scene info", sceneName)) return model.NewExperimentResult(sceneName, experimentContext) } experimentResult := model.NewExperimentResult(sceneName, experimentContext) var defaultExperimentRoom *model.ExperimentRoom var matchExperimentRoom *model.ExperimentRoom // first find base experiment room for _, experimentRoom := range scene.ExperimentRooms { if experimentRoom.Type == common.ExpRoom_Type_Base { defaultExperimentRoom = experimentRoom break } } // if experiment room has debug users then matchExperimentRoom for _, experimentRoom := range scene.ExperimentRooms { if experimentRoom.MatchDebugUsers(experimentContext) { matchExperimentRoom = experimentRoom break } } // if matchExperimentRoom is null, so no debug users found // then find no base experiment room is match if matchExperimentRoom == nil { for _, experimentRoom := range scene.ExperimentRooms { if experimentRoom.Type != common.ExpRoom_Type_Base { if experimentRoom.Match(experimentContext) { matchExperimentRoom = experimentRoom break } } } } if matchExperimentRoom == nil { matchExperimentRoom = defaultExperimentRoom } if matchExperimentRoom != nil { experimentResult.ExperimentRoom = matchExperimentRoom experimentResult.Layers = matchExperimentRoom.Layers for _, layer := range matchExperimentRoom.Layers { experimentGroup := layer.FindMatchExperimentGroup(experimentContext) if experimentGroup != nil { experimentResult.AddMatchExperimentGroup(layer.LayerName, experimentGroup) var defaultExperiment *model.Experiment var matchExperiment *model.Experiment for _, experiment := range experimentGroup.Experiments { if experiment.Type == common.Experiment_Type_Default { defaultExperiment = experiment } } // find match experiment if matchExperiment == nil { // first match debug users for _, experiment := range experimentGroup.Experiments { if experiment.Type != common.Experiment_Type_Default && experiment.MatchDebugUsers(experimentContext) { matchExperiment = experiment e.logInfo("match experiment debug users uid:%s", experimentContext.Uid) break } } if matchExperiment == nil { var hashKey string if experimentGroup.DistributionType == common.ExpGroup_Distribution_Type_TimeDuration { currTime := time.Now() duration := (currTime.Unix() % 86400) / int64((experimentGroup.DistributionTimeDuration * 60)) hashKey = fmt.Sprintf("%s_%d_EXPROOM%d_LAYER%d_EXPGROUP%d", currTime.Format("20060102"), duration, experimentGroup.ExpRoomId, experimentGroup.LayerId, experimentGroup.ExpGroupId) } else { hashKey = fmt.Sprintf("%s_EXPROOM%d_LAYER%d_EXPGROUP%d", experimentContext.Uid, experimentGroup.ExpRoomId, experimentGroup.LayerId, experimentGroup.ExpGroupId) } hashValue := e.hashValue(hashKey) //e.logInfo("match experiment hash key:%s, value:%d", hashKey, hashValue) hashValueStr := strconv.FormatUint(hashValue, 10) experimentContext.SetExperimentHashString(hashValueStr) for _, experiment := range experimentGroup.Experiments { if experiment.Type != common.Experiment_Type_Default && experiment.Match(experimentContext) { matchExperiment = experiment break } } } } if matchExperiment == nil { // if defaultExperiment not found ,set baseExperiment is defaultExperiment if defaultExperiment == nil { for _, experiment := range experimentGroup.Experiments { if experiment.Type == common.Experiment_Type_Base { defaultExperiment = experiment.Clone() defaultExperiment.Type = common.Experiment_Type_Default } } } matchExperiment = defaultExperiment } if matchExperiment != nil { experimentResult.AddMatchExperiment(layer.LayerName, matchExperiment) } } } } experimentResult.Init() return experimentResult } func (e *ExperimentClient) hashValue(hashKey string) uint64 { md5 := md5.Sum([]byte(hashKey)) hash := fnv.New64() hash.Write(md5[:]) return hash.Sum64() } func (e *ExperimentClient) logInfo(msg string, args ...interface{}) { if e.Logger != nil { e.Logger.Printf(msg, args...) } }