diff --git a/internal/osinfo/osinfo.go b/internal/osinfo/osinfo.go index 7519a917e9..32ddb763a1 100644 --- a/internal/osinfo/osinfo.go +++ b/internal/osinfo/osinfo.go @@ -1,21 +1,54 @@ // Unless explicitly stated otherwise all files in this repository are licensed // under the Apache License Version 2.0. // This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2022 Datadog, Inc. +// Copyright 2024 Datadog, Inc. -// Package osinfo provides information about the current operating system release package osinfo +import ( + "runtime" +) + +// Modified in init functions to provide OS-specific information +var ( + osName = runtime.GOOS + osVersion = "unknown" + arch = runtime.GOARCH + kernelName = "unknown" + kernelRelease = "unknown" + kernelVersion = "unknown" +) + // OSName returns the name of the operating system, including the distribution // for Linux when possible. func OSName() string { // call out to OS-specific implementation - return osName() + return osName } // OSVersion returns the operating system release, e.g. major/minor version // number and build ID. func OSVersion() string { // call out to OS-specific implementation - return osVersion() + return osVersion +} + +// Architecture returns the architecture of the operating system. +func Architecture() string { + return arch +} + +// KernelName returns the name of the kernel. +func KernelName() string { + return kernelName +} + +// KernelRelease returns the release of the kernel. +func KernelRelease() string { + return kernelRelease +} + +// KernelVersion returns the version of the kernel. +func KernelVersion() string { + return kernelVersion } diff --git a/internal/osinfo/osinfo_darwin.go b/internal/osinfo/osinfo_darwin.go deleted file mode 100644 index 32ead5fe05..0000000000 --- a/internal/osinfo/osinfo_darwin.go +++ /dev/null @@ -1,24 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package osinfo - -import ( - "os/exec" - "runtime" - "strings" -) - -func osName() string { - return runtime.GOOS -} - -func osVersion() string { - out, err := exec.Command("sw_vers", "-productVersion").Output() - if err != nil { - return "unknown" - } - return strings.Trim(string(out), "\n") -} diff --git a/internal/osinfo/osinfo_default.go b/internal/osinfo/osinfo_default.go deleted file mode 100644 index 72d70d3885..0000000000 --- a/internal/osinfo/osinfo_default.go +++ /dev/null @@ -1,21 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -//go:build !windows && !linux && !darwin && !freebsd -// +build !windows,!linux,!darwin,!freebsd - -package osinfo - -import ( - "runtime" -) - -func osName() string { - return runtime.GOOS -} - -func osVersion() string { - return "unknown" -} diff --git a/internal/osinfo/osinfo_freebsd.go b/internal/osinfo/osinfo_freebsd.go deleted file mode 100644 index 543f2ffdfd..0000000000 --- a/internal/osinfo/osinfo_freebsd.go +++ /dev/null @@ -1,24 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package osinfo - -import ( - "os/exec" - "runtime" - "strings" -) - -func osName() string { - return runtime.GOOS -} - -func osVersion() string { - out, err := exec.Command("uname", "-r").Output() - if err != nil { - return "unknown" - } - return strings.Split(string(out), "-")[0] -} diff --git a/internal/osinfo/osinfo_linux.go b/internal/osinfo/osinfo_linux.go deleted file mode 100644 index 96d1e66ad1..0000000000 --- a/internal/osinfo/osinfo_linux.go +++ /dev/null @@ -1,52 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed -// under the Apache License Version 2.0. -// This product includes software developed at Datadog (https://www.datadoghq.com/). -// Copyright 2016 Datadog, Inc. - -package osinfo - -import ( - "bufio" - "os" - "strings" -) - -func osName() string { - f, err := os.Open("/etc/os-release") - if err != nil { - return "Linux (Unknown Distribution)" - } - defer f.Close() - s := bufio.NewScanner(f) - name := "Linux (Unknown Distribution)" - for s.Scan() { - parts := strings.SplitN(s.Text(), "=", 2) - switch parts[0] { - case "NAME": - name = strings.Trim(parts[1], "\"") - } - } - return name -} - -func osVersion() string { - f, err := os.Open("/etc/os-release") - if err != nil { - return "unknown" - } - defer f.Close() - s := bufio.NewScanner(f) - version := "unknown" - for s.Scan() { - parts := strings.SplitN(s.Text(), "=", 2) - switch parts[0] { - case "VERSION": - version = strings.Trim(parts[1], "\"") - case "VERSION_ID": - if version == "" { - version = strings.Trim(parts[1], "\"") - } - } - } - return version -} diff --git a/internal/osinfo/osinfo_test.go b/internal/osinfo/osinfo_test.go new file mode 100644 index 0000000000..c971fc0125 --- /dev/null +++ b/internal/osinfo/osinfo_test.go @@ -0,0 +1,42 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +package osinfo_test + +import ( + "runtime" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/mod/semver" + + "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" +) + +func Test(t *testing.T) { + var ( + osName = osinfo.OSName() + osVersion = osinfo.OSVersion() + arch = osinfo.Architecture() + kernelName = osinfo.KernelName() + kernelRelease = osinfo.KernelRelease() + kernelVersion = osinfo.KernelVersion() + ) + + t.Logf("OS Name: %s\n", osName) + t.Logf("OS Version: %s\n", osVersion) + t.Logf("Architecture: %s\n", arch) + t.Logf("Kernel Name: %s\n", kernelName) + t.Logf("Kernel Release: %s\n", kernelRelease) + t.Logf("Kernel Version: %s\n", kernelVersion) + + switch runtime.GOOS { + case "linux": + require.Equal(t, "Linux", kernelName) + require.Truef(t, semver.IsValid("v"+kernelRelease), "invalid kernel version: %s", kernelRelease) + case "darwin": + require.Equal(t, "Darwin", kernelName) + } +} diff --git a/internal/osinfo/osinfo_unix.go b/internal/osinfo/osinfo_unix.go new file mode 100644 index 0000000000..36d96da3aa --- /dev/null +++ b/internal/osinfo/osinfo_unix.go @@ -0,0 +1,70 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2024 Datadog, Inc. + +//go:build unix + +package osinfo + +import ( + "bufio" + "bytes" + "os" + "os/exec" + "runtime" + "strings" + + "golang.org/x/sys/unix" +) + +func init() { + // Change the default values for backwards compatibility on scenarios + if runtime.GOOS == "linux" { + osName = "Linux (Unknown Distribution)" + kernelName = "Linux" + } + + if runtime.GOOS == "darwin" { + kernelName = "Darwin" + out, err := exec.Command("sw_vers", "-productVersion").Output() + if err != nil { + return + } + + osVersion = string(bytes.Trim(out, "\n")) + } + + var uts unix.Utsname + if err := unix.Uname(&uts); err == nil { + kernelName = string(bytes.TrimRight(uts.Sysname[:], "\x00")) + kernelVersion = string(bytes.TrimRight(uts.Version[:], "\x00")) + kernelRelease = strings.SplitN(strings.TrimRight(string(uts.Release[:]), "\x00"), "-", 2)[0] + + // Backwards compatibility on how data is reported for freebsd + if runtime.GOOS == "freebsd" { + osVersion = kernelRelease + } + } + + f, err := os.Open("/etc/os-release") + if err != nil { + return + } + + defer f.Close() + scanner := bufio.NewScanner(f) + for scanner.Scan() { + parts := strings.SplitN(scanner.Text(), "=", 2) + switch parts[0] { + case "NAME": + osName = strings.Trim(parts[1], "\"") + case "VERSION": + osVersion = strings.Trim(parts[1], "\"") + case "VERSION_ID": + if osVersion == "" { // Fallback to VERSION_ID if VERSION is not set + osVersion = strings.Trim(parts[1], "\"") + } + } + } +} diff --git a/internal/osinfo/osinfo_windows.go b/internal/osinfo/osinfo_windows.go index 659bd9ce6a..fd408b1383 100644 --- a/internal/osinfo/osinfo_windows.go +++ b/internal/osinfo/osinfo_windows.go @@ -3,21 +3,18 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016 Datadog, Inc. +//go:build windows + package osinfo import ( "fmt" - "runtime" "strings" "golang.org/x/sys/windows/registry" ) -func osName() string { - return runtime.GOOS -} - -func osVersion() string { +func init() { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) if err != nil { return "unknown" @@ -50,5 +47,6 @@ func osVersion() string { } else { version.WriteString(" Unknown Build") } - return version.String() + + osVersion = version.String() } diff --git a/internal/telemetry/client.go b/internal/telemetry/client.go index 5cf962c7e4..945d1963c8 100644 --- a/internal/telemetry/client.go +++ b/internal/telemetry/client.go @@ -13,7 +13,6 @@ import ( "fmt" "net" "net/http" - "os" "runtime" "runtime/debug" "strings" @@ -22,6 +21,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig" + "gopkg.in/DataDog/dd-trace-go.v1/internal/hostname" logger "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/osinfo" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" @@ -68,7 +68,6 @@ var ( }, Timeout: 5 * time.Second, } - hostname string // protects agentlessURL, which may be changed for testing purposes agentlessEndpointLock sync.RWMutex @@ -83,10 +82,6 @@ var ( ) func init() { - h, err := os.Hostname() - if err == nil { - hostname = h - } GlobalClient = new(client) } @@ -451,30 +446,11 @@ func (c *client) flush() { }() } -var ( - osName string - osNameOnce sync.Once - osVersion string - osVersionOnce sync.Once -) - -// XXX: is it actually safe to cache osName and osVersion? For example, can the -// kernel be updated without stopping execution? - -func getOSName() string { - osNameOnce.Do(func() { osName = osinfo.OSName() }) - return osName -} - -func getOSVersion() string { - osVersionOnce.Do(func() { osVersion = osinfo.OSVersion() }) - return osVersion -} - // newRequests populates a request with the common fields shared by all requests // sent through this Client func (c *client) newRequest(t RequestType) *Request { c.seqID++ + hostname := hostname.Get() body := &Body{ APIVersion: "v2", RequestType: t, @@ -491,11 +467,13 @@ func (c *client) newRequest(t RequestType) *Request { LanguageVersion: runtime.Version(), }, Host: Host{ - Hostname: hostname, - OS: getOSName(), - OSVersion: getOSVersion(), - Architecture: runtime.GOARCH, - // TODO (lievan): getting kernel name, release, version TBD + Hostname: hostname, + OS: osinfo.OSName(), + OSVersion: osinfo.OSVersion(), + Architecture: osinfo.Architecture(), + KernelName: osinfo.KernelName(), + KernelRelease: osinfo.KernelRelease(), + KernelVersion: osinfo.KernelVersion(), }, } diff --git a/internal/telemetry/message.go b/internal/telemetry/message.go index 27a72768d9..b5c9848327 100644 --- a/internal/telemetry/message.go +++ b/internal/telemetry/message.go @@ -94,11 +94,9 @@ type Application struct { // Host is identifying information about the host on which the app // is running type Host struct { - Hostname string `json:"hostname"` - OS string `json:"os"` - OSVersion string `json:"os_version,omitempty"` - // TODO: Do we care about the kernel stuff? internal/osinfo gets most of - // this information in OSName/OSVersion + Hostname string `json:"hostname"` + OS string `json:"os"` + OSVersion string `json:"os_version,omitempty"` Architecture string `json:"architecture"` KernelName string `json:"kernel_name"` KernelRelease string `json:"kernel_release"`