diff --git a/src/Sentry/MeasurementUnit.cs b/src/Sentry/MeasurementUnit.cs
index 48fe7452e5..a7f370d18d 100644
--- a/src/Sentry/MeasurementUnit.cs
+++ b/src/Sentry/MeasurementUnit.cs
@@ -6,18 +6,80 @@ namespace Sentry;
///
public readonly partial struct MeasurementUnit : IEquatable
{
- private readonly Enum? _unit;
+ private readonly UnitKind _kind;
+ private readonly int _value;
private readonly string? _name;
- private MeasurementUnit(Enum unit)
+ private enum UnitKind : byte
{
- _unit = unit;
+ None = 0,
+ Duration = 1,
+ Information = 2,
+ Fraction = 3,
+ Custom = 4
+ }
+
+ private static readonly string[] DurationNames =
+ [
+ "nanosecond",
+ "microsecond",
+ "millisecond",
+ "second",
+ "minute",
+ "hour",
+ "day",
+ "week"
+ ];
+
+ private static readonly string[] InformationNames =
+ [
+ "bit",
+ "byte",
+ "kilobyte",
+ "kibibyte",
+ "megabyte",
+ "mebibyte",
+ "gigabyte",
+ "gibibyte",
+ "terabyte",
+ "tebibyte",
+ "petabyte",
+ "pebibyte",
+ "exabyte",
+ "exbibyte"
+ ];
+
+ private static readonly string[] FractionNames =
+ [
+ "ratio",
+ "percent"
+ ];
+
+ private MeasurementUnit(Duration unit)
+ {
+ _kind = UnitKind.Duration;
+ _value = (int)unit;
+ _name = null;
+ }
+
+ private MeasurementUnit(Information unit)
+ {
+ _kind = UnitKind.Information;
+ _value = (int)unit;
+ _name = null;
+ }
+
+ private MeasurementUnit(Fraction unit)
+ {
+ _kind = UnitKind.Fraction;
+ _value = (int)unit;
_name = null;
}
private MeasurementUnit(string name)
{
- _unit = null;
+ _kind = UnitKind.Custom;
+ _value = default;
_name = name;
}
@@ -70,21 +132,32 @@ internal static MeasurementUnit Parse(string? name)
return Custom(name);
}
+ private string? GetPredefinedName()
+ {
+ return _kind switch
+ {
+ UnitKind.Duration when (uint)_value < (uint)DurationNames.Length => DurationNames[_value],
+ UnitKind.Information when (uint)_value < (uint)InformationNames.Length => InformationNames[_value],
+ UnitKind.Fraction when (uint)_value < (uint)FractionNames.Length => FractionNames[_value],
+ _ => null
+ };
+ }
+
///
/// Returns the string representation of the measurement unit, as it will be sent to Sentry.
///
- public override string ToString() => _unit?.ToString().ToLowerInvariant() ?? _name ?? "";
+ public override string ToString() => ToNullableString() ?? "";
- internal string? ToNullableString() => _unit?.ToString().ToLowerInvariant() ?? _name;
+ internal string? ToNullableString() => _name ?? GetPredefinedName();
///
- public bool Equals(MeasurementUnit other) => Equals(_unit, other._unit) && _name == other._name;
+ public bool Equals(MeasurementUnit other) => _kind == other._kind && _value == other._value && _name == other._name;
///
public override bool Equals(object? obj) => obj is MeasurementUnit other && Equals(other);
///
- public override int GetHashCode() => HashCode.Combine(_unit, _name, _unit?.GetType());
+ public override int GetHashCode() => HashCode.Combine(_kind, _value, _name);
///
/// Returns true if the operands are equal.
diff --git a/test/Sentry.Tests/MeasurementUnitTests.cs b/test/Sentry.Tests/MeasurementUnitTests.cs
index 64fca21525..dbf105451d 100644
--- a/test/Sentry.Tests/MeasurementUnitTests.cs
+++ b/test/Sentry.Tests/MeasurementUnitTests.cs
@@ -1,5 +1,7 @@
namespace Sentry.Tests;
+using System.Globalization;
+
public class MeasurementUnitTests
{
[Fact]
@@ -126,4 +128,40 @@ public void MixedInequalityWithCustom()
var m2 = MeasurementUnit.Duration.Second;
Assert.NotEqual(m1, m2);
}
+
+ [Fact]
+ public void DurationUnits_ToNullableString_MatchesLowercaseEnumNames()
+ {
+ foreach (var raw in Enum.GetValues(typeof(MeasurementUnit.Duration)))
+ {
+ var value = (MeasurementUnit.Duration)raw;
+ MeasurementUnit unit = (MeasurementUnit.Duration)raw;
+ var expected = value.ToString().ToLower(CultureInfo.InvariantCulture);
+ Assert.Equal(expected, unit.ToNullableString());
+ }
+ }
+
+ [Fact]
+ public void InformationUnits_ToNullableString_MatchesLowercaseEnumNames()
+ {
+ foreach (var raw in Enum.GetValues(typeof(MeasurementUnit.Information)))
+ {
+ var value = (MeasurementUnit.Information)raw;
+ MeasurementUnit unit = (MeasurementUnit.Information)raw;
+ var expected = value.ToString().ToLower(CultureInfo.InvariantCulture);
+ Assert.Equal(expected, unit.ToNullableString());
+ }
+ }
+
+ [Fact]
+ public void FractionUnits_ToNullableString_MatchesLowercaseEnumNames()
+ {
+ foreach (var raw in Enum.GetValues(typeof(MeasurementUnit.Fraction)))
+ {
+ var value = (MeasurementUnit.Fraction)raw;
+ MeasurementUnit unit = (MeasurementUnit.Fraction)raw;
+ var expected = value.ToString().ToLower(CultureInfo.InvariantCulture);
+ Assert.Equal(expected, unit.ToNullableString());
+ }
+ }
}