From 2443eff841cfa6cc5bd48eea7ff50ca820faade2 Mon Sep 17 00:00:00 2001 From: Brett Hazen <2651260+bhazen@users.noreply.github.com> Date: Tue, 3 Jun 2025 08:28:47 -0500 Subject: [PATCH] Move to individual incrementer variables over concurrent dictionary --- .../Licensing/V2/AtomicCounter.cs | 13 --- .../TokenIssueCountDiagnosticEntry.cs | 97 +++++++++++++------ .../TokenIssueCountDiagnosticEntryTests.cs | 60 ++++++++---- 3 files changed, 108 insertions(+), 62 deletions(-) delete mode 100644 identity-server/src/IdentityServer/Licensing/V2/AtomicCounter.cs diff --git a/identity-server/src/IdentityServer/Licensing/V2/AtomicCounter.cs b/identity-server/src/IdentityServer/Licensing/V2/AtomicCounter.cs deleted file mode 100644 index dd32453f6..000000000 --- a/identity-server/src/IdentityServer/Licensing/V2/AtomicCounter.cs +++ /dev/null @@ -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; -} diff --git a/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/TokenIssueCountDiagnosticEntry.cs b/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/TokenIssueCountDiagnosticEntry.cs index 4731b0cae..1f977712a 100644 --- a/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/TokenIssueCountDiagnosticEntry.cs +++ b/identity-server/src/IdentityServer/Licensing/V2/Diagnostics/DiagnosticEntries/TokenIssueCountDiagnosticEntry.cs @@ -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 _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([ - 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; } } } diff --git a/identity-server/test/IdentityServer.UnitTests/Licensing/v2/DiagnosticEntries/TokenIssueCountDiagnosticEntryTests.cs b/identity-server/test/IdentityServer.UnitTests/Licensing/v2/DiagnosticEntries/TokenIssueCountDiagnosticEntryTests.cs index 38c09e80f..59763f74e 100644 --- a/identity-server/test/IdentityServer.UnitTests/Licensing/v2/DiagnosticEntries/TokenIssueCountDiagnosticEntryTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Licensing/v2/DiagnosticEntries/TokenIssueCountDiagnosticEntryTests.cs @@ -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]