diff --git a/.gitignore b/.gitignore index 0f2bb3c..97c84f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Project binary /poseidon +# CPU profiling information +cmd/poseidon/default.pgo + # Configuration file configuration.yaml tests/e2e/configuration.yaml diff --git a/Makefile b/Makefile index 45b8bfe..d05082a 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,23 @@ -PROJECT_NAME := "poseidon" -REPOSITORY_OWNER = "openHPI" -PKG := "github.com/$(REPOSITORY_OWNER)/$(PROJECT_NAME)/cmd/$(PROJECT_NAME)" +PROJECT_NAME = poseidon +REPOSITORY_OWNER = openHPI +PKG = github.com/$(REPOSITORY_OWNER)/$(PROJECT_NAME)/cmd/$(PROJECT_NAME) UNIT_TESTS = $(shell go list ./... | grep -v /e2e | grep -v /recovery) -DOCKER_TAG := "poseidon:latest" -DOCKER_OPTS := -v $(shell pwd)/configuration.yaml:/configuration.yaml +# Define the PGO file to be used for the build +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) # Define image to be used in e2e tests. Requires `make` to be available. -E2E_TEST_DOCKER_CONTAINER := co_execenv_java -E2E_TEST_DOCKER_TAG := 17 -E2E_TEST_DOCKER_IMAGE = "$(LOWER_REPOSITORY_OWNER)/$(E2E_TEST_DOCKER_CONTAINER):$(E2E_TEST_DOCKER_TAG)" +E2E_TEST_DOCKER_CONTAINER = co_execenv_java +E2E_TEST_DOCKER_TAG = 17 +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. 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 @@ -45,7 +49,13 @@ git-hooks: .git/hooks/pre-commit ## Install the git-hooks .PHONY: build 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) +endif .PHONY: clean clean: ## Remove previous build @@ -101,7 +111,7 @@ deploy/dockerfiles: ## Clone Dockerfiles repository .PHONY: e2e-test-docker-image 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) .PHONY: e2e-test diff --git a/cmd/poseidon/main.go b/cmd/poseidon/main.go index e4cb116..a2c54ab 100644 --- a/cmd/poseidon/main.go +++ b/cmd/poseidon/main.go @@ -15,6 +15,8 @@ import ( "net/http" "os" "os/signal" + "runtime/pprof" + "strconv" "syscall" "time" ) @@ -22,9 +24,21 @@ import ( var ( gracefulShutdownWait = 15 * time.Second 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 { 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) { log.WithField("address", server.Addr).Info("Starting server") var err error @@ -139,9 +180,12 @@ func main() { log.WithError(err).Warn("Could not initialize configuration") } logging.InitializeLogging(config.Config.Logger.Level) - initSentry(&config.Config.Sentry) + initSentry(&config.Config.Sentry, config.Config.Profiling.Enabled) defer shutdownSentry() + stopProfiling := initProfiling(config.Config.Profiling) + defer stopProfiling() + cancel := monitoring.InitializeInfluxDB(&config.Config.InfluxDB) defer cancel() diff --git a/configuration.example.yaml b/configuration.example.yaml index 963c575..5b24f2a 100644 --- a/configuration.example.yaml +++ b/configuration.example.yaml @@ -61,6 +61,14 @@ logger: # Log level that is used after reading the config (INFO until then) 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 sentry: # The DSN of the sentry endpoint to use. diff --git a/internal/config/config.go b/internal/config/config.go index 64a3f54..61a469e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -123,6 +123,12 @@ type logger struct { 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. type InfluxDB struct { URL string @@ -134,12 +140,13 @@ type InfluxDB struct { // configuration contains the complete configuration of Poseidon. type configuration struct { - Server server - Nomad Nomad - AWS AWS - Logger logger - Sentry sentry.ClientOptions - InfluxDB InfluxDB + Server server + Nomad Nomad + AWS AWS + Logger logger + Profiling Profiling + Sentry sentry.ClientOptions + InfluxDB InfluxDB } // InitConfig merges configuration options from environment variables and