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()); + } + } }