diff --git a/app/api/api.go b/app/api/api.go index 8d0ff70a..7a013db9 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -50,6 +50,7 @@ import ( "github.com/datarhei/core/v16/srt" srturl "github.com/datarhei/core/v16/srt/url" "github.com/datarhei/core/v16/update" + "github.com/google/gops/agent" "github.com/lestrrat-go/strftime" "go.uber.org/automaxprocs/maxprocs" @@ -323,6 +324,15 @@ func (a *api) start(ctx context.Context) error { a.undoMaxprocs = undoMaxprocs } + if len(cfg.Debug.AgentAddress) != 0 { + if err := agent.Listen(agent.Options{ + Addr: cfg.Debug.AgentAddress, + ReuseSocketAddrAndPort: true, + }); err != nil { + a.log.logger.main.Error().WithError(err).Log("") + } + } + resources, err := resources.New(resources.Config{ MaxCPU: cfg.Resources.MaxCPUUsage, MaxMemory: cfg.Resources.MaxMemoryUsage, @@ -1836,6 +1846,9 @@ func (a *api) stop() { a.gcTickerStop = nil } + // Stop gops agent + agent.Close() + // Wait for all server goroutines to exit logger.Info().Log("Waiting for all servers to stop ...") a.wgStop.Wait() diff --git a/config/config.go b/config/config.go index bf08c25f..35669c70 100644 --- a/config/config.go +++ b/config/config.go @@ -149,7 +149,7 @@ func (d *Config) init() { d.vars.Register(value.NewTime(&d.CreatedAt, time.Now()), "created_at", "", nil, "Configuration file creation time", false, false) d.vars.Register(value.NewString(&d.ID, uuid.New().String()), "id", "CORE_ID", nil, "ID for this instance", true, false) d.vars.Register(value.NewString(&d.Name, haikunator.New().Haikunate()), "name", "CORE_NAME", nil, "A human readable name for this instance", false, false) - d.vars.Register(value.NewAddress(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false) + d.vars.Register(value.NewMustAddress(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false) d.vars.Register(value.NewBool(&d.CheckForUpdates, true), "update_check", "CORE_UPDATE_CHECK", nil, "Check for updates and send anonymized data", false, false) // Log @@ -183,7 +183,7 @@ func (d *Config) init() { d.vars.Register(value.NewTenantList(&d.API.Auth.Auth0.Tenants, []value.Auth0Tenant{}, ","), "api.auth.auth0.tenants", "CORE_API_AUTH_AUTH0_TENANTS", nil, "List of Auth0 tenants", false, false) // TLS - d.vars.Register(value.NewAddress(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false) + d.vars.Register(value.NewMustAddress(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false) d.vars.Register(value.NewBool(&d.TLS.Enable, false), "tls.enable", "CORE_TLS_ENABLE", nil, "Enable HTTPS", false, false) d.vars.Register(value.NewBool(&d.TLS.Auto, false), "tls.auto", "CORE_TLS_AUTO", nil, "Enable Let's Encrypt certificate", false, false) d.vars.Register(value.NewEmail(&d.TLS.Email, "cert@datarhei.com"), "tls.email", "CORE_TLS_EMAIL", nil, "Email for Let's Encrypt registration", false, false) @@ -221,14 +221,14 @@ func (d *Config) init() { // RTMP d.vars.Register(value.NewBool(&d.RTMP.Enable, false), "rtmp.enable", "CORE_RTMP_ENABLE", nil, "Enable RTMP server", false, false) d.vars.Register(value.NewBool(&d.RTMP.EnableTLS, false), "rtmp.enable_tls", "CORE_RTMP_ENABLE_TLS", nil, "Enable RTMPS server instead of RTMP", false, false) - d.vars.Register(value.NewAddress(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false) - d.vars.Register(value.NewAddress(&d.RTMP.AddressTLS, ":1936"), "rtmp.address_tls", "CORE_RTMP_ADDRESS_TLS", nil, "RTMPS server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.RTMP.AddressTLS, ":1936"), "rtmp.address_tls", "CORE_RTMP_ADDRESS_TLS", nil, "RTMPS server listen address", false, false) d.vars.Register(value.NewAbsolutePath(&d.RTMP.App, "/"), "rtmp.app", "CORE_RTMP_APP", nil, "RTMP app for publishing", false, false) d.vars.Register(value.NewString(&d.RTMP.Token, ""), "rtmp.token", "CORE_RTMP_TOKEN", nil, "RTMP token for publishing and playing", false, true) // SRT d.vars.Register(value.NewBool(&d.SRT.Enable, false), "srt.enable", "CORE_SRT_ENABLE", nil, "Enable SRT server", false, false) - d.vars.Register(value.NewAddress(&d.SRT.Address, ":6000"), "srt.address", "CORE_SRT_ADDRESS", nil, "SRT server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.SRT.Address, ":6000"), "srt.address", "CORE_SRT_ADDRESS", nil, "SRT server listen address", false, false) d.vars.Register(value.NewString(&d.SRT.Passphrase, ""), "srt.passphrase", "CORE_SRT_PASSPHRASE", nil, "SRT encryption passphrase", false, true) d.vars.Register(value.NewString(&d.SRT.Token, ""), "srt.token", "CORE_SRT_TOKEN", nil, "SRT token for publishing and playing", false, true) d.vars.Register(value.NewBool(&d.SRT.Log.Enable, false), "srt.log.enable", "CORE_SRT_LOG_ENABLE", nil, "Enable SRT server logging", false, false) @@ -255,6 +255,7 @@ func (d *Config) init() { d.vars.Register(value.NewInt(&d.Debug.ForceGC, 0), "debug.force_gc", "CORE_DEBUG_FORCE_GC", []string{"CORE_DEBUG_FORCEGC"}, "Number of seconds between forcing GC to return memory to the OS", false, false) d.vars.Register(value.NewInt64(&d.Debug.MemoryLimit, 0), "debug.memory_limit_mbytes", "CORE_DEBUG_MEMORY_LIMIT_MBYTES", nil, "Impose a soft memory limit for the core, in megabytes", false, false) d.vars.Register(value.NewBool(&d.Debug.AutoMaxProcs, false), "debug.auto_max_procs", "CORE_DEBUG_AUTO_MAX_PROCS", nil, "Enable setting GOMAXPROCS automatically", false, false) + d.vars.Register(value.NewAddress(&d.Debug.AgentAddress, ""), "debug.agent_address", "CORE_DEBUG_AGENT_ADDRESS", nil, "gops agent listening address", false, false) // Metrics d.vars.Register(value.NewBool(&d.Metrics.Enable, false), "metrics.enable", "CORE_METRICS_ENABLE", nil, "Enable collecting historic metrics data", false, false) @@ -289,7 +290,7 @@ func (d *Config) init() { // Cluster d.vars.Register(value.NewBool(&d.Cluster.Enable, false), "cluster.enable", "CORE_CLUSTER_ENABLE", nil, "Enable cluster mode", false, false) - d.vars.Register(value.NewClusterAddress(&d.Cluster.Address, "127.0.0.1:8000"), "cluster.address", "CORE_CLUSTER_ADDRESS", nil, "Raft listen address", true, false) + d.vars.Register(value.NewFullAddress(&d.Cluster.Address, "127.0.0.1:8000"), "cluster.address", "CORE_CLUSTER_ADDRESS", nil, "Raft listen address", true, false) d.vars.Register(value.NewClusterPeerList(&d.Cluster.Peers, []string{""}, ","), "cluster.peers", "CORE_CLUSTER_PEERS", nil, "Raft addresses of cores that are part of the cluster", false, false) d.vars.Register(value.NewInt64(&d.Cluster.StartupTimeout, 300), "cluster.startup_timeout_sec", "CORE_CLUSTER_STARTUP_TIMEOUT_SEC", nil, "Timeout for the cluster startup (leader election, acquiring certificates)", true, false) d.vars.Register(value.NewInt64(&d.Cluster.SyncInterval, 5), "cluster.sync_interval_sec", "CORE_CLUSTER_SYNC_INTERVAL_SEC", nil, "Interval between aligning the process in the cluster DB with the processes on the nodes", true, false) diff --git a/config/data.go b/config/data.go index 7e55af8b..b637b97f 100644 --- a/config/data.go +++ b/config/data.go @@ -141,10 +141,11 @@ type Data struct { MaxPort int `json:"max_port" format:"int"` } `json:"playout"` Debug struct { - Profiling bool `json:"profiling"` - ForceGC int `json:"force_gc" format:"int"` // deprecated, use MemoryLimit instead - MemoryLimit int64 `json:"memory_limit_mbytes" format:"int64"` - AutoMaxProcs bool `json:"auto_max_procs"` + Profiling bool `json:"profiling"` + ForceGC int `json:"force_gc" format:"int"` // deprecated, use MemoryLimit instead + MemoryLimit int64 `json:"memory_limit_mbytes" format:"int64"` + AutoMaxProcs bool `json:"auto_max_procs"` + AgentAddress string `json:"agent_address"` } `json:"debug"` Metrics struct { Enable bool `json:"enable"` @@ -262,6 +263,7 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) { data.Debug.Profiling = d.Debug.Profiling data.Debug.ForceGC = d.Debug.ForceGC data.Debug.MemoryLimit = 0 + data.Debug.AgentAddress = "" data.TLS.Enable = d.TLS.Enable data.TLS.Address = d.TLS.Address diff --git a/config/v1/config.go b/config/v1/config.go index b19639b0..b227ad84 100644 --- a/config/v1/config.go +++ b/config/v1/config.go @@ -118,7 +118,7 @@ func (d *Config) init() { d.vars.Register(value.NewTime(&d.CreatedAt, time.Now()), "created_at", "", nil, "Configuration file creation time", false, false) d.vars.Register(value.NewString(&d.ID, uuid.New().String()), "id", "CORE_ID", nil, "ID for this instance", true, false) d.vars.Register(value.NewString(&d.Name, haikunator.New().Haikunate()), "name", "CORE_NAME", nil, "A human readable name for this instance", false, false) - d.vars.Register(value.NewAddress(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false) + d.vars.Register(value.NewMustAddress(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false) d.vars.Register(value.NewBool(&d.CheckForUpdates, true), "update_check", "CORE_UPDATE_CHECK", nil, "Check for updates and send anonymized data", false, false) // Log @@ -152,7 +152,7 @@ func (d *Config) init() { d.vars.Register(value.NewTenantList(&d.API.Auth.Auth0.Tenants, []value.Auth0Tenant{}, ","), "api.auth.auth0.tenants", "CORE_API_AUTH_AUTH0_TENANTS", nil, "List of Auth0 tenants", false, false) // TLS - d.vars.Register(value.NewAddress(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false) + d.vars.Register(value.NewMustAddress(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false) d.vars.Register(value.NewBool(&d.TLS.Enable, false), "tls.enable", "CORE_TLS_ENABLE", nil, "Enable HTTPS", false, false) d.vars.Register(value.NewBool(&d.TLS.Auto, false), "tls.auto", "CORE_TLS_AUTO", nil, "Enable Let's Encrypt certificate", false, false) d.vars.Register(value.NewFile(&d.TLS.CertFile, "", d.fs), "tls.cert_file", "CORE_TLS_CERTFILE", nil, "Path to certificate file in PEM format", false, false) @@ -183,13 +183,13 @@ func (d *Config) init() { // RTMP d.vars.Register(value.NewBool(&d.RTMP.Enable, false), "rtmp.enable", "CORE_RTMP_ENABLE", nil, "Enable RTMP server", false, false) d.vars.Register(value.NewBool(&d.RTMP.EnableTLS, false), "rtmp.enable_tls", "CORE_RTMP_ENABLE_TLS", nil, "Enable RTMPS server instead of RTMP", false, false) - d.vars.Register(value.NewAddress(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false) d.vars.Register(value.NewAbsolutePath(&d.RTMP.App, "/"), "rtmp.app", "CORE_RTMP_APP", nil, "RTMP app for publishing", false, false) d.vars.Register(value.NewString(&d.RTMP.Token, ""), "rtmp.token", "CORE_RTMP_TOKEN", nil, "RTMP token for publishing and playing", false, true) // SRT d.vars.Register(value.NewBool(&d.SRT.Enable, false), "srt.enable", "CORE_SRT_ENABLE", nil, "Enable SRT server", false, false) - d.vars.Register(value.NewAddress(&d.SRT.Address, ":6000"), "srt.address", "CORE_SRT_ADDRESS", nil, "SRT server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.SRT.Address, ":6000"), "srt.address", "CORE_SRT_ADDRESS", nil, "SRT server listen address", false, false) d.vars.Register(value.NewString(&d.SRT.Passphrase, ""), "srt.passphrase", "CORE_SRT_PASSPHRASE", nil, "SRT encryption passphrase", false, true) d.vars.Register(value.NewString(&d.SRT.Token, ""), "srt.token", "CORE_SRT_TOKEN", nil, "SRT token for publishing and playing", false, true) d.vars.Register(value.NewBool(&d.SRT.Log.Enable, false), "srt.log.enable", "CORE_SRT_LOG_ENABLE", nil, "Enable SRT server logging", false, false) diff --git a/config/v2/config.go b/config/v2/config.go index 46c6c4b0..f6204a2e 100644 --- a/config/v2/config.go +++ b/config/v2/config.go @@ -118,7 +118,7 @@ func (d *Config) init() { d.vars.Register(value.NewTime(&d.CreatedAt, time.Now()), "created_at", "", nil, "Configuration file creation time", false, false) d.vars.Register(value.NewString(&d.ID, uuid.New().String()), "id", "CORE_ID", nil, "ID for this instance", true, false) d.vars.Register(value.NewString(&d.Name, haikunator.New().Haikunate()), "name", "CORE_NAME", nil, "A human readable name for this instance", false, false) - d.vars.Register(value.NewAddress(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false) + d.vars.Register(value.NewMustAddress(&d.Address, ":8080"), "address", "CORE_ADDRESS", nil, "HTTP listening address", false, false) d.vars.Register(value.NewBool(&d.CheckForUpdates, true), "update_check", "CORE_UPDATE_CHECK", nil, "Check for updates and send anonymized data", false, false) // Log @@ -152,7 +152,7 @@ func (d *Config) init() { d.vars.Register(value.NewTenantList(&d.API.Auth.Auth0.Tenants, []value.Auth0Tenant{}, ","), "api.auth.auth0.tenants", "CORE_API_AUTH_AUTH0_TENANTS", nil, "List of Auth0 tenants", false, false) // TLS - d.vars.Register(value.NewAddress(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false) + d.vars.Register(value.NewMustAddress(&d.TLS.Address, ":8181"), "tls.address", "CORE_TLS_ADDRESS", nil, "HTTPS listening address", false, false) d.vars.Register(value.NewBool(&d.TLS.Enable, false), "tls.enable", "CORE_TLS_ENABLE", nil, "Enable HTTPS", false, false) d.vars.Register(value.NewBool(&d.TLS.Auto, false), "tls.auto", "CORE_TLS_AUTO", nil, "Enable Let's Encrypt certificate", false, false) d.vars.Register(value.NewFile(&d.TLS.CertFile, "", d.fs), "tls.cert_file", "CORE_TLS_CERTFILE", nil, "Path to certificate file in PEM format", false, false) @@ -183,14 +183,14 @@ func (d *Config) init() { // RTMP d.vars.Register(value.NewBool(&d.RTMP.Enable, false), "rtmp.enable", "CORE_RTMP_ENABLE", nil, "Enable RTMP server", false, false) d.vars.Register(value.NewBool(&d.RTMP.EnableTLS, false), "rtmp.enable_tls", "CORE_RTMP_ENABLE_TLS", nil, "Enable RTMPS server instead of RTMP", false, false) - d.vars.Register(value.NewAddress(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false) - d.vars.Register(value.NewAddress(&d.RTMP.AddressTLS, ":1936"), "rtmp.address_tls", "CORE_RTMP_ADDRESS_TLS", nil, "RTMPS server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.RTMP.Address, ":1935"), "rtmp.address", "CORE_RTMP_ADDRESS", nil, "RTMP server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.RTMP.AddressTLS, ":1936"), "rtmp.address_tls", "CORE_RTMP_ADDRESS_TLS", nil, "RTMPS server listen address", false, false) d.vars.Register(value.NewAbsolutePath(&d.RTMP.App, "/"), "rtmp.app", "CORE_RTMP_APP", nil, "RTMP app for publishing", false, false) d.vars.Register(value.NewString(&d.RTMP.Token, ""), "rtmp.token", "CORE_RTMP_TOKEN", nil, "RTMP token for publishing and playing", false, true) // SRT d.vars.Register(value.NewBool(&d.SRT.Enable, false), "srt.enable", "CORE_SRT_ENABLE", nil, "Enable SRT server", false, false) - d.vars.Register(value.NewAddress(&d.SRT.Address, ":6000"), "srt.address", "CORE_SRT_ADDRESS", nil, "SRT server listen address", false, false) + d.vars.Register(value.NewMustAddress(&d.SRT.Address, ":6000"), "srt.address", "CORE_SRT_ADDRESS", nil, "SRT server listen address", false, false) d.vars.Register(value.NewString(&d.SRT.Passphrase, ""), "srt.passphrase", "CORE_SRT_PASSPHRASE", nil, "SRT encryption passphrase", false, true) d.vars.Register(value.NewString(&d.SRT.Token, ""), "srt.token", "CORE_SRT_TOKEN", nil, "SRT token for publishing and playing", false, true) d.vars.Register(value.NewBool(&d.SRT.Log.Enable, false), "srt.log.enable", "CORE_SRT_LOG_ENABLE", nil, "Enable SRT server logging", false, false) diff --git a/config/value/cluster.go b/config/value/cluster.go index dd4af0f6..15b5f69f 100644 --- a/config/value/cluster.go +++ b/config/value/cluster.go @@ -7,63 +7,6 @@ import ( "strings" ) -// cluster address (host:port) - -type ClusterAddress string - -func NewClusterAddress(p *string, val string) *ClusterAddress { - *p = val - - return (*ClusterAddress)(p) -} - -func (s *ClusterAddress) Set(val string) error { - // Check if the new value is only a port number - host, port, err := net.SplitHostPort(val) - if err != nil { - return err - } - - if len(host) == 0 || len(port) == 0 { - return fmt.Errorf("invalid address: host and port must be provided") - } - - re := regexp.MustCompile("^[0-9]+$") - if !re.MatchString(port) { - return fmt.Errorf("the port must be numerical") - } - - *s = ClusterAddress(val) - - return nil -} - -func (s *ClusterAddress) String() string { - return string(*s) -} - -func (s *ClusterAddress) Validate() error { - host, port, err := net.SplitHostPort(string(*s)) - if err != nil { - return err - } - - if len(host) == 0 || len(port) == 0 { - return fmt.Errorf("invalid address: host and port must be provided") - } - - re := regexp.MustCompile("^[0-9]+$") - if !re.MatchString(port) { - return fmt.Errorf("the port must be numerical") - } - - return nil -} - -func (s *ClusterAddress) IsEmpty() bool { - return s.Validate() != nil -} - // cluster peer (id@host:port) type ClusterPeer string diff --git a/config/value/cluster_test.go b/config/value/cluster_test.go index eef9f3e9..14dfe526 100644 --- a/config/value/cluster_test.go +++ b/config/value/cluster_test.go @@ -6,29 +6,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestClusterAddressValue(t *testing.T) { - var x string - - val := NewClusterAddress(&x, "foobar:8080") - - require.Equal(t, "foobar:8080", val.String()) - require.Equal(t, nil, val.Validate()) - require.Equal(t, false, val.IsEmpty()) - - x = "foobaz:9090" - - require.Equal(t, "foobaz:9090", val.String()) - require.Equal(t, nil, val.Validate()) - require.Equal(t, false, val.IsEmpty()) - - val.Set("fooboz:7070") - - require.Equal(t, "fooboz:7070", x) - - err := val.Set(":7070") - require.Error(t, err) -} - func TestClusterPeerValue(t *testing.T) { var x string diff --git a/config/value/network.go b/config/value/network.go index 62a9257d..a407219f 100644 --- a/config/value/network.go +++ b/config/value/network.go @@ -12,7 +12,7 @@ import ( "github.com/datarhei/core/v16/http/cors" ) -// address (host?:port) +// optional address type Address string @@ -23,6 +23,11 @@ func NewAddress(p *string, val string) *Address { } func (s *Address) Set(val string) error { + if len(val) == 0 { + *s = Address(val) + return nil + } + // Check if the new value is only a port number re := regexp.MustCompile("^[0-9]+$") if re.MatchString(val) { @@ -38,6 +43,10 @@ func (s *Address) String() string { } func (s *Address) Validate() error { + if len(string(*s)) == 0 { + return nil + } + _, port, err := net.SplitHostPort(string(*s)) if err != nil { return err @@ -55,6 +64,106 @@ func (s *Address) IsEmpty() bool { return s.Validate() != nil } +// address (host?:port) + +type MustAddress string + +func NewMustAddress(p *string, val string) *MustAddress { + *p = val + + return (*MustAddress)(p) +} + +func (s *MustAddress) Set(val string) error { + // Check if the new value is only a port number + re := regexp.MustCompile("^[0-9]+$") + if re.MatchString(val) { + val = ":" + val + } + + *s = MustAddress(val) + return nil +} + +func (s *MustAddress) String() string { + return string(*s) +} + +func (s *MustAddress) Validate() error { + _, port, err := net.SplitHostPort(string(*s)) + if err != nil { + return err + } + + re := regexp.MustCompile("^[0-9]+$") + if !re.MatchString(port) { + return fmt.Errorf("the port must be numerical") + } + + return nil +} + +func (s *MustAddress) IsEmpty() bool { + return s.Validate() != nil +} + +// full address (host:port) + +type FullAddress string + +func NewFullAddress(p *string, val string) *FullAddress { + *p = val + + return (*FullAddress)(p) +} + +func (s *FullAddress) Set(val string) error { + // Check if the new value is only a port number + host, port, err := net.SplitHostPort(val) + if err != nil { + return err + } + + if len(host) == 0 || len(port) == 0 { + return fmt.Errorf("invalid address: host and port must be provided") + } + + re := regexp.MustCompile("^[0-9]+$") + if !re.MatchString(port) { + return fmt.Errorf("the port must be numerical") + } + + *s = FullAddress(val) + + return nil +} + +func (s *FullAddress) String() string { + return string(*s) +} + +func (s *FullAddress) Validate() error { + host, port, err := net.SplitHostPort(string(*s)) + if err != nil { + return err + } + + if len(host) == 0 || len(port) == 0 { + return fmt.Errorf("invalid address: host and port must be provided") + } + + re := regexp.MustCompile("^[0-9]+$") + if !re.MatchString(port) { + return fmt.Errorf("the port must be numerical") + } + + return nil +} + +func (s *FullAddress) IsEmpty() bool { + return s.Validate() != nil +} + // array of CIDR notation IP adresses type CIDRList struct { diff --git a/config/value/network_test.go b/config/value/network_test.go index add7190a..28e2db65 100644 --- a/config/value/network_test.go +++ b/config/value/network_test.go @@ -24,6 +24,54 @@ func TestAddressValue(t *testing.T) { val.Set("fooboz:7070") require.Equal(t, "fooboz:7070", x) + + val.Set("") + + require.Equal(t, nil, val.Validate()) + require.Equal(t, false, val.IsEmpty()) +} + +func TestMustAddressValue(t *testing.T) { + var x string + + val := NewMustAddress(&x, ":8080") + + require.Equal(t, ":8080", val.String()) + require.Equal(t, nil, val.Validate()) + require.Equal(t, false, val.IsEmpty()) + + x = "foobaz:9090" + + require.Equal(t, "foobaz:9090", val.String()) + require.Equal(t, nil, val.Validate()) + require.Equal(t, false, val.IsEmpty()) + + val.Set("fooboz:7070") + + require.Equal(t, "fooboz:7070", x) +} + +func TestFullAddressValue(t *testing.T) { + var x string + + val := NewFullAddress(&x, "foobar:8080") + + require.Equal(t, "foobar:8080", val.String()) + require.Equal(t, nil, val.Validate()) + require.Equal(t, false, val.IsEmpty()) + + x = "foobaz:9090" + + require.Equal(t, "foobaz:9090", val.String()) + require.Equal(t, nil, val.Validate()) + require.Equal(t, false, val.IsEmpty()) + + val.Set("fooboz:7070") + + require.Equal(t, "fooboz:7070", x) + + err := val.Set(":7070") + require.Error(t, err) } func TestCIDRListValue(t *testing.T) { diff --git a/go.mod b/go.mod index 3388f217..9cc99793 100644 --- a/go.mod +++ b/go.mod @@ -16,12 +16,14 @@ require ( github.com/go-playground/validator/v10 v10.14.1 github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/google/gops v0.3.27 github.com/google/uuid v1.3.0 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/raft v1.5.0 github.com/hashicorp/raft-boltdb/v2 v2.2.2 github.com/invopop/jsonschema v0.4.0 github.com/joho/godotenv v1.5.1 + github.com/klauspost/cpuid/v2 v2.2.5 github.com/labstack/echo/v4 v4.10.2 github.com/lestrrat-go/strftime v1.0.6 github.com/lithammer/shortuuid/v4 v4.0.0 @@ -38,6 +40,7 @@ require ( go.etcd.io/bbolt v1.3.7 go.uber.org/automaxprocs v1.5.2 go.uber.org/zap v1.24.0 + golang.org/x/crypto v0.10.0 golang.org/x/mod v0.11.0 ) @@ -75,7 +78,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.6 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/libdns/libdns v0.2.1 // indirect @@ -113,7 +115,6 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.1.12 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.10.0 // indirect golang.org/x/net v0.11.0 // indirect golang.org/x/sys v0.9.0 // indirect golang.org/x/text v0.10.0 // indirect diff --git a/go.sum b/go.sum index c80b6dc0..5f4dafdd 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/adhocore/gronx v1.6.3 h1:bnm5vieTrY3QQPpsfB0hrAaeaHDpuZTUC2LLCVMLe9c= github.com/adhocore/gronx v1.6.3/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -48,20 +46,6 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230614141756-a25a5fc3c60e h1:iQKqGTyIdCyO7kY/G5MCKhzt3xZ5YPRubbJskVp5EvQ= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230614141756-a25a5fc3c60e/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230620131644-140b3a61d4c8 h1:CILOzUB7CJGHtZHOxMJn+dN6rKzH29TOOOOep0AnFWM= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230620131644-140b3a61d4c8/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230620190007-900b37caabbf h1:3/t2rLE/Vh51z7kJrT3WZYS+JKCQvZ1afdpLXLDi28o= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230620190007-900b37caabbf/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230620192304-1a2e44319306 h1:9CwCYAeo1rEccFDZ0yFkk4uHZ01rLdW6hARrZCU8ywg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230620192304-1a2e44319306/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230621090506-6b04f0277e7a h1:aFlwQNcgyp7k8Vv8ESEvKwmKUI2KiiK53qCFaHYk5wY= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230621090506-6b04f0277e7a/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230621090732-c6ae9699cea6 h1:iS1ji9i8gmYJe+LhCxHnHcvC/ieftBwdteqNg2yay+k= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230621090732-c6ae9699cea6/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230621092648-5cc658d8a73c h1:WUjP8x7hl1EsTbV5w8WEhK6c8t3uRqLUSn7+FFHDpJU= -github.com/datarhei/core-client-go/v16 v16.11.1-0.20230621092648-5cc658d8a73c/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= github.com/datarhei/core-client-go/v16 v16.11.1-0.20230627120001-16d06aa77802 h1:F4ILviOV6brxg25hMyzzyI0K/C9yLzYHgecPD1PaokQ= github.com/datarhei/core-client-go/v16 v16.11.1-0.20230627120001-16d06aa77802/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg= github.com/datarhei/gosrt v0.5.2 h1:eagqZwEIiGPNJW0rLep3gwceObyaZ17+iKRc+l4VEpc= @@ -87,7 +71,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -134,6 +117,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gops v0.3.27 h1:BDdWfedShsBbeatZ820oA4DbVOC8yJ4NI8xAlDFWfgI= +github.com/google/gops v0.3.27/go.mod h1:lYqabmfnq4Q6UumWNx96Hjup5BDAVc8zmfIy0SkNCSk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -171,13 +156,11 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -247,7 +230,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -299,7 +281,6 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= diff --git a/vendor/github.com/google/gops/LICENSE b/vendor/github.com/google/gops/LICENSE new file mode 100644 index 00000000..55e52a01 --- /dev/null +++ b/vendor/github.com/google/gops/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/gops/agent/agent.go b/vendor/github.com/google/gops/agent/agent.go new file mode 100644 index 00000000..b7978069 --- /dev/null +++ b/vendor/github.com/google/gops/agent/agent.go @@ -0,0 +1,284 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package agent provides hooks programs can register to retrieve +// diagnostics data by using gops. +package agent + +import ( + "bufio" + "context" + "encoding/binary" + "fmt" + "io" + "net" + "os" + gosignal "os/signal" + "path/filepath" + "runtime" + "runtime/debug" + "runtime/pprof" + "runtime/trace" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/google/gops/internal" + "github.com/google/gops/signal" +) + +const defaultAddr = "127.0.0.1:0" + +var ( + mu sync.Mutex + portfile string + listener net.Listener + + units = []string{" bytes", "KB", "MB", "GB", "TB", "PB"} +) + +// Options allows configuring the started agent. +type Options struct { + // Addr is the host:port the agent will be listening at. + // Optional. + Addr string + + // ConfigDir is the directory to store the configuration file, + // PID of the gops process, filename, port as well as content. + // Optional. + ConfigDir string + + // ShutdownCleanup automatically cleans up resources if the + // running process receives an interrupt. Otherwise, users + // can call Close before shutting down. + // Optional. + ShutdownCleanup bool + + // ReuseSocketAddrAndPort determines whether the SO_REUSEADDR and + // SO_REUSEPORT socket options should be set on the listening socket of + // the agent. This option is only effective on unix-like OSes and if + // Addr is set to a fixed host:port. + // Optional. + ReuseSocketAddrAndPort bool +} + +// Listen starts the gops agent on a host process. Once agent started, users +// can use the advanced gops features. The agent will listen to Interrupt +// signals and exit the process, if you need to perform further work on the +// Interrupt signal use the options parameter to configure the agent +// accordingly. +// +// Note: The agent exposes an endpoint via a TCP connection that can be used by +// any program on the system. Review your security requirements before starting +// the agent. +func Listen(opts Options) error { + mu.Lock() + defer mu.Unlock() + + if portfile != "" { + return fmt.Errorf("gops: agent already listening at: %v", listener.Addr()) + } + + // new + gopsdir := opts.ConfigDir + if gopsdir == "" { + cfgDir, err := internal.ConfigDir() + if err != nil { + return err + } + gopsdir = cfgDir + } + + err := os.MkdirAll(gopsdir, os.ModePerm) + if err != nil { + return err + } + if opts.ShutdownCleanup { + gracefulShutdown() + } + + addr := opts.Addr + if addr == "" { + addr = defaultAddr + } + var lc net.ListenConfig + if opts.ReuseSocketAddrAndPort { + lc.Control = setReuseAddrAndPortSockopts + } + listener, err = lc.Listen(context.Background(), "tcp", addr) + if err != nil { + return err + } + port := listener.Addr().(*net.TCPAddr).Port + portfile = filepath.Join(gopsdir, strconv.Itoa(os.Getpid())) + err = os.WriteFile(portfile, []byte(strconv.Itoa(port)), os.ModePerm) + if err != nil { + return err + } + + go listen(listener) + return nil +} + +func listen(l net.Listener) { + buf := make([]byte, 1) + for { + fd, err := l.Accept() + if err != nil { + // No great way to check for this, see https://golang.org/issues/4373. + if !strings.Contains(err.Error(), "use of closed network connection") { + fmt.Fprintf(os.Stderr, "gops: %v\n", err) + } + if netErr, ok := err.(net.Error); ok && !netErr.Temporary() { + break + } + continue + } + if _, err := fd.Read(buf); err != nil { + fmt.Fprintf(os.Stderr, "gops: %v\n", err) + continue + } + if err := handle(fd, buf); err != nil { + fmt.Fprintf(os.Stderr, "gops: %v\n", err) + continue + } + fd.Close() + } +} + +func gracefulShutdown() { + c := make(chan os.Signal, 1) + gosignal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + go func() { + // cleanup the socket on shutdown. + sig := <-c + Close() + ret := 1 + if sig == syscall.SIGTERM { + ret = 0 + } + os.Exit(ret) + }() +} + +// Close closes the agent, removing temporary files and closing the TCP listener. +// If no agent is listening, Close does nothing. +func Close() { + mu.Lock() + defer mu.Unlock() + + if portfile != "" { + os.Remove(portfile) + portfile = "" + } + if listener != nil { + listener.Close() + } +} + +func formatBytes(val uint64) string { + var i int + var target uint64 + for i = range units { + target = 1 << uint(10*(i+1)) + if val < target { + break + } + } + if i > 0 { + return fmt.Sprintf("%0.2f%s (%d bytes)", float64(val)/(float64(target)/1024), units[i], val) + } + return fmt.Sprintf("%d bytes", val) +} + +func handle(conn io.ReadWriter, msg []byte) error { + switch msg[0] { + case signal.StackTrace: + return pprof.Lookup("goroutine").WriteTo(conn, 2) + case signal.GC: + runtime.GC() + _, err := conn.Write([]byte("ok")) + return err + case signal.MemStats: + var s runtime.MemStats + runtime.ReadMemStats(&s) + fmt.Fprintf(conn, "alloc: %v\n", formatBytes(s.Alloc)) + fmt.Fprintf(conn, "total-alloc: %v\n", formatBytes(s.TotalAlloc)) + fmt.Fprintf(conn, "sys: %v\n", formatBytes(s.Sys)) + fmt.Fprintf(conn, "lookups: %v\n", s.Lookups) + fmt.Fprintf(conn, "mallocs: %v\n", s.Mallocs) + fmt.Fprintf(conn, "frees: %v\n", s.Frees) + fmt.Fprintf(conn, "heap-alloc: %v\n", formatBytes(s.HeapAlloc)) + fmt.Fprintf(conn, "heap-sys: %v\n", formatBytes(s.HeapSys)) + fmt.Fprintf(conn, "heap-idle: %v\n", formatBytes(s.HeapIdle)) + fmt.Fprintf(conn, "heap-in-use: %v\n", formatBytes(s.HeapInuse)) + fmt.Fprintf(conn, "heap-released: %v\n", formatBytes(s.HeapReleased)) + fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects) + fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse)) + fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys)) + fmt.Fprintf(conn, "stack-mspan-inuse: %v\n", formatBytes(s.MSpanInuse)) + fmt.Fprintf(conn, "stack-mspan-sys: %v\n", formatBytes(s.MSpanSys)) + fmt.Fprintf(conn, "stack-mcache-inuse: %v\n", formatBytes(s.MCacheInuse)) + fmt.Fprintf(conn, "stack-mcache-sys: %v\n", formatBytes(s.MCacheSys)) + fmt.Fprintf(conn, "other-sys: %v\n", formatBytes(s.OtherSys)) + fmt.Fprintf(conn, "gc-sys: %v\n", formatBytes(s.GCSys)) + fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC)) + lastGC := "-" + if s.LastGC != 0 { + lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC))) + } + fmt.Fprintf(conn, "last-gc: %v\n", lastGC) + fmt.Fprintf(conn, "gc-pause-total: %v\n", time.Duration(s.PauseTotalNs)) + fmt.Fprintf(conn, "gc-pause: %v\n", s.PauseNs[(s.NumGC+255)%256]) + fmt.Fprintf(conn, "gc-pause-end: %v\n", s.PauseEnd[(s.NumGC+255)%256]) + fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC) + fmt.Fprintf(conn, "num-forced-gc: %v\n", s.NumForcedGC) + fmt.Fprintf(conn, "gc-cpu-fraction: %v\n", s.GCCPUFraction) + fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC) + fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC) + case signal.Version: + fmt.Fprintf(conn, "%v\n", runtime.Version()) + case signal.HeapProfile: + return pprof.WriteHeapProfile(conn) + case signal.CPUProfile: + if err := pprof.StartCPUProfile(conn); err != nil { + return err + } + time.Sleep(30 * time.Second) + pprof.StopCPUProfile() + case signal.Stats: + fmt.Fprintf(conn, "goroutines: %v\n", runtime.NumGoroutine()) + fmt.Fprintf(conn, "OS threads: %v\n", pprof.Lookup("threadcreate").Count()) + fmt.Fprintf(conn, "GOMAXPROCS: %v\n", runtime.GOMAXPROCS(0)) + fmt.Fprintf(conn, "num CPU: %v\n", runtime.NumCPU()) + case signal.BinaryDump: + path, err := os.Executable() + if err != nil { + return err + } + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + _, err = bufio.NewReader(f).WriteTo(conn) + return err + case signal.Trace: + if err := trace.Start(conn); err != nil { + return err + } + time.Sleep(5 * time.Second) + trace.Stop() + case signal.SetGCPercent: + perc, err := binary.ReadVarint(bufio.NewReader(conn)) + if err != nil { + return err + } + fmt.Fprintf(conn, "New GC percent set to %v. Previous value was %v.\n", perc, debug.SetGCPercent(int(perc))) + } + return nil +} diff --git a/vendor/github.com/google/gops/agent/sockopt_reuseport.go b/vendor/github.com/google/gops/agent/sockopt_reuseport.go new file mode 100644 index 00000000..8311fe39 --- /dev/null +++ b/vendor/github.com/google/gops/agent/sockopt_reuseport.go @@ -0,0 +1,37 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !js && !plan9 && !solaris && !windows +// +build !js,!plan9,!solaris,!windows + +package agent + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// setReuseAddrAndPortSockopts sets the SO_REUSEADDR and SO_REUSEPORT socket +// options on c's underlying socket in order to increase the chance to re-bind() +// to the same address and port upon agent restart. +func setReuseAddrAndPortSockopts(network, address string, c syscall.RawConn) error { + var soerr error + if err := c.Control(func(su uintptr) { + sock := int(su) + // Allow reuse of recently-used addresses. This socket option is + // set by default on listeners in Go's net package, see + // net.setDefaultSockopts. + soerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + if soerr != nil { + return + } + // Allow reuse of recently-used ports. This gives the agent a + // better chance to re-bind upon restarts. + soerr = unix.SetsockoptInt(sock, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + }); err != nil { + return err + } + return soerr +} diff --git a/vendor/github.com/google/gops/agent/sockopt_unsupported.go b/vendor/github.com/google/gops/agent/sockopt_unsupported.go new file mode 100644 index 00000000..2493e8ca --- /dev/null +++ b/vendor/github.com/google/gops/agent/sockopt_unsupported.go @@ -0,0 +1,14 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (js && wasm) || plan9 || solaris || windows +// +build js,wasm plan9 solaris windows + +package agent + +import "syscall" + +func setReuseAddrAndPortSockopts(network, address string, c syscall.RawConn) error { + return nil +} diff --git a/vendor/github.com/google/gops/internal/internal.go b/vendor/github.com/google/gops/internal/internal.go new file mode 100644 index 00000000..7e3492aa --- /dev/null +++ b/vendor/github.com/google/gops/internal/internal.go @@ -0,0 +1,61 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +import ( + "errors" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" +) + +const gopsConfigDirEnvKey = "GOPS_CONFIG_DIR" + +func ConfigDir() (string, error) { + if configDir := os.Getenv(gopsConfigDirEnvKey); configDir != "" { + return configDir, nil + } + + if userConfigDir, err := os.UserConfigDir(); err == nil { + return filepath.Join(userConfigDir, "gops"), nil + } + + homeDir := guessUnixHomeDir() + if homeDir == "" { + return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty") + } + return filepath.Join(homeDir, ".config", "gops"), nil +} + +func guessUnixHomeDir() string { + usr, err := user.Current() + if err == nil { + return usr.HomeDir + } + return os.Getenv("HOME") +} + +func PIDFile(pid int) (string, error) { + gopsdir, err := ConfigDir() + if err != nil { + return "", err + } + return filepath.Join(gopsdir, strconv.Itoa(pid)), nil +} + +func GetPort(pid int) (string, error) { + portfile, err := PIDFile(pid) + if err != nil { + return "", err + } + b, err := os.ReadFile(portfile) + if err != nil { + return "", err + } + port := strings.TrimSpace(string(b)) + return port, nil +} diff --git a/vendor/github.com/google/gops/signal/signal.go b/vendor/github.com/google/gops/signal/signal.go new file mode 100644 index 00000000..c70764a0 --- /dev/null +++ b/vendor/github.com/google/gops/signal/signal.go @@ -0,0 +1,38 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package signal contains signals used to communicate to the gops agents. +package signal + +const ( + // StackTrace represents a command to print stack trace. + StackTrace = byte(0x1) + + // GC runs the garbage collector. + GC = byte(0x2) + + // MemStats reports memory stats. + MemStats = byte(0x3) + + // Version prints the Go version. + Version = byte(0x4) + + // HeapProfile starts `go tool pprof` with the current memory profile. + HeapProfile = byte(0x5) + + // CPUProfile starts `go tool pprof` with the current CPU profile + CPUProfile = byte(0x6) + + // Stats returns Go runtime statistics such as number of goroutines, GOMAXPROCS, and NumCPU. + Stats = byte(0x7) + + // Trace starts the Go execution tracer, waits 5 seconds and launches the trace tool. + Trace = byte(0x8) + + // BinaryDump returns running binary file. + BinaryDump = byte(0x9) + + // SetGCPercent sets the garbage collection target percentage. + SetGCPercent = byte(0x10) +) diff --git a/vendor/modules.txt b/vendor/modules.txt index cbe43399..7428321c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -177,6 +177,11 @@ github.com/golang-jwt/jwt/v4 # github.com/golang/protobuf v1.5.3 ## explicit; go 1.9 github.com/golang/protobuf/proto +# github.com/google/gops v0.3.27 +## explicit; go 1.17 +github.com/google/gops/agent +github.com/google/gops/internal +github.com/google/gops/signal # github.com/google/uuid v1.3.0 ## explicit github.com/google/uuid