Move to individual incrementer variables over concurrent dictionary

This commit is contained in:
Brett Hazen 2025-06-03 08:28:47 -05:00 committed by Brett Hazen
parent 99a456365f
commit 2443eff841
3 changed files with 108 additions and 62 deletions

View file

@ -1,13 +0,0 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
namespace Duende.IdentityServer.Licensing.V2;
internal class AtomicCounter(int initialCount = 0)
{
private long _count = initialCount;
public void Increment() => Interlocked.Increment(ref _count);
public long Count => _count;
}

View file

@ -2,7 +2,6 @@
// See LICENSE in the project root for license information.
#nullable enable
using System.Collections.Concurrent;
using System.Diagnostics.Metrics;
using System.Text.Json;
using Duende.IdentityServer.Models;
@ -11,21 +10,27 @@ namespace Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;
internal class TokenIssueCountDiagnosticEntry : IDiagnosticEntry
{
private readonly ConcurrentDictionary<string, AtomicCounter> _tokenCounts;
private long _jwtTokenIssued;
private long _referenceTokenIssued;
private long _refreshTokenIssued;
private long _jwtPoPDPoPTokenIssued;
private long _referencePoPDPoPTokenIssued;
private long _jwtPoPmTLSTokenIssued;
private long _referencePoPmTLSTokenIssued;
private long _idTokenIssued;
private long _implicitGrantTypeFlows;
private long _hybridGrantTypeFlows;
private long _authorizationCodeGrantTypeFlows;
private long _clientCredentialsGrantTypeFlows;
private long _resourceOwnerPasswordGrantTypeFlows;
private long _deviceFlowGrantTypeFlows;
private long _otherGrantTypeFlows;
private readonly MeterListener _meterListener;
public TokenIssueCountDiagnosticEntry()
{
_tokenCounts = new ConcurrentDictionary<string, AtomicCounter>([
new("Jwt", new AtomicCounter()),
new ("Reference", new AtomicCounter()),
new ("Refresh", new AtomicCounter()),
new("JwtPoPDPoP", new AtomicCounter()),
new("ReferencePoPDPoP", new AtomicCounter()),
new("JwtPoPmTLS", new AtomicCounter()),
new("ReferencePoPmTLS", new AtomicCounter()),
new("Id", new AtomicCounter())
]);
_meterListener = new MeterListener();
_meterListener.InstrumentPublished += (instrument, listener) =>
@ -46,10 +51,21 @@ internal class TokenIssueCountDiagnosticEntry : IDiagnosticEntry
writer.WritePropertyName("TokenIssueCounts");
writer.WriteStartObject();
foreach (var (tokenType, counter) in _tokenCounts)
{
writer.WriteNumber(tokenType, counter.Count);
}
writer.WriteNumber("Jwt", _jwtTokenIssued);
writer.WriteNumber("Reference", _referenceTokenIssued);
writer.WriteNumber("JwtPoPDPoP", _jwtPoPDPoPTokenIssued);
writer.WriteNumber("ReferencePoPDPoP", _referencePoPDPoPTokenIssued);
writer.WriteNumber("JwtPoPmTLS", _jwtPoPmTLSTokenIssued);
writer.WriteNumber("ReferencePoPmTLS", _referencePoPmTLSTokenIssued);
writer.WriteNumber("Refresh", _refreshTokenIssued);
writer.WriteNumber("Id", _idTokenIssued);
writer.WriteNumber(GrantType.Implicit, _implicitGrantTypeFlows);
writer.WriteNumber(GrantType.Hybrid, _hybridGrantTypeFlows);
writer.WriteNumber(GrantType.AuthorizationCode, _authorizationCodeGrantTypeFlows);
writer.WriteNumber(GrantType.ClientCredentials, _clientCredentialsGrantTypeFlows);
writer.WriteNumber(GrantType.ResourceOwnerPassword, _resourceOwnerPasswordGrantTypeFlows);
writer.WriteNumber(GrantType.DeviceFlow, _deviceFlowGrantTypeFlows);
writer.WriteNumber("Other", _otherGrantTypeFlows);
writer.WriteEndObject();
@ -106,44 +122,65 @@ internal class TokenIssueCountDiagnosticEntry : IDiagnosticEntry
switch (proofType)
{
case ProofType.None when accessTokenType == AccessTokenType.Jwt:
_tokenCounts["Jwt"].Increment();
Interlocked.Increment(ref _jwtTokenIssued);
break;
case ProofType.None when accessTokenType == AccessTokenType.Reference:
_tokenCounts["Reference"].Increment();
Interlocked.Increment(ref _referenceTokenIssued);
break;
case ProofType.DPoP when accessTokenType == AccessTokenType.Jwt:
_tokenCounts["JwtPoPDPoP"].Increment();
Interlocked.Increment(ref _jwtPoPDPoPTokenIssued);
break;
case ProofType.DPoP when accessTokenType == AccessTokenType.Reference:
_tokenCounts["ReferencePoPDPoP"].Increment();
Interlocked.Increment(ref _referencePoPDPoPTokenIssued);
break;
case ProofType.ClientCertificate when accessTokenType == AccessTokenType.Jwt:
_tokenCounts["JwtPoPmTLS"].Increment();
Interlocked.Increment(ref _jwtPoPmTLSTokenIssued);
break;
case ProofType.ClientCertificate when accessTokenType == AccessTokenType.Reference:
_tokenCounts["ReferencePoPmTLS"].Increment();
Interlocked.Increment(ref _referencePoPmTLSTokenIssued);
break;
}
}
if (refreshTokenIssued)
{
_tokenCounts["Refresh"].Increment();
Interlocked.Increment(ref _refreshTokenIssued);
}
if (identityTokenIssued)
{
_tokenCounts["Id"].Increment();
Interlocked.Increment(ref _idTokenIssued);
}
var tokenWasIssued = accessTokenIssued || refreshTokenIssued || identityTokenIssued;
if (tokenWasIssued && !string.IsNullOrEmpty(grantType))
if (!tokenWasIssued || string.IsNullOrEmpty(grantType))
{
_tokenCounts.AddOrUpdate(grantType, new AtomicCounter(1), (_, counter) =>
{
counter.Increment();
return counter;
});
return;
}
switch (grantType)
{
case GrantType.Implicit:
Interlocked.Increment(ref _implicitGrantTypeFlows);
break;
case GrantType.Hybrid:
Interlocked.Increment(ref _hybridGrantTypeFlows);
break;
case GrantType.AuthorizationCode:
Interlocked.Increment(ref _authorizationCodeGrantTypeFlows);
break;
case GrantType.ClientCredentials:
Interlocked.Increment(ref _clientCredentialsGrantTypeFlows);
break;
case GrantType.ResourceOwnerPassword:
Interlocked.Increment(ref _resourceOwnerPasswordGrantTypeFlows);
break;
case GrantType.DeviceFlow:
Interlocked.Increment(ref _deviceFlowGrantTypeFlows);
break;
default:
Interlocked.Increment(ref _otherGrantTypeFlows);
break;
}
}
}

View file

@ -1,6 +1,7 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
using System.Reflection;
using Duende.IdentityServer.Licensing.V2.Diagnostics.DiagnosticEntries;
using Duende.IdentityServer.Models;
@ -13,7 +14,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_JwtAccessToken()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.None, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -23,7 +24,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_JwtReferenceToken()
{
IssueToken("authorization_code", true, AccessTokenType.Reference, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Reference, false, ProofType.None, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -33,7 +34,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_JwtDPoPToken()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.DPoP, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.DPoP, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -43,7 +44,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_ReferenceDPoPToken()
{
IssueToken("authorization_code", true, AccessTokenType.Reference, false, ProofType.DPoP, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Reference, false, ProofType.DPoP, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -53,7 +54,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_JwtMTlsToken()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.ClientCertificate, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.ClientCertificate, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -63,7 +64,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_ReferenceMTlsToken()
{
IssueToken("authorization_code", true, AccessTokenType.Reference, false, ProofType.ClientCertificate, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Reference, false, ProofType.ClientCertificate, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -83,7 +84,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Count_IdToken()
{
IssueToken("authorization_code", false, AccessTokenType.Jwt, false, ProofType.None, true);
IssueToken(GrantType.AuthorizationCode, false, AccessTokenType.Jwt, false, ProofType.None, true);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -93,8 +94,8 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Handle_Multiple_Token_Types()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, true, ProofType.None, false);
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.DPoP, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, true, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.DPoP, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -107,7 +108,7 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Handle_No_Token_Issued()
{
IssueToken("authorization_code", false, null, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, false, null, false, ProofType.None, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
@ -125,37 +126,58 @@ public class TokenIssueCountDiagnosticEntryTests
[Fact]
public async Task Should_Handle_Initial_Grant_Type_Count()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.None, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
var tokenIssueCounts = result.RootElement.GetProperty("TokenIssueCounts");
tokenIssueCounts.GetProperty("authorization_code").GetInt64().ShouldBe(1);
tokenIssueCounts.GetProperty(GrantType.AuthorizationCode).GetInt64().ShouldBe(1);
}
[Fact]
public async Task Should_Handle_Multiple_Grant_Type_Counts()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken("client_credentials", true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken(GrantType.ClientCredentials, true, AccessTokenType.Jwt, false, ProofType.None, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
var tokenIssueCounts = result.RootElement.GetProperty("TokenIssueCounts");
tokenIssueCounts.GetProperty("authorization_code").GetInt64().ShouldBe(1);
tokenIssueCounts.GetProperty("client_credentials").GetInt64().ShouldBe(1);
tokenIssueCounts.GetProperty(GrantType.AuthorizationCode).GetInt64().ShouldBe(1);
tokenIssueCounts.GetProperty(GrantType.ClientCredentials).GetInt64().ShouldBe(1);
}
[Fact]
public async Task Should_Handle_Multiple_Grant_Type_Counts_With_Grant_Type()
{
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken("authorization_code", true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.None, false);
IssueToken(GrantType.AuthorizationCode, true, AccessTokenType.Jwt, false, ProofType.None, false);
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
var tokenIssueCounts = result.RootElement.GetProperty("TokenIssueCounts");
tokenIssueCounts.GetProperty("authorization_code").GetInt64().ShouldBe(2);
tokenIssueCounts.GetProperty(GrantType.AuthorizationCode).GetInt64().ShouldBe(2);
}
[Fact]
public async Task Should_Handle_Grant_Type_Counts_For_All_Grant_Types()
{
var grantTypes = typeof(GrantType).GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.Where(field => field.IsLiteral && !field.IsInitOnly)
.Select(field => field.GetValue(null)?.ToString())
.Where(value => value != null);
foreach (var grantType in grantTypes)
{
IssueToken(grantType, true, AccessTokenType.Jwt, false, ProofType.None, false);
}
var result = await DiagnosticEntryTestHelper.WriteEntryToJson(_subject);
var tokenIssueCounts = result.RootElement.GetProperty("TokenIssueCounts");
foreach (var grantType in grantTypes)
{
tokenIssueCounts.GetProperty(grantType).GetInt64().ShouldBe(1);
}
}
[Fact]