diff --git a/data/tariffplans/tutorial/Thresholds.csv b/data/tariffplans/tutorial/Thresholds.csv index d6916857f..9030766b3 100644 --- a/data/tariffplans/tutorial/Thresholds.csv +++ b/data/tariffplans/tutorial/Thresholds.csv @@ -1,2 +1,3 @@ + #Tenant[0],Id[1],FilterType[2],FilterFieldName[3],FilterFieldValues[4],ActivationInterval[5],MinItems[6],Recurrent[7],MinSleep[8],Blocker[9],Stored[10],Weight[11],ActionIDs[12] -Threshold1,*string,Account,1001;1002,2014-07-29T15:00:00Z,,1.2,10,true,1s,true,true,10,THRESH1;THRESH2 \ No newline at end of file +Threshold1,*string,Account,1001;1002,2014-07-29T15:00:00Z,,1.2,10,true,1s,true,true,10,THRESH1;THRESH2 diff --git a/engine/libstats.go b/engine/libstats.go index fbef8395f..bd61ab64c 100755 --- a/engine/libstats.go +++ b/engine/libstats.go @@ -128,6 +128,22 @@ func (se StatEvent) Pdd(timezone string) (pdd time.Duration, err error) { return utils.ParseDurationWithSecs(pddStr) } +// Destination returns the Destination of StatEvent +func (se StatEvent) Destination(timezone string) (ddc string, err error) { + ddcIf, has := se.Fields[utils.DESTINATION] + if !has { + return ddc, utils.ErrNotFound + } + if ddcInt, canCast := ddcIf.(int64); canCast { + return strconv.FormatInt(ddcInt, 64), nil + } + ddcStr, canCast := ddcIf.(string) + if !canCast { + return ddc, errors.New("cannot cast to string") + } + return ddcStr, nil +} + // NewStoredStatQueue initiates a StoredStatQueue out of StatQueue func NewStoredStatQueue(sq *StatQueue, ms Marshaler) (sSQ *StoredStatQueue, err error) { sSQ = &StoredStatQueue{ diff --git a/engine/statmetrics.go b/engine/statmetrics.go index 1f9b6440d..93990488b 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -36,6 +36,7 @@ func NewStatMetric(metricID string) (sm StatMetric, err error) { utils.MetaACC: NewACC, utils.MetaTCC: NewTCC, utils.MetaPDD: NewPDD, + utils.MetaDDC: NewDCC, } if _, has := metrics[metricID]; !has { return nil, fmt.Errorf("unsupported metric: %s", metricID) @@ -549,3 +550,72 @@ func (pdd *StatPDD) Marshal(ms Marshaler) (marshaled []byte, err error) { func (pdd *StatPDD) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { return ms.Unmarshal(marshaled, pdd) } + +func NewDCC() (StatMetric, error) { + return &StatDDC{Destinations: make(map[string]utils.StringMap), EventDestinations: make(map[string]string)}, nil +} + +type StatDDC struct { + Destinations map[string]utils.StringMap + EventDestinations map[string]string // map[EventTenantID]Destination +} + +func (ddc *StatDDC) GetStringValue(fmtOpts string) (val string) { + if len(ddc.Destinations) == 0 { + return utils.NOT_AVAILABLE + } + return fmt.Sprintf("%+v", len(ddc.Destinations)) +} + +func (ddc *StatDDC) GetValue() (v interface{}) { + return len(ddc.Destinations) +} + +func (ddc *StatDDC) GetFloat64Value() (v float64) { + if len(ddc.Destinations) == 0 { + return -1.0 + } + return float64(len(ddc.Destinations)) +} + +func (ddc *StatDDC) AddEvent(ev *StatEvent) (err error) { + var dest string + if at, err := ev.AnswerTime(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else if !at.IsZero() { + if destination, err := ev.Destination(config.CgrConfig().DefaultTimezone); err != nil { + return err + } else { + dest = destination + if _, has := ddc.Destinations[dest]; !has { + ddc.Destinations[dest] = make(map[string]bool) + } + ddc.Destinations[dest][ev.TenantID()] = true + } + } + ddc.EventDestinations[ev.TenantID()] = dest + return +} + +func (ddc *StatDDC) RemEvent(evTenantID string) (err error) { + destination, has := ddc.EventDestinations[evTenantID] + if !has { + return utils.ErrNotFound + } + if len(ddc.Destinations[destination]) == 1 { + delete(ddc.Destinations, destination) + } else { + delete(ddc.Destinations[destination], evTenantID) + } + + delete(ddc.EventDestinations, evTenantID) + return +} + +func (ddc *StatDDC) Marshal(ms Marshaler) (marshaled []byte, err error) { + return ms.Marshal(DDC) +} +func (ddc *StatDDC) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { + return ms.Unmarshal(marshaled, ddc) +} diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 8e96c4c0f..88bdb9896 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -832,3 +832,103 @@ func TestPDDGetValue(t *testing.T) { t.Errorf("wrong pdd value: %+v", v) } } + +func TestDDCGetStringValue(t *testing.T) { + ddc, _ := NewDCC() + ev := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_1", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.DESTINATION: "1002"}} + if strVal := ddc.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong ddc value: %s", strVal) + } + + ddc.AddEvent(ev) + if strVal := ddc.GetStringValue(""); strVal != "1" { + t.Errorf("wrong ddc value: %s", strVal) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.DESTINATION: "1002"}} + + ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.DESTINATION: "1001"}} + ddc.AddEvent(ev2) + ddc.AddEvent(ev3) + if strVal := ddc.GetStringValue(""); strVal != "2" { + t.Errorf("wrong ddc value: %s", strVal) + } + ddc.RemEvent(ev.TenantID()) + if strVal := ddc.GetStringValue(""); strVal != "2" { + t.Errorf("wrong ddc value: %s", strVal) + } + ddc.RemEvent(ev2.TenantID()) + if strVal := ddc.GetStringValue(""); strVal != "1" { + t.Errorf("wrong ddc value: %s", strVal) + } + ddc.RemEvent(ev3.TenantID()) + if strVal := ddc.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong ddc value: %s", strVal) + } +} + +func TestDDCGetFloat64Value(t *testing.T) { + ddc, _ := NewDCC() + ev := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_1", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + "Usage": time.Duration(10 * time.Second), + utils.PDD: time.Duration(5 * time.Second), + utils.DESTINATION: "1002"}} + ddc.AddEvent(ev) + if v := ddc.GetFloat64Value(); v != 1 { + t.Errorf("wrong ddc value: %v", v) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + ddc.AddEvent(ev2) + if v := ddc.GetFloat64Value(); v != 1 { + t.Errorf("wrong ddc value: %v", v) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2015, 7, 14, 14, 25, 0, 0, time.UTC), + utils.PDD: time.Duration(10 * time.Second), + utils.DESTINATION: "1001", + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2015, 7, 14, 14, 25, 0, 0, time.UTC), + utils.DESTINATION: "1003", + }, + } + ddc.AddEvent(ev4) + if strVal := ddc.GetFloat64Value(); strVal != 2 { + t.Errorf("wrong ddc value: %v", strVal) + } + ddc.AddEvent(ev5) + if strVal := ddc.GetFloat64Value(); strVal != 3 { + t.Errorf("wrong ddc value: %v", strVal) + } + ddc.RemEvent(ev2.TenantID()) + if strVal := ddc.GetFloat64Value(); strVal != 3 { + t.Errorf("wrong pdd value: %v", strVal) + } + ddc.RemEvent(ev4.TenantID()) + if strVal := ddc.GetFloat64Value(); strVal != 2 { + t.Errorf("wrong ddc value: %v", strVal) + } + ddc.RemEvent(ev.TenantID()) + if strVal := ddc.GetFloat64Value(); strVal != 1 { + t.Errorf("wrong ddc value: %v", strVal) + } + ddc.RemEvent(ev5.TenantID()) + if strVal := ddc.GetFloat64Value(); strVal != -1.0 { + t.Errorf("wrong ddc value: %v", strVal) + } +}