From 0438ca485f2b85feafc81ca6cb8efe0fd7ec3d91 Mon Sep 17 00:00:00 2001
From: CrazyMax <1951866+crazy-max@users.noreply.github.com>
Date: Sun, 25 Apr 2021 18:04:07 +0200
Subject: [PATCH] Add profiler flag (#336)

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
---
 cmd/main.go                           | 26 ++++++++++++++++++++++++++
 docs/usage/cli.md                     | 19 +++++++++++--------
 go.mod                                |  1 +
 go.sum                                |  2 ++
 internal/model/cli.go                 |  1 +
 internal/provider/docker/container.go |  1 +
 internal/provider/swarm/service.go    |  1 +
 pkg/docker/client.go                  |  7 +++++++
 8 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/cmd/main.go b/cmd/main.go
index da83bbe4..1bc277b4 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -14,6 +14,7 @@ import (
 	"github.com/crazy-max/diun/v4/internal/config"
 	"github.com/crazy-max/diun/v4/internal/logging"
 	"github.com/crazy-max/diun/v4/internal/model"
+	"github.com/pkg/profile"
 	"github.com/rs/zerolog/log"
 )
 
@@ -75,6 +76,31 @@ func main() {
 	}
 	log.Debug().Msg(cfg.String())
 
+	// Profiler
+	if len(cli.Profiler) > 0 {
+		profilePath := profile.ProfilePath(cfg.Db.Path)
+		switch cli.Profiler {
+		case "cpu":
+			defer profile.Start(profile.CPUProfile, profilePath).Stop()
+		case "mem":
+			defer profile.Start(profile.MemProfile, profilePath).Stop()
+		case "alloc":
+			defer profile.Start(profile.MemProfile, profile.MemProfileAllocs(), profilePath).Stop()
+		case "heap":
+			defer profile.Start(profile.MemProfile, profile.MemProfileHeap(), profilePath).Stop()
+		case "routines":
+			defer profile.Start(profile.GoroutineProfile, profilePath).Stop()
+		case "mutex":
+			defer profile.Start(profile.MutexProfile, profilePath).Stop()
+		case "threads":
+			defer profile.Start(profile.ThreadcreationProfile, profilePath).Stop()
+		case "block":
+			defer profile.Start(profile.BlockProfile, profilePath).Stop()
+		default:
+			log.Fatal().Msgf("Unknown profiler: %s", cli.Profiler)
+		}
+	}
+
 	// Init
 	if diun, err = app.New(meta, cli, cfg); err != nil {
 		log.Fatal().Err(err).Msgf("Cannot initialize %s", meta.Name)
diff --git a/docs/usage/cli.md b/docs/usage/cli.md
index 724e313f..06f4a886 100644
--- a/docs/usage/cli.md
+++ b/docs/usage/cli.md
@@ -15,14 +15,16 @@ Usage: diun
 Docker image update notifier. More info: https://github.com/crazy-max/diun
 
 Flags:
-  --help                Show context-sensitive help.
-  --version
-  --config=STRING       Diun configuration file ($CONFIG).
-  --log-level="info"    Set log level ($LOG_LEVEL).
-  --log-json            Enable JSON logging output ($LOG_JSON).
-  --log-caller          Add file:line of the caller to log output ($LOG_CALLER).
-  --log-nocolor         Disables the colorized output ($LOG_NOCOLOR).
-  --test-notif          Test notification settings.
+  -h, --help                Show context-sensitive help.
+      --version
+      --config=STRING       Diun configuration file ($CONFIG).
+      --profiler=STRING     Profiler to use ($PROFILER).
+      --log-level="info"    Set log level ($LOG_LEVEL).
+      --log-json            Enable JSON logging output ($LOG_JSON).
+      --log-caller          Add file:line of the caller to log output
+                            ($LOG_CALLER).
+      --log-nocolor         Disables the colorized output ($LOG_NOCOLOR).
+      --test-notif          Test notification settings.
 ```
 
 ## Environment variables
@@ -32,6 +34,7 @@ Following environment variables can be used in place:
 | Name               | Default       | Description   |
 |--------------------|---------------|---------------|
 | `CONFIG`           |               | Diun configuration file |
+| `PROFILER`         |               | Profiler to use |
 | `LOG_LEVEL`        | `info`        | Log level output |
 | `LOG_JSON`         | `false`       | Enable JSON logging output |
 | `LOG_CALLER`       | `false`       | Enable to add `file:line` of the caller |
diff --git a/go.mod b/go.mod
index 65eabb4e..d70d61b4 100644
--- a/go.mod
+++ b/go.mod
@@ -24,6 +24,7 @@ require (
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/panjf2000/ants/v2 v2.4.4
 	github.com/pkg/errors v0.9.1
+	github.com/pkg/profile v1.5.0
 	github.com/robfig/cron/v3 v3.0.1
 	github.com/rs/zerolog v1.21.0
 	github.com/russross/blackfriday/v2 v2.1.0
diff --git a/go.sum b/go.sum
index 21f395d4..b8759fed 100644
--- a/go.sum
+++ b/go.sum
@@ -693,6 +693,8 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
+github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM=
diff --git a/internal/model/cli.go b/internal/model/cli.go
index ce70bcea..0a87aa08 100644
--- a/internal/model/cli.go
+++ b/internal/model/cli.go
@@ -6,6 +6,7 @@ import "github.com/alecthomas/kong"
 type Cli struct {
 	Version    kong.VersionFlag
 	Cfgfile    string `kong:"name='config',env='CONFIG',help='Diun configuration file.'"`
+	Profiler   string `kong:"name='profiler',env='PROFILER',enum='cpu,mem,alloc,heap,routines,mutex,threads,block',help='Profiler to use.'"`
 	LogLevel   string `kong:"name='log-level',env='LOG_LEVEL',default='info',help='Set log level.'"`
 	LogJSON    bool   `kong:"name='log-json',env='LOG_JSON',default='false',help='Enable JSON logging output.'"`
 	LogCaller  bool   `kong:"name='log-caller',env='LOG_CALLER',default='false',help='Add file:line of the caller to log output.'"`
diff --git a/internal/provider/docker/container.go b/internal/provider/docker/container.go
index 0cc90b18..a2bbcea1 100644
--- a/internal/provider/docker/container.go
+++ b/internal/provider/docker/container.go
@@ -20,6 +20,7 @@ func (c *Client) listContainerImage() []model.Image {
 		c.logger.Error().Err(err).Msg("Cannot create Docker client")
 		return []model.Image{}
 	}
+	defer cli.Close()
 
 	ctnFilter := filters.NewArgs()
 	ctnFilter.Add("status", "running")
diff --git a/internal/provider/swarm/service.go b/internal/provider/swarm/service.go
index 90264407..01535044 100644
--- a/internal/provider/swarm/service.go
+++ b/internal/provider/swarm/service.go
@@ -20,6 +20,7 @@ func (c *Client) listServiceImage() []model.Image {
 		c.logger.Error().Err(err).Msg("Cannot create Docker client")
 		return []model.Image{}
 	}
+	defer cli.Close()
 
 	svcs, err := cli.ServiceList(filters.NewArgs())
 	if err != nil {
diff --git a/pkg/docker/client.go b/pkg/docker/client.go
index 27e8ed5a..5f2abaed 100644
--- a/pkg/docker/client.go
+++ b/pkg/docker/client.go
@@ -69,3 +69,10 @@ func New(opts Options) (*Client, error) {
 		API: cli,
 	}, err
 }
+
+// Close closes docker client
+func (c *Client) Close() {
+	if c.API != nil {
+		_ = c.API.Close()
+	}
+}