mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-05 22:17:20 +02:00
add profiler support
profiling is now available via the HTTP base URL /debug/pprof/ examples, use this URL to start and download a 30 seconds CPU profile: /debug/pprof/profile?seconds=30 use this URL to profile used memory: /debug/pprof/heap?gc=1 use this URL to profile allocated memory: /debug/pprof/allocs?gc=1 Full docs here: https://golang.org/pkg/net/http/pprof/
This commit is contained in:
@@ -91,6 +91,7 @@ Please take a look at the usage below to customize the serving parameters`,
|
|||||||
LogMaxAge: defaultLogMaxAge,
|
LogMaxAge: defaultLogMaxAge,
|
||||||
LogCompress: defaultLogCompress,
|
LogCompress: defaultLogCompress,
|
||||||
LogVerbose: defaultLogVerbose,
|
LogVerbose: defaultLogVerbose,
|
||||||
|
Profiler: defaultProfiler,
|
||||||
Shutdown: make(chan bool),
|
Shutdown: make(chan bool),
|
||||||
PortableMode: 1,
|
PortableMode: 1,
|
||||||
PortableUser: dataprovider.User{
|
PortableUser: dataprovider.User{
|
||||||
|
|||||||
14
cmd/root.go
14
cmd/root.go
@@ -30,6 +30,8 @@ const (
|
|||||||
logCompressKey = "log_compress"
|
logCompressKey = "log_compress"
|
||||||
logVerboseFlag = "log-verbose"
|
logVerboseFlag = "log-verbose"
|
||||||
logVerboseKey = "log_verbose"
|
logVerboseKey = "log_verbose"
|
||||||
|
profilerFlag = "profiler"
|
||||||
|
profilerKey = "profiler"
|
||||||
defaultConfigDir = "."
|
defaultConfigDir = "."
|
||||||
defaultConfigName = config.DefaultConfigName
|
defaultConfigName = config.DefaultConfigName
|
||||||
defaultLogFile = "sftpgo.log"
|
defaultLogFile = "sftpgo.log"
|
||||||
@@ -38,6 +40,7 @@ const (
|
|||||||
defaultLogMaxAge = 28
|
defaultLogMaxAge = 28
|
||||||
defaultLogCompress = false
|
defaultLogCompress = false
|
||||||
defaultLogVerbose = true
|
defaultLogVerbose = true
|
||||||
|
defaultProfiler = false
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -49,6 +52,7 @@ var (
|
|||||||
logMaxAge int
|
logMaxAge int
|
||||||
logCompress bool
|
logCompress bool
|
||||||
logVerbose bool
|
logVerbose bool
|
||||||
|
profiler bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "sftpgo",
|
Use: "sftpgo",
|
||||||
@@ -135,6 +139,13 @@ func addServeFlags(cmd *cobra.Command) {
|
|||||||
cmd.Flags().BoolVarP(&logVerbose, logVerboseFlag, "v", viper.GetBool(logVerboseKey), "Enable verbose logs. "+
|
cmd.Flags().BoolVarP(&logVerbose, logVerboseFlag, "v", viper.GetBool(logVerboseKey), "Enable verbose logs. "+
|
||||||
"This flag can be set using SFTPGO_LOG_VERBOSE env var too.")
|
"This flag can be set using SFTPGO_LOG_VERBOSE env var too.")
|
||||||
viper.BindPFlag(logVerboseKey, cmd.Flags().Lookup(logVerboseFlag))
|
viper.BindPFlag(logVerboseKey, cmd.Flags().Lookup(logVerboseFlag))
|
||||||
|
|
||||||
|
viper.SetDefault(profilerKey, defaultProfiler)
|
||||||
|
viper.BindEnv(profilerKey, "SFTPGO_PROFILER")
|
||||||
|
cmd.Flags().BoolVarP(&profiler, profilerFlag, "p", viper.GetBool(profilerKey), "Enable the built-in profiler. "+
|
||||||
|
"The profiler will be accessible via HTTP/HTTPS using the base URL \"/debug/pprof/\". "+
|
||||||
|
"This flag can be set using SFTPGO_PROFILER env var too.")
|
||||||
|
viper.BindPFlag(profilerKey, cmd.Flags().Lookup(profilerFlag))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCustomServeFlags() []string {
|
func getCustomServeFlags() []string {
|
||||||
@@ -170,5 +181,8 @@ func getCustomServeFlags() []string {
|
|||||||
if logCompress != defaultLogCompress {
|
if logCompress != defaultLogCompress {
|
||||||
result = append(result, "--"+logCompressFlag+"=true")
|
result = append(result, "--"+logCompressFlag+"=true")
|
||||||
}
|
}
|
||||||
|
if profiler != defaultProfiler {
|
||||||
|
result = append(result, "--"+profilerFlag+"=true")
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Please take a look at the usage below to customize the startup options`,
|
|||||||
LogMaxAge: logMaxAge,
|
LogMaxAge: logMaxAge,
|
||||||
LogCompress: logCompress,
|
LogCompress: logCompress,
|
||||||
LogVerbose: logVerbose,
|
LogVerbose: logVerbose,
|
||||||
|
Profiler: profiler,
|
||||||
Shutdown: make(chan bool),
|
Shutdown: make(chan bool),
|
||||||
}
|
}
|
||||||
if err := service.Start(); err == nil {
|
if err := service.Start(); err == nil {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var (
|
|||||||
LogMaxAge: logMaxAge,
|
LogMaxAge: logMaxAge,
|
||||||
LogCompress: logCompress,
|
LogCompress: logCompress,
|
||||||
LogVerbose: logVerbose,
|
LogVerbose: logVerbose,
|
||||||
|
Profiler: profiler,
|
||||||
Shutdown: make(chan bool),
|
Shutdown: make(chan bool),
|
||||||
}
|
}
|
||||||
winService := service.WindowsService{
|
winService := service.WindowsService{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const (
|
|||||||
dumpDataPath = "/api/v1/dumpdata"
|
dumpDataPath = "/api/v1/dumpdata"
|
||||||
loadDataPath = "/api/v1/loaddata"
|
loadDataPath = "/api/v1/loaddata"
|
||||||
metricsPath = "/metrics"
|
metricsPath = "/metrics"
|
||||||
|
pprofBasePath = "/debug"
|
||||||
webBasePath = "/web"
|
webBasePath = "/web"
|
||||||
webUsersPath = "/web/users"
|
webUsersPath = "/web/users"
|
||||||
webUserPath = "/web/user"
|
webUserPath = "/web/user"
|
||||||
@@ -85,7 +86,7 @@ func SetDataProvider(provider dataprovider.Provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the HTTP server
|
// Initialize the HTTP server
|
||||||
func (c Conf) Initialize(configDir string) error {
|
func (c Conf) Initialize(configDir string, profiler bool) error {
|
||||||
var err error
|
var err error
|
||||||
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
logger.Debug(logSender, "", "initializing HTTP server with config %+v", c)
|
||||||
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
backupsPath = getConfigPath(c.BackupsPath, configDir)
|
||||||
@@ -103,7 +104,7 @@ func (c Conf) Initialize(configDir string) error {
|
|||||||
certificateFile := getConfigPath(c.CertificateFile, configDir)
|
certificateFile := getConfigPath(c.CertificateFile, configDir)
|
||||||
certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
|
certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
|
||||||
loadTemplates(templatesPath)
|
loadTemplates(templatesPath)
|
||||||
initializeRouter(staticFilesPath)
|
initializeRouter(staticFilesPath, profiler)
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort),
|
Addr: fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort),
|
||||||
Handler: router,
|
Handler: router,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const (
|
|||||||
dumpDataPath = "/api/v1/dumpdata"
|
dumpDataPath = "/api/v1/dumpdata"
|
||||||
loadDataPath = "/api/v1/loaddata"
|
loadDataPath = "/api/v1/loaddata"
|
||||||
metricsPath = "/metrics"
|
metricsPath = "/metrics"
|
||||||
|
pprofPath = "/debug/pprof/"
|
||||||
webBasePath = "/web"
|
webBasePath = "/web"
|
||||||
webUsersPath = "/web/users"
|
webUsersPath = "/web/users"
|
||||||
webUserPath = "/web/user"
|
webUserPath = "/web/user"
|
||||||
@@ -117,7 +118,7 @@ func TestMain(m *testing.M) {
|
|||||||
httpd.SetDataProvider(dataProvider)
|
httpd.SetDataProvider(dataProvider)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := httpdConf.Initialize(configDir); err != nil {
|
if err := httpdConf.Initialize(configDir, true); err != nil {
|
||||||
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -133,7 +134,7 @@ func TestMain(m *testing.M) {
|
|||||||
httpdConf.CertificateKeyFile = keyPath
|
httpdConf.CertificateKeyFile = keyPath
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := httpdConf.Initialize(configDir); err != nil {
|
if err := httpdConf.Initialize(configDir, true); err != nil {
|
||||||
logger.Error(logSender, "", "could not start HTTPS server: %v", err)
|
logger.Error(logSender, "", "could not start HTTPS server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -157,7 +158,7 @@ func TestInitialization(t *testing.T) {
|
|||||||
httpdConf := config.GetHTTPDConfig()
|
httpdConf := config.GetHTTPDConfig()
|
||||||
httpdConf.BackupsPath = "test_backups"
|
httpdConf.BackupsPath = "test_backups"
|
||||||
httpdConf.AuthUserFile = "invalid file"
|
httpdConf.AuthUserFile = "invalid file"
|
||||||
err := httpdConf.Initialize(configDir)
|
err := httpdConf.Initialize(configDir, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Inizialize must fail")
|
t.Error("Inizialize must fail")
|
||||||
}
|
}
|
||||||
@@ -165,14 +166,14 @@ func TestInitialization(t *testing.T) {
|
|||||||
httpdConf.AuthUserFile = ""
|
httpdConf.AuthUserFile = ""
|
||||||
httpdConf.CertificateFile = "invalid file"
|
httpdConf.CertificateFile = "invalid file"
|
||||||
httpdConf.CertificateKeyFile = "invalid file"
|
httpdConf.CertificateKeyFile = "invalid file"
|
||||||
err = httpdConf.Initialize(configDir)
|
err = httpdConf.Initialize(configDir, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Inizialize must fail")
|
t.Error("Inizialize must fail")
|
||||||
}
|
}
|
||||||
httpdConf.CertificateFile = ""
|
httpdConf.CertificateFile = ""
|
||||||
httpdConf.CertificateKeyFile = ""
|
httpdConf.CertificateKeyFile = ""
|
||||||
httpdConf.TemplatesPath = "."
|
httpdConf.TemplatesPath = "."
|
||||||
err = httpdConf.Initialize(configDir)
|
err = httpdConf.Initialize(configDir, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Inizialize must fail")
|
t.Error("Inizialize must fail")
|
||||||
}
|
}
|
||||||
@@ -1681,6 +1682,12 @@ func TestMetricsMock(t *testing.T) {
|
|||||||
checkResponseCode(t, http.StatusOK, rr.Code)
|
checkResponseCode(t, http.StatusOK, rr.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPProfEndPointMock(t *testing.T) {
|
||||||
|
req, _ := http.NewRequest(http.MethodGet, pprofPath, nil)
|
||||||
|
rr := executeRequest(req)
|
||||||
|
checkResponseCode(t, http.StatusOK, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetWebRootMock(t *testing.T) {
|
func TestGetWebRootMock(t *testing.T) {
|
||||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||||
rr := executeRequest(req)
|
rr := executeRequest(req)
|
||||||
|
|||||||
@@ -18,13 +18,19 @@ func GetHTTPRouter() http.Handler {
|
|||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeRouter(staticFilesPath string) {
|
func initializeRouter(staticFilesPath string, profiler bool) {
|
||||||
router = chi.NewRouter()
|
router = chi.NewRouter()
|
||||||
router.Use(middleware.RequestID)
|
router.Use(middleware.RequestID)
|
||||||
router.Use(middleware.RealIP)
|
router.Use(middleware.RealIP)
|
||||||
router.Use(logger.NewStructuredLogger(logger.GetLogger()))
|
router.Use(logger.NewStructuredLogger(logger.GetLogger()))
|
||||||
router.Use(middleware.Recoverer)
|
router.Use(middleware.Recoverer)
|
||||||
|
|
||||||
|
if profiler {
|
||||||
|
logger.InfoToConsole("enabling the built-in profiler")
|
||||||
|
logger.Info(logSender, "", "enabling the built-in profiler")
|
||||||
|
router.Mount(pprofBasePath, middleware.Profiler())
|
||||||
|
}
|
||||||
|
|
||||||
router.NotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
router.NotFound(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
sendAPIResponse(w, r, nil, "Not Found", http.StatusNotFound)
|
sendAPIResponse(w, r, nil, "Not Found", http.StatusNotFound)
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type Service struct {
|
|||||||
LogVerbose bool
|
LogVerbose bool
|
||||||
PortableMode int
|
PortableMode int
|
||||||
PortableUser dataprovider.User
|
PortableUser dataprovider.User
|
||||||
|
Profiler bool
|
||||||
Shutdown chan bool
|
Shutdown chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +63,8 @@ func (s *Service) Start() error {
|
|||||||
}
|
}
|
||||||
version := utils.GetAppVersion()
|
version := utils.GetAppVersion()
|
||||||
logger.Info(logSender, "", "starting SFTPGo %v, config dir: %v, config file: %v, log max size: %v log max backups: %v "+
|
logger.Info(logSender, "", "starting SFTPGo %v, config dir: %v, config file: %v, log max size: %v log max backups: %v "+
|
||||||
"log max age: %v log verbose: %v, log compress: %v", version.GetVersionAsString(), s.ConfigDir, s.ConfigFile, s.LogMaxSize,
|
"log max age: %v log verbose: %v, log compress: %v, profile: %v", version.GetVersionAsString(), s.ConfigDir, s.ConfigFile,
|
||||||
s.LogMaxBackups, s.LogMaxAge, s.LogVerbose, s.LogCompress)
|
s.LogMaxSize, s.LogMaxBackups, s.LogMaxAge, s.LogVerbose, s.LogCompress, s.Profiler)
|
||||||
// in portable mode we don't read configuration from file
|
// in portable mode we don't read configuration from file
|
||||||
if s.PortableMode != 1 {
|
if s.PortableMode != 1 {
|
||||||
config.LoadConfig(s.ConfigDir, s.ConfigFile)
|
config.LoadConfig(s.ConfigDir, s.ConfigFile)
|
||||||
@@ -105,7 +106,7 @@ func (s *Service) Start() error {
|
|||||||
httpd.SetDataProvider(dataProvider)
|
httpd.SetDataProvider(dataProvider)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := httpdConf.Initialize(s.ConfigDir); err != nil {
|
if err := httpdConf.Initialize(s.ConfigDir, s.Profiler); err != nil {
|
||||||
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
||||||
logger.ErrorToConsole("could not start HTTP server: %v", err)
|
logger.ErrorToConsole("could not start HTTP server: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func TestMain(m *testing.M) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := httpdConf.Initialize(configDir); err != nil {
|
if err := httpdConf.Initialize(configDir, false); err != nil {
|
||||||
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
logger.Error(logSender, "", "could not start HTTP server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
Reference in New Issue
Block a user