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
This commit is contained in:
kim 2022-04-28 13:32:53 +01:00 committed by GitHub
parent 420e2fb22b
commit 69011d4901
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 248 additions and 37 deletions

View file

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

1
go.mod
View file

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

2
go.sum
View file

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

View file

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

View file

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

9
vendor/codeberg.org/gruf/go-debug/LICENSE generated vendored Normal file
View file

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

37
vendor/codeberg.org/gruf/go-debug/README.md generated vendored Normal file
View file

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

13
vendor/codeberg.org/gruf/go-debug/debug.go generated vendored Normal file
View file

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

9
vendor/codeberg.org/gruf/go-debug/debug_env.go generated vendored Normal file
View file

@ -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") != "")

7
vendor/codeberg.org/gruf/go-debug/debug_off.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
//go:build !debug && !debugenv
// +build !debug,!debugenv
package debug
// debug always off.
const debug = false

7
vendor/codeberg.org/gruf/go-debug/debug_on.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
//go:build debug && !debugenv
// +build debug,!debugenv
package debug
// debug always on.
const debug = true

16
vendor/codeberg.org/gruf/go-debug/pprof_off.go generated vendored Normal file
View file

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

63
vendor/codeberg.org/gruf/go-debug/pprof_on.go generated vendored Normal file
View file

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

17
vendor/codeberg.org/gruf/go-debug/run_tests.sh generated vendored Normal file
View file

@ -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!'

3
vendor/modules.txt vendored
View file

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