Skip to content

Change MetricFamily dictionary key from 32 to 64 bits hash to reduce collisions #342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 19, 2025
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
12 changes: 6 additions & 6 deletions src/Prometheus.Client/LabelsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ int GetTupleSize(Type tupleType)
#endif
}

public static int GetHashCode<TTuple>(TTuple values)
public static int GetHashCode<TTuple>(TTuple values, int seed = 0)
#if NET6_0_OR_GREATER
where TTuple : struct, ITuple, IEquatable<TTuple>
#else
where TTuple : struct, IEquatable<TTuple>
#endif
{
return TupleHelper<TTuple>.GetTupleHashCode(values);
return TupleHelper<TTuple>.GetTupleHashCode(values, seed);
}

public static int GetHashCode(IReadOnlyList<string> values)
public static int GetHashCode(IReadOnlyList<string> values, int seed = 0)
{
var result = 0;
var result = seed;

// ReSharper disable once ForCanBeConvertedToForeach
// do not use for-each here, it allocates which is easy to avoid by for loop
Expand Down Expand Up @@ -260,9 +260,9 @@ public static string[] ToArray(TTuple values)
});
}

public static int GetTupleHashCode(TTuple values)
public static int GetTupleHashCode(TTuple values, int seed)
{
return _hashCodeReducer(values, 0, (item, _, aggregated) =>
return _hashCodeReducer(values, seed, (item, _, aggregated) =>
{
if (item == null)
throw new ArgumentException("Label value cannot be empty");
Expand Down
40 changes: 34 additions & 6 deletions src/Prometheus.Client/MetricFamily.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public sealed class MetricFamily<TMetric, TImplementation, TLabels, TConfig> : I
private readonly IReadOnlyList<string> _metricNames;
private readonly Func<TConfig, IReadOnlyList<string>, TImplementation> _instanceFactory;
private readonly Lazy<TImplementation> _unlabelled;
private readonly ConcurrentDictionary<int, TImplementation> _labelledMetrics;
private readonly ConcurrentDictionary<MetricKey, TImplementation> _labelledMetrics;

public MetricFamily(TConfig configuration, MetricType metricType, Func<TConfig, IReadOnlyList<string>, TImplementation> instanceFactory)
{
Expand All @@ -35,7 +35,7 @@ public MetricFamily(TConfig configuration, MetricType metricType, Func<TConfig,
_unlabelled = new Lazy<TImplementation>(() => _instanceFactory(_configuration, default));
LabelNames = LabelsHelper.FromArray<TLabels>(configuration.LabelNames);
if (configuration.LabelNames.Count > 0)
_labelledMetrics = new ConcurrentDictionary<int, TImplementation>();
_labelledMetrics = new ConcurrentDictionary<MetricKey, TImplementation>();
}

public string Name => _configuration.Name;
Expand Down Expand Up @@ -64,7 +64,7 @@ TMetric IMetricFamily<TMetric>.WithLabels(params string[] labels)
if (labels.Length != _configuration.LabelNames.Count)
throw new ArgumentException("Wrong number of labels");

var key = LabelsHelper.GetHashCode(labels);
var key = new MetricKey(labels);

if (_labelledMetrics.TryGetValue(key, out var metric))
{
Expand All @@ -83,7 +83,7 @@ TMetric IMetricFamily<TMetric>.RemoveLabelled(params string[] labels)
if (labels.Length != _configuration.LabelNames.Count)
throw new ArgumentException("Wrong number of labels");

var key = LabelsHelper.GetHashCode(labels);
var key = new MetricKey(labels);
_labelledMetrics.TryRemove(key, out var removed);

return removed;
Expand All @@ -94,7 +94,7 @@ public TMetric WithLabels(TLabels labels)
if (_labelledMetrics == null)
throw new InvalidOperationException("Metric family does not have any labels");

var key = LabelsHelper.GetHashCode(labels);
var key = new MetricKey(labels);

if (_labelledMetrics.TryGetValue(key, out var metric))
{
Expand All @@ -110,7 +110,7 @@ public TMetric RemoveLabelled(TLabels labels)
if (_labelledMetrics == null)
throw new InvalidOperationException("Metric family does not have any labels");

var key = LabelsHelper.GetHashCode(labels);
var key = new MetricKey(labels);
_labelledMetrics.TryRemove(key, out var removed);

return removed;
Expand Down Expand Up @@ -148,4 +148,32 @@ private IEnumerable<KeyValuePair<IReadOnlyList<string>, TMetric>> EnumerateLabel
foreach (var labelled in _labelledMetrics)
yield return new KeyValuePair<IReadOnlyList<string>, TMetric>(labelled.Value.LabelValues, labelled.Value);
}

private readonly struct MetricKey : IEquatable<MetricKey>
{
private readonly int _hash1;
private readonly int _hash2;

public MetricKey(TLabels labels)
{
_hash1 = LabelsHelper.GetHashCode(labels, 0);
_hash2 = LabelsHelper.GetHashCode(labels, 42);
}

public MetricKey(params string[] labels)
{
_hash1 = LabelsHelper.GetHashCode(labels, 0);
_hash2 = LabelsHelper.GetHashCode(labels, 42);
}

public override int GetHashCode()
{
return _hash1;
}

public bool Equals(MetricKey other)
{
return _hash1 == other._hash1 && _hash2 == other._hash2;
}
}
}