mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
much better seconds for prefix
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ type MinuteBucket struct {
|
||||
Price float64
|
||||
DestinationId string
|
||||
destination *Destination
|
||||
precision int
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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.
@@ -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"]}
|
||||
]
|
||||
|
||||
@@ -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"}]}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user