many many things
This commit is contained in:
1 parent
fb9084794c
commit
af69e728a2
9 files changed
+253
-59
No files matched your search
+5
-4
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package subscriber
|
||||
Reference in new issue
Block a user