diff --git a/cdrstats/metrics.go b/cdrstats/metrics.go index 2a27268cf..f15752bad 100644 --- a/cdrstats/metrics.go +++ b/cdrstats/metrics.go @@ -18,12 +18,15 @@ along with this program. If not, see package cdrstats +import "time" + type Metric interface { AddCDR(*QCDR) RemoveCDR(*QCDR) + GetValue() float64 } -func CreateMetric(metric string) *Metric { +func CreateMetric(metric string) Metric { switch metric { case "ASR": return &ASRMetric{} @@ -38,23 +41,74 @@ func CreateMetric(metric string) *Metric { // ASR - Answer-Seizure Ratio // successfully answered Calls divided by the total number of Calls attempted and multiplied by 100 type ASRMetric struct { - sum float64 - count int64 + answered float64 + total float64 } -func (asr *ASRMetric) AddCdr(cdr *QCDR) { +func (asr *ASRMetric) AddCDR(cdr *QCDR) { + if !cdr.AnswerTime.IsZero() { + asr.answered += 1 + } + asr.total += 1 +} + +func (asr *ASRMetric) RemoveCDR(cdr *QCDR) { + if !cdr.AnswerTime.IsZero() { + asr.answered -= 1 + } + asr.total -= 1 +} + +func (asr *ASRMetric) GetValue() float64 { + return asr.answered / asr.total * 100 } // ACD – Average Call Duration // the sum of billable seconds (billsec) of answered calls divided by the number of these answered calls. type ACDMetric struct { - sum float64 - count int64 + sum time.Duration + count float64 +} + +func (acd *ACDMetric) AddCDR(cdr *QCDR) { + if !cdr.AnswerTime.IsZero() { + acd.sum += cdr.Usage + acd.count += 1 + } +} + +func (acd *ACDMetric) RemoveCDR(cdr *QCDR) { + if !cdr.AnswerTime.IsZero() { + acd.sum -= cdr.Usage + acd.count -= 1 + } +} + +func (acd *ACDMetric) GetValue() float64 { + return acd.sum.Seconds() / acd.count } // ACC – Average Call Cost // the sum of cost of answered calls divided by the number of these answered calls. type ACCMetric struct { sum float64 - count int64 + count float64 +} + +func (acc *ACCMetric) AddCDR(cdr *QCDR) { + if !cdr.AnswerTime.IsZero() && cdr.Cost >= 0 { + acc.sum += cdr.Cost + acc.count += 1 + } +} + +func (acc *ACCMetric) RemoveCDR(cdr *QCDR) { + if !cdr.AnswerTime.IsZero() && cdr.Cost >= 0 { + acc.sum -= cdr.Cost + acc.count -= 1 + } +} + +func (acc *ACCMetric) GetValue() float64 { + return acc.sum / acc.count } diff --git a/cdrstats/stats.go b/cdrstats/stats.go index ac6f509d1..0449b429f 100644 --- a/cdrstats/stats.go +++ b/cdrstats/stats.go @@ -18,149 +18,158 @@ along with this program. If not, see package cdrstats +import ( + "strings" + "time" + + "github.com/cgrates/cgrates/config" + "github.com/cgrates/cgrates/utils" +) + type StatsQueue struct { - cdrs []*QCDR - config *CdrStatsConfig - metrics map[string]*Metric + cdrs []*QCDR + conf *config.CdrStatsConfig + metrics map[string]Metric } // Simplified cdr structure containing only the necessary info type QCDR struct { - SetupTime time.Time - AnswerTime time.Time - Usage time.Duration - Cost float64 + SetupTime time.Time + AnswerTime time.Time + Usage time.Duration + Cost float64 } -func NewStatsQueue(config *CdrStatsConfig) *StatsQueue{ - sq := &StatsQueue{ - config:config - metrics:=make(map[string]*Metric, len(config.Metrics)) - } - for _, m := range config.Metrics { - metric := CreateMetric(m) - if metric != nil{ +func NewStatsQueue(conf *config.CdrStatsConfig) *StatsQueue { + sq := &StatsQueue{ + conf: conf, + metrics: make(map[string]Metric, len(conf.Metrics)), + } + for _, m := range conf.Metrics { + metric := CreateMetric(m) + if metric != nil { sq.metrics[m] = metric - } - } + } + } + return sq } -func (sq *StatsQueue) AppendCDR(cdr *utils.StoredCdr){ - if sq.AcceptCDR(cdr){ - qcdr := sq.SimplifyCDR(cdr) - sq.cdrs = append(sq.cdrs, qcdr) - sq.AddToMetrics(qcdr) - } +func (sq *StatsQueue) AppendCDR(cdr *utils.StoredCdr) { + if sq.AcceptCDR(cdr) { + qcdr := sq.SimplifyCDR(cdr) + sq.cdrs = append(sq.cdrs, qcdr) + sq.AddToMetrics(qcdr) + } } func (sq *StatsQueue) AddToMetrics(cdr *QCDR) { - for _, metric:= range sq.metrics { - metric.AddCdr(cdr) - } + for _, metric := range sq.metrics { + metric.AddCDR(cdr) + } } func (sq *StatsQueue) RemoveFromMetrics(cdr *QCDR) { - for _, metric:= range sq.metrics { - metric.RemoveCdr(cdr) - } + for _, metric := range sq.metrics { + metric.RemoveCDR(cdr) + } } -func (sq *StatsQueue) SimplifyCDR(cdr *utils.StoredCdr) *QCDR{ - return &QCDR{ - SetupTime: cdr.SetupTime, - AnswerTime: cdr.AnswerTime, - Usage: cdr.Usage, - Cost: cdr.Cost - } +func (sq *StatsQueue) SimplifyCDR(cdr *utils.StoredCdr) *QCDR { + return &QCDR{ + SetupTime: cdr.SetupTime, + AnswerTime: cdr.AnswerTime, + Usage: cdr.Usage, + Cost: cdr.Cost, + } } -func (sq *StatsQueue) PurgeObsoleteCDRs { - currentLength := len(sq.cdrs) - if currentLength>sq.config.QueuedItems{ - for _, cdr := range sq.cdrs[:currentLength-sq.config.QueuedItems] { - sq.RemoveFromMetrics(cdr) - } - sq.cdrs = sq.cdrs[currentLength-sq.config.QueuedItems:] - } - for i, cdr := range sq.cdrs { - if time.Now().Sub(cdr.SetupTime) > sq.config.TimeWindow { - sq.RemoveFromMetrics(cdr) - continue - } else { - if i > 0 { - sq.cdrs = sq.cdrs[i:] - } - break - } - } +func (sq *StatsQueue) PurgeObsoleteCDRs() { + currentLength := len(sq.cdrs) + if currentLength > sq.conf.QueuedItems { + for _, cdr := range sq.cdrs[:currentLength-sq.conf.QueuedItems] { + sq.RemoveFromMetrics(cdr) + } + sq.cdrs = sq.cdrs[currentLength-sq.conf.QueuedItems:] + } + for i, cdr := range sq.cdrs { + if time.Now().Sub(cdr.SetupTime) > sq.conf.TimeWindow { + sq.RemoveFromMetrics(cdr) + continue + } else { + if i > 0 { + sq.cdrs = sq.cdrs[i:] + } + break + } + } } func (sq *StatsQueue) AcceptCDR(cdr *utils.StoredCdr) bool { - if len(sq.config.SetupInterval) > 0 { - if cdr.SetupTime.Before(sq.config.SetupInterval[0]){ - return false - } - if len(sq.config.SetupInterval) > 1 && (cdr.SetupTime.Equals(sq.config.SetupInterval[1]) || cdr.SetupTime.After(sq.config.SetupInterval[1])){ - return false - } - } - if len(sq.config.TOR) >0 && !utils.IsSliceMember(sq.config.TOR, cdr.TOR){ - return false - } - if len(sq.config.CdrHost) >0 && !utils.IsSliceMember(sq.config.CdrHost, cdr.CdrHost){ - return false - } - if len(sq.config.CdrSource) >0 && !utils.IsSliceMember(sq.config.CdrSource, cdr.CdrSource){ - return false - } - if len(sq.config.ReqType) >0 && !utils.IsSliceMember(sq.config.ReqType, cdr.ReqType){ - return false - } - if len(sq.config.Direction) >0 && !utils.IsSliceMember(sq.config.Direction, cdr.Direction){ - return false - } - if len(sq.config.Tenant) >0 && !utils.IsSliceMember(sq.config.Tenant, cdr.Tenant){ - return false - } - if len(sq.config.Category) >0 && !utils.IsSliceMember(sq.config.Category, cdr.Category){ - return false - } - if len(sq.config.Account) >0 && !utils.IsSliceMember(sq.config.Account, cdr.Account){ - return false - } - if len(sq.config.Subject) >0 && !utils.IsSliceMember(sq.config.Subject, cdr.Subject){ - return false - } - if len(sq.config.DestinationPrefix)>0 { - found := false - for _, prefix := range sq.config.DestinationPrefix { - if cdr.Destination.HasPrefix(prefix){ - found=true - break - } - } - if !found{ - return false - } - } - if len(sq.config.UsageInterval) > 0 { - if cdr.Usage < sq.config.UsageInterval[0]{ - return false - } - if len(sq.config.UsageInterval) > 1 && cdr.Usage >= sq.config.UsageInterval[1]{ - return false - } - } - if len(sq.config.MediationRunIds) >0 && !utils.IsSliceMember(sq.config.MediationRunIds, cdr.MediationRunId){ - return false - } - if len(sq.config.CostInterval) > 0 { - if cdr.Cost < sq.config.CostInterval[0]{ - return false - } - if len(sq.config.CostInterval) > 1 && cdr.Cost >= sq.config.CostInterval[1]{ - return false - } - } - return true + if len(sq.conf.SetupInterval) > 0 { + if cdr.SetupTime.Before(sq.conf.SetupInterval[0]) { + return false + } + if len(sq.conf.SetupInterval) > 1 && (cdr.SetupTime.Equal(sq.conf.SetupInterval[1]) || cdr.SetupTime.After(sq.conf.SetupInterval[1])) { + return false + } + } + if len(sq.conf.TOR) > 0 && !utils.IsSliceMember(sq.conf.TOR, cdr.TOR) { + return false + } + if len(sq.conf.CdrHost) > 0 && !utils.IsSliceMember(sq.conf.CdrHost, cdr.CdrHost) { + return false + } + if len(sq.conf.CdrSource) > 0 && !utils.IsSliceMember(sq.conf.CdrSource, cdr.CdrSource) { + return false + } + if len(sq.conf.ReqType) > 0 && !utils.IsSliceMember(sq.conf.ReqType, cdr.ReqType) { + return false + } + if len(sq.conf.Direction) > 0 && !utils.IsSliceMember(sq.conf.Direction, cdr.Direction) { + return false + } + if len(sq.conf.Tenant) > 0 && !utils.IsSliceMember(sq.conf.Tenant, cdr.Tenant) { + return false + } + if len(sq.conf.Category) > 0 && !utils.IsSliceMember(sq.conf.Category, cdr.Category) { + return false + } + if len(sq.conf.Account) > 0 && !utils.IsSliceMember(sq.conf.Account, cdr.Account) { + return false + } + if len(sq.conf.Subject) > 0 && !utils.IsSliceMember(sq.conf.Subject, cdr.Subject) { + return false + } + if len(sq.conf.DestinationPrefix) > 0 { + found := false + for _, prefix := range sq.conf.DestinationPrefix { + if strings.HasPrefix(cdr.Destination, prefix) { + found = true + break + } + } + if !found { + return false + } + } + if len(sq.conf.UsageInterval) > 0 { + if cdr.Usage < sq.conf.UsageInterval[0] { + return false + } + if len(sq.conf.UsageInterval) > 1 && cdr.Usage >= sq.conf.UsageInterval[1] { + return false + } + } + if len(sq.conf.MediationRunIds) > 0 && !utils.IsSliceMember(sq.conf.MediationRunIds, cdr.MediationRunId) { + return false + } + if len(sq.conf.CostInterval) > 0 { + if cdr.Cost < sq.conf.CostInterval[0] { + return false + } + if len(sq.conf.CostInterval) > 1 && cdr.Cost >= sq.conf.CostInterval[1] { + return false + } + } + return true } diff --git a/config/cdrstatsconfig.go b/config/cdrstatsconfig.go index 0c9800857..365a3de0c 100644 --- a/config/cdrstatsconfig.go +++ b/config/cdrstatsconfig.go @@ -24,7 +24,7 @@ import ( type CdrStatsConfig struct { Id string // Config id, unique per config instance - QueuedItems int64 // Number of items in the stats buffer + QueuedItems int // Number of items in the stats buffer TimeWindow time.Duration // Will only keep the CDRs who's call setup time is not older than time.Now()-TimeWindow Metrics []string // ASR, ACD, ACC SetupInterval []time.Time // 2 or less items (>= start interval,< stop_interval) diff --git a/utils/storedcdr.go b/utils/storedcdr.go index 97d4b3042..aedd8c057 100644 --- a/utils/storedcdr.go +++ b/utils/storedcdr.go @@ -1,4 +1,4 @@ - *utilssssss/* +/* Real-time Charging System for Telecom & ISP environments Copyright (C) 2012-2014 ITsysCOM GmbH