diff --git a/apier/v1/stats_it_test.go b/apier/v1/stats_it_test.go index e8c633147..1d6948b16 100644 --- a/apier/v1/stats_it_test.go +++ b/apier/v1/stats_it_test.go @@ -203,7 +203,7 @@ func testV1STSProcessEvent(t *testing.T) { } expectedMetrics := map[string]string{ utils.MetaASR: "66.66667%", - utils.MetaACD: "0", + utils.MetaACD: "0s", } var metrics map[string]string if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", &utils.TenantID{"cgrates.org", "STATS_1"}, &metrics); err != nil { diff --git a/apier/v1/tp_it_test.go b/apier/v1/tp_it_test.go index 65e9a54e4..a5d7c4f7e 100644 --- a/apier/v1/tp_it_test.go +++ b/apier/v1/tp_it_test.go @@ -29,6 +29,7 @@ import ( "path" "reflect" "testing" + "time" ) var ( @@ -119,6 +120,7 @@ func testTPImportTPFromFolderPath(t *testing.T) { } else if reply != utils.OK { t.Error("Calling ApierV1.ImportTarrifPlanFromFolder got reply: ", reply) } + time.Sleep(time.Duration(2 * time.Second)) } func testTPExportTPToFolder(t *testing.T) { @@ -141,6 +143,7 @@ func testTPExportTPToFolder(t *testing.T) { } else if !reflect.DeepEqual(len(reply.ExportedFiles), len(expectedTPStas.ExportedFiles)) { t.Errorf("Expecting : %+v, received: %+v", len(reply.ExportedFiles), len(expectedTPStas.ExportedFiles)) } + time.Sleep(time.Duration(2 * time.Second)) } diff --git a/engine/libstats.go b/engine/libstats.go index 3aa4f5027..10d114191 100755 --- a/engine/libstats.go +++ b/engine/libstats.go @@ -21,10 +21,10 @@ package engine import ( "errors" "fmt" - "sort" - "time" - "github.com/cgrates/cgrates/utils" + "sort" + "strconv" + "time" ) // StatsConfig represents the configuration of a StatsInstance in StatS @@ -90,6 +90,22 @@ func (se StatEvent) Usage(timezone string) (at time.Duration, err error) { return utils.ParseDurationWithSecs(usStr) } +// Cost returns the Cost of StatEvent +func (se StatEvent) Cost(timezone string) (cs float64, err error) { + csIf, has := se.Fields[utils.COST] + if !has { + return cs, utils.ErrNotFound + } + if cs, canCast := csIf.(float64); canCast { + return cs, nil + } + csStr, canCast := csIf.(string) + if !canCast { + return cs, errors.New("cannot cast to string") + } + return strconv.ParseFloat(csStr, 64) +} + // NewStoredStatQueue initiates a StoredStatQueue out of StatQueue func NewStoredStatQueue(sq *StatQueue, ms Marshaler) (sSQ *StoredStatQueue, err error) { sSQ = &StoredStatQueue{ diff --git a/engine/onstor_it_test.go b/engine/onstor_it_test.go index bbde0bbf1..5a019df92 100644 --- a/engine/onstor_it_test.go +++ b/engine/onstor_it_test.go @@ -1959,7 +1959,7 @@ func testOnStorITCRUDStatQueueProfile(t *testing.T) { QueueLength: 2, TTL: timeTTL, Metrics: []string{}, - Store: true, + Stored: true, Thresholds: []string{}, } if _, rcvErr := onStor.GetStatQueueProfile(sq.Tenant, sq.ID, true, utils.NonTransactional); rcvErr != utils.ErrNotFound { diff --git a/engine/statmetrics.go b/engine/statmetrics.go index b65cf8594..ff750788a 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -20,9 +20,10 @@ package engine import ( "fmt" - "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" + "strconv" + "time" ) // NewStatMetric instantiates the StatMetric @@ -31,6 +32,9 @@ func NewStatMetric(metricID string) (sm StatMetric, err error) { metrics := map[string]func() (StatMetric, error){ utils.MetaASR: NewASR, utils.MetaACD: NewACD, + utils.MetaTCD: NewTCD, + utils.MetaACC: NewACC, + utils.MetaTCC: NewTCC, } if _, has := metrics[metricID]; !has { return nil, fmt.Errorf("unsupported metric: %s", metricID) @@ -134,25 +138,24 @@ func (asr *StatASR) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { } func NewACD() (StatMetric, error) { - return &StatACD{Events: make(map[string]float64)}, nil + return &StatACD{Events: make(map[string]time.Duration)}, nil } // ACD implements AverageCallDuration metric type StatACD struct { - Sum float64 - Count float64 - Events map[string]float64 // map[EventTenantID]Duration - val *float64 // cached ACD value + Sum time.Duration + Count int64 + Events map[string]time.Duration // map[EventTenantID]Duration + val *time.Duration // cached ACD value } -// getValue returns asr.val -func (acd *StatACD) getValue() float64 { +// getValue returns acr.val +func (acd *StatACD) getValue() time.Duration { if acd.val == nil { if acd.Count == 0 { - acd.val = utils.Float64Pointer(float64(STATS_NA)) + acd.val = utils.DurationPointer(time.Duration((-1) * time.Nanosecond)) } else { - acd.val = utils.Float64Pointer(utils.Round(acd.Sum/acd.Count, - config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + acd.val = utils.DurationPointer(time.Duration(acd.Sum.Nanoseconds() / acd.Count)) } } return *acd.val @@ -170,20 +173,27 @@ func (acd *StatACD) GetValue() (v interface{}) { } func (acd *StatACD) GetFloat64Value() (v float64) { - return acd.getValue() + if acd.Count == 0 { + return -1.0 + } + return acd.getValue().Seconds() } func (acd *StatACD) AddEvent(ev *StatEvent) (err error) { - var answered float64 + var value time.Duration if at, err := ev.AnswerTime(config.CgrConfig().DefaultTimezone); err != nil && err != utils.ErrNotFound { return err } else if !at.IsZero() { - duration, _ := ev.Usage(config.CgrConfig().DefaultTimezone) - answered = duration.Seconds() - acd.Sum += duration.Seconds() + if duration, err := ev.Usage(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else { + value = duration + acd.Sum += duration + } } - acd.Events[ev.TenantID()] = answered + acd.Events[ev.TenantID()] = value acd.Count += 1 acd.val = nil return @@ -209,3 +219,252 @@ func (acd *StatACD) Marshal(ms Marshaler) (marshaled []byte, err error) { func (acd *StatACD) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { return ms.Unmarshal(marshaled, acd) } + +func NewTCD() (StatMetric, error) { + return &StatTCD{Events: make(map[string]time.Duration)}, nil +} + +// TCD implements TotalCallDuration metric +type StatTCD struct { + Sum time.Duration + Count int64 + Events map[string]time.Duration // map[EventTenantID]Duration + val *time.Duration // cached TCD value +} + +// getValue returns tcd.val +func (tcd *StatTCD) getValue() time.Duration { + if tcd.val == nil { + if tcd.Count == 0 { + tcd.val = utils.DurationPointer(time.Duration((-1) * time.Nanosecond)) + } else { + tcd.val = utils.DurationPointer(time.Duration(tcd.Sum.Nanoseconds())) + } + } + return *tcd.val +} + +func (tcd *StatTCD) GetStringValue(fmtOpts string) (val string) { + if tcd.Count == 0 { + return utils.NOT_AVAILABLE + } + return fmt.Sprintf("%+v", tcd.getValue()) +} + +func (tcd *StatTCD) GetValue() (v interface{}) { + return tcd.getValue() +} + +func (tcd *StatTCD) GetFloat64Value() (v float64) { + if tcd.Count == 0 { + return -1.0 + } + return tcd.getValue().Seconds() +} + +func (tcd *StatTCD) AddEvent(ev *StatEvent) (err error) { + var value time.Duration + if at, err := ev.AnswerTime(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else if !at.IsZero() { + if duration, err := ev.Usage(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else { + value = duration + tcd.Sum += duration + } + + } + tcd.Events[ev.TenantID()] = value + tcd.Count += 1 + tcd.val = nil + return +} + +func (tcd *StatTCD) RemEvent(evTenantID string) (err error) { + duration, has := tcd.Events[evTenantID] + if !has { + return utils.ErrNotFound + } + if duration != 0 { + tcd.Sum -= duration + } + tcd.Count -= 1 + delete(tcd.Events, evTenantID) + tcd.val = nil + return +} + +func (tcd *StatTCD) Marshal(ms Marshaler) (marshaled []byte, err error) { + return ms.Marshal(tcd) +} + +func (tcd *StatTCD) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { + return ms.Unmarshal(marshaled, tcd) +} + +func NewACC() (StatMetric, error) { + return &StatACC{Events: make(map[string]float64)}, nil +} + +// ACC implements AverageCallCost metric +type StatACC struct { + Sum float64 + Count float64 + Events map[string]float64 // map[EventTenantID]Cost + val *float64 // cached ACC value +} + +// getValue returns tcd.val +func (acc *StatACC) getValue() float64 { + if acc.val == nil { + if acc.Count == 0 { + acc.val = utils.Float64Pointer(float64(STATS_NA)) + } else { + acc.val = utils.Float64Pointer(utils.Round((acc.Sum / acc.Count * 100), + config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + } + } + return *acc.val +} + +func (acc *StatACC) GetStringValue(fmtOpts string) (val string) { + if acc.Count == 0 { + return utils.NOT_AVAILABLE + } + return fmt.Sprintf("%s", strconv.FormatFloat(acc.getValue(), 'E', -1, 64)) +} + +func (acc *StatACC) GetValue() (v interface{}) { + return acc.getValue() +} + +func (acc *StatACC) GetFloat64Value() (v float64) { + return acc.getValue() +} + +func (acc *StatACC) AddEvent(ev *StatEvent) (err error) { + var value float64 + if at, err := ev.AnswerTime(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else if !at.IsZero() { + if cost, err := ev.Cost(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else if cost >= 0 { + value = cost + acc.Sum += cost + } + } + acc.Events[ev.TenantID()] = value + acc.Count += 1 + acc.val = nil + return +} + +func (acc *StatACC) RemEvent(evTenantID string) (err error) { + cost, has := acc.Events[evTenantID] + if !has { + return utils.ErrNotFound + } + if cost != 0 { + acc.Sum -= cost + } + acc.Count -= 1 + delete(acc.Events, evTenantID) + acc.val = nil + return +} + +func (acc *StatACC) Marshal(ms Marshaler) (marshaled []byte, err error) { + return ms.Marshal(acc) +} + +func (acc *StatACC) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { + return ms.Unmarshal(marshaled, acc) +} + +func NewTCC() (StatMetric, error) { + return &StatTCC{Events: make(map[string]float64)}, nil +} + +// TCC implements TotalCallCost metric +type StatTCC struct { + Sum float64 + Count float64 + Events map[string]float64 // map[EventTenantID]Cost + val *float64 // cached TCC value +} + +// getValue returns tcd.val +func (tcc *StatTCC) getValue() float64 { + if tcc.val == nil { + if tcc.Count == 0 { + tcc.val = utils.Float64Pointer(float64(STATS_NA)) + } else { + tcc.val = utils.Float64Pointer(utils.Round(tcc.Sum, + config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + } + } + return *tcc.val +} + +func (tcc *StatTCC) GetStringValue(fmtOpts string) (val string) { + if tcc.Count == 0 { + return utils.NOT_AVAILABLE + } + return fmt.Sprintf("%s", strconv.FormatFloat(tcc.getValue(), 'E', -1, 64)) +} + +func (tcc *StatTCC) GetValue() (v interface{}) { + return tcc.getValue() +} + +func (tcc *StatTCC) GetFloat64Value() (v float64) { + return tcc.getValue() +} + +func (tcc *StatTCC) AddEvent(ev *StatEvent) (err error) { + var value float64 + if at, err := ev.AnswerTime(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else if !at.IsZero() { + if cost, err := ev.Cost(config.CgrConfig().DefaultTimezone); err != nil && + err != utils.ErrNotFound { + return err + } else if cost >= 0 { + value = cost + tcc.Sum += cost + } + } + tcc.Events[ev.TenantID()] = value + tcc.Count += 1 + tcc.val = nil + return +} + +func (tcc *StatTCC) RemEvent(evTenantID string) (err error) { + cost, has := tcc.Events[evTenantID] + if !has { + return utils.ErrNotFound + } + if cost != 0 { + tcc.Sum -= cost + } + tcc.Count -= 1 + delete(tcc.Events, evTenantID) + tcc.val = nil + return +} + +func (tcc *StatTCC) Marshal(ms Marshaler) (marshaled []byte, err error) { + return ms.Marshal(tcc) +} + +func (tcc *StatTCC) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { + return ms.Unmarshal(marshaled, tcc) +} diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 0dd646c5e..3e9e33162 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -46,15 +46,27 @@ func TestASRGetStringValue(t *testing.T) { if strVal := asr.GetStringValue(""); strVal != "50%" { t.Errorf("wrong asr value: %s", strVal) } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}} + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}} + asr.AddEvent(ev4) + asr.AddEvent(ev5) asr.RemEvent(ev.TenantID()) - if strVal := asr.GetStringValue(""); strVal != "0%" { + if strVal := asr.GetStringValue(""); strVal != "66.66667%" { t.Errorf("wrong asr value: %s", strVal) } asr.RemEvent(ev2.TenantID()) + if strVal := asr.GetStringValue(""); strVal != "100%" { + t.Errorf("wrong asr value: %s", strVal) + } + asr.RemEvent(ev4.TenantID()) + asr.RemEvent(ev5.TenantID()) if strVal := asr.GetStringValue(""); strVal != utils.NOT_AVAILABLE { t.Errorf("wrong asr value: %s", strVal) } - } func TestASRGetValue(t *testing.T) { @@ -77,11 +89,27 @@ func TestASRGetValue(t *testing.T) { if v := asr.GetValue(); v != 50.0 { t.Errorf("wrong asr value: %f", v) } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}} + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}} + asr.AddEvent(ev4) + asr.AddEvent(ev5) asr.RemEvent(ev.TenantID()) - if v := asr.GetValue(); v != 0.0 { + if v := asr.GetValue(); v != 66.666670 { t.Errorf("wrong asr value: %f", v) } asr.RemEvent(ev2.TenantID()) + if v := asr.GetValue(); v != 100.0 { + t.Errorf("wrong asr value: %f", v) + } + asr.RemEvent(ev4.TenantID()) + if v := asr.GetValue(); v != 100.0 { + t.Errorf("wrong asr value: %f", v) + } + asr.RemEvent(ev5.TenantID()) if v := asr.GetValue(); v != -1.0 { t.Errorf("wrong asr value: %f", v) } @@ -98,29 +126,107 @@ func TestACDGetStringValue(t *testing.T) { t.Errorf("wrong acd value: %s", strVal) } acd.AddEvent(ev) - if strVal := acd.GetStringValue(""); strVal != "10" { + if strVal := acd.GetStringValue(""); strVal != "10s" { t.Errorf("wrong acd value: %s", strVal) } ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} acd.AddEvent(ev2) acd.AddEvent(ev3) - if strVal := acd.GetStringValue(""); strVal != "3.33333" { + if strVal := acd.GetStringValue(""); strVal != "3.333333333s" { t.Errorf("wrong acd value: %s", strVal) } acd.RemEvent(ev3.TenantID()) - if strVal := acd.GetStringValue(""); strVal != "5" { + if strVal := acd.GetStringValue(""); strVal != "5s" { t.Errorf("wrong acd value: %s", strVal) } acd.RemEvent(ev.TenantID()) - if strVal := acd.GetStringValue(""); strVal != "0" { + if strVal := acd.GetStringValue(""); strVal != "0s" { + t.Errorf("wrong acd value: %s", strVal) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + acd.AddEvent(ev4) + if strVal := acd.GetStringValue(""); strVal != "30s" { + t.Errorf("wrong acd value: %s", strVal) + } + acd.AddEvent(ev5) + if strVal := acd.GetStringValue(""); strVal != "50s" { t.Errorf("wrong acd value: %s", strVal) } acd.RemEvent(ev2.TenantID()) + if strVal := acd.GetStringValue(""); strVal != "1m15s" { + t.Errorf("wrong acd value: %s", strVal) + } + acd.RemEvent(ev5.TenantID()) + acd.RemEvent(ev4.TenantID()) + acd.RemEvent(ev5.TenantID()) if strVal := acd.GetStringValue(""); strVal != utils.NOT_AVAILABLE { t.Errorf("wrong acd value: %s", strVal) } +} +func TestACDGetFloat64Value(t *testing.T) { + acd, _ := NewACD() + 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)}} + acd.AddEvent(ev) + if v := acd.GetFloat64Value(); v != 10.0 { + t.Errorf("wrong acd value: %f", v) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + acd.AddEvent(ev2) + if v := acd.GetFloat64Value(); v != 5.0 { + t.Errorf("wrong acd value: %f", v) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + acd.AddEvent(ev4) + if strVal := acd.GetFloat64Value(); strVal != 23.333333333 { + t.Errorf("wrong acd value: %f", strVal) + } + acd.AddEvent(ev5) + if strVal := acd.GetFloat64Value(); strVal != 40.0 { + t.Errorf("wrong acd value: %f", strVal) + } + acd.RemEvent(ev2.TenantID()) + if strVal := acd.GetFloat64Value(); strVal != 53.333333333 { + t.Errorf("wrong acd value: %f", strVal) + } + acd.RemEvent(ev4.TenantID()) + if strVal := acd.GetFloat64Value(); strVal != 50.0 { + t.Errorf("wrong acd value: %f", strVal) + } + acd.RemEvent(ev.TenantID()) + if strVal := acd.GetFloat64Value(); strVal != 90.0 { + t.Errorf("wrong acd value: %f", strVal) + } + acd.RemEvent(ev5.TenantID()) + if strVal := acd.GetFloat64Value(); strVal != -1.0 { + t.Errorf("wrong acd value: %f", strVal) + } } func TestACDGetValue(t *testing.T) { @@ -130,27 +236,221 @@ func TestACDGetValue(t *testing.T) { "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), "Usage": time.Duration(10 * time.Second)}} acd.AddEvent(ev) - if v := acd.GetValue(); v != 10.0 { - t.Errorf("wrong asr value: %f", v) + if v := acd.GetValue(); v != time.Duration(10*time.Second) { + t.Errorf("wrong acd value: %+v", v) } - ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + "Usage": time.Duration(8 * time.Second)}} ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} acd.AddEvent(ev2) acd.AddEvent(ev3) - if v := acd.GetValue(); v != 3.33333 { - t.Errorf("wrong asr value: %f", v) - } - acd.RemEvent(ev3.TenantID()) - if v := acd.GetValue(); v != 5.0 { - t.Errorf("wrong asr value: %f", v) + if v := acd.GetValue(); v != time.Duration(6*time.Second) { + t.Errorf("wrong acd value: %+v", v) } acd.RemEvent(ev.TenantID()) - if v := acd.GetValue(); v != 0.0 { - t.Errorf("wrong asr value: %f", v) + if v := acd.GetValue(); v != time.Duration(4*time.Second) { + t.Errorf("wrong acd value: %+v", v) } acd.RemEvent(ev2.TenantID()) - if v := acd.GetValue(); v != -1.0 { - t.Errorf("wrong asr value: %f", v) + if v := acd.GetValue(); v != time.Duration(0*time.Second) { + t.Errorf("wrong acd value: %+v", v) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(4*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + acd.AddEvent(ev4) + acd.AddEvent(ev5) + if v := acd.GetValue(); v != time.Duration(1*time.Minute+50*time.Second) { + t.Errorf("wrong acd value: %+v", v) + } + acd.RemEvent(ev5.TenantID()) + acd.RemEvent(ev4.TenantID()) + if v := acd.GetValue(); v != time.Duration(0*time.Second) { + t.Errorf("wrong acd value: %+v", v) + } + acd.RemEvent(ev3.TenantID()) + if v := acd.GetValue(); v != time.Duration((-1)*time.Nanosecond) { + t.Errorf("wrong acd value: %+v", v) + } +} + +func TestTCDGetStringValue(t *testing.T) { + tcd, _ := NewTCD() + ev := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_1", + Fields: map[string]interface{}{ + "Usage": time.Duration(10 * time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }} + if strVal := tcd.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.AddEvent(ev) + if strVal := tcd.GetStringValue(""); strVal != "10s" { + t.Errorf("wrong tcd value: %s", strVal) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2", + Fields: map[string]interface{}{ + "Usage": time.Duration(10 * time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }} + ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} + tcd.AddEvent(ev2) + tcd.AddEvent(ev3) + if strVal := tcd.GetStringValue(""); strVal != "20s" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev2.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != "10s" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != "0s" { + t.Errorf("wrong tcd value: %s", strVal) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + tcd.AddEvent(ev4) + tcd.AddEvent(ev5) + if strVal := tcd.GetStringValue(""); strVal != "2m30s" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev4.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != "1m30s" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev5.TenantID()) + tcd.RemEvent(ev3.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong tcd value: %s", strVal) + } +} + +func TestTCDGetFloat64Value(t *testing.T) { + tcd, _ := NewTCD() + 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)}} + tcd.AddEvent(ev) + if v := tcd.GetFloat64Value(); v != 10.0 { + t.Errorf("wrong tcd value: %f", v) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + tcd.AddEvent(ev2) + if v := tcd.GetFloat64Value(); v != 10.0 { + t.Errorf("wrong tcd value: %f", v) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + tcd.AddEvent(ev4) + if strVal := tcd.GetFloat64Value(); strVal != 70.0 { + t.Errorf("wrong tcd value: %f", strVal) + } + tcd.AddEvent(ev5) + if strVal := tcd.GetFloat64Value(); strVal != 160.0 { + t.Errorf("wrong tcd value: %f", strVal) + } + tcd.RemEvent(ev2.TenantID()) + if strVal := tcd.GetFloat64Value(); strVal != 160.0 { + t.Errorf("wrong tcd value: %f", strVal) + } + tcd.RemEvent(ev4.TenantID()) + if strVal := tcd.GetFloat64Value(); strVal != 100.0 { + t.Errorf("wrong tcd value: %f", strVal) + } + tcd.RemEvent(ev.TenantID()) + if strVal := tcd.GetFloat64Value(); strVal != 90.0 { + t.Errorf("wrong tcd value: %f", strVal) + } + tcd.RemEvent(ev5.TenantID()) + if strVal := tcd.GetFloat64Value(); strVal != -1.0 { + t.Errorf("wrong tcd value: %f", strVal) + } +} + +func TestTCDGetValue(t *testing.T) { + tcd, _ := NewTCD() + 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)}} + tcd.AddEvent(ev) + if v := tcd.GetValue(); v != time.Duration(10*time.Second) { + t.Errorf("wrong tcd value: %+v", v) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + "Usage": time.Duration(5 * time.Second)}} + ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} + tcd.AddEvent(ev2) + tcd.AddEvent(ev3) + if v := tcd.GetValue(); v != time.Duration(15*time.Second) { + t.Errorf("wrong tcd value: %+v", v) + } + tcd.RemEvent(ev.TenantID()) + if v := tcd.GetValue(); v != time.Duration(5*time.Second) { + t.Errorf("wrong tcd value: %+v", v) + } + tcd.RemEvent(ev2.TenantID()) + if v := tcd.GetValue(); v != time.Duration(0*time.Second) { + t.Errorf("wrong tcd value: %+v", v) + } + ev4 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_4", + Fields: map[string]interface{}{ + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + ev5 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_5", + Fields: map[string]interface{}{ + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + }, + } + tcd.AddEvent(ev4) + tcd.AddEvent(ev5) + if v := tcd.GetValue(); v != time.Duration(2*time.Minute+30*time.Second) { + t.Errorf("wrong tcd value: %+v", v) + } + tcd.RemEvent(ev5.TenantID()) + tcd.RemEvent(ev4.TenantID()) + if v := tcd.GetValue(); v != time.Duration(0*time.Second) { + t.Errorf("wrong tcd value: %+v", v) + } + tcd.RemEvent(ev3.TenantID()) + if v := tcd.GetValue(); v != time.Duration((-1)*time.Nanosecond) { + t.Errorf("wrong tcd value: %+v", v) } - } diff --git a/utils/consts.go b/utils/consts.go index 565a6a19d..5bc451af6 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -420,6 +420,11 @@ const ( ID = "ID" MetaASR = "*asr" MetaACD = "*acd" + MetaTCD = "*tcd" + MetaACC = "*acc" + MetaTCC = "*tcc" + MetaPDD = "*pdd" + MetaDDC = "*ddc" CacheDestinations = "destinations" CacheReverseDestinations = "reverse_destinations" CacheRatingPlans = "rating_plans"