much better seconds for prefix

This commit is contained in:
Radu Ioan Fericean
2012-02-22 16:27:52 +02:00
parent 5a6ba1816c
commit 1f9fd63026
17 changed files with 129 additions and 67 deletions

View File

@@ -4,7 +4,7 @@ import (
"errors"
"github.com/rif/cgrates/timespans"
"log"
"runtime"
//"runtime"
"time"
"flag"
)
@@ -42,7 +42,7 @@ func CallRater(key *timespans.CallDescriptor) (reply *timespans.CallCost) {
func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU() - 1)
//runtime.GOMAXPROCS(runtime.NumCPU() - 1)
raterList = NewRaterList()
go StopSingnalHandler()

View File

@@ -4,7 +4,6 @@ import (
"os/signal"
"fmt"
"log"
"net"
"net/rpc"
"net/http"
"os"
@@ -21,13 +20,7 @@ func listenToRPCRaterRequests(){
raterServer := new(RaterServer)
rpc.Register(raterServer)
rpc.HandleHTTP()
l, e := net.Listen("tcp", *raterAddress)
if e != nil {
log.Fatal("listen error:", e)
}
log.Print("Listening for raters on ", *raterAddress)
http.Serve(l, nil)
http.ListenAndServe(*raterAddress, nil)
}
/*

View File

@@ -9,7 +9,7 @@ import (
)
var (
storage = flag.String("storage", "kyoto", "kyoto|redis|mongo")
storage = flag.String("storage", "all", "kyoto|redis|mongo")
kyotofile = flag.String("kyotofile", "storage.kch", "kyoto storage file (storage.kch)")
redisserver = flag.String("redisserver", "tcp:127.0.0.1:6379", "redis server address (tcp:127.0.0.1:6379)")
redisdb = flag.Int("rdb", 10, "redis database number (10)")
@@ -120,13 +120,22 @@ func main() {
defer storage.Close()
writeToStorage(storage, callDescriptors, destinations, tariffPlans, userBudgets)
case "mongo":
storage, _ := timespans.NewMongoStorage("127.0.0.1", "test")
storage, _ := timespans.NewMongoStorage(*mongoserver, *mongodb)
defer storage.Close()
writeToStorage(storage, callDescriptors, destinations, tariffPlans, userBudgets)
default:
case "redis":
storage, _ := timespans.NewRedisStorage(*redisserver, *redisdb)
defer storage.Close()
writeToStorage(storage, callDescriptors, destinations, tariffPlans, userBudgets)
default:
kyoto, _ := timespans.NewKyotoStorage(*kyotofile)
writeToStorage(kyoto, callDescriptors, destinations, tariffPlans, userBudgets)
kyoto.Close()
mongo, _ := timespans.NewMongoStorage(*mongoserver, *mongodb)
writeToStorage(mongo, callDescriptors, destinations, tariffPlans, userBudgets)
mongo.Close()
redis, _ := timespans.NewRedisStorage(*redisserver, *redisdb)
writeToStorage(redis, callDescriptors, destinations, tariffPlans, userBudgets)
redis.Close()
}
}

View File

@@ -21,10 +21,9 @@ func main() {
cd := timespans.CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0256", TimeStart: t1, TimeEnd: t2}
result := timespans.CallCost{}
client, _ := jsonrpc.Dial("tcp", "localhost:2001")
i := 0
if *parallel {
var divCall *rpc.Call
for ; i < *runs; i++ {
for i := 0; i < *runs; i++ {
divCall = client.Go("Responder.Get", cd, &result, nil)
}
<-divCall.Done
@@ -34,6 +33,5 @@ func main() {
}
}
log.Println(result)
log.Println(i)
client.Close()
}

View File

@@ -7,6 +7,11 @@ import (
"time"
)
const (
// the minimum length for a destination prefix to be matched.
MinPrefixLength = 2
)
/*
Utility function for rounding a float to a certain number of decimals (not present in math).
*/
@@ -53,7 +58,8 @@ func (cd *CallDescriptor) RestoreFromStorage(sg StorageGetter) (destPrefix strin
key := base + destPrefix
values, err := sg.GetActivationPeriods(key)
//get for a smaller prefix if the orignal one was not found
for i := len(cd.DestinationPrefix); err != nil && i > 1; values, err = sg.GetActivationPeriods(key) {
for i := len(cd.DestinationPrefix); err != nil && i >= MinPrefixLength; values, err = sg.GetActivationPeriods(key) {
i--
destPrefix = cd.DestinationPrefix[:i]
key = base + destPrefix
@@ -129,13 +135,16 @@ Creates a CallCost structure with the cost nformation calculated for the receive
*/
func (cd *CallDescriptor) GetCost(sg StorageGetter) (*CallCost, error) {
destPrefix, err := cd.RestoreFromStorage(sg)
cc := &CallCost{TOR: cd.TOR, CstmId: cd.CstmId, Subject: cd.Subject, DestinationPrefix: destPrefix}
userBudget, err := sg.GetUserBudget(cd.Subject)
if err != nil {
if userBudget, err := sg.GetUserBudget(cd.Subject); err == nil {
nbSeconds := cd.TimeEnd.Sub(cd.TimeStart).Seconds()
log.Print("seconds: ", nbSeconds)
avaliableNbSeconds := userBudget.getSecondsForPrefix(sg, cd.DestinationPrefix)
log.Print("available: ", avaliableNbSeconds)
if nbSeconds < avaliableNbSeconds {
return nil, nil
cc.Cost, cc.ConnectFee = 0, 0
return cc, nil
}
}
@@ -150,13 +159,8 @@ func (cd *CallDescriptor) GetCost(sg StorageGetter) (*CallCost, error) {
cost += ts.GetCost()
}
cc := &CallCost{TOR: cd.TOR,
CstmId: cd.CstmId,
Subject: cd.Subject,
DestinationPrefix: destPrefix,
Cost: cost,
ConnectFee: connectionFee,
Timespans: timespans}
cc.Cost, cc.ConnectFee, cc.Timespans = cost, connectionFee, timespans
return cc, err
}

View File

@@ -153,7 +153,7 @@ func TestUniquePrice(t *testing.T) {
}
}
func TestSecodCost(t *testing.T) {
func TestPresentSecodCost(t *testing.T) {
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
defer getter.Close()
@@ -167,6 +167,20 @@ func TestSecodCost(t *testing.T) {
}
}
func TestMinutesCost(t *testing.T) {
getter, _ := NewRedisStorage("tcp:127.0.0.1:6379", 10)
defer getter.Close()
t1 := time.Date(2012, time.February, 8, 22, 50, 0, 0, time.UTC)
t2 := time.Date(2012, time.February, 8, 23, 50, 21, 0, time.UTC)
cd := &CallDescriptor{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723", TimeStart: t1, TimeEnd: t2}
result, _ := cd.GetCost(getter)
expected := &CallCost{CstmId: "vdf", Subject: "rif", DestinationPrefix: "0723", Cost: 0, ConnectFee: 0}
if result.Cost != expected.Cost || result.ConnectFee != expected.ConnectFee {
t.Errorf("Expected %v was %v", expected, result)
}
}
/*********************************** BENCHMARKS ***************************************/
func BenchmarkRedisGetting(b *testing.B) {
b.StopTimer()

View File

@@ -1,6 +1,7 @@
package timespans
import (
//"log"
"strings"
)
@@ -30,11 +31,15 @@ func (d *Destination) restore(input string) {
/*
De-serializes the destination for the storage. Used for key-value storages.
*/
func (d *Destination) containsPrefix(prefix string) bool {
for _, p := range d.Prefixes {
if prefix == p {
return true
func (d *Destination) containsPrefix(prefix string) (bool, int) {
for i := len(prefix); i >= MinPrefixLength; {
for _, p := range d.Prefixes {
if p == prefix[:i] {
return true, i
}
}
i--
}
return false
return false, 0
}

View File

@@ -51,7 +51,8 @@ func TestDestinationContainsPrefix(t *testing.T) {
getter, _ := NewMongoStorage("127.0.0.1", "test")
defer getter.Close()
nationale = &Destination{Id: "nat", Prefixes: []string{"0257", "0256", "0723"}}
if !nationale.containsPrefix("0256") {
contains, precision := nationale.containsPrefix("0256")
if !contains || precision != len("0256") {
t.Error("Should contain prefix: ", nationale)
}

View File

@@ -14,7 +14,7 @@ type Interval struct {
Month time.Month
MonthDay int
WeekDays []time.Weekday
StartTime, EndTime string // ##:##:## format
StartTime, EndTime string // ##:##:## format
Ponder, ConnectFee, Price, BillingUnit float64
}
@@ -46,7 +46,7 @@ func (i *Interval) Contains(t time.Time) bool {
sh, _ := strconv.Atoi(split[0])
sm, _ := strconv.Atoi(split[1])
ss, _ := strconv.Atoi(split[2])
// if the hour is before or is the same hour but the minute is before
// if the hour is before or is the same hour but the minute is before
if t.Hour() < sh ||
(t.Hour() == sh && t.Minute() < sm) ||
(t.Hour() == sh && t.Minute() == sm && t.Second() < ss) {
@@ -59,7 +59,7 @@ func (i *Interval) Contains(t time.Time) bool {
eh, _ := strconv.Atoi(split[0])
em, _ := strconv.Atoi(split[1])
es, _ := strconv.Atoi(split[2])
// if the hour is after or is the same hour but the minute is after
// if the hour is after or is the same hour but the minute is after
if t.Hour() > eh ||
(t.Hour() == eh && t.Minute() > em) ||
(t.Hour() == eh && t.Minute() == em && t.Second() > es) {

View File

@@ -2,6 +2,7 @@ package timespans
import (
"github.com/fsouza/gokabinet/kc"
//"log"
"strings"
)
@@ -42,9 +43,10 @@ func (ks *KyotoStorage) SetActivationPeriods(key string, aps []*ActivationPeriod
}
func (ks *KyotoStorage) GetDestination(key string) (dest *Destination, err error) {
values, err := ks.db.Get(key)
dest = &Destination{Id: key}
dest.restore(values)
if values, err := ks.db.Get(key); err == nil {
dest = &Destination{Id: key}
dest.restore(values)
}
return
}
@@ -53,9 +55,10 @@ func (ks *KyotoStorage) SetDestination(dest *Destination) {
}
func (ks *KyotoStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) {
values, err := ks.db.Get(key)
tp = &TariffPlan{Id: key}
tp.restore(values)
if values, err := ks.db.Get(key); err == nil {
tp = &TariffPlan{Id: key}
tp.restore(values)
}
return
}
@@ -64,9 +67,10 @@ func (ks *KyotoStorage) SetTariffPlan(tp *TariffPlan) {
}
func (ks *KyotoStorage) GetUserBudget(key string) (ub *UserBudget, err error) {
values, err := ks.db.Get(key)
ub = &UserBudget{Id: key}
ub.restore(values)
if values, err := ks.db.Get(key); err == nil {
ub = &UserBudget{Id: key}
ub.restore(values)
}
return
}

View File

@@ -6,6 +6,7 @@ type MinuteBucket struct {
Price float64
DestinationId string
destination *Destination
precision int
}
/*

View File

@@ -46,9 +46,10 @@ func (rs *RedisStorage) SetActivationPeriods(key string, aps []*ActivationPeriod
func (rs *RedisStorage) GetDestination(key string) (dest *Destination, err error) {
//rs.db.Select(rs.dbNb + 1)
values, err := rs.db.Get(key)
dest = &Destination{Id: key}
dest.restore(values.String())
if values, err := rs.db.Get(key); err == nil {
dest = &Destination{Id: key}
dest.restore(values.String())
}
return
}
@@ -59,9 +60,10 @@ func (rs *RedisStorage) SetDestination(dest *Destination) {
func (rs *RedisStorage) GetTariffPlan(key string) (tp *TariffPlan, err error) {
//rs.db.Select(rs.dbNb + 2)
values, err := rs.db.Get(key)
tp = &TariffPlan{Id: key}
tp.restore(values.String())
if values, err := rs.db.Get(key); err == nil {
tp = &TariffPlan{Id: key}
tp.restore(values.String())
}
return
}
@@ -72,9 +74,10 @@ func (rs *RedisStorage) SetTariffPlan(tp *TariffPlan) {
func (rs *RedisStorage) GetUserBudget(key string) (ub *UserBudget, err error) {
//rs.db.Select(rs.dbNb + 3)
values, err := rs.db.Get(key)
ub = &UserBudget{Id: key}
ub.restore(values.String())
if values, err := rs.db.Get(key); err == nil {
ub = &UserBudget{Id: key}
ub.restore(values.String())
}
return
}

Binary file not shown.

View File

@@ -1,5 +1,6 @@
[
{"Id":"nationale", "Prefixes":["0256","0257","0723","0740"]},
{"Id":"retea", "Prefixes":["0723","0724"]},
{"Id":"mobil", "Prefixes":["0723","0740"]},
{"Id":"radu", "Prefixes":["0723045326"]}
]

View File

@@ -1,4 +1,4 @@
[
{"Id":"rif","Credit":21,"SmsCredit":0,"ResetDayOfTheMonth":10,"TariffPlanId":"","MinuteBuckets": [{"Seconds":10,"Priority":10,"Price":0.01,"DestinationId":"nationale"},
{"Seconds":100,"Priority":20,"Price":0,"DestinationId":"retea"}]}
{"Id":"rif","Credit":21,"SmsCredit":0,"ResetDayOfTheMonth":10,"TariffPlanId":"","MinuteBuckets": [{"Seconds":10,"Priority":10,"Price":0.01,"DestinationId":"nationale"},
{"Seconds":100,"Priority":20,"Price":0,"DestinationId":"retea"}]}
]

View File

@@ -4,6 +4,7 @@ import (
"log"
"math"
"strconv"
"sort"
"strings"
)
@@ -20,6 +21,25 @@ type UserBudget struct {
MinuteBuckets []*MinuteBucket
}
/*
Structure to store minute buckets according to priority, precision or price.
*/
type BucketSorter []*MinuteBucket
func (bs BucketSorter) Len() int {
return len(bs)
}
func (bs BucketSorter) Swap(i, j int) {
bs[i], bs[j] = bs[j], bs[i]
}
func (bs BucketSorter) Less(j, i int) bool {
return bs[i].Priority < bs[j].Priority ||
bs[i].precision < bs[j].precision ||
bs[i].Price < bs[j].Price
}
/*
Serializes the user budget for the storage. Used for key-value storages.
*/
@@ -60,7 +80,6 @@ func (ub *UserBudget) restore(input string) {
}
}
/*
Returns the tariff plan loading it from the storage if necessary.
*/
@@ -79,17 +98,27 @@ func (ub *UserBudget) getSecondsForPrefix(storage StorageGetter, prefix string)
log.Print("There are no minute buckets to check for user", ub.Id)
return
}
bestBucket := ub.MinuteBuckets[0]
var bucketList BucketSorter
for _, mb := range ub.MinuteBuckets {
d := mb.getDestination(storage)
if d.containsPrefix(prefix) && mb.Priority > bestBucket.Priority {
bestBucket = mb
if d == nil {
continue
}
contains, precision := d.containsPrefix(prefix)
if contains {
mb.precision = precision
bucketList = append(bucketList, mb)
}
}
seconds = float64(bestBucket.Seconds)
if bestBucket.Price > 0 {
seconds = math.Min(ub.Credit/bestBucket.Price, float64(seconds))
sort.Sort(bucketList)
credit := ub.Credit
for _, mb := range bucketList {
s := float64(mb.Seconds)
if mb.Price > 0 {
s = math.Min(credit/mb.Price, s)
credit -= s
}
seconds += s
}
return
}

View File

@@ -17,7 +17,7 @@ func TestGetSeconds(t *testing.T) {
ub1 := &UserBudget{Id: "rif", MinuteBuckets: []*MinuteBucket{b1, b2}, Credit: 200, tariffPlan: tf1, ResetDayOfTheMonth: 10}
seconds := ub1.getSecondsForPrefix(nil, "0723")
expected := 100.0
expected := 110.0
if seconds != expected {
t.Errorf("Expected %v was %v", expected, seconds)
}