Pull request 1935: upd-pprof

Squashed commit of the following:

commit 71d8936bddcf2d2b293015d3091df72aa1333270
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 20 18:48:08 2023 +0300

    next/websvc: fix pprof disabling

commit 30cc75d1eb89f7422555c18ad474324ab55eb13b
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Jul 20 18:30:29 2023 +0300

    all: upd golibs; add pprof to next
This commit is contained in:
Ainar Garipov
2023-07-20 18:57:06 +03:00
parent 5be0e84719
commit 84a2991ac2
14 changed files with 212 additions and 125 deletions

View File

@@ -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: []

View File

@@ -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]).

View File

@@ -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.

View File

@@ -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}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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{{}},
},

View File

@@ -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{{}},
},

View File

@@ -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
}

View File

@@ -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 },