From 69011d4901a042a9fba3d6d71061c25d20a9ecdc Mon Sep 17 00:00:00 2001 From: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com> Date: Thu, 28 Apr 2022 13:32:53 +0100 Subject: [PATCH] Add support for running profiling when debug build-tags provided (#491) * wrap root HTTP handler in debug.WithPprof(), rearrange router.Start() to support this * remove unused code * set debug buildtag in build script when $DEBUG set * update go-debug version with fixed handler * use clone of router.srv for LE cert manager, reset server timeouts in debug * add kim's other libraries to README --- README.md | 4 +- go.mod | 1 + go.sum | 2 + internal/router/router.go | 92 ++++++++++++------- scripts/build.sh | 5 +- vendor/codeberg.org/gruf/go-debug/LICENSE | 9 ++ vendor/codeberg.org/gruf/go-debug/README.md | 37 ++++++++ vendor/codeberg.org/gruf/go-debug/debug.go | 13 +++ .../codeberg.org/gruf/go-debug/debug_env.go | 9 ++ .../codeberg.org/gruf/go-debug/debug_off.go | 7 ++ vendor/codeberg.org/gruf/go-debug/debug_on.go | 7 ++ .../codeberg.org/gruf/go-debug/pprof_off.go | 16 ++++ vendor/codeberg.org/gruf/go-debug/pprof_on.go | 63 +++++++++++++ .../codeberg.org/gruf/go-debug/run_tests.sh | 17 ++++ vendor/modules.txt | 3 + 15 files changed, 248 insertions(+), 37 deletions(-) create mode 100644 vendor/codeberg.org/gruf/go-debug/LICENSE create mode 100644 vendor/codeberg.org/gruf/go-debug/README.md create mode 100644 vendor/codeberg.org/gruf/go-debug/debug.go create mode 100644 vendor/codeberg.org/gruf/go-debug/debug_env.go create mode 100644 vendor/codeberg.org/gruf/go-debug/debug_off.go create mode 100644 vendor/codeberg.org/gruf/go-debug/debug_on.go create mode 100644 vendor/codeberg.org/gruf/go-debug/pprof_off.go create mode 100644 vendor/codeberg.org/gruf/go-debug/pprof_on.go create mode 100644 vendor/codeberg.org/gruf/go-debug/run_tests.sh diff --git a/README.md b/README.md index 576af3b0c..02fd295a7 100644 --- a/README.md +++ b/README.md @@ -199,8 +199,10 @@ The following libraries and frameworks are used by GoToSocial, with gratitude - [google/wuffs](https://github.com/google/wuffs); png-stripping code. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html). - [go-playground/validator](https://github.com/go-playground/validator); struct validation. [MIT License](https://spdx.org/licenses/MIT.html) - [gorilla/websocket](https://github.com/gorilla/websocket); Websocket connectivity. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html). +- [gruf/go-debug](https://codeberg.org/gruf/go-debug); profiling support in debug builds. [MIT License](https://spdx.org/licenses/MIT.html). +- [gruf/go-mutexes](https://codeberg.org/gruf/go-mutexes); mutex map. [MIT License](https://spdx.org/licenses/MIT.html). - [gruf/go-runners](https://codeberg.org/gruf/go-runners); worker pool library. [MIT License](https://spdx.org/licenses/MIT.html). -- [gruf/go-store](https://codeberg.org/gruf/go-store); cacheing library. [MIT License](https://spdx.org/licenses/MIT.html). +- [gruf/go-store](https://codeberg.org/gruf/go-store); local media store. [MIT License](https://spdx.org/licenses/MIT.html). - [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html). - [jackc/pgx](https://github.com/jackc/pgx); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html). - [mcuadros/go-syslog](https://github.com/mcuadros/go-syslog); Syslog server library. [MIT License](https://spdx.org/licenses/MIT.html). diff --git a/go.mod b/go.mod index d83dd73ce..22c9f5011 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/superseriousbusiness/gotosocial go 1.18 require ( + codeberg.org/gruf/go-debug v1.1.2 codeberg.org/gruf/go-errors v1.0.5 codeberg.org/gruf/go-mutexes v1.1.2 codeberg.org/gruf/go-runners v1.2.0 diff --git a/go.sum b/go.sum index 2eb96ff48..118c5f4a8 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ codeberg.org/gruf/go-bytes v1.0.1/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9 codeberg.org/gruf/go-bytes v1.0.2 h1:malqE42Ni+h1nnYWBUAJaDDtEzF4aeN4uPN8DfMNNvo= codeberg.org/gruf/go-bytes v1.0.2/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg= codeberg.org/gruf/go-cache v1.1.2/go.mod h1:/Dbc+xU72Op3hMn6x2PXF3NE9uIDFeS+sXPF00hN/7o= +codeberg.org/gruf/go-debug v1.1.2 h1:7Tqkktg60M/4WtXTTNUFH2T/6irBw4tI4viv7IRLZDE= +codeberg.org/gruf/go-debug v1.1.2/go.mod h1:N+vSy9uJBQgpQcJUqjctvqFz7tBHJf+S/PIjLILzpLg= codeberg.org/gruf/go-errors v1.0.5 h1:rxV70oQkfasUdggLHxOX2QAoJOMFM7XWxHQR45Zx/Fg= codeberg.org/gruf/go-errors v1.0.5/go.mod h1:n03EpmvcmfzU3/xJKC0XXtleXXJUNFpT2fgISODvZ1Y= codeberg.org/gruf/go-fastcopy v1.1.1 h1:HhPCeFdVR5pwiSVDnQEGJ+J2ny9b5QgfiESc0zrWQAY= diff --git a/internal/router/router.go b/internal/router/router.go index 57e0d36b2..0367ba912 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -24,6 +24,7 @@ import ( "net/http" "time" + "codeberg.org/gruf/go-debug" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -32,7 +33,7 @@ import ( "golang.org/x/crypto/acme/autocert" ) -var ( +const ( readTimeout = 60 * time.Second writeTimeout = 30 * time.Second idleTimeout = 30 * time.Second @@ -69,38 +70,69 @@ func (r *router) AttachStaticFS(relativePath string, fs http.FileSystem) { // Start starts the router nicely. It will serve two handlers if letsencrypt is enabled, and only the web/API handler if letsencrypt is not enabled. func (r *router) Start() { - keys := config.Keys - leEnabled := viper.GetBool(keys.LetsEncryptEnabled) + var ( + keys = config.Keys - if leEnabled { - bindAddress := viper.GetString(keys.BindAddress) - lePort := viper.GetInt(keys.LetsEncryptPort) + // listen is the server start function, by + // default pointing to regular HTTP listener, + // but updated to TLS if LetsEncrypt is enabled. + listen = r.srv.ListenAndServe + ) - // serve the http handler on the selected letsencrypt port, for receiving letsencrypt requests and solving their devious riddles + if viper.GetBool(keys.LetsEncryptEnabled) { + // LetsEncrypt support is enabled + + // Prepare an HTTPS-redirect handler for LetsEncrypt fallback + redirect := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + target := "https://" + r.Host + r.URL.Path + if len(r.URL.RawQuery) > 0 { + target += "?" + r.URL.RawQuery + } + http.Redirect(rw, r, target, http.StatusTemporaryRedirect) + }) + + // Clone HTTP server but with autocert handler + srv := r.srv + srv.Handler = r.certManager.HTTPHandler(redirect) + + // Start the LetsEncrypt autocert manager HTTP server. go func() { - listen := fmt.Sprintf("%s:%d", bindAddress, lePort) - logrus.Infof("letsencrypt listening on %s", listen) - if err := http.ListenAndServe(listen, r.certManager.HTTPHandler(http.HandlerFunc(httpsRedirect))); err != nil && err != http.ErrServerClosed { + addr := fmt.Sprintf("%s:%d", + viper.GetString(keys.BindAddress), + viper.GetInt(keys.LetsEncryptPort), + ) + + logrus.Infof("letsencrypt listening on %s", addr) + + if err := srv.ListenAndServe(); err != nil && + err != http.ErrServerClosed { logrus.Fatalf("letsencrypt: listen: %s", err) } }() - // and serve the actual TLS handler - go func() { - logrus.Infof("listening on %s", r.srv.Addr) - if err := r.srv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { - logrus.Fatalf("listen: %s", err) - } - }() - } else { - // no tls required - go func() { - logrus.Infof("listening on %s", r.srv.Addr) - if err := r.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logrus.Fatalf("listen: %s", err) - } - }() + // TLS is enabled, update the listen function + listen = func() error { return r.srv.ListenAndServeTLS("", "") } } + + // Pass the server handler through a debug pprof middleware handler. + // For standard production builds this will be a no-op, but when the + // "debug" or "debugenv" build-tag is set pprof stats will be served + // at the standard "/debug/pprof" URL. + r.srv.Handler = debug.WithPprof(r.srv.Handler) + if debug.DEBUG() { + // Profiling requires timeouts longer than 30s, so reset these. + logrus.Warn("resetting http.Server{} timeout to support profiling") + r.srv.ReadTimeout = 0 + r.srv.WriteTimeout = 0 + } + + // Start the main listener. + go func() { + logrus.Infof("listening on %s", r.srv.Addr) + if err := listen(); err != nil && err != http.ErrServerClosed { + logrus.Fatalf("listen: %s", err) + } + }() } // Stop shuts down the router nicely @@ -193,13 +225,3 @@ func New(ctx context.Context, db db.DB) (Router, error) { certManager: m, }, nil } - -func httpsRedirect(w http.ResponseWriter, req *http.Request) { - target := "https://" + req.Host + req.URL.Path - - if len(req.URL.RawQuery) > 0 { - target += "?" + req.URL.RawQuery - } - - http.Redirect(w, req, target, http.StatusTemporaryRedirect) -} diff --git a/scripts/build.sh b/scripts/build.sh index 4ad10e03c..a71a0899a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,7 +2,10 @@ set -eu +# DEBUG returns whether DEBUG build is enabled. +DEBUG() { [ ! -z "${DEBUG-}" ]; } + CGO_ENABLED=0 go build -trimpath \ - -tags 'netgo osusergo static_build' \ + -tags "netgo osusergo static_build $(DEBUG && echo 'debugenv')" \ -ldflags="-s -w -extldflags '-static' -X 'main.Version=${VERSION:-$(git describe --tags --abbrev=0)}'" \ ./cmd/gotosocial diff --git a/vendor/codeberg.org/gruf/go-debug/LICENSE b/vendor/codeberg.org/gruf/go-debug/LICENSE new file mode 100644 index 000000000..e4163ae35 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2022 gruf + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/codeberg.org/gruf/go-debug/README.md b/vendor/codeberg.org/gruf/go-debug/README.md new file mode 100644 index 000000000..44cbc9d5f --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/README.md @@ -0,0 +1,37 @@ +# go-debug + +This library provides a very simple method for compile-time or runtime determined debug checks, set using build tags. + +The compile-time checks use Go constants, so when disabled your debug code will not be compiled. + +The possible build tags are: + +- "debug" || "" = debug determined at compile-time + +- "debugenv" = debug determined at runtime using the $DEBUG environment variable + +An example for how this works in practice can be seen by the following code: + +``` +func main() { + println("debug.DEBUG() =", debug.DEBUG()) +} +``` + +``` +# Debug determined at compile-time, it is disabled +$ go run . +debug.DEBUG() = false + +# Debug determined at compile-time, it is enabled +$ go run -tags=debug . +debug.DEBUG() = true + +# Debug determined at runtime, $DEBUG is not set +$ go run -tags=debugenv . +debug.DEBUG() = false + +# Debug determined at runtime, $DEBUG is set +$ DEBUG=y go run -tags=debugenv . +debug.DEBUG() = true +``` \ No newline at end of file diff --git a/vendor/codeberg.org/gruf/go-debug/debug.go b/vendor/codeberg.org/gruf/go-debug/debug.go new file mode 100644 index 000000000..61e4eda41 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/debug.go @@ -0,0 +1,13 @@ +package debug + +// DEBUG returns whether debugging is enabled. +func DEBUG() bool { + return debug +} + +// Run will only call fn if DEBUG is enabled. +func Run(fn func()) { + if debug { + fn() + } +} diff --git a/vendor/codeberg.org/gruf/go-debug/debug_env.go b/vendor/codeberg.org/gruf/go-debug/debug_env.go new file mode 100644 index 000000000..7ab231b08 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/debug_env.go @@ -0,0 +1,9 @@ +//go:build debugenv +// +build debugenv + +package debug + +import "os" + +// check if debug env variable is set +var debug = (os.Getenv("DEBUG") != "") diff --git a/vendor/codeberg.org/gruf/go-debug/debug_off.go b/vendor/codeberg.org/gruf/go-debug/debug_off.go new file mode 100644 index 000000000..a7eb9daac --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/debug_off.go @@ -0,0 +1,7 @@ +//go:build !debug && !debugenv +// +build !debug,!debugenv + +package debug + +// debug always off. +const debug = false diff --git a/vendor/codeberg.org/gruf/go-debug/debug_on.go b/vendor/codeberg.org/gruf/go-debug/debug_on.go new file mode 100644 index 000000000..744d70178 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/debug_on.go @@ -0,0 +1,7 @@ +//go:build debug && !debugenv +// +build debug,!debugenv + +package debug + +// debug always on. +const debug = true diff --git a/vendor/codeberg.org/gruf/go-debug/pprof_off.go b/vendor/codeberg.org/gruf/go-debug/pprof_off.go new file mode 100644 index 000000000..5ad77ce0d --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/pprof_off.go @@ -0,0 +1,16 @@ +//go:build !debug && !debugenv +// +build !debug,!debugenv + +package debug + +import "net/http" + +// ServePprof will start an HTTP server serving /debug/pprof only if debug enabled. +func ServePprof(addr string) error { + return nil +} + +// WithPprof will add /debug/pprof handling (provided by "net/http/pprof") only if debug enabled. +func WithPprof(handler http.Handler) http.Handler { + return handler +} diff --git a/vendor/codeberg.org/gruf/go-debug/pprof_on.go b/vendor/codeberg.org/gruf/go-debug/pprof_on.go new file mode 100644 index 000000000..a569ab823 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/pprof_on.go @@ -0,0 +1,63 @@ +//go:build debug || debugenv +// +build debug debugenv + +package debug + +import ( + "net/http" + "net/http/pprof" + "strings" +) + +// ServePprof will start an HTTP server serving /debug/pprof only if debug enabled. +func ServePprof(addr string) error { + if !debug { + // debug disabled in env + return nil + } + handler := WithPprof(nil) + return http.ListenAndServe(addr, handler) +} + +// WithPprof will add /debug/pprof handling (provided by "net/http/pprof") only if debug enabled. +func WithPprof(handler http.Handler) http.Handler { + if !debug { + // debug disabled in env + return handler + } + + // Default serve mux is setup with pprof + pprofmux := http.DefaultServeMux + + if pprofmux == nil { + // Someone nil'ed the default mux + pprofmux = &http.ServeMux{} + pprofmux.HandleFunc("/debug/pprof/", pprof.Index) + pprofmux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + pprofmux.HandleFunc("/debug/pprof/profile", pprof.Profile) + pprofmux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + pprofmux.HandleFunc("/debug/pprof/trace", pprof.Trace) + } + + if handler == nil { + // Ensure handler is non-nil + handler = http.NotFoundHandler() + } + + // Debug enabled, return wrapped handler func + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + const prefix = "/debug/pprof" + + // /debug/pprof(/.*)? -> pass to pprofmux + if strings.HasPrefix(r.URL.Path, prefix) { + path := r.URL.Path[len(prefix):] + if path == "" || path[0] == '/' { + pprofmux.ServeHTTP(rw, r) + return + } + } + + // .* -> pass to handler + handler.ServeHTTP(rw, r) + }) +} diff --git a/vendor/codeberg.org/gruf/go-debug/run_tests.sh b/vendor/codeberg.org/gruf/go-debug/run_tests.sh new file mode 100644 index 000000000..f7ef1d60d --- /dev/null +++ b/vendor/codeberg.org/gruf/go-debug/run_tests.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +( + # Run in subshell with cmd echo + set -ex + + # Run debug tests + DEBUG= go test -tags= -v + DEBUG= go test -tags=debug -v + DEBUG= go test -tags=debugenv -v + DEBUG=y go test -tags=debugenv -v + DEBUG=1 go test -tags=debugenv -v + DEBUG=y go test -tags=debugenv,debug -v + DEBUG=y go test -tags= -v +) + +echo 'success!' \ No newline at end of file diff --git a/vendor/modules.txt b/vendor/modules.txt index c90b40b1e..09b6a6fd6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,9 @@ # codeberg.org/gruf/go-bytes v1.0.2 ## explicit; go 1.14 codeberg.org/gruf/go-bytes +# codeberg.org/gruf/go-debug v1.1.2 +## explicit; go 1.16 +codeberg.org/gruf/go-debug # codeberg.org/gruf/go-errors v1.0.5 ## explicit; go 1.15 codeberg.org/gruf/go-errors