Skip to content

Commit 8bc2e51

Browse files
authored
feat: Metrics.Set now accepts string as value (#3092)
1 parent bc834f1 commit 8bc2e51

13 files changed

+385
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ If you have conflicts, you can opt-out by adding the following to your `csproj`:
1818

1919
### Features
2020

21+
- `SentrySdk.Metrics.Set` now additionally accepts `string` as value ([#3092](https://github.com/getsentry/sentry-dotnet/pull/3092))
2122
- Timing metrics can now be captured with `SentrySdk.Metrics.StartTimer` ([#3075](https://github.com/getsentry/sentry-dotnet/pull/3075))
2223

2324
### Fixes

src/Sentry/DisabledMetricAggregator.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ public void Set(string key, int value, MeasurementUnit? unit = null,
3030
// No Op
3131
}
3232

33+
public void Set(string key, string value, MeasurementUnit? unit = null,
34+
IDictionary<string, string>? tags = null,
35+
DateTimeOffset? timestamp = null, int stackLevel = 1)
36+
{
37+
// No Op
38+
}
39+
3340
public void Timing(string key, double value, MeasurementUnit.Duration unit = MeasurementUnit.Duration.Second,
3441
IDictionary<string, string>? tags = null,
3542
DateTimeOffset? timestamp = null, int stackLevel = 1)
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Namespace starting with Sentry makes sure the SDK cuts frames off before reporting
2+
namespace Sentry.Force.Crc32
3+
{
4+
/// <summary>
5+
/// Implementation of CRC-32.
6+
/// This class supports several convenient static methods returning the CRC as UInt32.
7+
/// </summary>
8+
internal class Crc32Algorithm : HashAlgorithm
9+
{
10+
private uint _currentCrc;
11+
12+
private readonly bool _isBigEndian = true;
13+
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="Crc32Algorithm"/> class.
16+
/// </summary>
17+
public Crc32Algorithm()
18+
{
19+
#if !NETCORE13
20+
HashSizeValue = 32;
21+
#endif
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="Crc32Algorithm"/> class.
26+
/// </summary>
27+
/// <param name="isBigEndian">Should return bytes result as big endian or little endian</param>
28+
// Crc32 by dariogriffo uses big endian, so, we need to be compatible and return big endian as default
29+
public Crc32Algorithm(bool isBigEndian = true)
30+
: this()
31+
{
32+
_isBigEndian = isBigEndian;
33+
}
34+
35+
/// <summary>
36+
/// Computes CRC-32 from multiple buffers.
37+
/// Call this method multiple times to chain multiple buffers.
38+
/// </summary>
39+
/// <param name="initial">
40+
/// Initial CRC value for the algorithm. It is zero for the first buffer.
41+
/// Subsequent buffers should have their initial value set to CRC value returned by previous call to this method.
42+
/// </param>
43+
/// <param name="input">Input buffer with data to be checksummed.</param>
44+
/// <param name="offset">Offset of the input data within the buffer.</param>
45+
/// <param name="length">Length of the input data in the buffer.</param>
46+
/// <returns>Accumulated CRC-32 of all buffers processed so far.</returns>
47+
public static uint Append(uint initial, byte[] input, int offset, int length)
48+
{
49+
if (input == null)
50+
throw new ArgumentNullException("input");
51+
if (offset < 0 || length < 0 || offset + length > input.Length)
52+
throw new ArgumentOutOfRangeException("length");
53+
return AppendInternal(initial, input, offset, length);
54+
}
55+
56+
/// <summary>
57+
/// Computes CRC-32 from multiple buffers.
58+
/// Call this method multiple times to chain multiple buffers.
59+
/// </summary>
60+
/// <param name="initial">
61+
/// Initial CRC value for the algorithm. It is zero for the first buffer.
62+
/// Subsequent buffers should have their initial value set to CRC value returned by previous call to this method.
63+
/// </param>
64+
/// <param name="input">Input buffer containing data to be checksummed.</param>
65+
/// <returns>Accumulated CRC-32 of all buffers processed so far.</returns>
66+
public static uint Append(uint initial, byte[] input)
67+
{
68+
if (input == null)
69+
throw new ArgumentNullException();
70+
return AppendInternal(initial, input, 0, input.Length);
71+
}
72+
73+
/// <summary>
74+
/// Computes CRC-32 from input buffer.
75+
/// </summary>
76+
/// <param name="input">Input buffer with data to be checksummed.</param>
77+
/// <param name="offset">Offset of the input data within the buffer.</param>
78+
/// <param name="length">Length of the input data in the buffer.</param>
79+
/// <returns>CRC-32 of the data in the buffer.</returns>
80+
public static uint Compute(byte[] input, int offset, int length)
81+
{
82+
return Append(0, input, offset, length);
83+
}
84+
85+
/// <summary>
86+
/// Computes CRC-32 from input buffer.
87+
/// </summary>
88+
/// <param name="input">Input buffer containing data to be checksummed.</param>
89+
/// <returns>CRC-32 of the buffer.</returns>
90+
public static uint Compute(byte[] input)
91+
{
92+
return Append(0, input);
93+
}
94+
95+
/// <summary>
96+
/// Computes CRC-32 from input buffer and writes it after end of data (buffer should have 4 bytes reserved space for it). Can be used in conjunction with <see cref="IsValidWithCrcAtEnd(byte[],int,int)"/>
97+
/// </summary>
98+
/// <param name="input">Input buffer with data to be checksummed.</param>
99+
/// <param name="offset">Offset of the input data within the buffer.</param>
100+
/// <param name="length">Length of the input data in the buffer.</param>
101+
/// <returns>CRC-32 of the data in the buffer.</returns>
102+
public static uint ComputeAndWriteToEnd(byte[] input, int offset, int length)
103+
{
104+
if (length + 4 > input.Length)
105+
throw new ArgumentOutOfRangeException("length", "Length of data should be less than array length - 4 bytes of CRC data");
106+
var crc = Append(0, input, offset, length);
107+
var r = offset + length;
108+
input[r] = (byte)crc;
109+
input[r + 1] = (byte)(crc >> 8);
110+
input[r + 2] = (byte)(crc >> 16);
111+
input[r + 3] = (byte)(crc >> 24);
112+
return crc;
113+
}
114+
115+
/// <summary>
116+
/// Computes CRC-32 from input buffer - 4 bytes and writes it as last 4 bytes of buffer. Can be used in conjunction with <see cref="IsValidWithCrcAtEnd(byte[])"/>
117+
/// </summary>
118+
/// <param name="input">Input buffer with data to be checksummed.</param>
119+
/// <returns>CRC-32 of the data in the buffer.</returns>
120+
public static uint ComputeAndWriteToEnd(byte[] input)
121+
{
122+
if (input.Length < 4)
123+
throw new ArgumentOutOfRangeException("input", "Input array should be 4 bytes at least");
124+
return ComputeAndWriteToEnd(input, 0, input.Length - 4);
125+
}
126+
127+
/// <summary>
128+
/// Validates correctness of CRC-32 data in source buffer with assumption that CRC-32 data located at end of buffer in reverse bytes order. Can be used in conjunction with <see cref="ComputeAndWriteToEnd(byte[],int,int)"/>
129+
/// </summary>
130+
/// <param name="input">Input buffer with data to be checksummed.</param>
131+
/// <param name="offset">Offset of the input data within the buffer.</param>
132+
/// <param name="lengthWithCrc">Length of the input data in the buffer with CRC-32 bytes.</param>
133+
/// <returns>Is checksum valid.</returns>
134+
public static bool IsValidWithCrcAtEnd(byte[] input, int offset, int lengthWithCrc)
135+
{
136+
return Append(0, input, offset, lengthWithCrc) == 0x2144DF1C;
137+
}
138+
139+
/// <summary>
140+
/// Validates correctness of CRC-32 data in source buffer with assumption that CRC-32 data located at end of buffer in reverse bytes order. Can be used in conjunction with <see cref="ComputeAndWriteToEnd(byte[],int,int)"/>
141+
/// </summary>
142+
/// <param name="input">Input buffer with data to be checksummed.</param>
143+
/// <returns>Is checksum valid.</returns>
144+
public static bool IsValidWithCrcAtEnd(byte[] input)
145+
{
146+
if (input.Length < 4)
147+
throw new ArgumentOutOfRangeException("input", "Input array should be 4 bytes at least");
148+
return Append(0, input, 0, input.Length) == 0x2144DF1C;
149+
}
150+
151+
/// <summary>
152+
/// Resets internal state of the algorithm. Used internally.
153+
/// </summary>
154+
public override void Initialize()
155+
{
156+
_currentCrc = 0;
157+
}
158+
159+
/// <summary>
160+
/// Appends CRC-32 from given buffer
161+
/// </summary>
162+
protected override void HashCore(byte[] input, int offset, int length)
163+
{
164+
_currentCrc = AppendInternal(_currentCrc, input, offset, length);
165+
}
166+
167+
/// <summary>
168+
/// Computes CRC-32 from <see cref="HashCore"/>
169+
/// </summary>
170+
protected override byte[] HashFinal()
171+
{
172+
if (_isBigEndian)
173+
return new[] { (byte)(_currentCrc >> 24), (byte)(_currentCrc >> 16), (byte)(_currentCrc >> 8), (byte)_currentCrc };
174+
else
175+
return new[] { (byte)_currentCrc, (byte)(_currentCrc >> 8), (byte)(_currentCrc >> 16), (byte)(_currentCrc >> 24) };
176+
}
177+
178+
private static readonly SafeProxy _proxy = new SafeProxy();
179+
180+
private static uint AppendInternal(uint initial, byte[] input, int offset, int length)
181+
{
182+
if (length > 0)
183+
{
184+
return _proxy.Append(initial, input, offset, length);
185+
}
186+
else
187+
return initial;
188+
}
189+
}
190+
}

src/Sentry/Force.Crc32/LICENSE

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Copied from https://github.com/force-net/Crc32.NET/blob/26c5a818a5c7a3d6a622c92d3cd08dba586c263c/LICENSE
2+
3+
The MIT License (MIT)
4+
5+
Copyright (c) 2016 force
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.

src/Sentry/Force.Crc32/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Files from force's `Crc32.NET` at `26c5a818a5c7a3d6a622c92d3cd08dba586c263c` copied:
2+
3+
https://github.com/force-net/Crc32.NET/commit/26c5a818a5c7a3d6a622c92d3cd08dba586c263c
4+
5+
Sentry's core package's goal is to be dependency-free. Because of that we use different strategies of vendoring-in code.
6+
`Ben.Demystifier` for example comes in through a git submodule, and a commit changing all types to `internal` is added.
7+
8+
Here, since no changes were done to this project in years, and it's just a handful of files,
9+
we're directly copying and changing them.
10+
11+
Main changes:
12+
13+
* Make everything internal.

src/Sentry/Force.Crc32/SafeProxy.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* This is .NET safe implementation of Crc32 algorithm.
2+
* This implementation was investigated as fastest from different variants. It based on Robert Vazan native implementations of Crc32C
3+
* Also, it is good for x64 and for x86, so, it seems, there is no sense to do 2 different realizations.
4+
*
5+
* Addition: some speed increase was found with splitting xor to 4 independent blocks. Also, some attempts to optimize unaligned tails was unsuccessfull (JIT limitations?).
6+
*
7+
*
8+
* Max Vysokikh, 2016-2017
9+
*/
10+
11+
// Namespace starting with Sentry makes sure the SDK cuts frames off before reporting
12+
namespace Sentry.Force.Crc32
13+
{
14+
internal class SafeProxy
15+
{
16+
private const uint Poly = 0xedb88320u;
17+
18+
private readonly uint[] _table = new uint[16 * 256];
19+
20+
internal SafeProxy()
21+
{
22+
Init(Poly);
23+
}
24+
25+
protected void Init(uint poly)
26+
{
27+
var table = _table;
28+
for (uint i = 0; i < 256; i++)
29+
{
30+
uint res = i;
31+
for (int t = 0; t < 16; t++)
32+
{
33+
for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? poly ^ (res >> 1) : (res >> 1);
34+
table[(t * 256) + i] = res;
35+
}
36+
}
37+
}
38+
39+
public uint Append(uint crc, byte[] input, int offset, int length)
40+
{
41+
uint crcLocal = uint.MaxValue ^ crc;
42+
43+
uint[] table = _table;
44+
while (length >= 16)
45+
{
46+
var a = table[(3 * 256) + input[offset + 12]]
47+
^ table[(2 * 256) + input[offset + 13]]
48+
^ table[(1 * 256) + input[offset + 14]]
49+
^ table[(0 * 256) + input[offset + 15]];
50+
51+
var b = table[(7 * 256) + input[offset + 8]]
52+
^ table[(6 * 256) + input[offset + 9]]
53+
^ table[(5 * 256) + input[offset + 10]]
54+
^ table[(4 * 256) + input[offset + 11]];
55+
56+
var c = table[(11 * 256) + input[offset + 4]]
57+
^ table[(10 * 256) + input[offset + 5]]
58+
^ table[(9 * 256) + input[offset + 6]]
59+
^ table[(8 * 256) + input[offset + 7]];
60+
61+
var d = table[(15 * 256) + ((byte)crcLocal ^ input[offset])]
62+
^ table[(14 * 256) + ((byte)(crcLocal >> 8) ^ input[offset + 1])]
63+
^ table[(13 * 256) + ((byte)(crcLocal >> 16) ^ input[offset + 2])]
64+
^ table[(12 * 256) + ((crcLocal >> 24) ^ input[offset + 3])];
65+
66+
crcLocal = d ^ c ^ b ^ a;
67+
offset += 16;
68+
length -= 16;
69+
}
70+
71+
while (--length >= 0)
72+
crcLocal = table[(byte)(crcLocal ^ input[offset++])] ^ crcLocal >> 8;
73+
74+
return crcLocal ^ uint.MaxValue;
75+
}
76+
}
77+
}

src/Sentry/IMetricAggregator.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ void Set(string key,
7878
DateTimeOffset? timestamp = null,
7979
int stackLevel = 1);
8080

81+
/// <summary>
82+
/// Emits a Set metric a
83+
/// </summary>
84+
/// <param name="key">A unique key identifying the metric</param>
85+
/// <param name="value">The value to be added</param>
86+
/// <param name="unit">An optional <see cref="MeasurementUnit"/></param>
87+
/// <param name="tags">Optional Tags to associate with the metric</param>
88+
/// <param name="timestamp">
89+
/// The time when the metric was emitted. Defaults to the time at which the metric is emitted, if no value is provided.
90+
/// </param>
91+
/// <param name="stackLevel">Optional number of stacks levels to ignore when determining the code location</param>
92+
void Set(string key,
93+
string value,
94+
MeasurementUnit? unit = null,
95+
IDictionary<string, string>? tags = null,
96+
DateTimeOffset? timestamp = null,
97+
int stackLevel = 1);
98+
8199
/// <summary>
82100
/// Emits a distribution with the time it takes to run a given code block.
83101
/// </summary>

src/Sentry/MetricAggregator.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Sentry.Extensibility;
2+
using Sentry.Force.Crc32;
23
using Sentry.Internal;
34
using Sentry.Internal.Extensions;
45
using Sentry.Protocol.Metrics;
@@ -132,14 +133,29 @@ public void Distribution(string key,
132133
DateTimeOffset? timestamp = null,
133134
int stackLevel = 1) => Emit(MetricType.Distribution, key, value, unit, tags, timestamp, stackLevel + 1);
134135

135-
/// <inheritdoc cref="IMetricAggregator.Set"/>
136+
/// <inheritdoc cref="IMetricAggregator.Set(string,int,MeasurementUnit?,System.Collections.Generic.IDictionary{string,string},DateTimeOffset?,int)"/>
136137
public void Set(string key,
137138
int value,
138139
MeasurementUnit? unit = null,
139140
IDictionary<string, string>? tags = null,
140141
DateTimeOffset? timestamp = null,
141142
int stackLevel = 1) => Emit(MetricType.Set, key, value, unit, tags, timestamp, stackLevel + 1);
142143

144+
/// <inheritdoc cref="IMetricAggregator.Set(string,string,MeasurementUnit?,System.Collections.Generic.IDictionary{string,string},DateTimeOffset?,int)"/>
145+
public void Set(string key,
146+
string value,
147+
MeasurementUnit? unit = null,
148+
IDictionary<string, string>? tags = null,
149+
DateTimeOffset? timestamp = null,
150+
int stackLevel = 1)
151+
{
152+
// Compute the CRC32 hash of the value as byte array and cast it to a 32-bit signed integer
153+
// Mask the lower 32 bits to ensure the result fits within the 32-bit integer range
154+
var hash = (int)(Crc32Algorithm.Compute(Encoding.UTF8.GetBytes(value)) & 0xFFFFFFFF);
155+
156+
Emit(MetricType.Set, key, hash, unit, tags, timestamp, stackLevel + 1);
157+
}
158+
143159
/// <inheritdoc cref="IMetricAggregator.Timing"/>
144160
public void Timing(string key,
145161
double value,

test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ namespace Sentry
267267
void Gauge(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary<string, string>? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1);
268268
void Increment(string key, double value = 1, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary<string, string>? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1);
269269
void Set(string key, int value, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary<string, string>? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1);
270+
void Set(string key, string value, Sentry.MeasurementUnit? unit = default, System.Collections.Generic.IDictionary<string, string>? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1);
270271
System.IDisposable StartTimer(string key, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary<string, string>? tags = null, int stackLevel = 1);
271272
void Timing(string key, double value, Sentry.MeasurementUnit.Duration unit = 3, System.Collections.Generic.IDictionary<string, string>? tags = null, System.DateTimeOffset? timestamp = default, int stackLevel = 1);
272273
}

0 commit comments

Comments
 (0)