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 (
"encoding/json"
"errors"
"fmt"
"log"
"math"
"math/big"
@@ -17,8 +16,6 @@ import (
"gopkg.in/mgo.v2/bson"
)
var _ = fmt.Print
var ONE *big.Rat = big.NewRat(1, 1)
var rand *mathrand.Rand
@@ -43,7 +40,7 @@ type Doorman struct {
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.Id = id
wab.Probabilities = Probabilities
@@ -143,6 +140,10 @@ func (w *Doorman) GetCaseFromData(data ...[]byte) uint {
return w.GetCase(random)
}
func (w *Doorman) GetCaseFromString(data string) uint {
return w.GetCaseFromData([]byte(data))
}
func (w *Doorman) GetRandomCase() uint {
r := rand.Float64()
return w.GetCase(new(big.Rat).SetFloat64(r))
+23 -16
View File
@@ -18,22 +18,16 @@ import (
var epsilon = 0.0
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 {
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 {
ret := make([]*big.Rat, len(probs))
for i, p := range probs {
ret[i], _ = new(big.Rat).SetString(fmt.Sprint(p))
ret[i] = new(big.Rat).SetFloat64(p)
}
return ret
}
@@ -72,7 +66,7 @@ func TestValidate(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 {
t.Error("expected 0 but received", c)
}
@@ -83,7 +77,7 @@ func TestGetCase(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
w.wg.Add(1)
go func() {
@@ -98,11 +92,11 @@ func TestGetCaseCoroutineSafety(t *testing.T) {
func TestGenerateRandomProbabilityFromBitSlice(t *testing.T) {
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) {
@@ -166,7 +160,7 @@ func randomBytes(n int) []byte {
func TestUpdateHard(t *testing.T) {
wab := NewDoorman(bson.ObjectIdHex(oid), getProbs())
wab := New(bson.ObjectIdHex(oid), getProbs())
createMessage := func() string {
msg := &shared.DoormanUpdater{Timestamp: 2, Probabilities: getProbs(0.5, 0.25, 0.25), Id: oid}
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) {
p := 0.5
n := 10000
wab := NewDoorman(bson.NewObjectId(), getProbs(p, 1-p))
wab := New(bson.NewObjectId(), getProbs(p, 1-p))
var sum = 0
for i := 0; i < n; i++ {
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) {
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++ {
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 (
"html/template"
"log"
"math/big"
"math/rand"
"net/http"
"os"
"strconv"
"github.com/didiercrunch/doorman"
"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() {
if err := wab.NanoMsgSubscriber("tcp://127.0.0.1:40899"); err != nil {
if err := wab.Subscriber("http://localhost:1999"); err != nil {
panic(err)
}
}
@@ -21,15 +32,11 @@ func init() {
func GetDoormanValue(r *http.Request) (string, int) {
switch wab.GetCaseFromData([]byte(r.URL.String())) {
case 0:
return "blue", 0
return "red", 0
case 1:
return "red", 1
return "green", 1
case 2:
return "pink", 2
case 3:
return "yellow", 3
case 4:
return "magenta", 4
return "blue", 2
}
log.Panic("impossible value returned by doorman")
return "", 0
@@ -43,8 +50,10 @@ var tmpl = `
<h1 style="text-align:center;">the doorman value is {{ .value }}</h1>
<p style="text-align:center;">
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>
</body>
</html>
`
@@ -56,6 +65,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
}
m := make(map[string]interface{})
m["color"], m["value"] = GetDoormanValue(r)
m["nextUrl"] = "/" + strconv.Itoa(rand.Int())
if err := t.Execute(w, m); err != nil {
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/nsqsubscriber"
"github.com/didiercrunch/doorman/shared"
"github.com/didiercrunch/doorman/subscriber"
"gopkg.in/mgo.v2/bson"
)
@@ -11,16 +12,21 @@ type Subscriber interface {
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)
}
func (w *Doorman) NSQSubscriber(NSQLookupdURl string) error {
sub := &nsqsubscriber.NSQSubscriber{NSQLookupdURl}
return w.Subscribe(sub)
return w.subscribe(sub)
}
func (w *Doorman) NanoMsgSubscriber(NanoMsgUrlLookupdURl string) error {
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