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