Skip to content

Commit 85c5d33

Browse files
authored
Merge pull request #342 from ogxd/32bit-collisions
Change MetricFamily dictionary key from 32 to 64 bits hash to reduce collisions
2 parents 657c476 + a86aa94 commit 85c5d33

File tree

2 files changed

+40
-12
lines changed

2 files changed

+40
-12
lines changed

src/Prometheus.Client/LabelsHelper.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,19 @@ int GetTupleSize(Type tupleType)
6161
#endif
6262
}
6363

64-
public static int GetHashCode<TTuple>(TTuple values)
64+
public static int GetHashCode<TTuple>(TTuple values, int seed = 0)
6565
#if NET6_0_OR_GREATER
6666
where TTuple : struct, ITuple, IEquatable<TTuple>
6767
#else
6868
where TTuple : struct, IEquatable<TTuple>
6969
#endif
7070
{
71-
return TupleHelper<TTuple>.GetTupleHashCode(values);
71+
return TupleHelper<TTuple>.GetTupleHashCode(values, seed);
7272
}
7373

74-
public static int GetHashCode(IReadOnlyList<string> values)
74+
public static int GetHashCode(IReadOnlyList<string> values, int seed = 0)
7575
{
76-
var result = 0;
76+
var result = seed;
7777

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

263-
public static int GetTupleHashCode(TTuple values)
263+
public static int GetTupleHashCode(TTuple values, int seed)
264264
{
265-
return _hashCodeReducer(values, 0, (item, _, aggregated) =>
265+
return _hashCodeReducer(values, seed, (item, _, aggregated) =>
266266
{
267267
if (item == null)
268268
throw new ArgumentException("Label value cannot be empty");

src/Prometheus.Client/MetricFamily.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public sealed class MetricFamily<TMetric, TImplementation, TLabels, TConfig> : I
2424
private readonly IReadOnlyList<string> _metricNames;
2525
private readonly Func<TConfig, IReadOnlyList<string>, TImplementation> _instanceFactory;
2626
private readonly Lazy<TImplementation> _unlabelled;
27-
private readonly ConcurrentDictionary<int, TImplementation> _labelledMetrics;
27+
private readonly ConcurrentDictionary<MetricKey, TImplementation> _labelledMetrics;
2828

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

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

67-
var key = LabelsHelper.GetHashCode(labels);
67+
var key = new MetricKey(labels);
6868

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

86-
var key = LabelsHelper.GetHashCode(labels);
86+
var key = new MetricKey(labels);
8787
_labelledMetrics.TryRemove(key, out var removed);
8888

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

97-
var key = LabelsHelper.GetHashCode(labels);
97+
var key = new MetricKey(labels);
9898

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

113-
var key = LabelsHelper.GetHashCode(labels);
113+
var key = new MetricKey(labels);
114114
_labelledMetrics.TryRemove(key, out var removed);
115115

116116
return removed;
@@ -148,4 +148,32 @@ private IEnumerable<KeyValuePair<IReadOnlyList<string>, TMetric>> EnumerateLabel
148148
foreach (var labelled in _labelledMetrics)
149149
yield return new KeyValuePair<IReadOnlyList<string>, TMetric>(labelled.Value.LabelValues, labelled.Value);
150150
}
151+
152+
private readonly struct MetricKey : IEquatable<MetricKey>
153+
{
154+
private readonly int _hash1;
155+
private readonly int _hash2;
156+
157+
public MetricKey(TLabels labels)
158+
{
159+
_hash1 = LabelsHelper.GetHashCode(labels, 0);
160+
_hash2 = LabelsHelper.GetHashCode(labels, 42);
161+
}
162+
163+
public MetricKey(params string[] labels)
164+
{
165+
_hash1 = LabelsHelper.GetHashCode(labels, 0);
166+
_hash2 = LabelsHelper.GetHashCode(labels, 42);
167+
}
168+
169+
public override int GetHashCode()
170+
{
171+
return _hash1;
172+
}
173+
174+
public bool Equals(MetricKey other)
175+
{
176+
return _hash1 == other._hash1 && _hash2 == other._hash2;
177+
}
178+
}
151179
}

0 commit comments

Comments
 (0)