Skip to content

Commit

Permalink
Cleaning up namespace implementation
Browse files Browse the repository at this point in the history
- Default experiment, if not set explicitly, is a no-op.
- Namespace.AddExperiment() now takes an Interpreter object
  instead of a map[string]interface{}
  • Loading branch information
tsujeeth committed May 27, 2015
1 parent 21dfabc commit 90cd54b
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 40 deletions.
9 changes: 6 additions & 3 deletions interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ type Interpreter struct {
parameterSalt string
}

func (interpreter *Interpreter) Run() (map[string]interface{}, bool) {
if interpreter.Evaluated {
return interpreter.Outputs, true
func (interpreter *Interpreter) Run(force ...bool) (map[string]interface{}, bool) {

if len(force) > 0 && force[0] == false {
if interpreter.Evaluated {
return interpreter.Outputs, true
}
}

defer func() (map[string]interface{}, bool) {
Expand Down
63 changes: 35 additions & 28 deletions namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ type SimpleNamespace struct {
Inputs map[string]interface{}
segmentAllocations map[uint64]string
availableSegments []int
currentExperiments map[string]interface{}
defaultExperiment map[string]interface{}
currentExperiments map[string]*Interpreter
defaultExperiment *Interpreter
selectedExperiment uint64
}

func NewSimpleNamespace(name string, numSegments int, primaryUnit string, inputs map[string]interface{}) SimpleNamespace {
Expand All @@ -27,44 +28,44 @@ func NewSimpleNamespace(name string, numSegments int, primaryUnit string, inputs
avail = append(avail, i)
}

noop := &Interpreter{
Name: name,
Salt: name,
Inputs: inputs,
Code: make(map[string]interface{}),
}

return SimpleNamespace{
Name: name,
PrimaryUnit: primaryUnit,
NumSegments: numSegments,
Inputs: inputs,
segmentAllocations: make(map[uint64]string),
availableSegments: avail,
currentExperiments: make(map[string]interface{}),
currentExperiments: make(map[string]*Interpreter),
selectedExperiment: uint64(numSegments + 1),
defaultExperiment: noop,
}
}

func (n *SimpleNamespace) Run() *Interpreter {
// Is the unit allocated to an experiment ?
interpreter := &Interpreter{
Name: n.Name,
Salt: n.Name,
Code: n.defaultExperiment,
Evaluated: false,
Inputs: n.Inputs,
Outputs: map[string]interface{}{},
Overrides: map[string]interface{}{},
}
interpreter := n.defaultExperiment

if name, ok := n.segmentAllocations[n.getSegment()]; ok {
interpreter.Name = n.Name + "-" + name
interpreter.Salt = n.Name + "." + name
interpreter.Code = n.currentExperiments[name]
interpreter = n.currentExperiments[name]
interpreter.Name = n.Name + "-" + interpreter.Name
interpreter.Salt = n.Name + "." + interpreter.Name
}

interpreter.Run()
return interpreter
}

func (n *SimpleNamespace) AddDefaultExperiment(code map[string]interface{}) {
n.defaultExperiment = code
func (n *SimpleNamespace) AddDefaultExperiment(defaultExperiment *Interpreter) {
n.defaultExperiment = defaultExperiment
}

func (n *SimpleNamespace) AddExperiment(name string, code map[string]interface{}, segments int) error {
func (n *SimpleNamespace) AddExperiment(name string, interpreter *Interpreter, segments int) error {
avail := len(n.availableSegments)
if avail < segments {
return fmt.Errorf("Not enough segments available %v to add the new experiment %v\n", avail, name)
Expand All @@ -76,7 +77,7 @@ func (n *SimpleNamespace) AddExperiment(name string, code map[string]interface{}

n.allocateExperiment(name, segments)

n.currentExperiments[name] = code
n.currentExperiments[name] = interpreter
return nil
}

Expand Down Expand Up @@ -137,6 +138,11 @@ func (n *SimpleNamespace) allocateExperiment(name string, segments int) {
}

func (n *SimpleNamespace) getSegment() uint64 {

if n.selectedExperiment != uint64(n.NumSegments+1) {
return n.selectedExperiment
}

// generate random integer min=0, max=num_segments, unit=primary_unit
// RandomInteger(min=0, max=self.num_segments, unit=itemgetter(*self.primary_unit)(self.inputs))
expt := &Interpreter{
Expand All @@ -154,17 +160,18 @@ func (n *SimpleNamespace) getSegment() uint64 {
args["max"] = n.NumSegments - 1
args["unit"] = n.Inputs[n.PrimaryUnit]
s := &randomInteger{}
return s.execute(args, expt).(uint64)
n.selectedExperiment = s.execute(args, expt).(uint64)
return n.selectedExperiment
}

func deallocateSegments(allocated []int, segmentToRemove int) []int {
for i := range allocated {
if allocated[i] == segmentToRemove {
outputs := make([]int, 0, len(allocated)-1)
outputs = append(outputs, allocated[:i]...)
outputs = append(outputs, allocated[i+1:]...)
return outputs
}
i := 0
n := len(allocated)
for i < n && allocated[i] != segmentToRemove {
i = i + 1
}
if i < n {
allocated = append(allocated[:i], allocated[i+1:]...)
}
return allocated
}
39 changes: 32 additions & 7 deletions namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,29 @@ import (
)

func TestSimpleNamespace(t *testing.T) {
js1 := readTest("test/simple_ops.json")
js2 := readTest("test/random_ops.json")
js3 := readTest("test/simple.json")

constructInterpreter := func(filename string, name, salt string, inputs map[string]interface{}) *Interpreter {
js := readTest(filename)
return &Interpreter{Name: name,
Salt: salt,
Inputs: inputs,
Overrides: make(map[string]interface{}),
Outputs: make(map[string]interface{}),
Code: js,
}
}

inputs := make(map[string]interface{})
inputs["userid"] = "test-id"

e1 := constructInterpreter("test/simple_ops.json", "simple_ops", "simple_ops_salt", inputs)
e2 := constructInterpreter("test/random_ops.json", "simple_ops", "simple_ops_salt", inputs)
e3 := constructInterpreter("test/simple.json", "simple_ops", "simple_ops_salt", inputs)

n := NewSimpleNamespace("simple_namespace", 100, "userid", inputs)
n.AddExperiment("simple ops", js1, 10)
n.AddExperiment("random ops", js2, 10)
n.AddExperiment("simple", js3, 80)
n.AddExperiment("simple ops", e1, 10)
n.AddExperiment("random ops", e2, 10)
n.AddExperiment("simple", e3, 80)

x := n.availableSegments

Expand All @@ -49,10 +61,23 @@ func TestSimpleNamespace(t *testing.T) {
}

n.RemoveExperiment("random ops")
n.AddExperiment("random ops", js2, 10)
n.AddExperiment("random ops", e2, 10)
y := n.availableSegments

if reflect.DeepEqual(x, y) == false {
t.Errorf("Removing and re-adding experiment to a namespace resulted in mismatched allocations. X: %v, Y: %v\n", x, y)
}

unitstr := generateString()
inputs["userid"] = unitstr
n = NewSimpleNamespace("simple_namespace", 100, "userid", inputs)
n.AddExperiment("simple ops", e1, 10)
n.AddExperiment("random ops", e2, 10)
n.AddExperiment("simple", e3, 80)
n.RemoveExperiment("random ops")
n.RemoveExperiment("simple ops")
n.RemoveExperiment("simple")
if len(n.availableSegments) != 100 {
t.Errorf("Expected all segments to be available. Actual %d\n", len(n.availableSegments))
}
}
2 changes: 1 addition & 1 deletion random.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (s *randomFloat) execute(args map[string]interface{}, interpreter *Interpre
type randomInteger struct{}

func (s *randomInteger) execute(args map[string]interface{}, interpreter *Interpreter) interface{} {
existOrPanic(args, []string{"unit"}, "RandomFloat")
existOrPanic(args, []string{"unit"}, "RandomInteger")
min_val, _ := toNumber(getOrElse(args, "min", 0.0))
max_val, _ := toNumber(getOrElse(args, "max", 0.0))
mod_val := uint64(max_val) - uint64(min_val) + 1
Expand Down
5 changes: 4 additions & 1 deletion random_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
"encoding/json"
"fmt"
"math"
"math/rand"
"reflect"
"testing"
"text/template"
"time"
)

func TestRandomOps(t *testing.T) {
Expand Down Expand Up @@ -254,8 +256,9 @@ func randomExperiment(t *testing.T, textTemplate string, data interface{}, runs

x := make([]interface{}, runs)
h := Histogram{hist: map[string]int{}}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < runs; i++ {
expt, _ := runExperimentWithInputs(code.Bytes(), map[string]interface{}{"i": i})
expt, _ := runExperimentWithInputs(code.Bytes(), map[string]interface{}{"i": r.Intn(math.MaxUint32)})
x[i], _ = expt.Get("x")
h.add(x[i])
}
Expand Down

0 comments on commit 90cd54b

Please sign in to comment.