Skip to content

Commit ce8c21e

Browse files
[OpenTelemetry.Instrumentation.AWS] Fix Memory Leak by Reusing ActivitySources, Meters, and Instruments (open-telemetry#2039)
1 parent f0a5a6e commit ce8c21e

File tree

9 files changed

+92
-23
lines changed

9 files changed

+92
-23
lines changed

src/OpenTelemetry.Instrumentation.AWS/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
* Fix Memory Leak by Reusing ActivitySources, Meters, and Instruments
6+
([#2039](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2039))
57
* Added instrumentation support for AWS Bedrock, BedrockRuntime, BedrockAgent, BedrockAgentRuntime.
68
([#1979](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1979))
79

src/OpenTelemetry.Instrumentation.AWS/Implementation/Metrics/AWSHistogram.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Collections.Concurrent;
45
using Amazon.Runtime.Telemetry;
56
using Amazon.Runtime.Telemetry.Metrics;
67

@@ -9,11 +10,25 @@ namespace OpenTelemetry.Instrumentation.AWS.Implementation.Metrics;
910
internal sealed class AWSHistogram<T> : Histogram<T>
1011
where T : struct
1112
{
13+
private static readonly ConcurrentDictionary<string, System.Diagnostics.Metrics.Histogram<T>> HistogramsDictionary
14+
= new ConcurrentDictionary<string, System.Diagnostics.Metrics.Histogram<T>>();
15+
1216
private readonly System.Diagnostics.Metrics.Histogram<T> histogram;
1317

14-
public AWSHistogram(System.Diagnostics.Metrics.Histogram<T> histogram)
18+
public AWSHistogram(
19+
System.Diagnostics.Metrics.Meter meter,
20+
string name,
21+
string? units = null,
22+
string? description = null)
1523
{
16-
this.histogram = histogram;
24+
if (HistogramsDictionary.TryGetValue(name, out System.Diagnostics.Metrics.Histogram<T>? histogram))
25+
{
26+
this.histogram = histogram;
27+
}
28+
29+
this.histogram = HistogramsDictionary.GetOrAdd(
30+
name,
31+
meter.CreateHistogram<T>(name, units, description));
1732
}
1833

1934
public override void Record(T value, Attributes? attributes = null)

src/OpenTelemetry.Instrumentation.AWS/Implementation/Metrics/AWSMeter.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ public override UpDownCounter<T> CreateUpDownCounter<T>(
2424
string? description = null)
2525
where T : struct
2626
{
27-
var upDownCounter = this.meter.CreateUpDownCounter<T>(name, units, description);
28-
return new AWSUpDownCounter<T>(upDownCounter);
27+
return new AWSUpDownCounter<T>(this.meter, name, units, description);
2928
}
3029

3130
public override MonotonicCounter<T> CreateMonotonicCounter<T>(
@@ -34,8 +33,7 @@ public override MonotonicCounter<T> CreateMonotonicCounter<T>(
3433
string? description = null)
3534
where T : struct
3635
{
37-
var counter = this.meter.CreateCounter<T>(name, units, description);
38-
return new AWSMonotonicCounter<T>(counter);
36+
return new AWSMonotonicCounter<T>(this.meter, name, units, description);
3937
}
4038

4139
public override Histogram<T> CreateHistogram<T>(
@@ -44,8 +42,7 @@ public override Histogram<T> CreateHistogram<T>(
4442
string? description = null)
4543
where T : struct
4644
{
47-
var histogram = this.meter.CreateHistogram<T>(name, units, description);
48-
return new AWSHistogram<T>(histogram);
45+
return new AWSHistogram<T>(this.meter, name, units, description);
4946
}
5047

5148
protected override void Dispose(bool disposing)
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Collections.Concurrent;
45
using Amazon.Runtime.Telemetry;
56
using Amazon.Runtime.Telemetry.Metrics;
67

78
namespace OpenTelemetry.Instrumentation.AWS.Implementation.Metrics;
89

910
internal sealed class AWSMeterProvider : MeterProvider
1011
{
12+
private static readonly ConcurrentDictionary<string, AWSMeter> MetersDictionary = new ConcurrentDictionary<string, AWSMeter>();
13+
1114
public override Meter GetMeter(string scope, Attributes? attributes = null)
1215
{
1316
// Passing attributes to the Meter is currently not possible due to version limitations
@@ -17,7 +20,15 @@ public override Meter GetMeter(string scope, Attributes? attributes = null)
1720
// update OpenTelemetry core component version(s) to `1.9.0` and allow passing tags to
1821
// the meter constructor.
1922

20-
var meter = new System.Diagnostics.Metrics.Meter(scope);
21-
return new AWSMeter(meter);
23+
if (MetersDictionary.TryGetValue(scope, out AWSMeter? meter))
24+
{
25+
return meter;
26+
}
27+
28+
var awsMeter = MetersDictionary.GetOrAdd(
29+
scope,
30+
new AWSMeter(new System.Diagnostics.Metrics.Meter(scope)));
31+
32+
return awsMeter;
2233
}
2334
}
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Collections.Concurrent;
45
using Amazon.Runtime.Telemetry;
56
using Amazon.Runtime.Telemetry.Metrics;
67

@@ -9,23 +10,37 @@ namespace OpenTelemetry.Instrumentation.AWS.Implementation.Metrics;
910
internal sealed class AWSMonotonicCounter<T> : MonotonicCounter<T>
1011
where T : struct
1112
{
12-
private readonly System.Diagnostics.Metrics.Counter<T> counter;
13+
private static readonly ConcurrentDictionary<string, System.Diagnostics.Metrics.Counter<T>> MonotonicCountersDictionary
14+
= new ConcurrentDictionary<string, System.Diagnostics.Metrics.Counter<T>>();
1315

14-
public AWSMonotonicCounter(System.Diagnostics.Metrics.Counter<T> counter)
16+
private readonly System.Diagnostics.Metrics.Counter<T> monotonicCounter;
17+
18+
public AWSMonotonicCounter(
19+
System.Diagnostics.Metrics.Meter meter,
20+
string name,
21+
string? units = null,
22+
string? description = null)
1523
{
16-
this.counter = counter;
24+
if (MonotonicCountersDictionary.TryGetValue(name, out System.Diagnostics.Metrics.Counter<T>? monotonicCounter))
25+
{
26+
this.monotonicCounter = monotonicCounter;
27+
}
28+
29+
this.monotonicCounter = MonotonicCountersDictionary.GetOrAdd(
30+
name,
31+
meter.CreateCounter<T>(name, units, description));
1732
}
1833

1934
public override void Add(T value, Attributes? attributes = null)
2035
{
2136
if (attributes != null)
2237
{
2338
// TODO: remove ToArray call and use when AttributesAsSpan expected to be added at AWS SDK v4.
24-
this.counter.Add(value, attributes.AllAttributes.ToArray());
39+
this.monotonicCounter.Add(value, attributes.AllAttributes.ToArray());
2540
}
2641
else
2742
{
28-
this.counter.Add(value);
43+
this.monotonicCounter.Add(value);
2944
}
3045
}
3146
}

src/OpenTelemetry.Instrumentation.AWS/Implementation/Metrics/AWSUpDownCounter.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Collections.Concurrent;
45
using Amazon.Runtime.Telemetry;
56
using Amazon.Runtime.Telemetry.Metrics;
67

@@ -9,11 +10,25 @@ namespace OpenTelemetry.Instrumentation.AWS.Implementation.Metrics;
910
internal sealed class AWSUpDownCounter<T> : UpDownCounter<T>
1011
where T : struct
1112
{
13+
private static readonly ConcurrentDictionary<string, System.Diagnostics.Metrics.UpDownCounter<T>> UpDownCountersDictionary
14+
= new ConcurrentDictionary<string, System.Diagnostics.Metrics.UpDownCounter<T>>();
15+
1216
private readonly System.Diagnostics.Metrics.UpDownCounter<T> upDownCounter;
1317

14-
public AWSUpDownCounter(System.Diagnostics.Metrics.UpDownCounter<T> upDownCounter)
18+
public AWSUpDownCounter(
19+
System.Diagnostics.Metrics.Meter meter,
20+
string name,
21+
string? units = null,
22+
string? description = null)
1523
{
16-
this.upDownCounter = upDownCounter;
24+
if (UpDownCountersDictionary.TryGetValue(name, out System.Diagnostics.Metrics.UpDownCounter<T>? upDownCounter))
25+
{
26+
this.upDownCounter = upDownCounter;
27+
}
28+
29+
this.upDownCounter = UpDownCountersDictionary.GetOrAdd(
30+
name,
31+
meter.CreateUpDownCounter<T>(name, units, description));
1732
}
1833

1934
public override void Add(T value, Attributes? attributes = null)

src/OpenTelemetry.Instrumentation.AWS/Implementation/Tracing/AWSTracer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ internal sealed class AWSTracer : Tracer
1414
/// <summary>
1515
/// Initializes a new instance of the <see cref="AWSTracer"/> class.
1616
/// </summary>
17-
/// <param name="scope">The name of the instrumentation scope that uniquely identifies the tracer.</param>
18-
public AWSTracer(string scope)
17+
/// <param name="activitySource">The ActivitySource used for creating and tracking the activities.</param>
18+
public AWSTracer(ActivitySource activitySource)
1919
{
20-
this.activitySource = new ActivitySource(scope);
20+
this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource));
2121
}
2222

2323
public override TraceSpan CreateSpan(
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using System.Collections.Concurrent;
5+
using System.Diagnostics;
46
using Amazon.Runtime.Telemetry.Tracing;
57

68
namespace OpenTelemetry.Instrumentation.AWS.Implementation.Tracing;
79

810
internal sealed class AWSTracerProvider : TracerProvider
911
{
12+
private static readonly ConcurrentDictionary<string, AWSTracer> TracersDictionary = new ConcurrentDictionary<string, AWSTracer>();
13+
1014
public override Tracer GetTracer(string scope)
1115
{
12-
return new AWSTracer(scope);
16+
if (TracersDictionary.TryGetValue(scope, out AWSTracer? awsTracer))
17+
{
18+
return awsTracer;
19+
}
20+
21+
awsTracer = TracersDictionary.GetOrAdd(
22+
scope,
23+
new AWSTracer(new ActivitySource(scope)));
24+
25+
return awsTracer;
1326
}
1427
}

test/OpenTelemetry.Instrumentation.AWS.Tests/TestAWSClientMetricsInstrumentation.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,16 @@ public void TestAWSUpDownCounterIsntCalledAfterMeterDispose()
186186

187187
var countAmount = 7;
188188
var counterName = "TestCounter";
189-
var meter = AWSConfigs.TelemetryProvider.MeterProvider.GetMeter($"{TelemetryConstants.TelemetryScopePrefix}.TestDisposedMeter");
189+
var meterName = $"{TelemetryConstants.TelemetryScopePrefix}.TestDisposedMeter";
190+
var meter = AWSConfigs.TelemetryProvider.MeterProvider.GetMeter(meterName);
190191
var counter = meter.CreateUpDownCounter<long>(counterName);
191192

192193
meter.Dispose();
193194
counter.Add(countAmount);
194195

195196
meterProvider.ForceFlush();
196197

197-
var counterMetric = exportedItems.FirstOrDefault(i => i.Name == counterName);
198+
var counterMetric = exportedItems.FirstOrDefault(i => i.MeterName == meterName && i.Name == counterName);
198199
Assert.Null(counterMetric);
199200
}
200201

0 commit comments

Comments
 (0)