Refactor HTTP profiling and flag handling

- hardcode the base path for profiling endpoints to '/debug/pprof/'
- change profiling flag 'httprof_path' to boolean 'http_pprof'
  (because of the above)
- remove redundant profiling endpoint registrations (handled by pprof.Index)
- move profiling registration log after actual registration
- make profiling registration log more descriptive
- use utils.Logger instead of log.Print for the log mentioned above
- refactor flags test into a table test (adding also verification for default
  flag values)
- change 'scheduledShutdown' flag type from string to time.Duration (to avoid
  unnecessary time parsing)
- revise flags usage descriptions
- rename flag 'singlecpu' to 'singleCPU'
- switch to 'ExitOnError' for flag parsing to simplify error handling and
  automatically handle 'ErrHelp' by exiting with status 0 when help is
  requested and status 2 for other parsing errors. Before the following error
  log would have been received:
  '<InitS> error received: <flag: help requested>, exiting!'
- update cgr-engine documentation
This commit is contained in:
ionutboangiu
2024-07-17 23:47:06 +03:00
committed by Dan Christian Bogos
parent 9eeee38824
commit 958aa267cf
6 changed files with 183 additions and 155 deletions

View File

@@ -50,22 +50,22 @@ import (
)
var (
cgrEngineFlags = flag.NewFlagSet(utils.CgrEngine, flag.ContinueOnError)
cfgPath = cgrEngineFlags.String(utils.CfgPathCgr, utils.ConfigPath, "Configuration directory path.")
version = cgrEngineFlags.Bool(utils.VersionCgr, false, "Prints the application version.")
printConfig = cgrEngineFlags.Bool(utils.PrintCfgCgr, false, "Print the configuration object in JSON format")
pidFile = cgrEngineFlags.String(utils.PidCgr, utils.EmptyString, "Write pid file")
httpPprofPath = cgrEngineFlags.String(utils.HttpPrfPthCgr, utils.EmptyString, "http address used for program profiling")
cpuProfDir = cgrEngineFlags.String(utils.CpuProfDirCgr, utils.EmptyString, "write cpu profile to files")
memProfDir = cgrEngineFlags.String(utils.MemProfDirCgr, utils.EmptyString, "write memory profile to file")
memProfInterval = cgrEngineFlags.Duration(utils.MemProfIntervalCgr, 5*time.Second, "Time between memory profile saves")
memProfNrFiles = cgrEngineFlags.Int(utils.MemProfNrFilesCgr, 1, "Number of memory profile to write")
scheduledShutdown = cgrEngineFlags.String(utils.ScheduledShutdownCgr, utils.EmptyString, "shutdown the engine after this duration")
singlecpu = cgrEngineFlags.Bool(utils.SingleCpuCgr, false, "Run on single CPU core")
syslogger = cgrEngineFlags.String(utils.LoggerCfg, utils.EmptyString, "logger <*syslog|*stdout>")
nodeID = cgrEngineFlags.String(utils.NodeIDCfg, utils.EmptyString, "The node ID of the engine")
logLevel = cgrEngineFlags.Int(utils.LogLevelCfg, -1, "Log level (0-emergency to 7-debug)")
preload = cgrEngineFlags.String(utils.PreloadCgr, utils.EmptyString, "LoaderIDs used to load the data before the engine starts")
cgrEngineFlags = flag.NewFlagSet(utils.CgrEngine, flag.ExitOnError)
cfgPath = cgrEngineFlags.String(utils.CfgPathCgr, utils.ConfigPath, "Configuration directory path")
version = cgrEngineFlags.Bool(utils.VersionCgr, false, "Print application version and exit")
printConfig = cgrEngineFlags.Bool(utils.PrintCfgCgr, false, "Print configuration object in JSON format")
pidFile = cgrEngineFlags.String(utils.PidCgr, utils.EmptyString, "Path to write the PID file")
httpPprof = cgrEngineFlags.Bool(utils.HttpPprofCgr, false, "Enable HTTP pprof profiling")
cpuProfDir = cgrEngineFlags.String(utils.CpuProfDirCgr, utils.EmptyString, "Directory for CPU profiles")
memProfDir = cgrEngineFlags.String(utils.MemProfDirCgr, utils.EmptyString, "Directory for memory profiles")
memProfInterval = cgrEngineFlags.Duration(utils.MemProfIntervalCgr, 5*time.Second, "Interval between memory profile saves")
memProfNrFiles = cgrEngineFlags.Int(utils.MemProfNrFilesCgr, 1, "Number of memory profiles to keep (most recent)")
scheduledShutdown = cgrEngineFlags.Duration(utils.ScheduledShutdownCgr, 0, "Shutdown the engine after the specified duration")
singleCPU = cgrEngineFlags.Bool(utils.SingleCpuCgr, false, "Run on a single CPU core")
syslogger = cgrEngineFlags.String(utils.LoggerCfg, utils.EmptyString, "Logger type <*syslog|*stdout>")
nodeID = cgrEngineFlags.String(utils.NodeIDCfg, utils.EmptyString, "Node ID of the engine")
logLevel = cgrEngineFlags.Int(utils.LogLevelCfg, -1, "Log level (0=emergency to 7=debug)")
preload = cgrEngineFlags.String(utils.PreloadCgr, utils.EmptyString, "Loader IDs used to load data before engine starts")
setVersions = cgrEngineFlags.Bool("set_versions", false, "Overwrite database versions (equivalent to cgr-migrator -exec=*set_versions)")
cfg *config.CGRConfig
@@ -330,9 +330,7 @@ func runPreload(loader *services.LoaderService, internalLoaderSChan chan birpc.C
}
func main() {
if err := cgrEngineFlags.Parse(os.Args[1:]); err != nil {
log.Fatalf("<%s> error received: <%s>, exiting!", utils.InitS, err.Error())
}
cgrEngineFlags.Parse(os.Args[1:])
vers, err := utils.GetCGRVersion()
if err != nil {
log.Fatalf("<%s> error received: <%s>, exiting!", utils.InitS, err.Error())
@@ -345,7 +343,7 @@ func main() {
if *pidFile != utils.EmptyString {
writePid()
}
if *singlecpu {
if *singleCPU {
runtime.GOMAXPROCS(1) // Having multiple cpus may slow down computing due to CPU management, to be reviewed in future Go releases
}
@@ -389,14 +387,10 @@ func main() {
}()
}
if *scheduledShutdown != utils.EmptyString {
shutdownDur, err := utils.ParseDurationWithNanosecs(*scheduledShutdown)
if err != nil {
log.Fatalf("<%s> error received: <%s>, exiting!", utils.InitS, err.Error())
}
if *scheduledShutdown != 0 {
shdWg.Add(1)
go func() { // Schedule shutdown
tm := time.NewTimer(shutdownDur)
tm := time.NewTimer(*scheduledShutdown)
select {
case <-tm.C:
shdChan.CloseOnce()
@@ -563,8 +557,9 @@ func main() {
if cfg.ConfigSCfg().Enabled {
server.RegisterHttpFunc(cfg.ConfigSCfg().URL, config.HandlerConfigS)
}
if *httpPprofPath != utils.EmptyString {
server.RegisterProfiler(*httpPprofPath)
if *httpPprof {
server.RegisterProfiler()
utils.Logger.Info("<HTTP> registered profiling endpoints at '/debug/pprof/'")
}
// Define internal connections via channels

View File

@@ -20,100 +20,141 @@ package main
import (
"path"
"reflect"
"testing"
"time"
)
// if the flag change this should fail
// do not use constants in this test
// If any flag changes, this test should fail.
// Do not use constants in this test to ensure these changes are detected.
func TestCgrEngineFlags(t *testing.T) {
if err := cgrEngineFlags.Parse([]string{"-config_path", path.Join("/conf", "samples", "tutorial")}); err != nil {
t.Fatal(err)
} else if *cfgPath != "/conf/samples/tutorial" {
t.Errorf("Expected /conf/samples/tutorial, received %+v", *cfgPath)
tests := []struct {
name string
flags []string
flagVar any
defaultVal any
want any
}{
{
name: "cfgPath",
flags: []string{"-config_path", path.Join("/usr", "share", "cgrates", "conf", "samples", "tutorial")},
flagVar: cfgPath,
defaultVal: "/etc/cgrates/",
want: "/usr/share/cgrates/conf/samples/tutorial",
},
{
name: "version",
flags: []string{"-version"},
flagVar: version,
defaultVal: false,
want: true,
},
{
name: "printConfig",
flags: []string{"-print_config"},
flagVar: printConfig,
defaultVal: false,
want: true,
},
{
name: "pidFile",
flags: []string{"-pid", "/run/cgrates/cgrates.pid"},
flagVar: pidFile,
defaultVal: "",
want: "/run/cgrates/cgrates.pid",
},
{
name: "httpPprof",
flags: []string{"-http_pprof"},
flagVar: httpPprof,
defaultVal: false,
want: true,
},
{
name: "cpuProfDir",
flags: []string{"-cpuprof_dir", "/tmp/profiling"},
flagVar: cpuProfDir,
defaultVal: "",
want: "/tmp/profiling",
},
{
name: "memProfDir",
flags: []string{"-memprof_dir", "/tmp/profiling"},
flagVar: memProfDir,
defaultVal: "",
want: "/tmp/profiling",
},
{
name: "memProfInterval",
flags: []string{"-memprof_interval", "1s"},
flagVar: memProfInterval,
defaultVal: 5 * time.Second,
want: time.Second,
},
{
name: "memProfNrFiles",
flags: []string{"-memprof_nrfiles", "3"},
flagVar: memProfNrFiles,
defaultVal: 1,
want: 3,
},
{
name: "scheduledShutdown",
flags: []string{"-scheduled_shutdown", "1h"},
flagVar: scheduledShutdown,
defaultVal: time.Duration(0),
want: time.Hour,
},
{
name: "singleCPU",
flags: []string{"-singlecpu"},
flagVar: singleCPU,
defaultVal: false,
want: true,
},
{
name: "syslogger",
flags: []string{"-logger", "*stdout"},
flagVar: syslogger,
defaultVal: "",
want: "*stdout",
},
{
name: "nodeID",
flags: []string{"-node_id", "CGRateS.org"},
flagVar: nodeID,
defaultVal: "",
want: "CGRateS.org",
},
{
name: "logLevel",
flags: []string{"-log_level", "7"},
flagVar: logLevel,
defaultVal: -1,
want: 7,
},
{
name: "preload",
flags: []string{"-preload", "TestPreloadID"},
flagVar: preload,
defaultVal: "",
want: "TestPreloadID",
},
}
if err := cgrEngineFlags.Parse([]string{"-version", "true"}); err != nil {
t.Fatal(err)
} else if *version != true {
t.Errorf("Expected true, received %+v", *version)
}
if err := cgrEngineFlags.Parse([]string{"-print_config", "true"}); err != nil {
t.Fatal(err)
} else if *printConfig != true {
t.Errorf("Expected true, received %+v", *printConfig)
}
if err := cgrEngineFlags.Parse([]string{"-pid", "usr/share/cgrates/cgrates.json"}); err != nil {
t.Fatal(err)
} else if *pidFile != "usr/share/cgrates/cgrates.json" {
t.Errorf("Expected usr/share/cgrates/cgrates.json, received %+v", *pidFile)
}
if err := cgrEngineFlags.Parse([]string{"-httprof_path", "http://example.com/"}); err != nil {
t.Fatal(err)
} else if *httpPprofPath != "http://example.com/" {
t.Errorf("Expected http://example.com/, received %+v", *httpPprofPath)
}
if err := cgrEngineFlags.Parse([]string{"-cpuprof_dir", "1"}); err != nil {
t.Fatal(err)
} else if *cpuProfDir != "1" {
t.Errorf("Expected 1, received %+v", *httpPprofPath)
}
if err := cgrEngineFlags.Parse([]string{"-memprof_dir", "true"}); err != nil {
t.Fatal(err)
} else if *memProfDir != "true" {
t.Errorf("Expected true received %+v", *memProfDir)
}
if err := cgrEngineFlags.Parse([]string{"-memprof_interval", "1s"}); err != nil {
t.Fatal(err)
} else if *memProfInterval != time.Second {
t.Errorf("Expected 1s, received %+v", *memProfInterval)
}
if err := cgrEngineFlags.Parse([]string{"-memprof_nrfiles", "3"}); err != nil {
t.Fatal(err)
} else if *memProfNrFiles != 3 {
t.Errorf("Expected 3, received %+v", *memProfNrFiles)
}
if err := cgrEngineFlags.Parse([]string{"-scheduled_shutdown", "1h"}); err != nil {
t.Fatal(err)
} else if *scheduledShutdown != "1h" {
t.Errorf("Expected 1h, received %+v", *scheduledShutdown)
}
if err := cgrEngineFlags.Parse([]string{"-singlecpu"}); err != nil {
t.Fatal(err)
} else if *singlecpu != true {
t.Errorf("Expected true, received %+v", *singlecpu)
}
if err := cgrEngineFlags.Parse([]string{"-logger", "*stdout"}); err != nil {
t.Fatal(err)
} else if *syslogger != "*stdout" {
t.Errorf("Expected *stdout, received %+v", *syslogger)
}
if err := cgrEngineFlags.Parse([]string{"-node_id", "CGRates.org"}); err != nil {
t.Fatal(err)
} else if *nodeID != "CGRates.org" {
t.Errorf("Expected CGRates.org, received %+v", *nodeID)
}
if err := cgrEngineFlags.Parse([]string{"-log_level", "7"}); err != nil {
t.Fatal(err)
} else if *logLevel != 7 {
t.Errorf("Expected 7, received %+v", *logLevel)
}
if err := cgrEngineFlags.Parse([]string{"-preload", "TestPreloadID"}); err != nil {
t.Fatal(err)
} else if *preload != "TestPreloadID" {
t.Errorf("Expected 7, received %+v", *preload)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flagVal := reflect.ValueOf(tt.flagVar).Elem().Interface()
if flagVal != tt.defaultVal {
t.Errorf("%s=%v, want default value %v", tt.name, flagVal, tt.defaultVal)
}
if err := cgrEngineFlags.Parse(tt.flags); err != nil {
t.Errorf("cgrEngineFlags.Parse(%v) returned unexpected error: %v", tt.flags, err)
}
flagVal = reflect.ValueOf(tt.flagVar).Elem().Interface()
if flagVal != tt.want {
t.Errorf("%s=%v, want %v", tt.name, flagVal, tt.want)
}
})
}
}