many many things

This commit is contained in:
didier amyot committed 2015-06-22 08:14:55 -04:00
1 parent fb9084794c
commit af69e728a2
9 files changed
+253 -59

No files matched your search

+5 -4
View File
@@ -3,7 +3,6 @@ package doorman
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"log" "log"
"math" "math"
"math/big" "math/big"
@@ -17,8 +16,6 @@ import (
"gopkg.in/mgo.v2/bson" "gopkg.in/mgo.v2/bson"
) )
var _ = fmt.Print
var ONE *big.Rat = big.NewRat(1, 1) var ONE *big.Rat = big.NewRat(1, 1)
var rand *mathrand.Rand var rand *mathrand.Rand
@@ -43,7 +40,7 @@ type Doorman struct {
wg sync.WaitGroup // waitgroup for goroutine safety wg sync.WaitGroup // waitgroup for goroutine safety
} }
func NewDoorman(id bson.ObjectId, Probabilities []*big.Rat) *Doorman { func New(id bson.ObjectId, Probabilities []*big.Rat) *Doorman {
wab := &Doorman{} wab := &Doorman{}
wab.Id = id wab.Id = id
wab.Probabilities = Probabilities wab.Probabilities = Probabilities
@@ -143,6 +140,10 @@ func (w *Doorman) GetCaseFromData(data ...[]byte) uint {
return w.GetCase(random) return w.GetCase(random)
} }
func (w *Doorman) GetCaseFromString(data string) uint {
return w.GetCaseFromData([]byte(data))
}
func (w *Doorman) GetRandomCase() uint { func (w *Doorman) GetRandomCase() uint {
r := rand.Float64() r := rand.Float64()
return w.GetCase(new(big.Rat).SetFloat64(r)) return w.GetCase(new(big.Rat).SetFloat64(r))
+23 -16
View File
@@ -18,22 +18,16 @@ import (
var epsilon = 0.0 var epsilon = 0.0
var ZERO = big.NewRat(0, 1) var ZERO = big.NewRat(0, 1)
func assertIsAbout(t *testing.T, expected, received *big.Rat) { func assertIsEqual(t *testing.T, expected, received *big.Rat) {
if expected.Cmp(received) != 0 { if expected.Cmp(received) != 0 {
t.Error("received", received, "but expected", expected) t.Error("received", received, "but expected", expected)
} }
} }
func assertIsNotAbout(t *testing.T, expected, received *big.Rat) {
if expected.Cmp(received) != 0 {
t.Error("received", received, "but expected something different than", expected)
}
}
func getProbs(probs ...float64) []*big.Rat { func getProbs(probs ...float64) []*big.Rat {
ret := make([]*big.Rat, len(probs)) ret := make([]*big.Rat, len(probs))
for i, p := range probs { for i, p := range probs {
ret[i], _ = new(big.Rat).SetString(fmt.Sprint(p)) ret[i] = new(big.Rat).SetFloat64(p)
} }
return ret return ret
} }
@@ -72,7 +66,7 @@ func TestValidate(t *testing.T) {
} }
func TestGetCase(t *testing.T) { func TestGetCase(t *testing.T) {
w := NewDoorman(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25)) w := New(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25))
if c := w.GetCase(ZERO); c != 0 { if c := w.GetCase(ZERO); c != 0 {
t.Error("expected 0 but received", c) t.Error("expected 0 but received", c)
} }
@@ -83,7 +77,7 @@ func TestGetCase(t *testing.T) {
} }
func TestGetCaseCoroutineSafety(t *testing.T) { func TestGetCaseCoroutineSafety(t *testing.T) {
w := NewDoorman(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25)) w := New(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25))
i := 0 i := 0
w.wg.Add(1) w.wg.Add(1)
go func() { go func() {
@@ -98,11 +92,11 @@ func TestGetCaseCoroutineSafety(t *testing.T) {
func TestGenerateRandomProbabilityFromBitSlice(t *testing.T) { func TestGenerateRandomProbabilityFromBitSlice(t *testing.T) {
w := new(Doorman) w := new(Doorman)
assertIsAbout(t, big.NewRat(1, 2), w.GenerateRandomProbabilityFromInteger(1)) assertIsEqual(t, big.NewRat(1, 2), w.GenerateRandomProbabilityFromInteger(1))
// assertIsAbout(t, 0.5+0*0.25+1*0.125, w.GenerateRandomProbabilityFromInteger(5)) assertIsEqual(t, new(big.Rat).SetFloat64(0.5+0*0.25+1*0.125), w.GenerateRandomProbabilityFromInteger(5))
assertIsAbout(t, ONE, w.GenerateRandomProbabilityFromInteger(math.MaxUint64)) assertIsEqual(t, ONE, w.GenerateRandomProbabilityFromInteger(math.MaxUint64))
} }
func TestHash(t *testing.T) { func TestHash(t *testing.T) {
@@ -166,7 +160,7 @@ func randomBytes(n int) []byte {
func TestUpdateHard(t *testing.T) { func TestUpdateHard(t *testing.T) {
wab := NewDoorman(bson.ObjectIdHex(oid), getProbs()) wab := New(bson.ObjectIdHex(oid), getProbs())
createMessage := func() string { createMessage := func() string {
msg := &shared.DoormanUpdater{Timestamp: 2, Probabilities: getProbs(0.5, 0.25, 0.25), Id: oid} msg := &shared.DoormanUpdater{Timestamp: 2, Probabilities: getProbs(0.5, 0.25, 0.25), Id: oid}
if ret, err := json.Marshal(msg); err != nil { if ret, err := json.Marshal(msg); err != nil {
@@ -209,7 +203,7 @@ func IsExtremeBinomialResult(x int, n, p float64) error {
func TestGetRandomCase(t *testing.T) { func TestGetRandomCase(t *testing.T) {
p := 0.5 p := 0.5
n := 10000 n := 10000
wab := NewDoorman(bson.NewObjectId(), getProbs(p, 1-p)) wab := New(bson.NewObjectId(), getProbs(p, 1-p))
var sum = 0 var sum = 0
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
sum += int(wab.GetRandomCase()) sum += int(wab.GetRandomCase())
@@ -220,9 +214,22 @@ func TestGetRandomCase(t *testing.T) {
} }
} }
func TestGetCaseFromString(t *testing.T) {
w := New(bson.NewObjectId(), getProbs(0.25, 0.5, 0.25))
expts := map[string]uint{
"j'aime la ratoutille": 2,
}
for data, expt := range expts {
if res := w.GetCaseFromString(data); res != expt {
t.Error("incorrect results for: ", data, res)
}
}
}
func BenchmarkGetCaseFromData(b *testing.B) { func BenchmarkGetCaseFromData(b *testing.B) {
var data = randomBytes(1024 * 1024) var data = randomBytes(1024 * 1024)
w := NewDoorman(bson.NewObjectId(), getProbs(0.1, 0.4, 0.4, 0.05, 0.05)) w := New(bson.NewObjectId(), getProbs(0.1, 0.4, 0.4, 0.05, 0.05))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
w.GetCaseFromData(data) w.GetCaseFromData(data)
} }
-26
View File
@@ -1,26 +0,0 @@
package main
import (
"testing"
"github.com/didiercrunch/doorman"
"gopkg.in/mgo.v2/bson"
)
func TestDoormanDistribution(t *testing.T) {
wab = doorman.NewDoorman(bson.NewObjectId(), []float64{0.25, 0.5, 0.10, 0.05, 0.1})
n := 10000
cases := make([]float64, wab.Length())
for i := 0; i <= n; i++ {
cases[wab.GetRandomCase()] += 1
}
expt := make([]float64, wab.Length())
for i, p := range wab.Probabilities {
expt[i] = p * float64(n)
}
// Todo: use chi square test to assert the results are not absurde
//if err := AssertTwoDistributionAreProbabliNotDifferent(expt, cases, 0.001); err != nil {
// t.Error(err)
//}
}
+20 -10
View File
@@ -3,17 +3,28 @@ package main
import ( import (
"html/template" "html/template"
"log" "log"
"math/big"
"math/rand"
"net/http" "net/http"
"os" "os"
"strconv"
"github.com/didiercrunch/doorman" "github.com/didiercrunch/doorman"
"gopkg.in/mgo.v2/bson" "gopkg.in/mgo.v2/bson"
) )
var wab = doorman.NewDoorman(bson.ObjectIdHex("55048bfff6fb5e213d000001"), []float64{0.25, 0.5, 0.10, 0.05, 0.1}) func getProbs(probs ...float64) []*big.Rat {
ret := make([]*big.Rat, len(probs))
for i, p := range probs {
ret[i] = new(big.Rat).SetFloat64(p)
}
return ret
}
var wab = doorman.New(bson.ObjectIdHex("558701a2f6fb5e29a4000003"), getProbs(1, 0, 0))
func init() { func init() {
if err := wab.NanoMsgSubscriber("tcp://127.0.0.1:40899"); err != nil { if err := wab.Subscriber("http://localhost:1999"); err != nil {
panic(err) panic(err)
} }
} }
@@ -21,15 +32,11 @@ func init() {
func GetDoormanValue(r *http.Request) (string, int) { func GetDoormanValue(r *http.Request) (string, int) {
switch wab.GetCaseFromData([]byte(r.URL.String())) { switch wab.GetCaseFromData([]byte(r.URL.String())) {
case 0: case 0:
return "blue", 0 return "red", 0
case 1: case 1:
return "red", 1 return "green", 1
case 2: case 2:
return "pink", 2 return "blue", 2
case 3:
return "yellow", 3
case 4:
return "magenta", 4
} }
log.Panic("impossible value returned by doorman") log.Panic("impossible value returned by doorman")
return "", 0 return "", 0
@@ -43,8 +50,10 @@ var tmpl = `
<h1 style="text-align:center;">the doorman value is {{ .value }}</h1> <h1 style="text-align:center;">the doorman value is {{ .value }}</h1>
<p style="text-align:center;"> <p style="text-align:center;">
this doorman depend solely on the url. If you hit different url you this doorman depend solely on the url. If you hit different url you
will have different doorman results. will have different doorman results. For example, here is a random
url: <a href="{{ .nextUrl }}" style="color: black;"> {{ .nextUrl }} </a>
</p> </p>
</body> </body>
</html> </html>
` `
@@ -56,6 +65,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
} }
m := make(map[string]interface{}) m := make(map[string]interface{})
m["color"], m["value"] = GetDoormanValue(r) m["color"], m["value"] = GetDoormanValue(r)
m["nextUrl"] = "/" + strconv.Itoa(rand.Int())
if err := t.Execute(w, m); err != nil { if err := t.Execute(w, m); err != nil {
panic(err) panic(err)
} }
+54
View File
@@ -0,0 +1,54 @@
package httpsubscriber
import (
"encoding/json"
"errors"
"log"
"net/http"
"time"
"github.com/didiercrunch/doorman/shared"
"gopkg.in/mgo.v2/bson"
)
type HttpSubscriber struct {
Url string
HartBeat time.Duration
}
func (s *HttpSubscriber) GetDoormanUpdater() (*shared.DoormanUpdater, error) {
resp, err := http.Get(s.Url)
if err != nil {
return nil, err
} else if resp.StatusCode != 200 {
return nil, errors.New("bad http status when GETting update, " + resp.Status)
}
defer resp.Body.Close()
de := json.NewDecoder(resp.Body)
ret := new(shared.DoormanUpdater)
err = de.Decode(ret)
return ret, err
}
func (s *HttpSubscriber) callUpdateHandlerFunction(f shared.UpdateHandlerFunc, data []byte) error {
wu := &shared.DoormanUpdater{}
if err := json.Unmarshal(data, wu); err != nil {
return err
} else {
return f(wu)
}
}
func (s *HttpSubscriber) Subscribe(abtestId bson.ObjectId, update shared.UpdateHandlerFunc) error {
go func() {
c := time.Tick(s.HartBeat)
for _ = range c {
if du, err := s.GetDoormanUpdater(); err != nil {
log.Printf("error retrieving doorman %v \n%v\n", abtestId, err)
} else {
update(du)
}
}
}()
return nil
}
+56
View File
@@ -0,0 +1,56 @@
package httpsubscriber
import (
"encoding/json"
"math/big"
"net/http"
"net/http/httptest"
"testing"
"github.com/didiercrunch/doorman/shared"
)
func TestCallUpdateHandlerFunction(t *testing.T) {
f := func(m *shared.DoormanUpdater) error {
if m.Id != "foo" {
t.Error("bad doorman id", m.Id)
}
return nil
}
data, err := json.Marshal(&shared.DoormanUpdater{Id: "foo"})
if err != nil {
t.Error(err)
return
}
if new(HttpSubscriber).callUpdateHandlerFunction(f, data); err != nil {
t.Error(err)
}
}
func MockEndpoint(w http.ResponseWriter, r *http.Request) {
du := &shared.DoormanUpdater{
"b64",
897987,
[]*big.Rat{big.NewRat(1, 4), big.NewRat(3, 4)},
}
enc := json.NewEncoder(w)
if err := enc.Encode(du); err != nil {
panic(err)
}
}
func TestGetDoormanUpdater(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(MockEndpoint))
defer ts.Close()
s := HttpSubscriber{Url: ts.URL}
if d, err := s.GetDoormanUpdater(); err != nil {
t.Error(err)
} else if d.Id != "b64" {
t.Error()
}
}
+9 -3
View File
@@ -4,6 +4,7 @@ import (
"github.com/didiercrunch/doorman/nanomsgsubscriber" "github.com/didiercrunch/doorman/nanomsgsubscriber"
"github.com/didiercrunch/doorman/nsqsubscriber" "github.com/didiercrunch/doorman/nsqsubscriber"
"github.com/didiercrunch/doorman/shared" "github.com/didiercrunch/doorman/shared"
"github.com/didiercrunch/doorman/subscriber"
"gopkg.in/mgo.v2/bson" "gopkg.in/mgo.v2/bson"
) )
@@ -11,16 +12,21 @@ type Subscriber interface {
Subscribe(doormanId bson.ObjectId, update shared.UpdateHandlerFunc) error Subscribe(doormanId bson.ObjectId, update shared.UpdateHandlerFunc) error
} }
func (w *Doorman) Subscribe(sub Subscriber) error { func (w *Doorman) subscribe(sub Subscriber) error {
return sub.Subscribe(w.Id, w.Update) return sub.Subscribe(w.Id, w.Update)
} }
func (w *Doorman) NSQSubscriber(NSQLookupdURl string) error { func (w *Doorman) NSQSubscriber(NSQLookupdURl string) error {
sub := &nsqsubscriber.NSQSubscriber{NSQLookupdURl} sub := &nsqsubscriber.NSQSubscriber{NSQLookupdURl}
return w.Subscribe(sub) return w.subscribe(sub)
} }
func (w *Doorman) NanoMsgSubscriber(NanoMsgUrlLookupdURl string) error { func (w *Doorman) NanoMsgSubscriber(NanoMsgUrlLookupdURl string) error {
sub := &nanomsgsubscriber.NanoMsgSubscriber{NanoMsgUrlLookupdURl} sub := &nanomsgsubscriber.NanoMsgSubscriber{NanoMsgUrlLookupdURl}
return w.Subscribe(sub) return w.subscribe(sub)
}
func (w *Doorman) Subscriber(serverUrl string) error {
sub := &subscriber.Subscriber{serverUrl}
return w.subscribe(sub)
} }
+85
View File
@@ -0,0 +1,85 @@
package subscriber
import (
"encoding/json"
"errors"
"fmt"
"github.com/didiercrunch/doorman/httpsubscriber"
"github.com/didiercrunch/doorman/nanomsgsubscriber"
"github.com/didiercrunch/doorman/shared"
"gopkg.in/mgo.v2/bson"
"net/http"
"time"
)
type Subscriber struct {
URL string
}
type subscriber interface {
Subscribe(abtestId bson.ObjectId, update shared.UpdateHandlerFunc) error
}
type ServerSpecification struct {
HostName string `json:"hostname"`
Port int `json:"port"`
MessageQueue string `json:"message_queue"`
NanoMsg map[string]string `json:"nano_msg"`
}
func (s *Subscriber) getServerSpecification() (*ServerSpecification, error) {
url := s.URL + "/api/server"
fmt.Println(">>>", url)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, errors.New(resp.Status)
}
defer resp.Body.Close()
ret := new(ServerSpecification)
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(ret)
fmt.Println("ok!")
return ret, err
}
func (sub *Subscriber) GetSubsciber(serverSpec *ServerSpecification, doormanId bson.ObjectId) subscriber {
switch serverSpec.MessageQueue {
case "nanomsg":
return &nanomsgsubscriber.NanoMsgSubscriber{serverSpec.NanoMsg["url"]}
}
fmt.Println("shit!", serverSpec)
return &httpsubscriber.HttpSubscriber{sub.getDoormanStatusUrl(doormanId), time.Second * 5}
}
func (sub *Subscriber) getDoormanStatusUrl(doormanId bson.ObjectId) string {
return sub.URL + "/api/doormen/" + doormanId.Hex() + "/status"
}
func (sub *Subscriber) SetInitialState(doormanId bson.ObjectId, update shared.UpdateHandlerFunc) error {
httpSUbscriber := &httpsubscriber.HttpSubscriber{Url: sub.getDoormanStatusUrl(doormanId)}
if du, err := httpSUbscriber.GetDoormanUpdater(); err != nil {
return err
} else {
return update(du)
}
}
func startHartBeat() error {
return nil
}
func (sub *Subscriber) Subscribe(doormanId bson.ObjectId, update shared.UpdateHandlerFunc) error {
spec, err := sub.getServerSpecification()
if err != nil {
return err
}
if err = sub.SetInitialState(doormanId, update); err != nil {
return err
}
subscriber := sub.GetSubsciber(spec, doormanId)
return subscriber.Subscribe(doormanId, update)
}
+1
View File
@@ -0,0 +1 @@
package subscriber