diff --git a/cmd/loader/loader.go b/cmd/loader/loader.go index cca8528f4..46c04ecfc 100644 --- a/cmd/loader/loader.go +++ b/cmd/loader/loader.go @@ -64,7 +64,6 @@ func writeToStorage(storage timespans.StorageGetter, func main() { flag.Parse() - log.Printf("Reading from %s, %s, %s", *apfile, *destfile, *tpfile) // reading activation periods diff --git a/timespans/activationperiod.go b/timespans/activationperiod.go index 20642268d..e85b18660 100644 --- a/timespans/activationperiod.go +++ b/timespans/activationperiod.go @@ -18,8 +18,6 @@ along with this program. If not, see package timespans import ( - "strconv" - "strings" "time" //"log" ) @@ -44,54 +42,54 @@ func (ap *ActivationPeriod) AddInterval(is ...*Interval) { /* Serializes the activation periods for the storage. Used for key-value storages. */ -func (ap *ActivationPeriod) store() (result string) { - result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + ";" - for _, i := range ap.Intervals { - var is string - is = strconv.Itoa(int(i.Month)) + "|" - is += strconv.Itoa(i.MonthDay) + "|" - for _, wd := range i.WeekDays { - is += strconv.Itoa(int(wd)) + "," - } - is = strings.TrimRight(is, ",") + "|" - is += i.StartTime + "|" - is += i.EndTime + "|" - is += strconv.FormatFloat(i.Ponder, 'f', -1, 64) + "|" - is += strconv.FormatFloat(i.ConnectFee, 'f', -1, 64) + "|" - is += strconv.FormatFloat(i.Price, 'f', -1, 64) + "|" - is += strconv.FormatFloat(i.BillingUnit, 'f', -1, 64) - result += is + ";" - } - return -} +// func (ap *ActivationPeriod) store() (result string) { +// result += strconv.FormatInt(ap.ActivationTime.UnixNano(), 10) + ";" +// for _, i := range ap.Intervals { +// var is string +// is = strconv.Itoa(int(i.Month)) + "|" +// is += strconv.Itoa(i.MonthDay) + "|" +// for _, wd := range i.WeekDays { +// is += strconv.Itoa(int(wd)) + "," +// } +// is = strings.TrimRight(is, ",") + "|" +// is += i.StartTime + "|" +// is += i.EndTime + "|" +// is += strconv.FormatFloat(i.Ponder, 'f', -1, 64) + "|" +// is += strconv.FormatFloat(i.ConnectFee, 'f', -1, 64) + "|" +// is += strconv.FormatFloat(i.Price, 'f', -1, 64) + "|" +// is += strconv.FormatFloat(i.BillingUnit, 'f', -1, 64) +// result += is + ";" +// } +// return +// } /* De-serializes the activation periods for the storage. Used for key-value storages. */ -func (ap *ActivationPeriod) restore(input string) { - elements := strings.Split(input, ";") - unixNano, _ := strconv.ParseInt(elements[0], 10, 64) - ap.ActivationTime = time.Unix(0, unixNano).In(time.UTC) - ap.Intervals = make([]*Interval, 0) - for _, is := range elements[1 : len(elements)-1] { - i := &Interval{} - ise := strings.Split(is, "|") - month, _ := strconv.Atoi(ise[0]) - i.Month = time.Month(month) - i.MonthDay, _ = strconv.Atoi(ise[1]) - for _, d := range strings.Split(ise[2], ",") { - if d != "" { - wd, _ := strconv.Atoi(d) - i.WeekDays = append(i.WeekDays, time.Weekday(wd)) - } - } - i.StartTime = ise[3] - i.EndTime = ise[4] - i.Ponder, _ = strconv.ParseFloat(ise[5], 64) - i.ConnectFee, _ = strconv.ParseFloat(ise[6], 64) - i.Price, _ = strconv.ParseFloat(ise[7], 64) - i.BillingUnit, _ = strconv.ParseFloat(ise[8], 64) +// func (ap *ActivationPeriod) restore(input string) { +// elements := strings.Split(input, ";") +// unixNano, _ := strconv.ParseInt(elements[0], 10, 64) +// ap.ActivationTime = time.Unix(0, unixNano).In(time.UTC) +// ap.Intervals = make([]*Interval, 0) +// for _, is := range elements[1 : len(elements)-1] { +// i := &Interval{} +// ise := strings.Split(is, "|") +// month, _ := strconv.Atoi(ise[0]) +// i.Month = time.Month(month) +// i.MonthDay, _ = strconv.Atoi(ise[1]) +// for _, d := range strings.Split(ise[2], ",") { +// if d != "" { +// wd, _ := strconv.Atoi(d) +// i.WeekDays = append(i.WeekDays, time.Weekday(wd)) +// } +// } +// i.StartTime = ise[3] +// i.EndTime = ise[4] +// i.Ponder, _ = strconv.ParseFloat(ise[5], 64) +// i.ConnectFee, _ = strconv.ParseFloat(ise[6], 64) +// i.Price, _ = strconv.ParseFloat(ise[7], 64) +// i.BillingUnit, _ = strconv.ParseFloat(ise[8], 64) - ap.Intervals = append(ap.Intervals, i) - } -} +// ap.Intervals = append(ap.Intervals, i) +// } +// } diff --git a/timespans/activationperiod_test.go b/timespans/activationperiod_test.go index 4efdab981..3ada91f9b 100644 --- a/timespans/activationperiod_test.go +++ b/timespans/activationperiod_test.go @@ -18,12 +18,37 @@ along with this program. If not, see package timespans import ( + "reflect" "testing" "time" //"log" ) +func TestApRestoreKyoto(t *testing.T) { + getter, _ := NewKyotoStorage("test.kch") + defer getter.Close() + + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", storageGetter: getter} + cd.SearchStorageForPrefix() + if len(cd.ActivationPeriods) != 2 { + t.Error("Error restoring activation periods: ", cd.ActivationPeriods) + } +} + +func TestApRestoreRedis(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() + + cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", storageGetter: getter} + cd.SearchStorageForPrefix() + if len(cd.ActivationPeriods) != 2 { + t.Error("Error restoring activation periods: ", cd.ActivationPeriods) + } +} + func TestApStoreRestore(t *testing.T) { + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) + defer getter.Close() d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) i := &Interval{Month: time.February, MonthDay: 1, @@ -32,72 +57,14 @@ func TestApStoreRestore(t *testing.T) { EndTime: "15:00:00"} ap := &ActivationPeriod{ActivationTime: d} ap.AddInterval(i) - result := ap.store() - expected := "1328106601000000000;2|1|3,4|14:30:00|15:00:00|0|0|0|0;" - if result != expected { - t.Errorf("Expected %q was %q", expected, result) - } - ap1 := ActivationPeriod{} - ap1.restore(result) - if ap1.ActivationTime != ap.ActivationTime { - t.Errorf("Expected %v was %v", ap.ActivationTime, ap1.ActivationTime) - } - i1 := ap1.Intervals[0] - if i1.Month != i.Month { - t.Errorf("Expected %q was %q", i.Month, i1.Month) - } - if i1.MonthDay != i.MonthDay { - t.Errorf("Expected %q was %q", i.MonthDay, i1.MonthDay) - } - for j, wd := range i1.WeekDays { - if wd != i1.WeekDays[j] { - t.Errorf("Expected %q was %q", i.StartTime, i1.StartTime) - } - } - if i1.StartTime != i.StartTime { - t.Errorf("Expected %q was %q", i.StartTime, i1.StartTime) - } - if i1.EndTime != i.EndTime { - t.Errorf("Expected %q was %q", i.EndTime, i1.EndTime) - } - if i1.Ponder != i.Ponder { - t.Errorf("Expected %q was %q", i.Ponder, i1.Ponder) - } - if i1.ConnectFee != i.ConnectFee { - t.Errorf("Expected %q was %q", i.ConnectFee, i1.ConnectFee) - } - if i1.Price != i.Price { - t.Errorf("Expected %q was %q", i.Price, i1.Price) - } - if i1.BillingUnit != i.BillingUnit { - t.Errorf("Expected %q was %q", i.BillingUnit, i1.BillingUnit) + + getter.SetActivationPeriods("storerestore", []*ActivationPeriod{ap}) + aps, err := getter.GetActivationPeriods("storerestore") + if err != nil || len(aps) != 1 || !reflect.DeepEqual(ap, aps[0]) { + t.Log(aps) + t.Errorf("Expected %v was %v ", ap, aps) } + } /**************************** Benchmarks *************************************/ - -func BenchmarkActivationPeriodRestore(b *testing.B) { - ap := ActivationPeriod{} - for i := 0; i < b.N; i++ { - ap.restore("1328106601;2|1|3,4|14:30:00|15:00:00|0|0|0|0;") - } -} - -func BenchmarkActivationPeriodStoreRestore(b *testing.B) { - b.StopTimer() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Month: time.February, - MonthDay: 1, - WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, - StartTime: "14:30:00", - EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) - - ap1 := ActivationPeriod{} - b.StartTimer() - for i := 0; i < b.N; i++ { - result := ap.store() - ap1.restore(result) - } -} diff --git a/timespans/calldesc.go b/timespans/calldesc.go index 01ef57f15..888081061 100644 --- a/timespans/calldesc.go +++ b/timespans/calldesc.go @@ -86,9 +86,9 @@ func (cd *CallDescriptor) SetStorageGetter(sg StorageGetter) { } /* -Restores the activation periods from storage. +Restores the activation periods for the specified prefix from storage. */ -func (cd *CallDescriptor) RestoreFromStorage() (destPrefix string, err error) { +func (cd *CallDescriptor) SearchStorageForPrefix() (destPrefix string, err error) { cd.ActivationPeriods = make([]*ActivationPeriod, 0) base := fmt.Sprintf("%s:%s:", cd.CstmId, cd.Subject) destPrefix = cd.DestinationPrefix @@ -200,14 +200,14 @@ func (cd *CallDescriptor) splitTimeSpan(firstSpan *TimeSpan) (timespans []*TimeS Creates a CallCost structure with the cost nformation calculated for the received CallDescriptor. */ func (cd *CallDescriptor) GetCost() (*CallCost, error) { - destPrefix, err := cd.RestoreFromStorage() + destPrefix, err := cd.SearchStorageForPrefix() timespans := cd.splitInTimeSpans() cost := 0.0 connectionFee := 0.0 for i, ts := range timespans { - if ts.MinuteInfo == nil && i == 0 { + if i == 0 && ts.MinuteInfo == nil && ts.Interval != nil { connectionFee = ts.Interval.ConnectFee } cost += ts.GetCost(cd) @@ -227,7 +227,7 @@ func (cd *CallDescriptor) GetCost() (*CallCost, error) { Returns the cost of a second in the present time conditions. */ func (cd *CallDescriptor) getPresentSecondCost() (cost float64, err error) { - _, err = cd.RestoreFromStorage() + _, err = cd.SearchStorageForPrefix() now := time.Now() oneSecond, _ := time.ParseDuration("1s") ts := &TimeSpan{TimeStart: now, TimeEnd: now.Add(oneSecond)} @@ -245,7 +245,7 @@ and will decrease it by 10% for nine times. So if the user has little credit it If the user has no credit then it will return 0. */ func (cd *CallDescriptor) GetMaxSessionTime() (seconds float64, err error) { - _, err = cd.RestoreFromStorage() + _, err = cd.SearchStorageForPrefix() now := time.Now() availableCredit, availableSeconds := 0.0, 0.0 if userBudget, err := cd.getUserBudget(); err == nil && userBudget != nil { diff --git a/timespans/calldesc_test.go b/timespans/calldesc_test.go index 04f5f6ddf..736203666 100644 --- a/timespans/calldesc_test.go +++ b/timespans/calldesc_test.go @@ -23,29 +23,6 @@ import ( //"log" ) -func TestKyotoStoreRestore(t *testing.T) { - getter, _ := NewKyotoStorage("test.kch") - defer getter.Close() - d := time.Date(2012, time.February, 1, 14, 30, 1, 0, time.UTC) - i := &Interval{Month: time.February, - MonthDay: 1, - WeekDays: []time.Weekday{time.Wednesday, time.Thursday}, - StartTime: "14:30:00", - EndTime: "15:00:00"} - ap := &ActivationPeriod{ActivationTime: d} - ap.AddInterval(i) - - cd := &CallDescriptor{CstmId: "vdf", Subject: "storerestore", DestinationPrefix: "0256"} - cd.AddActivationPeriod(ap) - - getter.SetActivationPeriods(cd.GetKey(), cd.ActivationPeriods) - aps, err := getter.GetActivationPeriods(cd.GetKey()) - if err != nil || len(aps) != 1 { - t.Log(aps) - t.Errorf("Expected %v was %v ", ap, aps) - } -} - func TestKyotoSplitSpans(t *testing.T) { getter, _ := NewKyotoStorage("test.kch") defer getter.Close() @@ -54,7 +31,7 @@ func TestKyotoSplitSpans(t *testing.T) { t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} - cd.RestoreFromStorage() + cd.SearchStorageForPrefix() timespans := cd.splitInTimeSpans() if len(timespans) != 2 { t.Log(cd.ActivationPeriods) @@ -70,7 +47,7 @@ func TestRedisSplitSpans(t *testing.T) { t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", TimeStart: t1, TimeEnd: t2, storageGetter: getter} - cd.RestoreFromStorage() + cd.SearchStorageForPrefix() timespans := cd.splitInTimeSpans() if len(timespans) != 2 { t.Log(cd.ActivationPeriods) @@ -136,6 +113,7 @@ func TestFullDestNotFound(t *testing.T) { result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", Cost: 540, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { + t.Log(cd.ActivationPeriods) t.Errorf("Expected %v was %v", expected, result) } } @@ -150,8 +128,7 @@ func TestMultipleActivationPeriods(t *testing.T) { result, _ := cd.GetCost() expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0257", Cost: 330, ConnectFee: 0} if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee { - t.Log(result.Timespans[0].ActivationPeriod) - t.Log(result.Timespans[1].ActivationPeriod) + t.Log(result.Timespans) t.Errorf("Expected %v was %v", expected, result) } } @@ -258,7 +235,7 @@ func TestMaxSessionTimeNoCredit(t *testing.T) { } func TestGetCostWithVolumeDiscount(t *testing.T) { - getter, _ := NewKyotoStorage("test.kch") + getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10) defer getter.Close() vd1 := &VolumeDiscount{100, 10} vd2 := &VolumeDiscount{500, 20} @@ -298,7 +275,7 @@ func BenchmarkRedisRestoring(b *testing.B) { cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { - cd.RestoreFromStorage() + cd.SearchStorageForPrefix() } } @@ -341,7 +318,7 @@ func BenchmarkKyotoRestoring(b *testing.B) { cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} b.StartTimer() for i := 0; i < b.N; i++ { - cd.RestoreFromStorage() + cd.SearchStorageForPrefix() } } @@ -353,7 +330,7 @@ func BenchmarkSplitting(b *testing.B) { t1 := time.Date(2012, time.February, 2, 17, 30, 0, 0, time.UTC) t2 := time.Date(2012, time.February, 2, 18, 30, 0, 0, time.UTC) cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2, storageGetter: getter} - cd.RestoreFromStorage() + cd.SearchStorageForPrefix() b.StartTimer() for i := 0; i < b.N; i++ { cd.splitInTimeSpans() diff --git a/timespans/kyoto_storage.go b/timespans/kyoto_storage.go index eef3eb6c5..1c7b42205 100644 --- a/timespans/kyoto_storage.go +++ b/timespans/kyoto_storage.go @@ -18,79 +18,89 @@ along with this program. If not, see package timespans import ( - "github.com/fsouza/gokabinet/kc" - //"log" - "strings" + "bitbucket.org/ww/cabinet" + "bytes" + "encoding/gob" + //"github.com/fsouza/gokabinet/kc" + // "log" + "sync" ) type KyotoStorage struct { - db *kc.DB + //db *kc.DB + db *cabinet.KCDB + buf bytes.Buffer + dec *gob.Decoder + mux sync.Mutex // we need norma lock because we reset the buf variable } func NewKyotoStorage(filaName string) (*KyotoStorage, error) { - ndb, err := kc.Open(filaName, kc.WRITE) - return &KyotoStorage{db: ndb}, err + //ndb, err := kc.Open(filaName, kc.WRITE) + ndb := cabinet.New() + err := ndb.Open(filaName, cabinet.KCOWRITER|cabinet.KCOCREATE) + ks := &KyotoStorage{db: ndb} + ks.dec = gob.NewDecoder(&ks.buf) + return ks, err } func (ks *KyotoStorage) Close() { ks.db.Close() } -func (ks *KyotoStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { - values, err := ks.db.Get(key) +func (ks *KyotoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { + ks.mux.Lock() + defer ks.mux.Unlock() - if err == nil { - for _, ap_string := range strings.Split(values, "\n") { - if len(ap_string) > 0 { - ap := &ActivationPeriod{} - ap.restore(ap_string) - aps = append(aps, ap) - } - } - } - return aps, err + var writeBuf bytes.Buffer + encoder := gob.NewEncoder(&writeBuf) + encoder.Encode(aps) + return ks.db.Set([]byte(key), writeBuf.Bytes()) } -func (ks *KyotoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { - result := "" - for _, ap := range aps { - result += ap.store() + "\n" - } - return ks.db.Set(key, result) +func (ks *KyotoStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { + ks.mux.Lock() + defer ks.mux.Unlock() + + values, err := ks.db.Get([]byte(key)) + + ks.buf.Reset() + ks.buf.Write(values) + ks.dec.Decode(&aps) + return } func (ks *KyotoStorage) GetDestination(key string) (dest *Destination, err error) { - if values, err := ks.db.Get(key); err == nil { + if values, err := ks.db.Get([]byte(key)); err == nil { dest = &Destination{Id: key} - dest.restore(values) + dest.restore(string(values)) } return } func (ks *KyotoStorage) SetDestination(dest *Destination) error { - return ks.db.Set(dest.Id, dest.store()) + return ks.db.Set([]byte(dest.Id), []byte(dest.store())) } func (ks *KyotoStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) { - if values, err := ks.db.Get(key); err == nil { + if values, err := ks.db.Get([]byte(key)); err == nil { tp = &TariffPlan{Id: key} - tp.restore(values) + tp.restore(string(values)) } return } func (ks *KyotoStorage) SetTariffPlan(tp *TariffPlan) error { - return ks.db.Set(tp.Id, tp.store()) + return ks.db.Set([]byte(tp.Id), []byte(tp.store())) } func (ks *KyotoStorage) GetUserBudget(key string) (ub *UserBudget, err error) { - if values, err := ks.db.Get(key); err == nil { + if values, err := ks.db.Get([]byte(key)); err == nil { ub = &UserBudget{Id: key} - ub.restore(values) + ub.restore(string(values)) } return } func (ks *KyotoStorage) SetUserBudget(ub *UserBudget) error { - return ks.db.Set(ub.Id, ub.store()) + return ks.db.Set([]byte(ub.Id), []byte(ub.store())) } diff --git a/timespans/redis_storage.go b/timespans/redis_storage.go index c70d5e0d1..a7520decb 100644 --- a/timespans/redis_storage.go +++ b/timespans/redis_storage.go @@ -18,47 +18,54 @@ along with this program. If not, see package timespans import ( + "bytes" + "encoding/gob" "github.com/simonz05/godis" - "strings" + // "log" + "sync" ) type RedisStorage struct { dbNb int db *godis.Client + buf bytes.Buffer + dec *gob.Decoder + mux sync.Mutex } func NewRedisStorage(address string, db int) (*RedisStorage, error) { ndb := godis.New(address, db, "") - return &RedisStorage{db: ndb, dbNb: db}, nil + rs := &RedisStorage{db: ndb, dbNb: db} + + rs.dec = gob.NewDecoder(&rs.buf) + return rs, nil } func (rs *RedisStorage) Close() { rs.db.Quit() } -func (rs *RedisStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { - //rs.db.Select(rs.dbNb) - elem, err := rs.db.Get(key) - values := elem.String() - if err == nil { - for _, ap_string := range strings.Split(values, "\n") { - if len(ap_string) > 0 { - ap := &ActivationPeriod{} - ap.restore(ap_string) - aps = append(aps, ap) - } - } - } - return aps, err -} - func (rs *RedisStorage) SetActivationPeriods(key string, aps []*ActivationPeriod) error { //.db.Select(rs.dbNb) - result := "" - for _, ap := range aps { - result += ap.store() + "\n" - } - return rs.db.Set(key, result) + rs.mux.Lock() + defer rs.mux.Unlock() + var writeBuf bytes.Buffer + encoder := gob.NewEncoder(&writeBuf) + encoder.Encode(aps) + return rs.db.Set(key, writeBuf.Bytes()) +} + +func (rs *RedisStorage) GetActivationPeriods(key string) (aps []*ActivationPeriod, err error) { + //rs.db.Select(rs.dbNb) + rs.mux.Lock() + defer rs.mux.Unlock() + + elem, err := rs.db.Get(key) + rs.buf.Reset() + rs.buf.Write(elem.Bytes()) + + rs.dec.Decode(&aps) + return } func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) { diff --git a/timespans/test.kch b/timespans/test.kch index 10bc6a086..b8ae51658 100644 Binary files a/timespans/test.kch and b/timespans/test.kch differ