Enable profiler and profile-guided builds

I used the chance to simplify the Makefile, as this is required for the file check to work correctly. Variables should not contain quotes, as these will be included in the value otherwise.
This commit is contained in:
Sebastian Serth
2023-02-26 20:16:08 +01:00
committed by Sebastian Serth
parent 00952ca212
commit 1a378ce640
5 changed files with 90 additions and 18 deletions

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
# Project binary # Project binary
/poseidon /poseidon
# CPU profiling information
cmd/poseidon/default.pgo
# Configuration file # Configuration file
configuration.yaml configuration.yaml
tests/e2e/configuration.yaml tests/e2e/configuration.yaml

View File

@ -1,19 +1,23 @@
PROJECT_NAME := "poseidon" PROJECT_NAME = poseidon
REPOSITORY_OWNER = "openHPI" REPOSITORY_OWNER = openHPI
PKG := "github.com/$(REPOSITORY_OWNER)/$(PROJECT_NAME)/cmd/$(PROJECT_NAME)" PKG = github.com/$(REPOSITORY_OWNER)/$(PROJECT_NAME)/cmd/$(PROJECT_NAME)
UNIT_TESTS = $(shell go list ./... | grep -v /e2e | grep -v /recovery) UNIT_TESTS = $(shell go list ./... | grep -v /e2e | grep -v /recovery)
DOCKER_TAG := "poseidon:latest" # Define the PGO file to be used for the build
DOCKER_OPTS := -v $(shell pwd)/configuration.yaml:/configuration.yaml PGO_FILE = ./cmd/$(PROJECT_NAME)/default.pgo
# Docker options
DOCKER_TAG = poseidon:latest
DOCKER_OPTS = -v $(shell pwd)/configuration.yaml:/configuration.yaml
LOWER_REPOSITORY_OWNER = $(shell echo $(REPOSITORY_OWNER) | tr A-Z a-z) LOWER_REPOSITORY_OWNER = $(shell echo $(REPOSITORY_OWNER) | tr A-Z a-z)
# Define image to be used in e2e tests. Requires `make` to be available. # Define image to be used in e2e tests. Requires `make` to be available.
E2E_TEST_DOCKER_CONTAINER := co_execenv_java E2E_TEST_DOCKER_CONTAINER = co_execenv_java
E2E_TEST_DOCKER_TAG := 17 E2E_TEST_DOCKER_TAG = 17
E2E_TEST_DOCKER_IMAGE = "$(LOWER_REPOSITORY_OWNER)/$(E2E_TEST_DOCKER_CONTAINER):$(E2E_TEST_DOCKER_TAG)" E2E_TEST_DOCKER_IMAGE = $(LOWER_REPOSITORY_OWNER)/$(E2E_TEST_DOCKER_CONTAINER):$(E2E_TEST_DOCKER_TAG)
# The base image of the e2e test image. This is used to build the base image as well. # The base image of the e2e test image. This is used to build the base image as well.
E2E_TEST_BASE_CONTAINER := docker_exec_phusion E2E_TEST_BASE_CONTAINER := docker_exec_phusion
E2E_TEST_BASE_IMAGE = "$(LOWER_REPOSITORY_OWNER)/$(E2E_TEST_BASE_CONTAINER)" E2E_TEST_BASE_IMAGE = $(LOWER_REPOSITORY_OWNER)/$(E2E_TEST_BASE_CONTAINER)
default: help default: help
@ -45,7 +49,13 @@ git-hooks: .git/hooks/pre-commit ## Install the git-hooks
.PHONY: build .PHONY: build
build: deps ## Build the binary build: deps ## Build the binary
ifneq ("$(wildcard $(PGO_FILE))","")
# PGO_FILE exists
@go build -pgo=$(PGO_FILE) -ldflags "-X main.pgoEnabled=true" -o $(PROJECT_NAME) -v $(PKG)
else
# PGO_FILE does not exist
@go build -o $(PROJECT_NAME) -v $(PKG) @go build -o $(PROJECT_NAME) -v $(PKG)
endif
.PHONY: clean .PHONY: clean
clean: ## Remove previous build clean: ## Remove previous build
@ -101,7 +111,7 @@ deploy/dockerfiles: ## Clone Dockerfiles repository
.PHONY: e2e-test-docker-image .PHONY: e2e-test-docker-image
e2e-test-docker-image: deploy/dockerfiles ## Build Docker image that is used in e2e tests e2e-test-docker-image: deploy/dockerfiles ## Build Docker image that is used in e2e tests
@docker build -t $(E2E_TEST_BASE_IMAGE) -f deploy/dockerfiles/$(E2E_TEST_BASE_CONTAINER) @docker build -t $(E2E_TEST_BASE_IMAGE) deploy/dockerfiles/$(E2E_TEST_BASE_CONTAINER)
@docker build -t $(E2E_TEST_DOCKER_IMAGE) deploy/dockerfiles/$(E2E_TEST_DOCKER_CONTAINER)/$(E2E_TEST_DOCKER_TAG) @docker build -t $(E2E_TEST_DOCKER_IMAGE) deploy/dockerfiles/$(E2E_TEST_DOCKER_CONTAINER)/$(E2E_TEST_DOCKER_TAG)
.PHONY: e2e-test .PHONY: e2e-test

View File

@ -15,6 +15,8 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime/pprof"
"strconv"
"syscall" "syscall"
"time" "time"
) )
@ -22,9 +24,21 @@ import (
var ( var (
gracefulShutdownWait = 15 * time.Second gracefulShutdownWait = 15 * time.Second
log = logging.GetLogger("main") log = logging.GetLogger("main")
// If pgoEnabled is true, the binary was built with PGO enabled.
// This is set during compilation with our Makefile as a STRING.
pgoEnabled = "false"
) )
func initSentry(options *sentry.ClientOptions) { func initSentry(options *sentry.ClientOptions, profilingEnabled bool) {
options.BeforeSendTransaction = func(event *sentry.Event, _ *sentry.EventHint) *sentry.Event {
if event.Tags == nil {
event.Tags = make(map[string]string)
}
event.Tags["go_pgo"] = pgoEnabled
event.Tags["go_profiling"] = strconv.FormatBool(profilingEnabled)
return event
}
if err := sentry.Init(*options); err != nil { if err := sentry.Init(*options); err != nil {
log.Errorf("sentry.Init: %s", err) log.Errorf("sentry.Init: %s", err)
} }
@ -37,6 +51,33 @@ func shutdownSentry() {
} }
} }
func initProfiling(options config.Profiling) (cancel func()) {
if options.Enabled {
profile, err := os.Create(options.File)
if err != nil {
log.WithError(err).Error("Error while opening the profile file")
}
log.Debug("Starting CPU profiler")
if err := pprof.StartCPUProfile(profile); err != nil {
log.WithError(err).Error("Error while starting the CPU profiler!!")
}
cancel = func() {
if options.Enabled {
log.Debug("Stopping CPU profiler")
pprof.StopCPUProfile()
if err := profile.Close(); err != nil {
log.WithError(err).Error("Error while closing profile file")
}
}
}
} else {
cancel = func() {}
}
return cancel
}
func runServer(server *http.Server) { func runServer(server *http.Server) {
log.WithField("address", server.Addr).Info("Starting server") log.WithField("address", server.Addr).Info("Starting server")
var err error var err error
@ -139,9 +180,12 @@ func main() {
log.WithError(err).Warn("Could not initialize configuration") log.WithError(err).Warn("Could not initialize configuration")
} }
logging.InitializeLogging(config.Config.Logger.Level) logging.InitializeLogging(config.Config.Logger.Level)
initSentry(&config.Config.Sentry) initSentry(&config.Config.Sentry, config.Config.Profiling.Enabled)
defer shutdownSentry() defer shutdownSentry()
stopProfiling := initProfiling(config.Config.Profiling)
defer stopProfiling()
cancel := monitoring.InitializeInfluxDB(&config.Config.InfluxDB) cancel := monitoring.InitializeInfluxDB(&config.Config.InfluxDB)
defer cancel() defer cancel()

View File

@ -61,6 +61,14 @@ logger:
# Log level that is used after reading the config (INFO until then) # Log level that is used after reading the config (INFO until then)
level: DEBUG level: DEBUG
# Configuration of the embedded profiler
profiling:
# Enables the runtime profiler
enabled: false
# The file to which the profile is written to.
# The default location `cmd/poseidon/default.pgo` will be picked up during the build process to create a profile-guided build.
file: cmd/poseidon/default.pgo
# Configuration of the sentry logging # Configuration of the sentry logging
sentry: sentry:
# The DSN of the sentry endpoint to use. # The DSN of the sentry endpoint to use.

View File

@ -123,6 +123,12 @@ type logger struct {
Level string Level string
} }
// Profiling configures the usage of a runtime profiler to create optimized binaries.
type Profiling struct {
Enabled bool
File string
}
// InfluxDB configures the usage of an Influx db monitoring. // InfluxDB configures the usage of an Influx db monitoring.
type InfluxDB struct { type InfluxDB struct {
URL string URL string
@ -134,12 +140,13 @@ type InfluxDB struct {
// configuration contains the complete configuration of Poseidon. // configuration contains the complete configuration of Poseidon.
type configuration struct { type configuration struct {
Server server Server server
Nomad Nomad Nomad Nomad
AWS AWS AWS AWS
Logger logger Logger logger
Sentry sentry.ClientOptions Profiling Profiling
InfluxDB InfluxDB Sentry sentry.ClientOptions
InfluxDB InfluxDB
} }
// InitConfig merges configuration options from environment variables and // InitConfig merges configuration options from environment variables and