From be0d8b27d8037e9a0f1d6c8de520ab4d800d211f Mon Sep 17 00:00:00 2001 From: TeoV Date: Mon, 25 Sep 2017 14:11:55 +0300 Subject: [PATCH 1/8] Add StatACD and add test for ACD and ASR --- engine/libstats.go | 16 ++++++ engine/statmetrics.go | 59 ++++++++++++++++---- engine/statmetrics_test.go | 108 ++++++++++++++++++++++++++++++------- 3 files changed, 154 insertions(+), 29 deletions(-) diff --git a/engine/libstats.go b/engine/libstats.go index b8e47dc5e..ce09b4d4a 100755 --- a/engine/libstats.go +++ b/engine/libstats.go @@ -75,6 +75,22 @@ func (se StatEvent) AnswerTime(timezone string) (at time.Time, err error) { return utils.ParseTimeDetectLayout(atStr, timezone) } +// Usage returns the Usage of StatEvent +func (se StatEvent) Usage(timezone string) (at time.Duration, err error) { + usIf, has := se.Fields[utils.USAGE] + if !has { + return at, utils.ErrNotFound + } + if us, canCast := usIf.(time.Duration); canCast { + return us, nil + } + usStr, canCast := usIf.(string) + if !canCast { + return at, errors.New("cannot cast to string") + } + return utils.ParseDurationWithSecs(usStr) +} + // 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 1fb76d789..b65cf8594 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -20,7 +20,6 @@ package engine import ( "fmt" - "time" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" @@ -51,7 +50,7 @@ type StatMetric interface { } func NewASR() (StatMetric, error) { - return new(StatASR), nil + return &StatASR{Events: make(map[string]bool)}, nil } // ASR implements AverageSuccessRatio metric @@ -135,38 +134,78 @@ func (asr *StatASR) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { } func NewACD() (StatMetric, error) { - return new(StatACD), nil + return &StatACD{Events: make(map[string]float64)}, nil } // ACD implements AverageCallDuration metric type StatACD struct { - Sum time.Duration - Count int + Sum float64 + Count float64 + Events map[string]float64 // map[EventTenantID]Duration + val *float64 // cached ACD value +} + +// getValue returns asr.val +func (acd *StatACD) getValue() float64 { + if acd.val == nil { + if acd.Count == 0 { + acd.val = utils.Float64Pointer(float64(STATS_NA)) + } else { + acd.val = utils.Float64Pointer(utils.Round(acd.Sum/acd.Count, + config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + } + } + return *acd.val } func (acd *StatACD) GetStringValue(fmtOpts string) (val string) { - return + if acd.Count == 0 { + return utils.NOT_AVAILABLE + } + return fmt.Sprintf("%+v", acd.getValue()) } func (acd *StatACD) GetValue() (v interface{}) { - return + return acd.getValue() } func (acd *StatACD) GetFloat64Value() (v float64) { - return float64(STATS_NA) + return acd.getValue() } func (acd *StatACD) AddEvent(ev *StatEvent) (err error) { + var answered float64 + 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() + } + acd.Events[ev.TenantID()] = answered + acd.Count += 1 + acd.val = nil return } func (acd *StatACD) RemEvent(evTenantID string) (err error) { + duration, has := acd.Events[evTenantID] + if !has { + return utils.ErrNotFound + } + if duration != 0 { + acd.Sum -= duration + } + acd.Count -= 1 + delete(acd.Events, evTenantID) + acd.val = nil return } func (acd *StatACD) Marshal(ms Marshaler) (marshaled []byte, err error) { - return + return ms.Marshal(acd) } func (acd *StatACD) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { - return + return ms.Unmarshal(marshaled, acd) } diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 87beed89d..0dd646c5e 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -17,39 +17,40 @@ along with this program. If not, see */ package engine -/* import ( + "github.com/cgrates/cgrates/utils" "testing" "time" - - "github.com/cgrates/cgrates/utils" ) func TestASRGetStringValue(t *testing.T) { asr, _ := NewASR() + ev := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_1", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}} if strVal := asr.GetStringValue(""); strVal != utils.NOT_AVAILABLE { t.Errorf("wrong asr value: %s", strVal) } - ev := engine.StatsEvent{ - "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)} asr.AddEvent(ev) if strVal := asr.GetStringValue(""); strVal != "100%" { t.Errorf("wrong asr value: %s", strVal) } - asr.AddEvent(engine.StatsEvent{}) - asr.AddEvent(engine.StatsEvent{}) + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} + asr.AddEvent(ev2) + asr.AddEvent(ev3) if strVal := asr.GetStringValue(""); strVal != "33.33333%" { t.Errorf("wrong asr value: %s", strVal) } - asr.RemEvent(engine.StatsEvent{}) + asr.RemEvent(ev3.TenantID()) if strVal := asr.GetStringValue(""); strVal != "50%" { t.Errorf("wrong asr value: %s", strVal) } - asr.RemEvent(ev) + asr.RemEvent(ev.TenantID()) if strVal := asr.GetStringValue(""); strVal != "0%" { t.Errorf("wrong asr value: %s", strVal) } - asr.RemEvent(engine.StatsEvent{}) + asr.RemEvent(ev2.TenantID()) if strVal := asr.GetStringValue(""); strVal != utils.NOT_AVAILABLE { t.Errorf("wrong asr value: %s", strVal) } @@ -58,29 +59,98 @@ func TestASRGetStringValue(t *testing.T) { func TestASRGetValue(t *testing.T) { asr, _ := NewASR() - ev := engine.StatsEvent{ - "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), - } + ev := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_1", + Fields: map[string]interface{}{ + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}} asr.AddEvent(ev) if v := asr.GetValue(); v != 100.0 { t.Errorf("wrong asr value: %f", v) } - asr.AddEvent(engine.StatsEvent{}) - asr.AddEvent(engine.StatsEvent{}) + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} + asr.AddEvent(ev2) + asr.AddEvent(ev3) if v := asr.GetValue(); v != 33.33333 { t.Errorf("wrong asr value: %f", v) } - asr.RemEvent(engine.StatsEvent{}) + asr.RemEvent(ev3.TenantID()) if v := asr.GetValue(); v != 50.0 { t.Errorf("wrong asr value: %f", v) } - asr.RemEvent(ev) + asr.RemEvent(ev.TenantID()) if v := asr.GetValue(); v != 0.0 { t.Errorf("wrong asr value: %f", v) } - asr.RemEvent(engine.StatsEvent{}) + asr.RemEvent(ev2.TenantID()) if v := asr.GetValue(); v != -1.0 { t.Errorf("wrong asr value: %f", v) } } -*/ + +func TestACDGetStringValue(t *testing.T) { + acd, _ := NewACD() + 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 := acd.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong acd value: %s", strVal) + } + acd.AddEvent(ev) + if strVal := acd.GetStringValue(""); strVal != "10" { + 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" { + t.Errorf("wrong acd value: %s", strVal) + } + acd.RemEvent(ev3.TenantID()) + if strVal := acd.GetStringValue(""); strVal != "5" { + t.Errorf("wrong acd value: %s", strVal) + } + acd.RemEvent(ev.TenantID()) + if strVal := acd.GetStringValue(""); strVal != "0" { + t.Errorf("wrong acd value: %s", strVal) + } + acd.RemEvent(ev2.TenantID()) + if strVal := acd.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong acd value: %s", strVal) + } + +} + +func TestACDGetValue(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.GetValue(); v != 10.0 { + t.Errorf("wrong asr value: %f", v) + } + ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} + 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) + } + acd.RemEvent(ev.TenantID()) + if v := acd.GetValue(); v != 0.0 { + t.Errorf("wrong asr value: %f", v) + } + acd.RemEvent(ev2.TenantID()) + if v := acd.GetValue(); v != -1.0 { + t.Errorf("wrong asr value: %f", v) + } + +} From c04a7b74e8253f7e53bd6c77848a3a46d2b0eff7 Mon Sep 17 00:00:00 2001 From: TeoV Date: Mon, 25 Sep 2017 16:46:28 +0300 Subject: [PATCH 2/8] Modify the struct of StatACD (Sum float64 -> Sum time.Duration) add StatTCD and test for it --- engine/statmetrics.go | 112 +++++++++++++++++++++++++++++++++---- engine/statmetrics_test.go | 85 ++++++++++++++++++++++++++-- utils/consts.go | 5 ++ 3 files changed, 185 insertions(+), 17 deletions(-) diff --git a/engine/statmetrics.go b/engine/statmetrics.go index b65cf8594..32a23379f 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -20,9 +20,9 @@ package engine import ( "fmt" - "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" + "time" ) // NewStatMetric instantiates the StatMetric @@ -31,6 +31,7 @@ func NewStatMetric(metricID string) (sm StatMetric, err error) { metrics := map[string]func() (StatMetric, error){ utils.MetaASR: NewASR, utils.MetaACD: NewACD, + utils.MetaTCD: NewTCD, } if _, has := metrics[metricID]; !has { return nil, fmt.Errorf("unsupported metric: %s", metricID) @@ -134,24 +135,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 + Sum time.Duration Count float64 - Events map[string]float64 // map[EventTenantID]Duration - val *float64 // cached ACD value + Events map[string]time.Duration // map[EventTenantID]Duration + val *float64 // cached ACD value } -// getValue returns asr.val +// getValue returns acr.val func (acd *StatACD) getValue() float64 { if acd.val == nil { if acd.Count == 0 { acd.val = utils.Float64Pointer(float64(STATS_NA)) } else { - acd.val = utils.Float64Pointer(utils.Round(acd.Sum/acd.Count, + acd.val = utils.Float64Pointer(utils.Round(acd.Sum.Seconds()/acd.Count, config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) } } @@ -174,16 +175,20 @@ func (acd *StatACD) GetFloat64Value() (v float64) { } 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 +214,86 @@ 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 float64 + Events map[string]time.Duration // map[EventTenantID]Duration + val *float64 // cached TCD value +} + +// getValue returns tcd.val +func (tcd *StatTCD) getValue() float64 { + if tcd.val == nil { + if tcd.Count == 0 { + tcd.val = utils.Float64Pointer(float64(STATS_NA)) + } else { + tcd.val = utils.Float64Pointer(utils.Round(tcd.Sum.Seconds(), + config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + } + } + 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) { + return tcd.getValue() +} + +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) +} diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 0dd646c5e..32845ca8c 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -131,26 +131,101 @@ func TestACDGetValue(t *testing.T) { "Usage": time.Duration(10 * time.Second)}} acd.AddEvent(ev) if v := acd.GetValue(); v != 10.0 { - t.Errorf("wrong asr value: %f", v) + t.Errorf("wrong acd value: %f", v) } ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2"} 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) + t.Errorf("wrong acd value: %f", v) } acd.RemEvent(ev3.TenantID()) if v := acd.GetValue(); v != 5.0 { - t.Errorf("wrong asr value: %f", v) + t.Errorf("wrong acd value: %f", v) } acd.RemEvent(ev.TenantID()) if v := acd.GetValue(); v != 0.0 { - t.Errorf("wrong asr value: %f", v) + t.Errorf("wrong acd value: %f", v) } acd.RemEvent(ev2.TenantID()) if v := acd.GetValue(); v != -1.0 { - t.Errorf("wrong asr value: %f", v) + t.Errorf("wrong acd value: %f", 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 != "10" { + 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 != "20" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev2.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != "10" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != "0" { + t.Errorf("wrong tcd value: %s", strVal) + } + tcd.RemEvent(ev3.TenantID()) + if strVal := tcd.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong tcd value: %s", 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 != 10.0 { + t.Errorf("wrong tcd value: %f", 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 != 15.000000 { + t.Errorf("wrong tcd value: %f", v) + } + tcd.RemEvent(ev.TenantID()) + if v := tcd.GetValue(); v != 5.000000 { + t.Errorf("wrong tcd value: %f", v) + } + tcd.RemEvent(ev2.TenantID()) + if v := tcd.GetValue(); v != 0.0 { + t.Errorf("wrong tcd value: %f", v) + } + tcd.RemEvent(ev3.TenantID()) + if v := tcd.GetValue(); v != -1.0 { + t.Errorf("wrong tcd value: %f", v) } } diff --git a/utils/consts.go b/utils/consts.go index 473b23bcd..7332eb3fe 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" From 52063c8899781243169f292443b477c0a6abe417 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 Sep 2017 14:28:19 +0200 Subject: [PATCH 3/8] StatsV1.GetQueueIDs API, add integration tests for StatS --- apier/v1/stats.go | 5 ++ apier/v1/stats_it_test.go | 79 +++++++++++++---------------- data/tariffplans/tutorial/Stats.csv | 2 +- engine/stats.go | 15 ++++++ engine/tp_reader.go | 14 ++++- 5 files changed, 69 insertions(+), 46 deletions(-) diff --git a/apier/v1/stats.go b/apier/v1/stats.go index 3c46e457f..bc857c176 100644 --- a/apier/v1/stats.go +++ b/apier/v1/stats.go @@ -100,6 +100,11 @@ func (stsv1 *StatSV1) Call(serviceMethod string, args interface{}, reply interfa return err } +// GetQueueIDs returns list of queueIDs registered for a tenant +func (stsv1 *StatSV1) GetQueueIDs(tenant string, qIDs *[]string) error { + return stsv1.sS.V1GetQueueIDs(tenant, qIDs) +} + // ProcessEvent returns processes a new Event func (stsv1 *StatSV1) ProcessEvent(ev *engine.StatEvent, reply *string) error { return stsv1.sS.V1ProcessEvent(ev, reply) diff --git a/apier/v1/stats_it_test.go b/apier/v1/stats_it_test.go index b32173f09..68d0adb30 100644 --- a/apier/v1/stats_it_test.go +++ b/apier/v1/stats_it_test.go @@ -19,7 +19,6 @@ along with this program. If not, see */ package v1 -/* import ( "math/rand" "net/rpc" @@ -43,16 +42,22 @@ var ( statsDelay int ) -var evs = []engine.StatsEvent{ - engine.StatsEvent{ - utils.ID: "event1", - utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local()}, - engine.StatsEvent{ - utils.ID: "event2", - utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local()}, - engine.StatsEvent{ - utils.ID: "event3", - utils.SETUP_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local()}, +var evs = []*engine.StatEvent{ + &engine.StatEvent{ + Tenant: "cgrates.org", + ID: "event1", + Fields: map[string]interface{}{ + utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local()}}, + &engine.StatEvent{ + Tenant: "cgrates.org", + ID: "event2", + Fields: map[string]interface{}{ + utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local()}}, + &engine.StatEvent{ + Tenant: "cgrates.org", + ID: "event3", + Fields: map[string]interface{}{ + utils.SETUP_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC).Local()}}, } func init() { @@ -66,15 +71,15 @@ var sTestsStatSV1 = []func(t *testing.T){ testV1STSRpcConn, testV1STSFromFolder, testV1STSGetStats, - testV1STSProcessEvent, - testV1STSGetStatQueueProfileBeforeSet, - testV1STSSetStatQueueProfile, - testV1STSGetStatQueueProfileAfterSet, - testV1STSUpdateStatQueueProfile, - testV1STSGetStatQueueProfileAfterUpdate, - testV1STSRemoveStatQueueProfile, - testV1STSGetStatQueueProfileAfterRemove, - testV1STSStopEngine, + //testV1STSProcessEvent, + //testV1STSGetStatQueueProfileBeforeSet, + //testV1STSSetStatQueueProfile, + //testV1STSGetStatQueueProfileAfterSet, + //testV1STSUpdateStatQueueProfile, + //testV1STSGetStatQueueProfileAfterUpdate, + //testV1STSRemoveStatQueueProfile, + //testV1STSGetStatQueueProfileAfterRemove, + //testV1STSStopEngine, } //Test start here @@ -128,47 +133,35 @@ func testV1STSRpcConn(t *testing.T) { func testV1STSFromFolder(t *testing.T) { var reply string - time.Sleep(time.Duration(2000) * time.Millisecond) attrs := &utils.AttrLoadTpFromFolder{FolderPath: path.Join(*dataDir, "tariffplans", "tutorial")} if err := stsV1Rpc.Call("ApierV1.LoadTariffPlanFromFolder", attrs, &reply); err != nil { t.Error(err) } - time.Sleep(time.Duration(1000) * time.Millisecond) } func testV1STSGetStats(t *testing.T) { var reply []string - // first attempt should be empty since there is no queue in cache yet - if err := stsV1Rpc.Call("StatSV1.GetQueueIDs", struct{}{}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() { - t.Error(err) - } - var metrics map[string]string - if err := stsV1Rpc.Call("StatSV1.GetStringMetrics", "Stats1", &metrics); err == nil || err.Error() != utils.ErrNotFound.Error() { - t.Error(err) - } - var replyStr string - if err := stsV1Rpc.Call("StatSV1.LoadQueues", nil, &replyStr); err != nil { - t.Error(err) - } else if replyStr != utils.OK { - t.Errorf("reply received: %s", replyStr) - } - expectedIDs := []string{"Stats1"} - if err := stsV1Rpc.Call("StatSV1.GetQueueIDs", struct{}{}, &reply); err != nil { + expectedIDs := []string{"STATS_1"} + if err := stsV1Rpc.Call("StatSV1.GetQueueIDs", "cgrates.org", &reply); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectedIDs, reply) { t.Errorf("expecting: %+v, received reply: %s", expectedIDs, reply) } + var metrics map[string]string expectedMetrics := map[string]string{ utils.MetaASR: utils.NOT_AVAILABLE, utils.MetaACD: "", } - if err := stsV1Rpc.Call("StatSV1.GetStringMetrics", "Stats1", &metrics); err != nil { + if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", + &utils.TenantID{"cgrates.org", expectedIDs[0]}, &metrics); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectedMetrics, metrics) { t.Errorf("expecting: %+v, received reply: %s", expectedMetrics, metrics) } } +/* + func testV1STSProcessEvent(t *testing.T) { var reply string if err := stsV1Rpc.Call("StatSV1.ProcessEvent", @@ -203,7 +196,7 @@ func testV1STSProcessEvent(t *testing.T) { utils.MetaACD: "", } var metrics map[string]string - if err := stsV1Rpc.Call("StatSV1.GetStringMetrics", "Stats1", &metrics); err != nil { + if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", "Stats1", &metrics); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectedMetrics, metrics) { t.Errorf("expecting: %+v, received reply: %s", expectedMetrics, metrics) @@ -337,11 +330,11 @@ func BenchmarkStatSV1SetEvent(b *testing.B) { } } -// BenchmarkStatSV1GetStringMetrics 20000 94607 ns/op -func BenchmarkStatSV1GetStringMetrics(b *testing.B) { +// BenchmarkStatSV1GetQueueStringMetrics 20000 94607 ns/op +func BenchmarkStatSV1GetQueueStringMetrics(b *testing.B) { for i := 0; i < b.N; i++ { var metrics map[string]string - if err := stsV1Rpc.Call("StatSV1.GetStringMetrics", "Stats1", + if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", "Stats1", &metrics); err != nil { b.Error(err) } diff --git a/data/tariffplans/tutorial/Stats.csv b/data/tariffplans/tutorial/Stats.csv index 6832735d3..b3122b4db 100755 --- a/data/tariffplans/tutorial/Stats.csv +++ b/data/tariffplans/tutorial/Stats.csv @@ -1,2 +1,2 @@ #Tenant[0],Id[1],FilterType[2],FilterFieldName[3],FilterFieldValues[4],ActivationInterval[5],QueueLength[6],TTL[7],Metrics[8],Blocker[9],Stored[10],Weight[11],Thresholds[12] -cgrates.org,STATS_1,*string,Account,1001;1002,2014-07-29T15:00:00Z,100,1s,*asr;*acd;*acc,true,true,20,THRESH1;THRESH2 \ No newline at end of file +cgrates.org,STATS_1,*string,Account,1001;1002,2014-07-29T15:00:00Z,100,1s,*asr;*acd,true,true,20,THRESH1;THRESH2 \ No newline at end of file diff --git a/engine/stats.go b/engine/stats.go index 1d878755a..00418245e 100644 --- a/engine/stats.go +++ b/engine/stats.go @@ -307,3 +307,18 @@ func (sS *StatService) V1GetQueueFloatMetrics(args *utils.TenantID, reply *map[s *reply = metrics return } + +// V1GetQueueIDs returns list of queueIDs registered for a tenant +func (sS *StatService) V1GetQueueIDs(tenant string, qIDs *[]string) (err error) { + prfx := utils.StatQueuePrefix + tenant + utils.CONCATENATED_KEY_SEP + keys, err := sS.dm.DataDB().GetKeysForPrefix(prfx) + if err != nil { + return err + } + retIDs := make([]string, len(keys)) + for i, key := range keys { + retIDs[i] = key[len(prfx):] + } + *qIDs = retIDs + return +} diff --git a/engine/tp_reader.go b/engine/tp_reader.go index 9c2073ce6..f5040bafc 100755 --- a/engine/tp_reader.go +++ b/engine/tp_reader.go @@ -34,6 +34,7 @@ type TpReader struct { tpid string timezone string dataStorage DataDB + dm *DataManager lr LoadReader actions map[string][]*Action actionPlans map[string]*ActionPlan @@ -69,6 +70,7 @@ func NewTpReader(db DataDB, lr LoadReader, tpid, timezone string) *TpReader { tpid: tpid, timezone: timezone, dataStorage: db, + dm: NewDataManager(db), lr: lr, } tpr.Init() @@ -2010,8 +2012,16 @@ func (tpr *TpReader) WriteToDatabase(flush, verbose, disable_reverse bool) (err log.Print("StatQueues:") } for _, sqTntID := range tpr.statQueues { - if err = tpr.dataStorage.SetStoredStatQueue(&StoredStatQueue{Tenant: sqTntID.Tenant, ID: sqTntID.ID, - SQMetrics: make(map[string][]byte)}); err != nil { + sq := &StatQueue{Tenant: sqTntID.Tenant, ID: sqTntID.ID, + SQMetrics: make(map[string]StatMetric)} + for _, metricID := range tpr.sqProfiles[sqTntID.Tenant][sqTntID.ID].Metrics { + if metric, err := NewStatMetric(metricID); err != nil { + return err + } else { + sq.SQMetrics[metricID] = metric + } + } + if err = tpr.dm.SetStatQueue(sq); err != nil { return } if verbose { From b44b439dcd159efb849b55e10f96d944a7c3cf32 Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 Sep 2017 18:51:20 +0200 Subject: [PATCH 4/8] StatS matching only fields in filters, integration tests for StatSV1.ProcessEvent --- apier/v1/stats_it_test.go | 46 +++++++++++++++++++++++++------------- apier/v1/tp_it_test.go | 5 ----- config/config_defaults.go | 8 +++---- config/config_json_test.go | 13 ++++++----- config/config_test.go | 10 ++++----- engine/libstats.go | 1 - engine/stats.go | 2 +- utils/consts.go | 8 +++---- 8 files changed, 51 insertions(+), 42 deletions(-) diff --git a/apier/v1/stats_it_test.go b/apier/v1/stats_it_test.go index 68d0adb30..e8c633147 100644 --- a/apier/v1/stats_it_test.go +++ b/apier/v1/stats_it_test.go @@ -71,7 +71,7 @@ var sTestsStatSV1 = []func(t *testing.T){ testV1STSRpcConn, testV1STSFromFolder, testV1STSGetStats, - //testV1STSProcessEvent, + testV1STSProcessEvent, //testV1STSGetStatQueueProfileBeforeSet, //testV1STSSetStatQueueProfile, //testV1STSGetStatQueueProfileAfterSet, @@ -79,7 +79,7 @@ var sTestsStatSV1 = []func(t *testing.T){ //testV1STSGetStatQueueProfileAfterUpdate, //testV1STSRemoveStatQueueProfile, //testV1STSGetStatQueueProfileAfterRemove, - //testV1STSStopEngine, + testV1STSStopEngine, } //Test start here @@ -150,7 +150,7 @@ func testV1STSGetStats(t *testing.T) { var metrics map[string]string expectedMetrics := map[string]string{ utils.MetaASR: utils.NOT_AVAILABLE, - utils.MetaACD: "", + utils.MetaACD: utils.NOT_AVAILABLE, } if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", &utils.TenantID{"cgrates.org", expectedIDs[0]}, &metrics); err != nil { @@ -160,32 +160,42 @@ func testV1STSGetStats(t *testing.T) { } } -/* - func testV1STSProcessEvent(t *testing.T) { var reply string if err := stsV1Rpc.Call("StatSV1.ProcessEvent", - engine.StatsEvent{ - utils.ID: "event1", - utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}, + engine.StatEvent{ + Tenant: "cgrates.org", + ID: "event1", + Fields: map[string]interface{}{ + utils.ACCOUNT: "1001", + utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.USAGE: time.Duration(125 * time.Second)}}, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("received reply: %s", reply) } if err := stsV1Rpc.Call("StatSV1.ProcessEvent", - engine.StatsEvent{ - utils.ID: "event2", - utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}, + engine.StatEvent{ + Tenant: "cgrates.org", + ID: "event2", + Fields: map[string]interface{}{ + utils.ACCOUNT: "1002", + utils.ANSWER_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.USAGE: time.Duration(45 * time.Second)}}, &reply); err != nil { t.Error(err) } else if reply != utils.OK { t.Errorf("received reply: %s", reply) } if err := stsV1Rpc.Call("StatSV1.ProcessEvent", - engine.StatsEvent{ - utils.ID: "event3", - utils.SETUP_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC)}, + engine.StatEvent{ + Tenant: "cgrates.org", + ID: "event3", + Fields: map[string]interface{}{ + utils.ACCOUNT: "1002", + utils.SETUP_TIME: time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.USAGE: 0}}, &reply); err != nil { t.Error(err) } else if reply != utils.OK { @@ -193,16 +203,18 @@ func testV1STSProcessEvent(t *testing.T) { } expectedMetrics := map[string]string{ utils.MetaASR: "66.66667%", - utils.MetaACD: "", + utils.MetaACD: "0", } var metrics map[string]string - if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", "Stats1", &metrics); err != nil { + if err := stsV1Rpc.Call("StatSV1.GetQueueStringMetrics", &utils.TenantID{"cgrates.org", "STATS_1"}, &metrics); err != nil { t.Error(err) } else if !reflect.DeepEqual(expectedMetrics, metrics) { t.Errorf("expecting: %+v, received reply: %s", expectedMetrics, metrics) } } +/* + func testV1STSGetStatQueueProfileBeforeSet(t *testing.T) { var reply *engine.StatQueueProfile if err := stsV1Rpc.Call("ApierV1.GetStatQueueProfile", &AttrGetStatsCfg{ID: "SCFG1"}, &reply); err == nil || err.Error() != utils.ErrNotFound.Error() { @@ -300,6 +312,7 @@ func testV1STSGetStatQueueProfileAfterRemove(t *testing.T) { t.Error(err) } } +*/ func testV1STSStopEngine(t *testing.T) { if err := engine.KillEngine(100); err != nil { @@ -307,6 +320,7 @@ func testV1STSStopEngine(t *testing.T) { } } +/* // BenchmarkStatSV1SetEvent 5000 263437 ns/op func BenchmarkStatSV1SetEvent(b *testing.B) { if _, err := engine.StopStartEngine(stsV1CfgPath, 1000); err != nil { diff --git a/apier/v1/tp_it_test.go b/apier/v1/tp_it_test.go index b548b5152..65e9a54e4 100644 --- a/apier/v1/tp_it_test.go +++ b/apier/v1/tp_it_test.go @@ -73,7 +73,6 @@ func TestTPITPG(t *testing.T) { } func testTPInitCfg(t *testing.T) { - utils.Logger.Debug("init config") var err error tpCfgPath = path.Join(tpDataDir, "conf", "samples", tpConfigDIR) tpCfg, err = config.NewCGRConfigFromFolder(tpCfgPath) @@ -92,7 +91,6 @@ func testTPInitCfg(t *testing.T) { // Wipe out the cdr database func testTPResetStorDb(t *testing.T) { - utils.Logger.Debug("ResetStorDB") if err := engine.InitStorDb(tpCfg); err != nil { t.Fatal(err) } @@ -100,7 +98,6 @@ func testTPResetStorDb(t *testing.T) { // Start CGR Engine func testTPStartEngine(t *testing.T) { - utils.Logger.Debug("StartEngine") if _, err := engine.StopStartEngine(tpCfgPath, tpDelay); err != nil { t.Fatal(err) } @@ -108,7 +105,6 @@ func testTPStartEngine(t *testing.T) { // Connect rpc client to rater func testTPRpcConn(t *testing.T) { - utils.Logger.Debug("RPCCONN") var err error tpRPC, err = jsonrpc.Dial("tcp", tpCfg.RPCJSONListen) // We connect over JSON so we can also troubleshoot if needed if err != nil { @@ -149,7 +145,6 @@ func testTPExportTPToFolder(t *testing.T) { } func testTPKillEngine(t *testing.T) { - utils.Logger.Debug("KillEngine") if err := engine.KillEngine(tpDelay); err != nil { t.Error(err) } diff --git a/config/config_defaults.go b/config/config_defaults.go index 0ce7301c9..153f4fa99 100755 --- a/config/config_defaults.go +++ b/config/config_defaults.go @@ -29,7 +29,7 @@ const CGRATES_CFG_JSON = ` "general": { "instance_id": "", // identifier of this instance in the cluster, if empty it will be autogenerated - "logger":"*syslog", // controls the destination of logs <*syslog|*stdout> + "logger":"*syslog", // controls the destination of logs <*syslog|*stdout> "log_level": 6, // control the level of messages logged (0-emerg to 7-debug) "http_skip_tls_verify": false, // if enabled Http Client will accept any TLS certificate "rounding_decimals": 5, // system level precision for floats @@ -66,12 +66,12 @@ const CGRATES_CFG_JSON = ` "aliases": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // aliases caching "reverse_aliases": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // reverse aliases index caching "derived_chargers": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // derived charging rule caching + "timings": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // timings caching "resource_profiles": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // control resource profiles caching "resources": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // control resources caching "event_resources": {"limit": -1, "ttl": "1m", "static_ttl": false}, // matching resources to events - "timings": {"limit": -1, "ttl": "", "static_ttl": false, "precache": false}, // timings caching - "stats_queues": {"limit": -1, "ttl": "1m", "static_ttl": false, "precache": false}, // queues with metrics - "event_queues": {"limit": -1, "ttl": "1m", "static_ttl": false}, // matching queues to events + "statqueue_profiles": {"limit": -1, "ttl": "1m", "static_ttl": false, "precache": false}, // statqueue profiles + "statqueues": {"limit": -1, "ttl": "1m", "static_ttl": false, "precache": false}, // statqueues with metrics }, diff --git a/config/config_json_test.go b/config/config_json_test.go index ce60eca24..3fde3d27c 100755 --- a/config/config_json_test.go +++ b/config/config_json_test.go @@ -110,6 +110,9 @@ func TestCacheJsonCfg(t *testing.T) { utils.CacheDerivedChargers: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), Precache: utils.BoolPointer(false)}, + utils.CacheTimings: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), + Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), + Precache: utils.BoolPointer(false)}, utils.CacheResourceProfiles: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), Precache: utils.BoolPointer(false)}, @@ -118,14 +121,12 @@ func TestCacheJsonCfg(t *testing.T) { Precache: utils.BoolPointer(false)}, utils.CacheEventResources: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), Ttl: utils.StringPointer("1m"), Static_ttl: utils.BoolPointer(false)}, - utils.CacheTimings: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), - Ttl: utils.StringPointer(""), Static_ttl: utils.BoolPointer(false), - Precache: utils.BoolPointer(false)}, - utils.CacheStatSQueues: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), + utils.CacheStatQueueProfiles: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), + Ttl: utils.StringPointer("1m"), Static_ttl: utils.BoolPointer(false), + Precache: utils.BoolPointer(false)}, + utils.CacheStatQueues: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), Ttl: utils.StringPointer("1m"), Static_ttl: utils.BoolPointer(false), Precache: utils.BoolPointer(false)}, - utils.CacheEventQueues: &CacheParamJsonCfg{Limit: utils.IntPointer(-1), - Ttl: utils.StringPointer("1m"), Static_ttl: utils.BoolPointer(false)}, } if gCfg, err := dfCgrJsonCfg.CacheJsonCfg(); err != nil { diff --git a/config/config_test.go b/config/config_test.go index a1a7a662b..8cf538ade 100755 --- a/config/config_test.go +++ b/config/config_test.go @@ -438,18 +438,18 @@ func TestCgrCfgJSONDefaultsCacheCFG(t *testing.T) { TTL: time.Duration(0), StaticTTL: false, Precache: false}, utils.CacheDerivedChargers: &CacheParamConfig{Limit: -1, TTL: time.Duration(0), StaticTTL: false, Precache: false}, + utils.CacheTimings: &CacheParamConfig{Limit: -1, + TTL: time.Duration(0), StaticTTL: false, Precache: false}, utils.CacheResourceProfiles: &CacheParamConfig{Limit: -1, TTL: time.Duration(0), StaticTTL: false, Precache: false}, utils.CacheResources: &CacheParamConfig{Limit: -1, TTL: time.Duration(0), StaticTTL: false, Precache: false}, utils.CacheEventResources: &CacheParamConfig{Limit: -1, TTL: time.Duration(1 * time.Minute), StaticTTL: false}, - utils.CacheTimings: &CacheParamConfig{Limit: -1, - TTL: time.Duration(0), StaticTTL: false, Precache: false}, - utils.CacheStatSQueues: &CacheParamConfig{Limit: -1, + utils.CacheStatQueueProfiles: &CacheParamConfig{Limit: -1, + TTL: time.Duration(1 * time.Minute), StaticTTL: false, Precache: false}, + utils.CacheStatQueues: &CacheParamConfig{Limit: -1, TTL: time.Duration(1 * time.Minute), StaticTTL: false, Precache: false}, - utils.CacheEventQueues: &CacheParamConfig{Limit: -1, - TTL: time.Duration(1 * time.Minute), StaticTTL: false}, } if !reflect.DeepEqual(eCacheCfg, cgrCfg.CacheConfig) { t.Errorf("received: %s, \nexpecting: %s", utils.ToJSON(eCacheCfg), utils.ToJSON(cgrCfg.CacheConfig)) diff --git a/engine/libstats.go b/engine/libstats.go index ce09b4d4a..3aa4f5027 100755 --- a/engine/libstats.go +++ b/engine/libstats.go @@ -36,7 +36,6 @@ type StatQueueProfile struct { QueueLength int TTL time.Duration Metrics []string // list of metrics to build - Store bool // store to DB Thresholds []string // list of thresholds to be checked after changes Blocker bool // blocker flag to stop processing on filters matched Stored bool diff --git a/engine/stats.go b/engine/stats.go index 00418245e..ed7e68ee2 100644 --- a/engine/stats.go +++ b/engine/stats.go @@ -154,7 +154,7 @@ func (sS *StatService) matchingStatQueuesForEvent(ev *StatEvent) (sqs StatQueues } passAllFilters := true for _, fltr := range sqPrfl.Filters { - if pass, err := fltr.Pass(ev, "", sS); err != nil { + if pass, err := fltr.Pass(ev.Fields, "", sS); err != nil { return nil, err } else if !pass { passAllFilters = false diff --git a/utils/consts.go b/utils/consts.go index 7332eb3fe..5bc451af6 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -59,8 +59,8 @@ var ( CacheResources: ResourcesPrefix, CacheEventResources: EventResourcesPrefix, CacheTimings: TimingsPrefix, - CacheStatSQueues: META_NONE, - CacheEventQueues: META_NONE, + CacheStatQueueProfiles: StatQueueProfilePrefix, + CacheStatQueues: StatQueuePrefix, } CachePrefixToInstance map[string]string // will be built on init ) @@ -446,9 +446,9 @@ const ( CostSource = "CostSource" ExtraInfo = "ExtraInfo" MetaPrefix = "*" - CacheStatSQueues = "stats_queues" - CacheEventQueues = "event_queues" CacheEventResources = "event_resources" + CacheStatQueueProfiles = "statqueue_profiles" + CacheStatQueues = "statqueues" EventResourcesPrefix = "ers_" MetaSysLog = "*syslog" MetaStdLog = "*stdout" From 6f00f745543295786bc7c03766006e853dcebc4b Mon Sep 17 00:00:00 2001 From: DanB Date: Mon, 25 Sep 2017 20:18:17 +0200 Subject: [PATCH 5/8] Loader test using non supported *acc metric --- engine/loader_csv_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/loader_csv_test.go b/engine/loader_csv_test.go index 2fc583059..0f860dc56 100755 --- a/engine/loader_csv_test.go +++ b/engine/loader_csv_test.go @@ -274,7 +274,7 @@ cgrates.org,ResGroup22,*destinations,HdrDestination,DST_FS,2014-07-29T15:00:00Z, ` stats = ` #Tenant[0],Id[1],FilterType[2],FilterFieldName[3],FilterFieldValues[4],ActivationInterval[5],QueueLength[6],TTL[7],Metrics[8],Blocker[9],Stored[10],Weight[11],Thresholds[12] -cgrates.org,Stats1,*string,Account,1001;1002,2014-07-29T15:00:00Z,100,1s,*asr;*acd;*acc,true,true,20,THRESH1;THRESH2 +cgrates.org,Stats1,*string,Account,1001;1002,2014-07-29T15:00:00Z,100,1s,*asr;*acd,true,true,20,THRESH1;THRESH2 ` thresholds = ` #Id[0],FilterType[1],FilterFieldName[2],FilterFieldValues[3],ActivationInterval[4],ThresholdType[5],ThresholdValue[6],MinItems[7],Recurrent[8],MinSleep[9],Blocker[10],Stored[11],Weight[12],ActionIDs[13] @@ -1452,7 +1452,7 @@ func TestLoadStats(t *testing.T) { }, QueueLength: 100, TTL: "1s", - Metrics: []string{"*asr", "*acd", "*acc"}, + Metrics: []string{"*asr", "*acd"}, Thresholds: []string{"THRESH1", "THRESH2"}, Blocker: true, Stored: true, From e9cca2f3f30e4e5bae4d6485a09cdfd7aea467c0 Mon Sep 17 00:00:00 2001 From: TeoV Date: Tue, 26 Sep 2017 09:52:29 +0300 Subject: [PATCH 6/8] Test metrics with more events and (statmetrics_test.go) --- engine/statmetrics.go | 32 ++-- engine/statmetrics_test.go | 297 ++++++++++++++++++++++++++++++++----- 2 files changed, 279 insertions(+), 50 deletions(-) diff --git a/engine/statmetrics.go b/engine/statmetrics.go index 32a23379f..5cdd664bb 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -141,19 +141,18 @@ func NewACD() (StatMetric, error) { // ACD implements AverageCallDuration metric type StatACD struct { Sum time.Duration - Count float64 + Count int64 Events map[string]time.Duration // map[EventTenantID]Duration - val *float64 // cached ACD value + val *time.Duration // cached ACD value } // getValue returns acr.val -func (acd *StatACD) getValue() float64 { +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.Seconds()/acd.Count, - config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + acd.val = utils.DurationPointer(time.Duration(acd.Sum.Nanoseconds() / acd.Count)) } } return *acd.val @@ -171,7 +170,10 @@ 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) { @@ -222,19 +224,18 @@ func NewTCD() (StatMetric, error) { // TCD implements TotalCallDuration metric type StatTCD struct { Sum time.Duration - Count float64 + Count int64 Events map[string]time.Duration // map[EventTenantID]Duration - val *float64 // cached TCD value + val *time.Duration // cached TCD value } // getValue returns tcd.val -func (tcd *StatTCD) getValue() float64 { +func (tcd *StatTCD) getValue() time.Duration { if tcd.val == nil { if tcd.Count == 0 { - tcd.val = utils.Float64Pointer(float64(STATS_NA)) + tcd.val = utils.DurationPointer(time.Duration((-1) * time.Nanosecond)) } else { - tcd.val = utils.Float64Pointer(utils.Round(tcd.Sum.Seconds(), - config.CgrConfig().RoundingDecimals, utils.ROUNDING_MIDDLE)) + tcd.val = utils.DurationPointer(time.Duration(tcd.Sum.Nanoseconds())) } } return *tcd.val @@ -252,7 +253,10 @@ func (tcd *StatTCD) GetValue() (v interface{}) { } func (tcd *StatTCD) GetFloat64Value() (v float64) { - return tcd.getValue() + if tcd.Count == 0 { + return -1.0 + } + return tcd.getValue().Seconds() } func (tcd *StatTCD) AddEvent(ev *StatEvent) (err error) { diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 32845ca8c..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,29 +236,53 @@ 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 acd 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 acd value: %f", v) - } - acd.RemEvent(ev3.TenantID()) - if v := acd.GetValue(); v != 5.0 { - t.Errorf("wrong acd 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 acd 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 acd 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) { @@ -166,7 +296,7 @@ func TestTCDGetStringValue(t *testing.T) { t.Errorf("wrong tcd value: %s", strVal) } tcd.AddEvent(ev) - if strVal := tcd.GetStringValue(""); strVal != "10" { + if strVal := tcd.GetStringValue(""); strVal != "10s" { t.Errorf("wrong tcd value: %s", strVal) } ev2 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_2", @@ -177,22 +307,96 @@ func TestTCDGetStringValue(t *testing.T) { ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} tcd.AddEvent(ev2) tcd.AddEvent(ev3) - if strVal := tcd.GetStringValue(""); strVal != "20" { + if strVal := tcd.GetStringValue(""); strVal != "20s" { t.Errorf("wrong tcd value: %s", strVal) } tcd.RemEvent(ev2.TenantID()) - if strVal := tcd.GetStringValue(""); strVal != "10" { + if strVal := tcd.GetStringValue(""); strVal != "10s" { t.Errorf("wrong tcd value: %s", strVal) } tcd.RemEvent(ev.TenantID()) - if strVal := tcd.GetStringValue(""); strVal != "0" { + 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) { @@ -202,8 +406,8 @@ func TestTCDGetValue(t *testing.T) { "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 != 10.0 { - t.Errorf("wrong tcd value: %f", v) + 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{}{ @@ -212,20 +416,41 @@ func TestTCDGetValue(t *testing.T) { ev3 := &StatEvent{Tenant: "cgrates.org", ID: "EVENT_3"} tcd.AddEvent(ev2) tcd.AddEvent(ev3) - if v := tcd.GetValue(); v != 15.000000 { - t.Errorf("wrong tcd value: %f", v) + 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 != 5.000000 { - t.Errorf("wrong tcd value: %f", v) + 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 != 0.0 { - t.Errorf("wrong tcd value: %f", v) + 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 != -1.0 { - t.Errorf("wrong tcd value: %f", v) + if v := tcd.GetValue(); v != time.Duration((-1)*time.Nanosecond) { + t.Errorf("wrong tcd value: %+v", v) } - } From 1e379641ea59c01ddad2d067416e3a1b0c37fe31 Mon Sep 17 00:00:00 2001 From: TeoV Date: Tue, 26 Sep 2017 11:13:50 +0300 Subject: [PATCH 7/8] Add ACC and TCC metrics --- apier/v1/stats_it_test.go | 2 +- engine/libstats.go | 22 ++++- engine/onstor_it_test.go | 2 +- engine/statmetrics.go | 167 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 188 insertions(+), 5 deletions(-) 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/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 5cdd664bb..ff750788a 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" + "strconv" "time" ) @@ -32,6 +33,8 @@ func NewStatMetric(metricID string) (sm StatMetric, err 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) @@ -301,3 +304,167 @@ func (tcd *StatTCD) Marshal(ms Marshaler) (marshaled []byte, err error) { 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) +} From 7802f1cc9e3476101f042eaa6e304d6b9b68bc69 Mon Sep 17 00:00:00 2001 From: TeoV Date: Tue, 26 Sep 2017 11:28:43 +0300 Subject: [PATCH 8/8] Add time.Sleep in tp_it_test.go(import and export methond need more time for reading from disk and writing to disk) --- apier/v1/tp_it_test.go | 3 +++ 1 file changed, 3 insertions(+) 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)) }