diff --git a/pkg/telemetry/heartbeat.go b/pkg/telemetry/heartbeat.go new file mode 100644 index 0000000000..25ecab712c --- /dev/null +++ b/pkg/telemetry/heartbeat.go @@ -0,0 +1,35 @@ +package telemetry + +import ( + "context" + "fmt" + "os/exec" + "runtime" + "strings" + + "github.com/pkg/errors" +) + +// HostInfoClient is a collection of functionality for retrieving +// information about the host. +type HostInfoClient struct{} + +// KernelVersion returs the version of the kernel running on the host system. +func (p *HostInfoClient) KernelVersion(ctx context.Context) (string, error) { + var cmd *exec.Cmd + + switch runtime.GOOS { + case "windows": + cmd = exec.CommandContext(ctx, "powershell", "-command", "$([Environment]::OSVersion).VersionString") + case "linux": + cmd = exec.CommandContext(ctx, "uname", "-r") + default: + return "", fmt.Errorf("unsupported platform %q", runtime.GOOS) + } + + output, err := cmd.CombinedOutput() + if err != nil { + return "", errors.Wrapf(err, "failed to get %s kernel version: %s", runtime.GOOS, string(output)) + } + return strings.TrimSuffix(string(output), "\n"), nil +} diff --git a/pkg/telemetry/heartbeat_unix.go b/pkg/telemetry/heartbeat_unix.go deleted file mode 100644 index dc20736099..0000000000 --- a/pkg/telemetry/heartbeat_unix.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:build unix - -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package telemetry - -import ( - "context" - "os/exec" - "strings" - - "github.com/pkg/errors" -) - -func KernelVersion(ctx context.Context) (string, error) { - cmd := exec.CommandContext(ctx, "uname", "-r") - output, err := cmd.CombinedOutput() - if err != nil { - return "", errors.Wrapf(err, "failed to get linux kernel version: %s", string(output)) - } - return strings.TrimSuffix(string(output), "\n"), nil -} diff --git a/pkg/telemetry/heartbeat_unix_test.go b/pkg/telemetry/heartbeat_unix_test.go index 3f4cf747d5..4e2b68287c 100644 --- a/pkg/telemetry/heartbeat_unix_test.go +++ b/pkg/telemetry/heartbeat_unix_test.go @@ -16,7 +16,8 @@ func TestLinuxPlatformKernelVersion(t *testing.T) { InitAppInsights("", "") ctx := context.TODO() - str, err := KernelVersion(ctx) + client := &HostInfoClient{} + str, err := client.KernelVersion(ctx) require.NoError(t, err) require.NotEmpty(t, str) } diff --git a/pkg/telemetry/heartbeat_windows.go b/pkg/telemetry/heartbeat_windows.go deleted file mode 100644 index 40c878c8ae..0000000000 --- a/pkg/telemetry/heartbeat_windows.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package telemetry - -import ( - "context" - "os/exec" - "strings" - - "github.com/pkg/errors" -) - -func KernelVersion(ctx context.Context) (string, error) { - cmd := exec.CommandContext(ctx, "powershell", "-command", "$([Environment]::OSVersion).VersionString") - output, err := cmd.CombinedOutput() - if err != nil { - return "", errors.Wrapf(err, "failed to get windows kernel version: %s", string(output)) - } - return strings.TrimSuffix(string(output), "\r\n"), nil -} diff --git a/pkg/telemetry/heartbeat_windows_test.go b/pkg/telemetry/heartbeat_windows_test.go index 101fa04a74..461f7fad54 100644 --- a/pkg/telemetry/heartbeat_windows_test.go +++ b/pkg/telemetry/heartbeat_windows_test.go @@ -14,7 +14,8 @@ func TestWindowsGetKernelVersion(t *testing.T) { InitAppInsights("", "") ctx := context.TODO() - str, err := KernelVersion(ctx) + client := &HostInfoClient{} + str, err := client.KernelVersion(ctx) require.NoError(t, err) require.NotEmpty(t, str) } diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 4984df42db..b6b5af918f 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -22,7 +22,6 @@ var ( client appinsights.TelemetryClient version string mbShift uint64 = 20 - // property keys kernelversion = "kernelversion" sysmem = "sysmem" @@ -86,6 +85,10 @@ type TelemetryClient struct { processName string properties map[string]string profile Perf + + kernelInfoClient interface { + KernelVersion(context.Context) (string, error) + } } func NewAppInsightsTelemetryClient(processName string, additionalproperties map[string]string) (*TelemetryClient, error) { @@ -105,9 +108,10 @@ func NewAppInsightsTelemetryClient(processName string, additionalproperties map[ } return &TelemetryClient{ - processName: processName, - properties: properties, - profile: perfProfile, + processName: processName, + kernelInfoClient: &HostInfoClient{}, + properties: properties, + profile: perfProfile, }, nil } @@ -161,7 +165,7 @@ func (t *TelemetryClient) trackWarning(err error, msg string) { } func (t *TelemetryClient) heartbeat(ctx context.Context) { - kernelVersion, err := KernelVersion(ctx) + kernelVersion, err := t.kernelInfoClient.KernelVersion(ctx) if err != nil { t.trackWarning(err, "failed to get kernel version") } @@ -224,6 +228,20 @@ func (t *TelemetryClient) TrackMetric(metricname string, value float64, properti func (t *TelemetryClient) TrackTrace(name string, severity contracts.SeverityLevel, properties map[string]string) { trace := appinsights.NewTraceTelemetry(name, severity) + + kernelVersion, err := t.kernelInfoClient.KernelVersion(context.Background()) + if err != nil { + if client != nil { + errtrace := appinsights.NewTraceTelemetry(fmt.Sprintf("failed to get kernel version for trace: %v", err), contracts.Error) + for k, v := range t.properties { + errtrace.Properties[k] = v + } + client.Track(errtrace) + } + } else { + properties[kernelversion] = kernelVersion + } + if t.properties != nil { t.RLock() for k, v := range t.properties { diff --git a/pkg/telemetry/telemetry_test.go b/pkg/telemetry/telemetry_test.go index ae8c050dd9..a8e8e38b83 100644 --- a/pkg/telemetry/telemetry_test.go +++ b/pkg/telemetry/telemetry_test.go @@ -11,6 +11,7 @@ import ( "sync" "testing" + "github.com/microsoft/ApplicationInsights-Go/appinsights" "github.com/stretchr/testify/require" ) @@ -22,6 +23,40 @@ func TestNewAppInsightsTelemetryClient(t *testing.T) { require.NotPanics(t, func() { NewAppInsightsTelemetryClient("test", map[string]string{}) }) } +type MockKernelVersionClient struct { + KernelVersionF func(context.Context) (string, error) +} + +func (m *MockKernelVersionClient) KernelVersion(ctx context.Context) (string, error) { + return m.KernelVersionF(ctx) +} + +func TestTrackTraceIncludesKernelVersion(t *testing.T) { + mockKernelVersion := "5.15.0-101-generic" + called := false + mockClient := &MockKernelVersionClient{ + KernelVersionF: func(_ context.Context) (string, error) { + called = true + return mockKernelVersion, nil + }, + } + + client := &TelemetryClient{ + kernelInfoClient: mockClient, + properties: map[string]string{ + "test_key": "test_value", + }, + } + + traceProperties := map[string]string{} + + client.TrackTrace("test_trace_event", appinsights.Information, traceProperties) + + require.Equal(t, mockKernelVersion, traceProperties[kernelversion], "kernelVersion should be included in trace properties") + + require.True(t, called, "expected KernelVersion to be called but wasn't") +} + func TestGetEnvironmentProerties(t *testing.T) { properties := GetEnvironmentProperties()