Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,7 @@ void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolut

if (metrics.Count > 0 &&
(metrics.Count == PowertoolsConfigurations.MaxMetrics ||
metrics.FirstOrDefault(x => x.Name == key)
?.Values.Count == PowertoolsConfigurations.MaxMetrics))
GetExistingMetric(metrics, key)?.Values.Count == PowertoolsConfigurations.MaxMetrics))
{
Instance.Flush(true);
}
Expand Down Expand Up @@ -536,9 +535,17 @@ private Dictionary<string, string> ListToDictionary(List<DimensionSet> dimension
var dictionary = new Dictionary<string, string>();
try
{
return dimensions != null
? new Dictionary<string, string>(dimensions.SelectMany(x => x.Dimensions))
: dictionary;
if (dimensions != null)
{
foreach (var dimensionSet in dimensions)
{
foreach (var kvp in dimensionSet.Dimensions)
{
dictionary[kvp.Key] = kvp.Value;
}
}
}
return dictionary;
}
catch (Exception e)
{
Expand Down Expand Up @@ -624,6 +631,49 @@ public static void Flush(bool metricsOverflow = false)
Instance.Flush(metricsOverflow);
}

/// <summary>
/// Safely searches for an existing metric by name without using LINQ enumeration
/// </summary>
/// <param name="metrics">The metrics collection to search</param>
/// <param name="key">The metric name to search for</param>
/// <returns>The found metric or null if not found</returns>
private static MetricDefinition GetExistingMetric(List<MetricDefinition> metrics, string key)
{
// Use a traditional for loop instead of LINQ to avoid enumeration issues
// when the collection is modified concurrently
if (metrics == null || string.IsNullOrEmpty(key))
return null;

// Create a snapshot of the count to avoid issues with concurrent modifications
var count = metrics.Count;
for (int i = 0; i < count; i++)
{
try
{
// Check bounds again in case collection was modified
if (i >= metrics.Count)
break;

var metric = metrics[i];
if (metric != null && string.Equals(metric.Name, key, StringComparison.Ordinal))
{
return metric;
}
}
catch (ArgumentOutOfRangeException)
{
// Collection was modified during iteration, return null to be safe
break;
}
catch (IndexOutOfRangeException)
{
// Collection was modified during iteration, return null to be safe
break;
}
}
return null;
}

/// <summary>
/// Helper method for testing purposes. Clears static instance between test execution
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,16 @@ public DimensionSet(string key, string value)
/// Gets the dimension keys.
/// </summary>
/// <value>The dimension keys.</value>
public List<string> DimensionKeys => Dimensions.Keys.ToList();
public List<string> DimensionKeys
{
get
{
var keys = new List<string>();
foreach (var key in Dimensions.Keys)
{
keys.Add(key);
}
return keys;
}
}
}
21 changes: 16 additions & 5 deletions libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text.Json.Serialization;

Expand All @@ -15,7 +16,7 @@ public class Metadata
public Metadata()
{
CloudWatchMetrics = new List<MetricDirective> { new() };
CustomMetadata = new Dictionary<string, object>();
CustomMetadata = new ConcurrentDictionary<string, object>();
}

/// <summary>
Expand Down Expand Up @@ -43,14 +44,17 @@ public Metadata()
/// </summary>
/// <value>The custom metadata.</value>
[JsonIgnore]
public Dictionary<string, object> CustomMetadata { get; }
public ConcurrentDictionary<string, object> CustomMetadata { get; }

/// <summary>
/// Deletes all metrics from memory
/// </summary>
internal void ClearMetrics()
{
_metricDirective.Metrics.Clear();
lock (_metricDirective._lockObj)
{
_metricDirective.Metrics.Clear();
}
CustomMetadata?.Clear();
}

Expand All @@ -59,7 +63,10 @@ internal void ClearMetrics()
/// </summary>
internal void ClearNonDefaultDimensions()
{
_metricDirective.Dimensions.Clear();
lock (_metricDirective._lockObj)
{
_metricDirective.Dimensions.Clear();
}
}

/// <summary>
Expand Down Expand Up @@ -143,7 +150,11 @@ internal void SetDefaultDimensions(List<DimensionSet> defaultDimensionSets)
/// <returns>List of metrics stored in memory</returns>
internal List<MetricDefinition> GetMetrics()
{
return _metricDirective.Metrics;
// Return a snapshot to avoid concurrent modification issues during serialization
lock (_metricDirective._lockObj)
{
return new List<MetricDefinition>(_metricDirective.Metrics);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public MetricDefinition(string name, MetricUnit unit, List<double> values, Metri
/// <param name="value">Metric value to add to existing key</param>
public void AddValue(double value)
{
Values.Add(value);
lock (Values)
{
Values.Add(value);
}
}
}
Loading
Loading