/* Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments Copyright (C) ITsysCOM GmbH This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see */ package utils import ( "fmt" "sync" "time" "github.com/cgrates/cron" ) // ExporterMetrics stores export statistics with thread-safe access and // cron-scheduled resets. type ExporterMetrics struct { mu sync.RWMutex MapStorage MapStorage cron *cron.Cron loc *time.Location } // NewExporterMetrics creates metrics with optional automatic reset. // schedule is a cron expression for reset timing (empty to disable). func NewExporterMetrics(schedule, timezone string) (*ExporterMetrics, error) { loc, err := time.LoadLocation(timezone) if err != nil { return nil, err } m := &ExporterMetrics{ loc: loc, } m.Reset() // init MapStorage with default values if schedule != "" { m.cron = cron.New(cron.WithLocation(loc)) if _, err := m.cron.AddFunc(schedule, func() { m.Reset() }); err != nil { // Only fails if schedule is an invalid cron expression. return nil, fmt.Errorf("invalid cron expr %q: %v", schedule, err) } m.cron.Start() } return m, nil } // Reset immediately clears all metrics and resets them to initial values. func (m *ExporterMetrics) Reset() { m.mu.Lock() defer m.mu.Unlock() m.MapStorage = MapStorage{ NumberOfEvents: int64(0), PositiveExports: StringSet{}, NegativeExports: StringSet{}, TimeNow: time.Now().In(m.loc), } } // StopCron stops the automatic reset schedule if one is active. func (m *ExporterMetrics) StopCron() { if m.cron == nil { return } m.cron.Stop() // ctx := m.cron.Stop() // <-ctx.Done() // wait for any running jobs to complete } // String returns the map as json string. func (m *ExporterMetrics) String() string { m.mu.RLock() defer m.mu.RUnlock() return m.MapStorage.String() } // FieldAsInterface returns the value from the path. func (m *ExporterMetrics) FieldAsInterface(fldPath []string) (val any, err error) { m.mu.RLock() defer m.mu.RUnlock() return m.MapStorage.FieldAsInterface(fldPath) } // FieldAsString returns the value from path as string. func (m *ExporterMetrics) FieldAsString(fldPath []string) (str string, err error) { m.mu.RLock() defer m.mu.RUnlock() return m.MapStorage.FieldAsString(fldPath) } // Set sets the value at the given path. func (m *ExporterMetrics) Set(fldPath []string, val any) (err error) { m.mu.Lock() defer m.mu.Unlock() return m.MapStorage.Set(fldPath, val) } // GetKeys returns all the keys from map. func (m *ExporterMetrics) GetKeys(nested bool, nestedLimit int, prefix string) (keys []string) { m.mu.RLock() defer m.mu.RUnlock() return m.MapStorage.GetKeys(nested, nestedLimit, prefix) } // Remove removes the item at path func (m *ExporterMetrics) Remove(fldPath []string) (err error) { m.mu.Lock() defer m.mu.Unlock() return m.MapStorage.Remove(fldPath) } func (m *ExporterMetrics) ClonedMapStorage() (msClone MapStorage) { m.mu.RLock() defer m.mu.RUnlock() return m.MapStorage.Clone() } // IncrementEvents increases the event counter (NumberOfEvents) by 1. func (m *ExporterMetrics) IncrementEvents() { m.mu.Lock() defer m.mu.Unlock() count, _ := m.MapStorage[NumberOfEvents].(int64) m.MapStorage[NumberOfEvents] = count + 1 } // Lock locks the ExporterMetrics mutex. func (m *ExporterMetrics) Lock() { m.mu.Lock() } // Unlock unlocks the ExporterMetrics mutex. func (m *ExporterMetrics) Unlock() { m.mu.Unlock() } // RLock locks the ExporterMetrics mutex for reading. func (m *ExporterMetrics) RLock() { m.mu.RLock() } // RUnlock unlocks the read lock on the ExporterMetrics mutex. func (m *ExporterMetrics) RUnlock() { m.mu.RUnlock() }