Fix memory leak caused by not closing resp.Body (#62) and also fix the server to work when the DD integration is disabled

This commit is contained in:
David Dworken 2022-12-23 17:17:44 -08:00
parent 226b39609f
commit f502cbee1d
No known key found for this signature in database
2 changed files with 45 additions and 12 deletions

View File

@ -25,7 +25,7 @@ build-static:
docker build -t gcr.io/dworken-k8s/hishtory-static -f backend/web/caddy/Dockerfile . docker build -t gcr.io/dworken-k8s/hishtory-static -f backend/web/caddy/Dockerfile .
build-api: build-api:
rm hishtory rm hishtory server || true
docker build -t gcr.io/dworken-k8s/hishtory-api -f backend/server/Dockerfile . docker build -t gcr.io/dworken-k8s/hishtory-api -f backend/server/Dockerfile .
deploy-static: build-static deploy-static: build-static

View File

@ -16,6 +16,8 @@ import (
"strings" "strings"
"time" "time"
pprofhttp "net/http/pprof"
"github.com/DataDog/datadog-go/statsd" "github.com/DataDog/datadog-go/statsd"
"github.com/ddworken/hishtory/shared" "github.com/ddworken/hishtory/shared"
"github.com/jackc/pgx/v4/stdlib" "github.com/jackc/pgx/v4/stdlib"
@ -27,11 +29,10 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/profiler" "gopkg.in/DataDog/dd-trace-go.v1/profiler"
"gorm.io/gorm/logger"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger"
) )
const ( const (
@ -197,7 +198,9 @@ func apiSubmitHandler(ctx context.Context, w http.ResponseWriter, r *http.Reques
if err != nil { if err != nil {
panic(fmt.Errorf("failed to execute transaction to add entries to DB: %v", err)) panic(fmt.Errorf("failed to execute transaction to add entries to DB: %v", err))
} }
GLOBAL_STATSD.Count("hishtory.submit", int64(len(devices)), []string{}, 1.0) if GLOBAL_STATSD != nil {
GLOBAL_STATSD.Count("hishtory.submit", int64(len(devices)), []string{}, 1.0)
}
} }
func apiBootstrapHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { func apiBootstrapHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@ -256,7 +259,9 @@ func apiQueryHandler(ctx context.Context, w http.ResponseWriter, r *http.Request
} }
} }
GLOBAL_STATSD.Incr("hishtory.query", []string{}, 1.0) if GLOBAL_STATSD != nil {
GLOBAL_STATSD.Incr("hishtory.query", []string{}, 1.0)
}
} }
func incrementReadCounts(ctx context.Context, deviceId string) error { func incrementReadCounts(ctx context.Context, deviceId string) error {
@ -293,7 +298,10 @@ func apiRegisterHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
checkGormResult(GLOBAL_DB.WithContext(ctx).Create(&shared.DumpRequest{UserId: userId, RequestingDeviceId: deviceId, RequestTime: time.Now()})) checkGormResult(GLOBAL_DB.WithContext(ctx).Create(&shared.DumpRequest{UserId: userId, RequestingDeviceId: deviceId, RequestTime: time.Now()}))
} }
updateUsageData(ctx, r, userId, deviceId, 0, false) updateUsageData(ctx, r, userId, deviceId, 0, false)
GLOBAL_STATSD.Incr("hishtory.register", []string{}, 1.0)
if GLOBAL_STATSD != nil {
GLOBAL_STATSD.Incr("hishtory.register", []string{}, 1.0)
}
} }
func apiGetPendingDumpRequestsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { func apiGetPendingDumpRequestsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
@ -555,6 +563,12 @@ func cron(ctx context.Context) error {
if err != nil { if err != nil {
panic(err) panic(err)
} }
if GLOBAL_STATSD != nil {
err = GLOBAL_STATSD.Flush()
if err != nil {
panic(err)
}
}
return nil return nil
} }
@ -585,6 +599,7 @@ func updateReleaseVersion() error {
if err != nil { if err != nil {
return fmt.Errorf("failed to get latest release version: %v", err) return fmt.Errorf("failed to get latest release version: %v", err)
} }
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) respBody, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return fmt.Errorf("failed to read github API response body: %v", err) return fmt.Errorf("failed to read github API response body: %v", err)
@ -635,6 +650,7 @@ func assertValidUpdate(updateInfo shared.UpdateInfo) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to retrieve URL %#v: %v", url, err) return fmt.Errorf("failed to retrieve URL %#v: %v", url, err)
} }
defer resp.Body.Close()
if resp.StatusCode == 404 { if resp.StatusCode == 404 {
return fmt.Errorf("URL %#v returned 404", url) return fmt.Errorf("URL %#v returned 404", url)
} }
@ -735,7 +751,10 @@ func feedbackHandler(ctx context.Context, w http.ResponseWriter, r *http.Request
} }
fmt.Printf("feedbackHandler: received request containg feedback %#v\n", feedback) fmt.Printf("feedbackHandler: received request containg feedback %#v\n", feedback)
checkGormResult(GLOBAL_DB.WithContext(ctx).Create(feedback)) checkGormResult(GLOBAL_DB.WithContext(ctx).Create(feedback))
GLOBAL_STATSD.Incr("hishtory.uninstall", []string{}, 1.0)
if GLOBAL_STATSD != nil {
GLOBAL_STATSD.Incr("hishtory.uninstall", []string{}, 1.0)
}
} }
type loggedResponseData struct { type loggedResponseData struct {
@ -782,8 +801,10 @@ func withLogging(h func(context.Context, http.ResponseWriter, *http.Request)) ht
duration := time.Since(start) duration := time.Since(start)
fmt.Printf("%s %s %#v %s %s %s\n", getRemoteAddr(r), r.Method, r.RequestURI, getHishtoryVersion(r), duration.String(), byteCountToString(responseData.size)) fmt.Printf("%s %s %#v %s %s %s\n", getRemoteAddr(r), r.Method, r.RequestURI, getHishtoryVersion(r), duration.String(), byteCountToString(responseData.size))
GLOBAL_STATSD.Distribution("hishtory.request_duration", float64(duration.Microseconds())/1_000, []string{"HANDLER=" + getFunctionName(h)}, 1.0) if GLOBAL_STATSD != nil {
GLOBAL_STATSD.Incr("hishtory.request", []string{}, 1.0) GLOBAL_STATSD.Distribution("hishtory.request_duration", float64(duration.Microseconds())/1_000, []string{"HANDLER=" + getFunctionName(h)}, 1.0)
GLOBAL_STATSD.Incr("hishtory.request", []string{}, 1.0)
}
} }
return http.HandlerFunc(logFn) return http.HandlerFunc(logFn)
} }
@ -853,7 +874,8 @@ func deepCleanDatabase(ctx context.Context) {
} }
} }
func configureObservability() func() { func configureObservability(mux *httptrace.ServeMux) func() {
// Profiler
err := profiler.Start( err := profiler.Start(
profiler.WithService("hishtory-api"), profiler.WithService("hishtory-api"),
profiler.WithVersion(ReleaseVersion), profiler.WithVersion(ReleaseVersion),
@ -867,17 +889,27 @@ func configureObservability() func() {
if err != nil { if err != nil {
fmt.Printf("Failed to start DataDog profiler: %v\n", err) fmt.Printf("Failed to start DataDog profiler: %v\n", err)
} }
// Tracer
tracer.Start( tracer.Start(
tracer.WithRuntimeMetrics(), tracer.WithRuntimeMetrics(),
tracer.WithService("hishtory-api"), tracer.WithService("hishtory-api"),
tracer.WithUDS("/var/run/datadog/apm.socket"), tracer.WithUDS("/var/run/datadog/apm.socket"),
) )
defer tracer.Stop() defer tracer.Stop()
// Stats
ddStats, err := statsd.New("unix:///var/run/datadog/dsd.socket") ddStats, err := statsd.New("unix:///var/run/datadog/dsd.socket")
if err != nil { if err != nil {
fmt.Printf("Failed to start DataDog statsd: %v\n", err) fmt.Printf("Failed to start DataDog statsd: %v\n", err)
} }
GLOBAL_STATSD = ddStats GLOBAL_STATSD = ddStats
// Pprof
mux.HandleFunc("/debug/pprof/", pprofhttp.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprofhttp.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprofhttp.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprofhttp.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprofhttp.Trace)
// Func to stop all of the above
return func() { return func() {
profiler.Stop() profiler.Stop()
tracer.Stop() tracer.Stop()
@ -885,12 +917,13 @@ func configureObservability() func() {
} }
func main() { func main() {
mux := httptrace.NewServeMux()
if isProductionEnvironment() { if isProductionEnvironment() {
go configureObservability()() defer configureObservability(mux)()
go deepCleanDatabase(context.Background()) go deepCleanDatabase(context.Background())
} }
mux := httptrace.NewServeMux()
mux.Handle("/api/v1/submit", withLogging(apiSubmitHandler)) mux.Handle("/api/v1/submit", withLogging(apiSubmitHandler))
mux.Handle("/api/v1/get-dump-requests", withLogging(apiGetPendingDumpRequestsHandler)) mux.Handle("/api/v1/get-dump-requests", withLogging(apiGetPendingDumpRequestsHandler))
mux.Handle("/api/v1/submit-dump", withLogging(apiSubmitDumpHandler)) mux.Handle("/api/v1/submit-dump", withLogging(apiSubmitDumpHandler))