diff --git a/go.mod b/go.mod index 7567553b..79e0a168 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/AdguardTeam/dnsproxy v0.52.0 - github.com/AdguardTeam/golibs v0.13.5 + github.com/AdguardTeam/golibs v0.13.6 github.com/AdguardTeam/urlfilter v0.16.1 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.2.7 @@ -31,10 +31,10 @@ require ( github.com/stretchr/testify v1.8.4 github.com/ti-mo/netfilter v0.5.0 go.etcd.io/bbolt v1.3.7 - golang.org/x/crypto v0.10.0 - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df - golang.org/x/net v0.11.0 - golang.org/x/sys v0.9.0 + golang.org/x/crypto v0.11.0 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + golang.org/x/net v0.12.0 + golang.org/x/sys v0.10.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 howett.net/plist v1.0.0 @@ -61,6 +61,6 @@ require ( github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/tools v0.10.0 // indirect ) diff --git a/go.sum b/go.sum index 1cef6a07..d39288fb 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/AdguardTeam/dnsproxy v0.52.0 h1:uZxCXflHSAwtJ7uTYXP6qgWcxaBsH0pJvldpw github.com/AdguardTeam/dnsproxy v0.52.0/go.mod h1:Jo2zeRe97Rxt3yikXc+fn0LdLtqCj0Xlyh1PNBj6bpM= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.10.4/go.mod h1:rSfQRGHIdgfxriDDNgNJ7HmE5zRoURq8R+VdR81Zuzw= -github.com/AdguardTeam/golibs v0.13.5 h1:fpa30Yr9Rcn4vJ88nE4XHSompY7/qMOq2aNS/4PGymA= -github.com/AdguardTeam/golibs v0.13.5/go.mod h1:wkJ6EUsN4np/9Gp7+9QeooY9E2U2WCLJYAioLCzkHsI= +github.com/AdguardTeam/golibs v0.13.6 h1:z/0Q25pRLdaQxtoxvfSaooz5mdv8wj0R8KREj54q8yQ= +github.com/AdguardTeam/golibs v0.13.6/go.mod h1:hOtcb8dPfKcFjWTPA904hTA4dl1aWvzeebdJpE72IPk= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/urlfilter v0.16.1 h1:ZPi0rjqo8cQf2FVdzo6cqumNoHZx2KPXj2yZa1A5BBw= github.com/AdguardTeam/urlfilter v0.16.1/go.mod h1:46YZDOV1+qtdRDuhZKVPSSp7JWWes0KayqHrKAFBdEI= @@ -134,10 +134,10 @@ go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -152,8 +152,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210929193557-e81a3d93ecf6/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= @@ -177,16 +177,16 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/internal/home/pprof.go b/internal/home/pprof.go deleted file mode 100644 index b8ef8e74..00000000 --- a/internal/home/pprof.go +++ /dev/null @@ -1,39 +0,0 @@ -package home - -import ( - "net/http" - "net/http/pprof" - "runtime" - - "github.com/AdguardTeam/golibs/log" -) - -// startPprof launches the debug and profiling server on addr. -func startPprof(addr string) { - runtime.SetBlockProfileRate(1) - runtime.SetMutexProfileFraction(1) - - mux := http.NewServeMux() - - mux.HandleFunc("/debug/pprof/", pprof.Index) - mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) - - // See profileSupportsDelta in src/net/http/pprof/pprof.go. - mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs")) - mux.Handle("/debug/pprof/block", pprof.Handler("block")) - mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) - mux.Handle("/debug/pprof/heap", pprof.Handler("heap")) - mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex")) - mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) - - go func() { - defer log.OnPanic("pprof server") - - log.Info("pprof: listening on %q", addr) - err := http.ListenAndServe(addr, mux) - log.Info("pprof server errors: %v", err) - }() -} diff --git a/internal/home/web.go b/internal/home/web.go index 0578776a..6649dfe2 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -6,6 +6,7 @@ import ( "io/fs" "net/http" "net/netip" + "runtime" "sync" "time" @@ -15,6 +16,7 @@ import ( "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" + "github.com/AdguardTeam/golibs/pprofutil" "github.com/NYTimes/gziphandler" "github.com/quic-go/quic-go/http3" "golang.org/x/net/http2" @@ -309,3 +311,22 @@ func (web *webAPI) mustStartHTTP3(address string) { log.Fatalf("web: http3: %s", err) } } + +// startPprof launches the debug and profiling server on addr. +func startPprof(addr string) { + runtime.SetBlockProfileRate(1) + runtime.SetMutexProfileFraction(1) + + mux := http.NewServeMux() + pprofutil.RoutePprof(mux) + + go func() { + defer log.OnPanic("pprof server") + + log.Info("pprof: listening on %q", addr) + err := http.ListenAndServe(addr, mux) + if !errors.Is(err, http.ErrServerClosed) { + log.Error("pprof: shutting down: %s", err) + } + }() +} diff --git a/internal/next/AdGuardHome.example.yaml b/internal/next/AdGuardHome.example.yaml index db99f990..1bdd8313 100644 --- a/internal/next/AdGuardHome.example.yaml +++ b/internal/next/AdGuardHome.example.yaml @@ -18,6 +18,9 @@ dns: bootstrap_prefer_ipv6: true use_dns64: true http: + pprof: + enabled: true + port: 6060 addresses: - '0.0.0.0:3000' secure_addresses: [] diff --git a/internal/next/changelog.md b/internal/next/changelog.md index 59126ee3..224184bc 100644 --- a/internal/next/changelog.md +++ b/internal/next/changelog.md @@ -7,6 +7,7 @@ enough. ### Added +- The ability to change the port of the pprof debug API. - The ability to log to stderr using `--logFile=stderr`. - The new `--web-addr` flag to set the Web UI address in a `host:port` form. - `SIGHUP` now reloads all configuration from the configuration file ([#5676]). diff --git a/internal/next/configmgr/config.go b/internal/next/configmgr/config.go index cca53672..5d67a372 100644 --- a/internal/next/configmgr/config.go +++ b/internal/next/configmgr/config.go @@ -17,8 +17,6 @@ type config struct { Log *logConfig `yaml:"log"` // TODO(a.garipov): Use. SchemaVersion int `yaml:"schema_version"` - // TODO(a.garipov): Use. - DebugPprof bool `yaml:"debug_pprof"` } const errNoConf errors.Error = "configuration not found" @@ -84,6 +82,8 @@ func (c *dnsConfig) validate() (err error) { // httpConfig is the on-disk web API configuration. type httpConfig struct { + Pprof *httpPprofConfig `yaml:"pprof"` + // TODO(a.garipov): Document the configuration change. Addresses []netip.AddrPort `yaml:"addresses"` SecureAddresses []netip.AddrPort `yaml:"secure_addresses"` @@ -101,10 +101,25 @@ func (c *httpConfig) validate() (err error) { case c.Timeout.Duration <= 0: return newMustBePositiveError("timeout", c.Timeout) default: - return nil + return c.Pprof.validate() } } +// httpPprofConfig is the on-disk pprof configuration. +type httpPprofConfig struct { + Port uint16 `yaml:"port"` + Enabled bool `yaml:"enabled"` +} + +// validate returns an error if the pprof configuration structure is invalid. +func (c *httpPprofConfig) validate() (err error) { + if c == nil { + return errNoConf + } + + return nil +} + // logConfig is the on-disk web API configuration. type logConfig struct { // TODO(a.garipov): Use. diff --git a/internal/next/configmgr/configmgr.go b/internal/next/configmgr/configmgr.go index dc2d999f..adc6bbee 100644 --- a/internal/next/configmgr/configmgr.go +++ b/internal/next/configmgr/configmgr.go @@ -151,6 +151,10 @@ func (m *Manager) assemble( } webSvcConf := &websvc.Config{ + Pprof: &websvc.PprofConfig{ + Port: conf.HTTP.Pprof.Port, + Enabled: conf.HTTP.Pprof.Enabled, + }, ConfigManager: m, Frontend: frontend, // TODO(a.garipov): Fill from config file. @@ -259,9 +263,6 @@ func (m *Manager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) { m.updMu.Lock() defer m.updMu.Unlock() - // TODO(a.garipov): Update and write the configuration file. Return an - // error if something went wrong. - err = m.updateWeb(ctx, c) if err != nil { return fmt.Errorf("reassembling websvc: %w", err) @@ -291,6 +292,8 @@ func (m *Manager) updateWeb(ctx context.Context, c *websvc.Config) (err error) { // updateCurrentWeb updates the web configuration in the current config. func (m *Manager) updateCurrentWeb(c *websvc.Config) { + // TODO(a.garipov): Update pprof from API? + m.current.HTTP.Addresses = slices.Clone(c.Addresses) m.current.HTTP.SecureAddresses = slices.Clone(c.SecureAddresses) m.current.HTTP.Timeout = timeutil.Duration{Duration: c.Timeout} diff --git a/internal/next/websvc/config.go b/internal/next/websvc/config.go new file mode 100644 index 00000000..36a145c5 --- /dev/null +++ b/internal/next/websvc/config.go @@ -0,0 +1,79 @@ +package websvc + +import ( + "crypto/tls" + "io/fs" + "net/netip" + "time" +) + +// Config is the AdGuard Home web service configuration structure. +type Config struct { + // Pprof is the configuration for the pprof debug API. It must not be nil. + Pprof *PprofConfig + + // ConfigManager is used to show information about services as well as + // dynamically reconfigure them. + ConfigManager ConfigManager + + // Frontend is the filesystem with the frontend and other statically + // compiled files. + Frontend fs.FS + + // TLS is the optional TLS configuration. If TLS is not nil, + // SecureAddresses must not be empty. + TLS *tls.Config + + // Start is the time of start of AdGuard Home. + Start time.Time + + // OverrideAddress is the initial or override address for the HTTP API. If + // set, it is used instead of [Addresses] and [SecureAddresses]. + OverrideAddress netip.AddrPort + + // Addresses are the addresses on which to serve the plain HTTP API. + Addresses []netip.AddrPort + + // SecureAddresses are the addresses on which to serve the HTTPS API. If + // SecureAddresses is not empty, TLS must not be nil. + SecureAddresses []netip.AddrPort + + // Timeout is the timeout for all server operations. + Timeout time.Duration + + // ForceHTTPS tells if all requests to Addresses should be redirected to a + // secure address instead. + // + // TODO(a.garipov): Use; define rules, which address to redirect to. + ForceHTTPS bool +} + +// PprofConfig is the configuration for the pprof debug API. +type PprofConfig struct { + Port uint16 `yaml:"port"` + Enabled bool `yaml:"enabled"` +} + +// Config returns the current configuration of the web service. Config must not +// be called simultaneously with Start. If svc was initialized with ":0" +// addresses, addrs will not return the actual bound ports until Start is +// finished. +func (svc *Service) Config() (c *Config) { + c = &Config{ + Pprof: &PprofConfig{ + Port: svc.pprofPort, + Enabled: svc.pprof != nil, + }, + ConfigManager: svc.confMgr, + TLS: svc.tls, + // Leave Addresses and SecureAddresses empty and get the actual + // addresses that include the :0 ones later. + Start: svc.start, + Timeout: svc.timeout, + ForceHTTPS: svc.forceHTTPS, + } + + c.Addresses, c.SecureAddresses = svc.addrs() + + return c +} diff --git a/internal/next/websvc/http.go b/internal/next/websvc/http.go index 2813d9fe..7e4785cd 100644 --- a/internal/next/websvc/http.go +++ b/internal/next/websvc/http.go @@ -52,6 +52,10 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque } newConf := &Config{ + Pprof: &PprofConfig{ + Port: svc.pprofPort, + Enabled: svc.pprof != nil, + }, ConfigManager: svc.confMgr, Frontend: svc.frontend, TLS: svc.tls, diff --git a/internal/next/websvc/http_test.go b/internal/next/websvc/http_test.go index b4ac64c6..c04921f6 100644 --- a/internal/next/websvc/http_test.go +++ b/internal/next/websvc/http_test.go @@ -25,6 +25,9 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) { } svc, err := websvc.New(&websvc.Config{ + Pprof: &websvc.PprofConfig{ + Enabled: false, + }, TLS: &tls.Config{ Certificates: []tls.Certificate{{}}, }, diff --git a/internal/next/websvc/settings_test.go b/internal/next/websvc/settings_test.go index fc086524..cacf28a9 100644 --- a/internal/next/websvc/settings_test.go +++ b/internal/next/websvc/settings_test.go @@ -49,6 +49,9 @@ func TestService_HandleGetSettingsAll(t *testing.T) { } svc, err := websvc.New(&websvc.Config{ + Pprof: &websvc.PprofConfig{ + Enabled: false, + }, TLS: &tls.Config{ Certificates: []tls.Certificate{{}}, }, diff --git a/internal/next/websvc/websvc.go b/internal/next/websvc/websvc.go index ce4da4ac..27aef970 100644 --- a/internal/next/websvc/websvc.go +++ b/internal/next/websvc/websvc.go @@ -15,6 +15,7 @@ import ( "net" "net/http" "net/netip" + "runtime" "sync" "time" @@ -22,6 +23,8 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc" "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/mathutil" + "github.com/AdguardTeam/golibs/pprofutil" httptreemux "github.com/dimfeld/httptreemux/v5" ) @@ -34,54 +37,18 @@ type ConfigManager interface { UpdateWeb(ctx context.Context, c *Config) (err error) } -// Config is the AdGuard Home web service configuration structure. -type Config struct { - // ConfigManager is used to show information about services as well as - // dynamically reconfigure them. - ConfigManager ConfigManager - - // Frontend is the filesystem with the frontend and other statically - // compiled files. - Frontend fs.FS - - // TLS is the optional TLS configuration. If TLS is not nil, - // SecureAddresses must not be empty. - TLS *tls.Config - - // Start is the time of start of AdGuard Home. - Start time.Time - - // OverrideAddress is the initial or override address for the HTTP API. If - // set, it is used instead of [Addresses] and [SecureAddresses]. - OverrideAddress netip.AddrPort - - // Addresses are the addresses on which to serve the plain HTTP API. - Addresses []netip.AddrPort - - // SecureAddresses are the addresses on which to serve the HTTPS API. If - // SecureAddresses is not empty, TLS must not be nil. - SecureAddresses []netip.AddrPort - - // Timeout is the timeout for all server operations. - Timeout time.Duration - - // ForceHTTPS tells if all requests to Addresses should be redirected to a - // secure address instead. - // - // TODO(a.garipov): Use; define rules, which address to redirect to. - ForceHTTPS bool -} - // Service is the AdGuard Home web service. A nil *Service is a valid // [agh.Service] that does nothing. type Service struct { confMgr ConfigManager frontend fs.FS tls *tls.Config + pprof *http.Server start time.Time overrideAddr netip.AddrPort servers []*http.Server timeout time.Duration + pprofPort uint16 forceHTTPS bool } @@ -120,9 +87,35 @@ func New(c *Config) (svc *Service, err error) { } } + svc.setupPprof(c.Pprof) + return svc, nil } +// setupPprof sets the pprof properties of svc. +func (svc *Service) setupPprof(c *PprofConfig) { + if !c.Enabled { + // Set to zero explicitly in case pprof used to be enabled before a + // reconfiguration took place. + runtime.SetBlockProfileRate(0) + runtime.SetMutexProfileFraction(0) + + return + } + + runtime.SetBlockProfileRate(1) + runtime.SetMutexProfileFraction(1) + + pprofMux := http.NewServeMux() + pprofutil.RoutePprof(pprofMux) + + svc.pprofPort = c.Port + addr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), c.Port) + + // TODO(a.garipov): Consider making pprof timeout configurable. + svc.pprof = newSrv(addr, nil, pprofMux, 10*time.Minute) +} + // newSrv returns a new *http.Server with the given parameters. func newSrv( addr netip.AddrPort, @@ -254,12 +247,19 @@ func (svc *Service) Start() (err error) { return nil } + pprofEnabled := svc.pprof != nil + srvNum := len(svc.servers) + mathutil.BoolToNumber[int](pprofEnabled) + wg := &sync.WaitGroup{} - wg.Add(len(svc.servers)) + wg.Add(srvNum) for _, srv := range svc.servers { go serve(srv, wg) } + if pprofEnabled { + go serve(svc.pprof, wg) + } + wg.Wait() return nil @@ -310,9 +310,20 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) { var errs []error for _, srv := range svc.servers { - serr := srv.Shutdown(ctx) - if serr != nil { - errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, serr)) + shutdownErr := srv.Shutdown(ctx) + if shutdownErr != nil { + errs = append(errs, fmt.Errorf("shutting down srv %s: %w", srv.Addr, shutdownErr)) + } + } + + if svc.pprof != nil { + shutdownErr := svc.pprof.Shutdown(ctx) + if shutdownErr != nil { + errs = append(errs, fmt.Errorf( + "shutting down pprof srv %s: %w", + svc.pprof.Addr, + shutdownErr, + )) } } @@ -322,23 +333,3 @@ func (svc *Service) Shutdown(ctx context.Context) (err error) { return nil } - -// Config returns the current configuration of the web service. Config must not -// be called simultaneously with Start. If svc was initialized with ":0" -// addresses, addrs will not return the actual bound ports until Start is -// finished. -func (svc *Service) Config() (c *Config) { - c = &Config{ - ConfigManager: svc.confMgr, - TLS: svc.tls, - // Leave Addresses and SecureAddresses empty and get the actual - // addresses that include the :0 ones later. - Start: svc.start, - Timeout: svc.timeout, - ForceHTTPS: svc.forceHTTPS, - } - - c.Addresses, c.SecureAddresses = svc.addrs() - - return c -} diff --git a/internal/next/websvc/websvc_test.go b/internal/next/websvc/websvc_test.go index ab8e485d..6a2505d5 100644 --- a/internal/next/websvc/websvc_test.go +++ b/internal/next/websvc/websvc_test.go @@ -89,6 +89,9 @@ func newTestServer( t.Helper() c := &websvc.Config{ + Pprof: &websvc.PprofConfig{ + Enabled: false, + }, ConfigManager: confMgr, Frontend: &fakefs.FS{ OnOpen: func(_ string) (_ fs.File, _ error) { return nil, fs.ErrNotExist },