From 4274edf69efe8c7ad3fc5dab1659700709c6a802 Mon Sep 17 00:00:00 2001 From: TeoV Date: Wed, 27 Feb 2019 15:06:31 +0200 Subject: [PATCH] Add new metric type (*distinct) for StatS --- engine/statmetrics.go | 91 +++++++++++++++++++++++--- engine/statmetrics_test.go | 129 +++++++++++++++++++++++++++++++++++++ utils/consts.go | 19 +++--- 3 files changed, 221 insertions(+), 18 deletions(-) diff --git a/engine/statmetrics.go b/engine/statmetrics.go index 62bb76ac6..d0feade50 100644 --- a/engine/statmetrics.go +++ b/engine/statmetrics.go @@ -34,15 +34,16 @@ const STATS_NA = -1.0 // cfg serves as general purpose container to pass config options to metric func NewStatMetric(metricID string, minItems int) (sm StatMetric, err error) { metrics := map[string]func(int, string) (StatMetric, error){ - utils.MetaASR: NewASR, - utils.MetaACD: NewACD, - utils.MetaTCD: NewTCD, - utils.MetaACC: NewACC, - utils.MetaTCC: NewTCC, - utils.MetaPDD: NewPDD, - utils.MetaDDC: NewDCC, - utils.MetaSum: NewStatSum, - utils.MetaAverage: NewStatAverage, + utils.MetaASR: NewASR, + utils.MetaACD: NewACD, + utils.MetaTCD: NewTCD, + utils.MetaACC: NewACC, + utils.MetaTCC: NewTCC, + utils.MetaPDD: NewPDD, + utils.MetaDDC: NewDCC, + utils.MetaSum: NewStatSum, + utils.MetaAverage: NewStatAverage, + utils.MetaDistinct: NewStatDistinct, } // split the metricID // in case of *sum we have *sum#FieldName @@ -661,6 +662,7 @@ func (ddc *StatDDC) Marshal(ms Marshaler) (marshaled []byte, err error) { func (ddc *StatDDC) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { return ms.Unmarshal(marshaled, ddc) } + func NewStatSum(minItems int, extraParams string) (StatMetric, error) { return &StatSum{Events: make(map[string]float64), MinItems: minItems, FieldName: extraParams}, nil } @@ -820,3 +822,74 @@ func (avg *StatAverage) Marshal(ms Marshaler) (marshaled []byte, err error) { func (avg *StatAverage) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { return ms.Unmarshal(marshaled, avg) } + +func NewStatDistinct(minItems int, extraParams string) (StatMetric, error) { + return &StatDistinct{Events: make(map[string]struct{}), MinItems: minItems, FieldName: extraParams}, nil +} + +type StatDistinct struct { + Numbers float64 + Events map[string]struct{} // map[EventTenantID]Cost + MinItems int + FieldName string + val *float64 // cached sum value +} + +// getValue returns tcd.val +func (sum *StatDistinct) getValue() float64 { + if sum.val == nil { + if len(sum.Events) == 0 || len(sum.Events) < sum.MinItems { + sum.val = utils.Float64Pointer(STATS_NA) + } else { + sum.val = utils.Float64Pointer(utils.Round(sum.Numbers, + config.CgrConfig().GeneralCfg().RoundingDecimals, + utils.ROUNDING_MIDDLE)) + } + } + return *sum.val +} + +func (sum *StatDistinct) GetStringValue(fmtOpts string) (valStr string) { + if val := sum.getValue(); val == STATS_NA { + valStr = utils.NOT_AVAILABLE + } else { + valStr = strconv.FormatFloat(sum.getValue(), 'f', -1, 64) + } + return +} + +func (sum *StatDistinct) GetValue() (v interface{}) { + return sum.getValue() +} + +func (sum *StatDistinct) GetFloat64Value() (v float64) { + return sum.getValue() +} + +func (sum *StatDistinct) AddEvent(ev *utils.CGREvent) (err error) { + if has := ev.HasField(sum.FieldName); has { + sum.Numbers += 1 + } + sum.Events[ev.ID] = struct{}{} + sum.val = nil + return +} + +func (sum *StatDistinct) RemEvent(evID string) (err error) { + _, has := sum.Events[evID] + if !has { + return utils.ErrNotFound + } + delete(sum.Events, evID) + sum.Numbers -= 1 + sum.val = nil + return +} + +func (sum *StatDistinct) Marshal(ms Marshaler) (marshaled []byte, err error) { + return ms.Marshal(sum) +} + +func (sum *StatDistinct) LoadMarshaled(ms Marshaler, marshaled []byte) (err error) { + return ms.Unmarshal(marshaled, sum) +} diff --git a/engine/statmetrics_test.go b/engine/statmetrics_test.go index 4dceadf75..94cd92002 100644 --- a/engine/statmetrics_test.go +++ b/engine/statmetrics_test.go @@ -1152,6 +1152,112 @@ func TestStatAverageGetStringValue(t *testing.T) { } } +func TestStatDistinctGetFloat64Value(t *testing.T) { + statDistinct, _ := NewStatDistinct(2, "Usage") + ev := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_1", + Event: map[string]interface{}{ + "Cost": "20", + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + "Usage": time.Duration(10 * time.Second), + utils.PDD: time.Duration(5 * time.Second), + utils.Destination: "1002"}} + statDistinct.AddEvent(ev) + if v := statDistinct.GetFloat64Value(); v != -1.0 { + t.Errorf("wrong statDistinct value: %v", v) + } + ev2 := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_2"} + statDistinct.AddEvent(ev2) + if v := statDistinct.GetFloat64Value(); v != 1.0 { + t.Errorf("wrong statDistinct value: %v", v) + } + ev4 := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_4", + Event: map[string]interface{}{ + "Cost": "20", + "Usage": time.Duration(1 * time.Minute), + "AnswerTime": time.Date(2015, 7, 14, 14, 25, 0, 0, time.UTC), + utils.PDD: time.Duration(10 * time.Second), + utils.Destination: "1001", + }, + } + ev5 := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_5", + Event: map[string]interface{}{ + "Cost": "20", + "Usage": time.Duration(1*time.Minute + 30*time.Second), + "AnswerTime": time.Date(2015, 7, 14, 14, 25, 0, 0, time.UTC), + utils.Destination: "1003", + }, + } + statDistinct.AddEvent(ev4) + if strVal := statDistinct.GetFloat64Value(); strVal != 2 { + t.Errorf("wrong statDistinct value: %v", strVal) + } + statDistinct.AddEvent(ev5) + if strVal := statDistinct.GetFloat64Value(); strVal != 3 { + t.Errorf("wrong statDistinct value: %v", strVal) + } + statDistinct.RemEvent(ev2.ID) + if strVal := statDistinct.GetFloat64Value(); strVal != 2 { + t.Errorf("wrong statDistinct value: %v", strVal) + } + statDistinct.RemEvent(ev4.ID) + if strVal := statDistinct.GetFloat64Value(); strVal != 1 { + t.Errorf("wrong statDistinct value: %v", strVal) + } + statDistinct.RemEvent(ev.ID) + if strVal := statDistinct.GetFloat64Value(); strVal != -1 { + t.Errorf("wrong statDistinct value: %v", strVal) + } + statDistinct.RemEvent(ev5.ID) + if strVal := statDistinct.GetFloat64Value(); strVal != -1 { + t.Errorf("wrong statDistinct value: %v", strVal) + } +} + +func TestStatDistinctGetStringValue(t *testing.T) { + statDistinct, _ := NewStatDistinct(2, "Cost") + ev := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_1", + Event: map[string]interface{}{ + "Cost": "20", + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.Destination: "1002"}} + if strVal := statDistinct.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong statDistinct value: %s", strVal) + } + + statDistinct.AddEvent(ev) + if strVal := statDistinct.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong statDistinct value: %s", strVal) + } + ev2 := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_2", + Event: map[string]interface{}{ + "Cost": "20", + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.Destination: "1002"}} + + ev3 := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_3", + Event: map[string]interface{}{ + "Cost": "20", + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + utils.Destination: "1001"}} + statDistinct.AddEvent(ev2) + statDistinct.AddEvent(ev3) + if strVal := statDistinct.GetStringValue(""); strVal != "3" { + t.Errorf("wrong statDistinct value: %s", strVal) + } + statDistinct.RemEvent(ev.ID) + if strVal := statDistinct.GetStringValue(""); strVal != "2" { + t.Errorf("wrong statDistinct value: %s", strVal) + } + statDistinct.RemEvent(ev2.ID) + if strVal := statDistinct.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong statDistinct value: %s", strVal) + } + statDistinct.RemEvent(ev3.ID) + if strVal := statDistinct.GetStringValue(""); strVal != utils.NOT_AVAILABLE { + t.Errorf("wrong statDistinct value: %s", strVal) + } +} + var jMarshaler JSONMarshaler func TestASRMarshal(t *testing.T) { @@ -1341,3 +1447,26 @@ func TestStatAverageMarshal(t *testing.T) { t.Errorf("Expected: %s , recived: %s", utils.ToJSON(statAvg), utils.ToJSON(nstatAvg)) } } + +func TestStatDistrictMarshal(t *testing.T) { + statDistinct, _ := NewStatDistinct(2, "Usage") + ev := &utils.CGREvent{Tenant: "cgrates.org", ID: "EVENT_1", + Event: map[string]interface{}{ + "Cost": "20", + "AnswerTime": time.Date(2014, 7, 14, 14, 25, 0, 0, time.UTC), + "Usage": time.Duration(10 * time.Second), + utils.PDD: time.Duration(5 * time.Second), + utils.Destination: "1002"}} + statDistinct.AddEvent(ev) + var nStatDistinct StatDistinct + expected := []byte(`{"Numbers":1,"Events":{"EVENT_1":{}},"MinItems":2,"FieldName":"Usage"}`) + if b, err := statDistinct.Marshal(&jMarshaler); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(expected, b) { + t.Errorf("Expected: %s , recived: %s", string(expected), string(b)) + } else if err := nStatDistinct.LoadMarshaled(&jMarshaler, b); err != nil { + t.Error(err) + } else if reflect.DeepEqual(statDistinct, nStatDistinct) { + t.Errorf("Expected: %s , recived: %s", utils.ToJSON(statDistinct), utils.ToJSON(nStatDistinct)) + } +} diff --git a/utils/consts.go b/utils/consts.go index 0154b9178..ab8c3a6b1 100755 --- a/utils/consts.go +++ b/utils/consts.go @@ -559,15 +559,16 @@ const ( // MetaMetrics const ( - MetaASR = "*asr" - MetaACD = "*acd" - MetaTCD = "*tcd" - MetaACC = "*acc" - MetaTCC = "*tcc" - MetaPDD = "*pdd" - MetaDDC = "*ddc" - MetaSum = "*sum" - MetaAverage = "*average" + MetaASR = "*asr" + MetaACD = "*acd" + MetaTCD = "*tcd" + MetaACC = "*acc" + MetaTCC = "*tcc" + MetaPDD = "*pdd" + MetaDDC = "*ddc" + MetaSum = "*sum" + MetaAverage = "*average" + MetaDistinct = "*distinct" ) // Services