Browse code

dockerversion: DockerUserAgent(): use sync.Once to construct User-Agent

The User-Agent includes the kernel version, which involves making a syscall
(and parsing the results) on Linux, and reading (plus parsing) the registry
on Windows. These operations are relatively costly, and we should not perform
those on every request that uses the User-Agent.

This patch adds a sync.Once so that we only perform these actions once for
the lifetime of the daemon's process.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2023/03/22 23:32:53
Showing 1 changed files
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 	"runtime"
7
+	"sync"
7 8
 
8 9
 	"github.com/docker/docker/pkg/parsers/kernel"
9 10
 	"github.com/docker/docker/pkg/useragent"
... ...
@@ -17,23 +18,43 @@ type UAStringKey struct{}
17 17
 //
18 18
 //	[docker client's UA] UpstreamClient([upstream client's UA])
19 19
 func DockerUserAgent(ctx context.Context) string {
20
-	httpVersion := make([]useragent.VersionInfo, 0, 6)
21
-	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
22
-	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
23
-	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
24
-	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
25
-		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
20
+	daemonUA := getDaemonUserAgent()
21
+	if upstreamUA := getUserAgentFromContext(ctx); len(upstreamUA) > 0 {
22
+		return insertUpstreamUserAgent(upstreamUA, daemonUA)
26 23
 	}
27
-	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
28
-	httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
24
+	return daemonUA
25
+}
29 26
 
30
-	dockerUA := useragent.AppendVersions("", httpVersion...)
31
-	upstreamUA := getUserAgentFromContext(ctx)
32
-	if len(upstreamUA) > 0 {
33
-		ret := insertUpstreamUserAgent(upstreamUA, dockerUA)
34
-		return ret
35
-	}
36
-	return dockerUA
27
+var (
28
+	daemonUAOnce sync.Once
29
+	daemonUA     string
30
+)
31
+
32
+// getUserAgentFromContext returns the user-agent to use for requests made by
33
+// the daemon.
34
+//
35
+// It includes;
36
+//
37
+// - the docker version
38
+// - go version
39
+// - git-commit
40
+// - kernel version
41
+// - os
42
+// - architecture
43
+func getDaemonUserAgent() string {
44
+	daemonUAOnce.Do(func() {
45
+		httpVersion := make([]useragent.VersionInfo, 0, 6)
46
+		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "docker", Version: Version})
47
+		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "go", Version: runtime.Version()})
48
+		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "git-commit", Version: GitCommit})
49
+		if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
50
+			httpVersion = append(httpVersion, useragent.VersionInfo{Name: "kernel", Version: kernelVersion.String()})
51
+		}
52
+		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "os", Version: runtime.GOOS})
53
+		httpVersion = append(httpVersion, useragent.VersionInfo{Name: "arch", Version: runtime.GOARCH})
54
+		daemonUA = useragent.AppendVersions("", httpVersion...)
55
+	})
56
+	return daemonUA
37 57
 }
38 58
 
39 59
 // getUserAgentFromContext returns the previously saved user-agent context stored in ctx, if one exists