diff --git a/.cspell/other.txt b/.cspell/other.txt
index a0b3134eed..f53aa5cee6 100644
--- a/.cspell/other.txt
+++ b/.cspell/other.txt
@@ -44,6 +44,7 @@ NETRUNTIME
Npgsql
NSERVICEBUS
omnisharp
+OPAMP
OPENTRACING
OPERATINGSYSTEM
ORACLEMDA
@@ -58,9 +59,9 @@ protos
RABBITMQ
Serilog
spdlog
-srcs
SQLCLIENT
sqlserver
+srcs
STACKEXCHANGEREDIS
TMPDIR
tracesexporter
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75b839fdda..e86146b23a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ This component adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.h
### Added
+- Experimental support for OpAMP (by default the client is disabled).
+
### Changed
#### Dependency updates
diff --git a/docs/config.md b/docs/config.md
index 5c66d05509..7dfee45324 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -203,10 +203,10 @@ due to lack of stable semantic convention.
**Status**: [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md).
-| ID | Instrumented library | Supported versions | Instrumentation type | Status |
-|-----------|---------------------------------------------------------------------------------------------------------------------------------|--------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
+| ID | Instrumented library | Supported versions | Instrumentation type | Status |
+|-----------|---------------------------------------------------------------------------------------------------------------------------------|--------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `ILOGGER` | [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) **Not supported on .NET Framework** | ≥9.0.0 | bytecode or source \[1\] | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
-| `LOG4NET` | [log4net](https://www.nuget.org/packages/log4net) \[2\] | ≥2.0.13 && < 4.0.0 | bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
+| `LOG4NET` | [log4net](https://www.nuget.org/packages/log4net) \[2\] | ≥2.0.13 && < 4.0.0 | bytecode | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
\[1\]: For ASP.NET Core applications, the `LoggingBuilder` instrumentation
can be enabled without using the .NET CLR Profiler by setting
@@ -499,3 +499,11 @@ instead.
|----------------------------------|-------------------------------------------------------------------------|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `OTEL_DOTNET_AUTO_LOG_DIRECTORY` | Directory of the .NET Tracer logs. | *See the previous note on default paths* | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
| `OTEL_LOG_LEVEL` | SDK log level. (supported values: `none`,`error`,`warn`,`info`,`debug`) | `info` | [Stable](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
+
+## OpAMP Client
+
+| Environment variable | Description | Default value | Status |
+|------------------------------------------|--------------------------------------------|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
+| `OTEL_DOTNET_AUTO_OPAMP_ENABLED` | Enables OpAMP client. | `false` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
+| `OTEL_DOTNET_AUTO_OPAMP_SERVER_URL` | OpAMP server url. | `https://localhost:4318/v1/opamp` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
+| `OTEL_DOTNET_AUTO_OPAMP_CONNECTION_TYPE` | OpAMP connection type (http or websocket). | `http` | [Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/versioning-and-stability.md) |
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index ab162c9387..48971cb167 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -24,6 +24,7 @@
+
@@ -46,6 +47,7 @@
+
@@ -60,12 +62,18 @@
+
+
+
+
+
+
diff --git a/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h b/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h
index 937da8851f..993c3e66ab 100644
--- a/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h
+++ b/src/OpenTelemetry.AutoInstrumentation.Native/netfx_assembly_redirection.h
@@ -18,6 +18,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap()
const USHORT auto_major = atoi(AUTO_MAJOR);
assembly_version_redirect_map_.insert({
+ { L"Google.Protobuf", {3, 31, 1, 0} },
{ L"Microsoft.Bcl.AsyncInterfaces", {9, 0, 0, 8} },
{ L"Microsoft.Extensions.Configuration", {9, 0, 0, 8} },
{ L"Microsoft.Extensions.Configuration.Abstractions", {9, 0, 0, 8} },
@@ -50,6 +51,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap()
{ L"OpenTelemetry.Instrumentation.Runtime", {1, 12, 0, 496} },
{ L"OpenTelemetry.Instrumentation.SqlClient", {1, 12, 0, 667} },
{ L"OpenTelemetry.Instrumentation.Wcf", {1, 12, 0, 500} },
+ { L"OpenTelemetry.OpAmp.Client", {0, 1, 0, 664} },
{ L"OpenTelemetry.Resources.Azure", {1, 12, 0, 501} },
{ L"OpenTelemetry.Resources.Host", {1, 12, 0, 503} },
{ L"OpenTelemetry.Resources.OperatingSystem", {1, 12, 0, 504} },
@@ -61,6 +63,7 @@ void CorProfiler::InitNetFxAssemblyRedirectsMap()
{ L"System.Buffers", {4, 0, 5, 0} },
{ L"System.Collections", {4, 0, 11, 0} },
{ L"System.Collections.Concurrent", {4, 0, 11, 0} },
+ { L"System.Collections.Immutable", {8, 0, 0, 0} },
{ L"System.Collections.NonGeneric", {4, 0, 3, 0} },
{ L"System.Collections.Specialized", {4, 0, 3, 0} },
{ L"System.ComponentModel", {4, 0, 1, 0} },
diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs
index 6ace88c28c..419f98f49b 100644
--- a/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs
+++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/ConfigurationKeys.cs
@@ -36,6 +36,21 @@ internal partial class ConfigurationKeys
///
public const string SetupSdk = "OTEL_DOTNET_AUTO_SETUP_SDK";
+ ///
+ /// Configuration key for enabling OpAmp client.
+ ///
+ public const string OpAmpEnabled = "OTEL_DOTNET_AUTO_OPAMP_ENABLED";
+
+ ///
+ /// Configuration key for OpAmp server url.
+ ///
+ public const string OpAmpServerUrl = "OTEL_DOTNET_AUTO_OPAMP_SERVER_URL";
+
+ ///
+ /// Configuration key for OpAmp server connection type.
+ ///
+ public const string OpAmpConnectionType = "OTEL_DOTNET_AUTO_OPAMP_CONNECTION_TYPE";
+
///
/// Configuration key for enabling all instrumentations.
///
diff --git a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs
index 3f54323096..976926a2a9 100644
--- a/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs
+++ b/src/OpenTelemetry.AutoInstrumentation/Configurations/GeneralSettings.cs
@@ -27,6 +27,11 @@ internal class GeneralSettings : Settings
///
public bool ProfilerEnabled { get; private set; }
+ ///
+ /// Gets a value indicating whether the OpAmp client is enabled.
+ ///
+ public bool OpAmpClientEnabled { get; private set; }
+
protected override void OnLoadEnvVar(Configuration configuration)
{
var providerPlugins = configuration.GetString(ConfigurationKeys.ProviderPlugins);
@@ -42,5 +47,6 @@ protected override void OnLoadEnvVar(Configuration configuration)
SetupSdk = configuration.GetBool(ConfigurationKeys.SetupSdk) ?? true;
ProfilerEnabled = configuration.GetString(ConfigurationKeys.ProfilingEnabled) == "1";
+ OpAmpClientEnabled = configuration.GetBool(ConfigurationKeys.OpAmpEnabled) ?? false;
}
}
diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs
index b75bb583e1..52f6247b9f 100644
--- a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs
+++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs
@@ -13,6 +13,7 @@
using OpenTelemetry.AutoInstrumentation.Loading;
using OpenTelemetry.AutoInstrumentation.Logging;
using OpenTelemetry.AutoInstrumentation.Plugins;
+using OpenTelemetry.AutoInstrumentation.Util;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
@@ -207,6 +208,13 @@ public static void Initialize()
{
OpenTracingHelper.EnableOpenTracing(_tracerProvider);
}
+
+ if (GeneralSettings.Value.OpAmpClientEnabled)
+ {
+ var resources = ResourceHelper.AggregateResources(_tracerProvider, _meterProvider, LoggerProvider);
+
+ OpAmpHelper.EnableOpAmpClient(resources);
+ }
}
#if NET
@@ -525,6 +533,8 @@ private static void OnExit(object? sender, EventArgs e)
try
{
+ OpAmpHelper.StopOpAmpClientIfRunning();
+
#if NET
LazyInstrumentationLoader?.Dispose();
_sampleExporter?.Dispose();
diff --git a/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs b/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs
new file mode 100644
index 0000000000..04c527d80f
--- /dev/null
+++ b/src/OpenTelemetry.AutoInstrumentation/OpAmpHelper.cs
@@ -0,0 +1,160 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+using System.Reflection;
+using OpenTelemetry.AutoInstrumentation.Configurations;
+using OpenTelemetry.AutoInstrumentation.Logging;
+using OpenTelemetry.OpAmp.Client;
+using OpenTelemetry.OpAmp.Client.Settings;
+using OpenTelemetry.Resources;
+
+namespace OpenTelemetry.AutoInstrumentation;
+
+internal static class OpAmpHelper
+{
+ private static readonly CancellationTokenSource _cts = new();
+ private static readonly IOtelLogger Logger = OtelLogging.GetLogger();
+
+ private static OpAmpClient? _client;
+ private static Task? _clientRunningTask;
+
+ public static bool IsRunning { get; private set; }
+
+ public static void EnableOpAmpClient(Resource resources)
+ {
+ try
+ {
+ _client = new OpAmpClient(settings => ConfigureClient(settings, resources));
+
+ _clientRunningTask = Task.Run(async () =>
+ {
+ try
+ {
+ await _client.StartAsync(_cts.Token);
+
+ IsRunning = true;
+ }
+ catch (Exception ex)
+ {
+ Logger.Warning(ex, "OpAmp client stopped unexpectedly.");
+
+ IsRunning = false;
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "An error occurred while initializing the OpAmp client.");
+ }
+ }
+
+ public static void StopOpAmpClientIfRunning()
+ {
+ if (!IsRunning)
+ {
+ return;
+ }
+
+ try
+ {
+ _client?.StopAsync().GetAwaiter().GetResult();
+
+ if (_clientRunningTask != null)
+ {
+ _cts.Cancel();
+ _clientRunningTask.GetAwaiter().GetResult();
+ }
+
+ _client?.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "An error occurred while stopping the OpAmp client.");
+ }
+ finally
+ {
+ IsRunning = false;
+ }
+ }
+
+ private static void ConfigureClient(OpAmpClientSettings settings, Resource resources)
+ {
+ // Configure connection type
+ var connectionType = GetConnectionType();
+ if (connectionType.HasValue)
+ {
+ settings.ConnectionType = connectionType.Value;
+ }
+
+ // Configure server URL
+ var serverUrl = GetServerUrl();
+ if (serverUrl != null)
+ {
+ settings.ServerUrl = serverUrl;
+ }
+
+ // Configure resource attributes for identification
+ foreach (var resourceAttribute in resources.Attributes)
+ {
+ if (resourceAttribute.Value == null)
+ {
+ continue;
+ }
+
+ var value = resourceAttribute.Value.ToString();
+ if (!string.IsNullOrWhiteSpace(value))
+ {
+ if (resourceAttribute.Key == "service.name")
+ {
+ settings.Identification.AddIdentifyingAttribute(resourceAttribute.Key, value);
+ }
+ else
+ {
+ settings.Identification.AddNonIdentifyingAttribute(resourceAttribute.Key, value);
+ }
+ }
+ }
+
+ settings.Identification.AddNonIdentifyingAttribute("opamp.version", GetOpAmpVersion());
+ }
+
+ private static Uri? GetServerUrl()
+ {
+ var url = Environment.GetEnvironmentVariable(ConfigurationKeys.OpAmpServerUrl);
+
+ if (string.IsNullOrWhiteSpace(url))
+ {
+ // indicates that the default value should be used
+ return null;
+ }
+
+ return new Uri(url);
+ }
+
+ private static ConnectionType? GetConnectionType()
+ {
+ var type = Environment.GetEnvironmentVariable(ConfigurationKeys.OpAmpConnectionType);
+
+ if (string.IsNullOrWhiteSpace(type))
+ {
+ // indicates that the default value should be used
+ return null;
+ }
+
+ return type.ToLower() switch
+ {
+ "websocket" => ConnectionType.WebSocket,
+ "http" => ConnectionType.Http,
+ _ => throw new InvalidOperationException($"{ConfigurationKeys.OpAmpConnectionType} environment variable has an invalid value. Valid values are 'websocket' and 'http'."),
+ };
+ }
+
+ private static string GetOpAmpVersion()
+ {
+ var assembly = typeof(OpAmpClient).Assembly;
+
+ return assembly
+ .GetCustomAttribute()?
+ .InformationalVersion?.Split(['+'], 2)[0] ?? "unknown";
+ }
+}
diff --git a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj
index 11b865a9c1..a897ea8d76 100644
--- a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj
+++ b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj
@@ -36,6 +36,7 @@
+
@@ -60,6 +61,7 @@
+
@@ -74,12 +76,18 @@
+
+
+
+
+
+
diff --git a/src/OpenTelemetry.AutoInstrumentation/Util/ResourceHelper.cs b/src/OpenTelemetry.AutoInstrumentation/Util/ResourceHelper.cs
new file mode 100644
index 0000000000..5238b22973
--- /dev/null
+++ b/src/OpenTelemetry.AutoInstrumentation/Util/ResourceHelper.cs
@@ -0,0 +1,37 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+using OpenTelemetry.Resources;
+
+namespace OpenTelemetry.AutoInstrumentation.Util;
+
+internal static class ResourceHelper
+{
+ public static Resource AggregateResources(params BaseProvider?[] providers)
+ {
+ var resource = Resource.Empty;
+
+ foreach (var provider in providers)
+ {
+ if (provider == null)
+ {
+ continue;
+ }
+
+ try
+ {
+ var providerResource = provider.GetResource();
+ if (providerResource != null)
+ {
+ resource = resource.Merge(providerResource);
+ }
+ }
+ catch (Exception)
+ {
+ // intentionally empty
+ }
+ }
+
+ return resource;
+ }
+}
diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt
index 7c7c26e312..6f2a5e5c4f 100644
--- a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt
+++ b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-arm64.verified.txt
@@ -4,6 +4,7 @@
/LICENSE,
/instrument.sh,
/linux-musl-arm64/OpenTelemetry.AutoInstrumentation.Native.so,
+ /net/Google.Protobuf.dll,
/net/Microsoft.Extensions.Diagnostics.Abstractions.dll,
/net/OpenTelemetry.Api.ProviderBuilderExtensions.dll,
/net/OpenTelemetry.Api.dll,
@@ -30,6 +31,7 @@
/net/OpenTelemetry.Instrumentation.SqlClient.dll,
/net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll,
/net/OpenTelemetry.Instrumentation.Wcf.dll,
+ /net/OpenTelemetry.OpAmp.Client.dll,
/net/OpenTelemetry.Resources.Azure.dll,
/net/OpenTelemetry.Resources.Container.dll,
/net/OpenTelemetry.Resources.Host.dll,
diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt
index db81591ff2..5d9e727ad6 100644
--- a/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt
+++ b/test/IntegrationTests/BuildTests.DistributionStructure_alpine-linux-x64.verified.txt
@@ -4,6 +4,7 @@
/LICENSE,
/instrument.sh,
/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so,
+ /net/Google.Protobuf.dll,
/net/Microsoft.Extensions.Diagnostics.Abstractions.dll,
/net/OpenTelemetry.Api.ProviderBuilderExtensions.dll,
/net/OpenTelemetry.Api.dll,
@@ -30,6 +31,7 @@
/net/OpenTelemetry.Instrumentation.SqlClient.dll,
/net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll,
/net/OpenTelemetry.Instrumentation.Wcf.dll,
+ /net/OpenTelemetry.OpAmp.Client.dll,
/net/OpenTelemetry.Resources.Azure.dll,
/net/OpenTelemetry.Resources.Container.dll,
/net/OpenTelemetry.Resources.Host.dll,
diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt
index 93d33bbaaa..f6e5df6aaa 100644
--- a/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt
+++ b/test/IntegrationTests/BuildTests.DistributionStructure_linux-arm64.verified.txt
@@ -4,6 +4,7 @@
/LICENSE,
/instrument.sh,
/linux-arm64/OpenTelemetry.AutoInstrumentation.Native.so,
+ /net/Google.Protobuf.dll,
/net/Microsoft.Extensions.Diagnostics.Abstractions.dll,
/net/OpenTelemetry.Api.ProviderBuilderExtensions.dll,
/net/OpenTelemetry.Api.dll,
@@ -30,6 +31,7 @@
/net/OpenTelemetry.Instrumentation.SqlClient.dll,
/net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll,
/net/OpenTelemetry.Instrumentation.Wcf.dll,
+ /net/OpenTelemetry.OpAmp.Client.dll,
/net/OpenTelemetry.Resources.Azure.dll,
/net/OpenTelemetry.Resources.Container.dll,
/net/OpenTelemetry.Resources.Host.dll,
diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt
index 4a6ef4451c..c1ef51a9d7 100644
--- a/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt
+++ b/test/IntegrationTests/BuildTests.DistributionStructure_linux-x64.verified.txt
@@ -4,6 +4,7 @@
/LICENSE,
/instrument.sh,
/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so,
+ /net/Google.Protobuf.dll,
/net/Microsoft.Extensions.Diagnostics.Abstractions.dll,
/net/OpenTelemetry.Api.ProviderBuilderExtensions.dll,
/net/OpenTelemetry.Api.dll,
@@ -30,6 +31,7 @@
/net/OpenTelemetry.Instrumentation.SqlClient.dll,
/net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll,
/net/OpenTelemetry.Instrumentation.Wcf.dll,
+ /net/OpenTelemetry.OpAmp.Client.dll,
/net/OpenTelemetry.Resources.Azure.dll,
/net/OpenTelemetry.Resources.Container.dll,
/net/OpenTelemetry.Resources.Host.dll,
diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt
index f9fa7a92be..f6bd853937 100644
--- a/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt
+++ b/test/IntegrationTests/BuildTests.DistributionStructure_osx.verified.txt
@@ -3,6 +3,7 @@
/AdditionalDeps/shared/Microsoft.NETCore.App/9.0.0/OpenTelemetry.AutoInstrumentation.AdditionalDeps.deps.json,
/LICENSE,
/instrument.sh,
+ /net/Google.Protobuf.dll,
/net/Microsoft.Extensions.Diagnostics.Abstractions.dll,
/net/OpenTelemetry.Api.ProviderBuilderExtensions.dll,
/net/OpenTelemetry.Api.dll,
@@ -29,6 +30,7 @@
/net/OpenTelemetry.Instrumentation.SqlClient.dll,
/net/OpenTelemetry.Instrumentation.StackExchangeRedis.dll,
/net/OpenTelemetry.Instrumentation.Wcf.dll,
+ /net/OpenTelemetry.OpAmp.Client.dll,
/net/OpenTelemetry.Resources.Azure.dll,
/net/OpenTelemetry.Resources.Container.dll,
/net/OpenTelemetry.Resources.Host.dll,
diff --git a/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt b/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt
index 4cec95c98b..f93d2cde18 100644
--- a/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt
+++ b/test/IntegrationTests/BuildTests.DistributionStructure_windows.verified.txt
@@ -3,6 +3,7 @@
\AdditionalDeps\shared\Microsoft.NETCore.App\9.0.0\OpenTelemetry.AutoInstrumentation.AdditionalDeps.deps.json,
\LICENSE,
\instrument.sh,
+ \net\Google.Protobuf.dll,
\net\Microsoft.Extensions.Diagnostics.Abstractions.dll,
\net\OpenTelemetry.Api.ProviderBuilderExtensions.dll,
\net\OpenTelemetry.Api.dll,
@@ -29,6 +30,7 @@
\net\OpenTelemetry.Instrumentation.SqlClient.dll,
\net\OpenTelemetry.Instrumentation.StackExchangeRedis.dll,
\net\OpenTelemetry.Instrumentation.Wcf.dll,
+ \net\OpenTelemetry.OpAmp.Client.dll,
\net\OpenTelemetry.Resources.Azure.dll,
\net\OpenTelemetry.Resources.Container.dll,
\net\OpenTelemetry.Resources.Host.dll,
@@ -43,6 +45,7 @@
\net\System.ServiceModel.Primitives.dll,
\net\System.ServiceModel.dll,
\net\ruleEngine.json,
+ \netfx\Google.Protobuf.dll,
\netfx\Microsoft.Bcl.AsyncInterfaces.dll,
\netfx\Microsoft.Extensions.Configuration.Abstractions.dll,
\netfx\Microsoft.Extensions.Configuration.Binder.dll,
@@ -75,6 +78,7 @@
\netfx\OpenTelemetry.Instrumentation.Runtime.dll,
\netfx\OpenTelemetry.Instrumentation.SqlClient.dll,
\netfx\OpenTelemetry.Instrumentation.Wcf.dll,
+ \netfx\OpenTelemetry.OpAmp.Client.dll,
\netfx\OpenTelemetry.Resources.Azure.dll,
\netfx\OpenTelemetry.Resources.Host.dll,
\netfx\OpenTelemetry.Resources.OperatingSystem.dll,
@@ -86,6 +90,7 @@
\netfx\System.AppContext.dll,
\netfx\System.Buffers.dll,
\netfx\System.Collections.Concurrent.dll,
+ \netfx\System.Collections.Immutable.dll,
\netfx\System.Collections.NonGeneric.dll,
\netfx\System.Collections.Specialized.dll,
\netfx\System.Collections.dll,
diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Util/ResourceHelperTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Util/ResourceHelperTests.cs
new file mode 100644
index 0000000000..88250ae050
--- /dev/null
+++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Util/ResourceHelperTests.cs
@@ -0,0 +1,67 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+using OpenTelemetry.AutoInstrumentation.Util;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+using Xunit;
+
+namespace OpenTelemetry.AutoInstrumentation.Tests.Util;
+
+public class ResourceHelperTests
+{
+ [Fact]
+ public void AggregateResources_AllProvidersNull_ReturnsEmptyResource()
+ {
+ var attributes1 = new Dictionary
+ {
+ { "service.name", "my-service" },
+ { "service.version", "1.0.0" }
+ };
+
+ var attributes2 = new Dictionary
+ {
+ { "service.name", "my-service-2" },
+ { "service.namespace", "my-namespace" }
+ };
+
+ var tracerProvider = Sdk
+ .CreateTracerProviderBuilder()
+ .ConfigureResource(resource =>
+ {
+ resource.Clear();
+ resource.AddAttributes(attributes1);
+ })
+ .Build();
+
+ var meterProvider = Sdk
+ .CreateMeterProviderBuilder()
+ .ConfigureResource(resource =>
+ {
+ resource.Clear();
+ resource.AddAttributes(attributes2);
+ })
+ .Build();
+
+ var resource = ResourceHelper.AggregateResources(tracerProvider, meterProvider);
+
+ Assert.Collection(
+ resource.Attributes,
+ attribute =>
+ {
+ Assert.Equal("service.name", attribute.Key);
+ Assert.Equal("my-service-2", attribute.Value);
+ },
+ attribute =>
+ {
+ Assert.Equal("service.namespace", attribute.Key);
+ Assert.Equal("my-namespace", attribute.Value);
+ },
+ attribute =>
+ {
+ Assert.Equal("service.version", attribute.Key);
+ Assert.Equal("1.0.0", attribute.Value);
+ });
+ }
+}