/* 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 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ package cores import ( "errors" "fmt" "io" "os" "path" "runtime" "runtime/pprof" "sync" "time" "github.com/cgrates/birpc/context" "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/engine" "github.com/cgrates/cgrates/utils" ) func NewCoreService(cfg *config.CGRConfig, caps *engine.Caps, fileCPU io.Closer, fileMem string, stopChan chan struct{}, stopMemPrf chan struct{}, shdWg *sync.WaitGroup, shtDw context.CancelFunc) *CoreS { var st *engine.CapsStats if caps.IsLimited() && cfg.CoreSCfg().CapsStatsInterval != 0 { st = engine.NewCapsStats(cfg.CoreSCfg().CapsStatsInterval, caps, stopChan) } return &CoreS{ shdWg: shdWg, stopMemPrf: stopMemPrf, shtDw: shtDw, cfg: cfg, CapsStats: st, fileCPU: fileCPU, fileMEM: fileMem, } } type CoreS struct { cfg *config.CGRConfig CapsStats *engine.CapsStats shdWg *sync.WaitGroup stopMemPrf chan struct{} fileMEM string fileCPU io.Closer fileMx sync.Mutex shtDw context.CancelFunc } func (cS *CoreS) ShutdownEngine() { cS.shtDw() } // Shutdown is called to shutdown the service func (cS *CoreS) Shutdown() { utils.Logger.Info(fmt.Sprintf("<%s> shutdown initialized", utils.CoreS)) if cS.stopMemPrf != nil { close(cS.stopMemPrf) cS.stopMemPrf = nil } cS.StopCPUProfiling() utils.Logger.Info(fmt.Sprintf("<%s> shutdown complete", utils.CoreS)) } // stopChanMemProf will stop the MemoryProfiling Channel in order to create // the final MemoryProfiling when CoreS subsystem will stop. func (cS *CoreS) stopChanMemProf() { if cS.stopMemPrf != nil { MemProfFile(cS.fileMEM) close(cS.stopMemPrf) cS.stopMemPrf = nil } } func StartCPUProfiling(path string) (file io.WriteCloser, err error) { file, err = os.Create(path) if err != nil { return nil, fmt.Errorf("could not create CPU profile: %v", err) } err = pprof.StartCPUProfile(file) return } func MemProfFile(memProfPath string) bool { f, err := os.Create(memProfPath) if err != nil { utils.Logger.Crit(fmt.Sprintf(" could not create memory profile file: %s", err)) return false } runtime.GC() // get up-to-date statistics if err := pprof.WriteHeapProfile(f); err != nil { utils.Logger.Crit(fmt.Sprintf(" could not write memory profile: %s", err)) f.Close() return false } f.Close() return true } func MemProfiling(memProfDir string, interval time.Duration, nrFiles int, shdWg *sync.WaitGroup, stopChan chan struct{}, shDw context.CancelFunc) { tm := time.NewTimer(interval) for i := 1; ; i++ { select { case <-stopChan: tm.Stop() shdWg.Done() return case <-tm.C: } if !MemProfFile(path.Join(memProfDir, fmt.Sprintf("mem%v.prof", i))) { shDw() shdWg.Done() return } if i%nrFiles == 0 { i = 0 // reset the counting } tm.Reset(interval) } } // V1Status returns the status of the engine func (cS *CoreS) V1Status(_ *context.Context, _ *utils.TenantWithAPIOpts, reply *map[string]interface{}) (err error) { memstats := new(runtime.MemStats) runtime.ReadMemStats(memstats) response := make(map[string]interface{}) response[utils.NodeID] = cS.cfg.GeneralCfg().NodeID response[utils.MemoryUsage] = utils.SizeFmt(float64(memstats.HeapAlloc), "") response[utils.ActiveGoroutines] = runtime.NumGoroutine() if response[utils.VersionName], err = utils.GetCGRVersion(); err != nil { utils.Logger.Err(err.Error()) err = nil } response[utils.RunningSince] = utils.GetStartTime() response[utils.GoVersion] = runtime.Version() *reply = response return } // StartCPUProfiling is used to start CPUProfiling in the given path func (cS *CoreS) StartCPUProfiling(argPath string) (err error) { cS.fileMx.Lock() defer cS.fileMx.Unlock() if cS.fileCPU != nil { return fmt.Errorf("CPU profiling already started") } if argPath == utils.EmptyString { return utils.NewErrMandatoryIeMissing("Path") } cS.fileCPU, err = StartCPUProfiling(argPath) return } // StopCPUProfiling is used to stop CPUProfiling in the given path func (cS *CoreS) StopCPUProfiling() (err error) { cS.fileMx.Lock() defer cS.fileMx.Unlock() if cS.fileCPU != nil { pprof.StopCPUProfile() err = cS.fileCPU.Close() cS.fileCPU = nil return } return fmt.Errorf(" cannot stop because CPUProfiling is not active") } // StartMemoryProfiling is used to start MemoryProfiling in the given path func (cS *CoreS) StartMemoryProfiling(args *utils.MemoryPrf) (err error) { if args.DirPath == utils.EmptyString { return utils.NewErrMandatoryIeMissing("Path") } if cS.stopMemPrf != nil { return errors.New("Memory Profiling already started") } if args.Interval <= 0 { args.Interval = 5 * time.Second } if args.NrFiles == 0 { args.NrFiles = 1 } cS.shdWg.Add(1) cS.stopMemPrf = make(chan struct{}) cS.fileMEM = args.DirPath go MemProfiling(args.DirPath, args.Interval, args.NrFiles, cS.shdWg, cS.stopMemPrf, cS.shtDw) return } // StopMemoryProfiling is used to stop MemoryProfiling func (cS *CoreS) StopMemoryProfiling() (err error) { if cS.stopMemPrf == nil { return errors.New("Memory Profiling is not started") } cS.fileMEM = path.Join(cS.fileMEM, utils.MemProfFileCgr) cS.stopChanMemProf() return } // Sleep is used to test the concurrent requests mechanism func (cS *CoreS) V1Sleep(_ *context.Context, arg *utils.DurationArgs, reply *string) error { time.Sleep(arg.Duration) *reply = utils.OK return nil } func (cS *CoreS) V1Shutdown(_ *context.Context, _ *utils.CGREvent, reply *string) error { cS.ShutdownEngine() *reply = utils.OK return nil } // StartCPUProfiling is used to start CPUProfiling in the given path func (cS *CoreS) V1StartCPUProfiling(_ *context.Context, args *utils.DirectoryArgs, reply *string) error { if err := cS.StartCPUProfiling(path.Join(args.DirPath, utils.CpuPathCgr)); err != nil { return err } *reply = utils.OK return nil } // StopCPUProfiling is used to stop CPUProfiling. The file should be written on the path // where the CPUProfiling already started func (cS *CoreS) V1StopCPUProfiling(_ *context.Context, _ *utils.TenantWithAPIOpts, reply *string) error { if err := cS.StopCPUProfiling(); err != nil { return err } *reply = utils.OK return nil } // StartMemoryProfiling is used to start MemoryProfiling in the given path func (cS *CoreS) V1StartMemoryProfiling(_ *context.Context, args *utils.MemoryPrf, reply *string) error { if err := cS.StartMemoryProfiling(args); err != nil { return err } *reply = utils.OK return nil } // StopMemoryProfiling is used to stop MemoryProfiling. The file should be written on the path // where the MemoryProfiling already started func (cS *CoreS) V1StopMemoryProfiling(_ *context.Context, _ *utils.TenantWithAPIOpts, reply *string) error { if err := cS.StopMemoryProfiling(); err != nil { return err } *reply = utils.OK return nil } // V1Panic is used print the Message sent as a panic func (cS *CoreS) V1Panic(_ *context.Context, args *utils.PanicMessageArgs, _ *string) error { panic(args.Message) }