diff --git a/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj b/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj index 22bf430928da..5f13920b61f5 100644 --- a/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj +++ b/src/Identity/Core/src/Microsoft.AspNetCore.Identity.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Identity/Core/src/SignInManager.cs b/src/Identity/Core/src/SignInManager.cs index 011ba2096b87..2391e36c0e72 100644 --- a/src/Identity/Core/src/SignInManager.cs +++ b/src/Identity/Core/src/SignInManager.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security.Claims; @@ -169,15 +170,16 @@ public virtual async Task CanSignInAsync(TUser user) /// The task object representing the asynchronous operation. public virtual async Task RefreshSignInAsync(TUser user) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var (success, isPersistent) = await RefreshSignInCoreAsync(user); var signInResult = success ? SignInResult.Success : SignInResult.Failed; - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, signInResult, SignInType.Refresh, isPersistent); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, signInResult, SignInType.Refresh, isPersistent, startTimestamp); } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Refresh, isPersistent: null, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Refresh, isPersistent: null, startTimestamp, ex); throw; } } @@ -391,6 +393,7 @@ public virtual async Task ValidateSecurityStampAsync(TUser? user, string? public virtual async Task PasswordSignInAsync(TUser user, string password, bool isPersistent, bool lockoutOnFailure) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ArgumentNullException.ThrowIfNull(user); @@ -399,13 +402,13 @@ public virtual async Task PasswordSignInAsync(TUser user, string p var result = attempt.Succeeded ? await SignInOrTwoFactorAsync(user, isPersistent) : attempt; - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Password, isPersistent, startTimestamp); return result; } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Password, isPersistent, startTimestamp, ex); throw; } } @@ -423,10 +426,11 @@ public virtual async Task PasswordSignInAsync(TUser user, string p public virtual async Task PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure) { + var startTimestamp = Stopwatch.GetTimestamp(); var user = await UserManager.FindByNameAsync(userName); if (user == null) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, SignInResult.Failed, SignInType.Password, isPersistent, startTimestamp); return SignInResult.Failed; } @@ -635,16 +639,17 @@ public virtual async Task> PerformPasskeyAssertion /// public virtual async Task PasskeySignInAsync(string credentialJson) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await PasskeySignInCoreAsync(credentialJson); - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.Passkey, isPersistent: false, startTimestamp); return result; } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.Passkey, isPersistent: false, startTimestamp, ex); throw; } } @@ -787,16 +792,17 @@ public virtual async Task ForgetTwoFactorClientAsync() /// public virtual async Task TwoFactorRecoveryCodeSignInAsync(string recoveryCode) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await TwoFactorRecoveryCodeSignInCoreAsync(recoveryCode); - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorRecoveryCode, isPersistent: false, startTimestamp); return result; } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorRecoveryCode, isPersistent: false, startTimestamp, ex); throw; } } @@ -868,16 +874,17 @@ private async Task DoTwoFactorSignInAsync(TUser user, TwoFactorAut /// for the sign-in attempt. public virtual async Task TwoFactorAuthenticatorSignInAsync(string code, bool isPersistent, bool rememberClient) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await TwoFactorAuthenticatorSignInCoreAsync(code, isPersistent, rememberClient); - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactorAuthenticator, isPersistent, startTimestamp); return result; } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactorAuthenticator, isPersistent, startTimestamp, ex); throw; } } @@ -932,16 +939,17 @@ private async Task TwoFactorAuthenticatorSignInCoreAsync(string co /// for the sign-in attempt. public virtual async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await TwoFactorSignInCoreAsync(provider, code, isPersistent, rememberClient); - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.TwoFactor, isPersistent, startTimestamp); return result; } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.TwoFactor, isPersistent, startTimestamp, ex); throw; } } @@ -1021,16 +1029,17 @@ public virtual Task ExternalLoginSignInAsync(string loginProvider, /// for the sign-in attempt. public virtual async Task ExternalLoginSignInAsync(string loginProvider, string providerKey, bool isPersistent, bool bypassTwoFactor) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await ExternalLoginSignInCoreAsync(loginProvider, providerKey, isPersistent, bypassTwoFactor); - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result, SignInType.External, isPersistent, startTimestamp); return result; } catch (Exception ex) { - _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, ex); + _metrics?.AuthenticateSignIn(typeof(TUser).FullName!, AuthenticationScheme, result: null, SignInType.External, isPersistent, startTimestamp, ex); throw; } } diff --git a/src/Identity/Core/src/SignInManagerMetrics.cs b/src/Identity/Core/src/SignInManagerMetrics.cs index f14c37edf042..f55a37af2895 100644 --- a/src/Identity/Core/src/SignInManagerMetrics.cs +++ b/src/Identity/Core/src/SignInManagerMetrics.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using System.Diagnostics.Metrics; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Identity; @@ -10,31 +12,55 @@ internal sealed class SignInManagerMetrics : IDisposable { public const string MeterName = "Microsoft.AspNetCore.Identity"; - public const string AuthenticateCounterName = "aspnetcore.identity.sign_in.authenticate"; - public const string RememberTwoFactorCounterName = "aspnetcore.identity.sign_in.remember_two_factor"; - public const string ForgetTwoFactorCounterName = "aspnetcore.identity.sign_in.forget_two_factor"; - public const string CheckPasswordCounterName = "aspnetcore.identity.sign_in.check_password"; - public const string SignInUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_in"; - public const string SignOutUserPrincipalCounterName = "aspnetcore.identity.sign_in.sign_out"; + public const string AuthenticateDurationName = "aspnetcore.identity.sign_in.authenticate.duration"; + public const string RememberedTwoFactorCounterName = "aspnetcore.identity.sign_in.two_factor_clients_remembered"; + public const string ForgottenTwoFactorCounterName = "aspnetcore.identity.sign_in.two_factor_clients_forgotten"; + public const string CheckPasswordAttemptsCounterName = "aspnetcore.identity.sign_in.check_password_attempts"; + public const string SignInsCounterName = "aspnetcore.identity.sign_in.sign_ins"; + public const string SignOutsCounterName = "aspnetcore.identity.sign_in.sign_outs"; private readonly Meter _meter; - private readonly Counter _authenticateCounter; + private readonly Histogram _authenticateDuration; private readonly Counter _rememberTwoFactorClientCounter; private readonly Counter _forgetTwoFactorCounter; private readonly Counter _checkPasswordCounter; - private readonly Counter _signInUserPrincipalCounter; - private readonly Counter _signOutUserPrincipalCounter; + private readonly Counter _signInsCounter; + private readonly Counter _signOutsCounter; public SignInManagerMetrics(IMeterFactory meterFactory) { _meter = meterFactory.Create(MeterName); - _authenticateCounter = _meter.CreateCounter(AuthenticateCounterName, "{count}", "The number of authenticate attempts. The authenticate counter is incremented by sign in methods such as PasswordSignInAsync and TwoFactorSignInAsync."); - _rememberTwoFactorClientCounter = _meter.CreateCounter(RememberTwoFactorCounterName, "{count}", "The number of two factor clients remembered."); - _forgetTwoFactorCounter = _meter.CreateCounter(ForgetTwoFactorCounterName, "{count}", "The number of two factor clients forgotten."); - _checkPasswordCounter = _meter.CreateCounter(CheckPasswordCounterName, "{check}", "The number of check password attempts. Checks that the account is in a state that can log in and that the password is valid using the UserManager.CheckPasswordAsync method."); - _signInUserPrincipalCounter = _meter.CreateCounter(SignInUserPrincipalCounterName, "{sign_in}", "The number of calls to sign in user principals."); - _signOutUserPrincipalCounter = _meter.CreateCounter(SignOutUserPrincipalCounterName, "{sign_out}", "The number of calls to sign out user principals."); + _authenticateDuration = _meter.CreateHistogram( + AuthenticateDurationName, + unit: "s", + description: "The duration of authenticate attempts. The authenticate metrics is recorded by sign in methods such as PasswordSignInAsync and TwoFactorSignInAsync.", + advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries }); + + _rememberTwoFactorClientCounter = _meter.CreateCounter( + RememberedTwoFactorCounterName, + unit: "{client}", + description: "The total number of two factor clients remembered."); + + _forgetTwoFactorCounter = _meter.CreateCounter( + ForgottenTwoFactorCounterName, + unit: "{client}", + description: "The total number of two factor clients forgotten."); + + _checkPasswordCounter = _meter.CreateCounter( + CheckPasswordAttemptsCounterName, + unit: "{attempt}", + description: "The total number of check password attempts. Checks that the account is in a state that can log in and that the password is valid using the UserManager.CheckPasswordAsync method."); + + _signInsCounter = _meter.CreateCounter( + SignInsCounterName, + unit: "{sign_in}", + description: "The total number of calls to sign in user principals."); + + _signOutsCounter = _meter.CreateCounter( + SignOutsCounterName, + unit: "{sign_out}", + description: "The total number of calls to sign out user principals."); } internal void CheckPasswordSignIn(string userType, SignInResult? result, Exception? exception = null) @@ -54,9 +80,9 @@ internal void CheckPasswordSignIn(string userType, SignInResult? result, Excepti _checkPasswordCounter.Add(1, tags); } - internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool? isPersistent, Exception? exception = null) + internal void AuthenticateSignIn(string userType, string authenticationScheme, SignInResult? result, SignInType signInType, bool? isPersistent, long startTimestamp, Exception? exception = null) { - if (!_authenticateCounter.Enabled) + if (!_authenticateDuration.Enabled) { return; } @@ -64,19 +90,20 @@ internal void AuthenticateSignIn(string userType, string authenticationScheme, S var tags = new TagList { { "aspnetcore.identity.user_type", userType }, - { "aspnetcore.identity.authentication_scheme", authenticationScheme }, + { "aspnetcore.authentication.scheme", authenticationScheme }, { "aspnetcore.identity.sign_in.type", GetSignInType(signInType) }, }; AddIsPersistent(ref tags, isPersistent); AddSignInResult(ref tags, result); AddErrorTag(ref tags, exception); - _authenticateCounter.Add(1, tags); + var duration = ValueStopwatch.GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp()); + _authenticateDuration.Record(duration.TotalSeconds, tags); } internal void SignInUserPrincipal(string userType, string authenticationScheme, bool? isPersistent, Exception? exception = null) { - if (!_signInUserPrincipalCounter.Enabled) + if (!_signInsCounter.Enabled) { return; } @@ -84,17 +111,17 @@ internal void SignInUserPrincipal(string userType, string authenticationScheme, var tags = new TagList { { "aspnetcore.identity.user_type", userType }, - { "aspnetcore.identity.authentication_scheme", authenticationScheme }, + { "aspnetcore.authentication.scheme", authenticationScheme }, }; AddIsPersistent(ref tags, isPersistent); AddErrorTag(ref tags, exception); - _signInUserPrincipalCounter.Add(1, tags); + _signInsCounter.Add(1, tags); } internal void SignOutUserPrincipal(string userType, string authenticationScheme, Exception? exception = null) { - if (!_signOutUserPrincipalCounter.Enabled) + if (!_signOutsCounter.Enabled) { return; } @@ -102,11 +129,11 @@ internal void SignOutUserPrincipal(string userType, string authenticationScheme, var tags = new TagList { { "aspnetcore.identity.user_type", userType }, - { "aspnetcore.identity.authentication_scheme", authenticationScheme }, + { "aspnetcore.authentication.scheme", authenticationScheme }, }; AddErrorTag(ref tags, exception); - _signOutUserPrincipalCounter.Add(1, tags); + _signOutsCounter.Add(1, tags); } internal void RememberTwoFactorClient(string userType, string authenticationScheme, Exception? exception = null) @@ -119,7 +146,7 @@ internal void RememberTwoFactorClient(string userType, string authenticationSche var tags = new TagList { { "aspnetcore.identity.user_type", userType }, - { "aspnetcore.identity.authentication_scheme", authenticationScheme } + { "aspnetcore.authentication.scheme", authenticationScheme } }; AddErrorTag(ref tags, exception); @@ -136,7 +163,7 @@ internal void ForgetTwoFactorClient(string userType, string authenticationScheme var tags = new TagList { { "aspnetcore.identity.user_type", userType }, - { "aspnetcore.identity.authentication_scheme", authenticationScheme } + { "aspnetcore.authentication.scheme", authenticationScheme } }; AddErrorTag(ref tags, exception); @@ -152,7 +179,7 @@ private static void AddIsPersistent(ref TagList tags, bool? isPersistent) { if (isPersistent != null) { - tags.Add("aspnetcore.identity.sign_in.is_persistent", isPersistent.Value); + tags.Add("aspnetcore.authentication.is_persistent", isPersistent.Value); } } diff --git a/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj b/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj index a1315dd79254..538396395fc3 100644 --- a/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj +++ b/src/Identity/Extensions.Core/src/Microsoft.Extensions.Identity.Core.csproj @@ -15,7 +15,9 @@ + + diff --git a/src/Identity/Extensions.Core/src/UserManager.cs b/src/Identity/Extensions.Core/src/UserManager.cs index 073b36e9c426..e184534b41ce 100644 --- a/src/Identity/Extensions.Core/src/UserManager.cs +++ b/src/Identity/Extensions.Core/src/UserManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; @@ -481,15 +482,17 @@ public virtual Task GenerateConcurrencyStampAsync(TUser user) /// public virtual async Task CreateAsync(TUser user) { + var startTimeStamp = Stopwatch.GetTimestamp(); + try { var result = await CreateCoreAsync(user).ConfigureAwait(false); - _metrics?.CreateUser(typeof(TUser).FullName!, result); + _metrics?.CreateUser(typeof(TUser).FullName!, result, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.CreateUser(typeof(TUser).FullName!, result: null, ex); + _metrics?.CreateUser(typeof(TUser).FullName!, result: null, startTimeStamp, ex); throw; } } @@ -523,16 +526,18 @@ private async Task CreateCoreAsync(TUser user) /// public virtual async Task UpdateAsync(TUser user) { + var startTimeStamp = Stopwatch.GetTimestamp(); + try { ThrowIfDisposed(); ArgumentNullThrowHelper.ThrowIfNull(user); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.Update).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.Update, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.Update, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.Update, startTimeStamp, ex); throw; } } @@ -547,19 +552,21 @@ public virtual async Task UpdateAsync(TUser user) /// public virtual async Task DeleteAsync(TUser user) { + var startTimeStamp = Stopwatch.GetTimestamp(); + try { ThrowIfDisposed(); ArgumentNullThrowHelper.ThrowIfNull(user); var result = await Store.DeleteAsync(user, CancellationToken).ConfigureAwait(false); - _metrics?.DeleteUser(typeof(TUser).FullName!, result); + _metrics?.DeleteUser(typeof(TUser).FullName!, result, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.DeleteUser(typeof(TUser).FullName!, result: null, ex); + _metrics?.DeleteUser(typeof(TUser).FullName!, result: null, startTimeStamp, ex); throw; } } @@ -625,6 +632,8 @@ public virtual async Task DeleteAsync(TUser user) /// public virtual async Task CreateAsync(TUser user, string password) { + var startTimeStamp = Stopwatch.GetTimestamp(); + try { ThrowIfDisposed(); @@ -634,13 +643,13 @@ public virtual async Task CreateAsync(TUser user, string passwor var result = await UpdatePasswordHash(passwordStore, user, password).ConfigureAwait(false); if (!result.Succeeded) { - _metrics?.CreateUser(typeof(TUser).FullName!, result); + _metrics?.CreateUser(typeof(TUser).FullName!, result, startTimeStamp); return result; } } catch (Exception ex) { - _metrics?.CreateUser(typeof(TUser).FullName!, result: null, ex); + _metrics?.CreateUser(typeof(TUser).FullName!, result: null, startTimeStamp, ex); throw; } @@ -710,6 +719,7 @@ public virtual async Task UpdateNormalizedUserNameAsync(TUser user) /// The that represents the asynchronous operation. public virtual async Task SetUserNameAsync(TUser user, string? userName) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -717,11 +727,11 @@ public virtual async Task SetUserNameAsync(TUser user, string? u await Store.SetUserNameAsync(user, userName, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.UserName).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetUserName, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.UserName, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetUserName, startTimeStamp, ex); throw; } } @@ -781,8 +791,9 @@ public virtual async Task CheckPasswordAsync(TUser user, string password) if (result == PasswordVerificationResult.SuccessRehashNeeded) { + var startTimeStamp = Stopwatch.GetTimestamp(); await UpdatePasswordHash(passwordStore, user, password, validatePassword: false).ConfigureAwait(false); - await UpdateUserAndRecordMetricAsync(user, UserUpdateType.PasswordRehash).ConfigureAwait(false); + await UpdateUserAndRecordMetricAsync(user, UserUpdateType.PasswordRehash, startTimeStamp).ConfigureAwait(false); } return (result, false); @@ -817,15 +828,16 @@ public virtual Task HasPasswordAsync(TUser user) /// public virtual async Task AddPasswordAsync(TUser user, string password) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await AddPasswordCoreAsync(user, password).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddPassword); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddPassword, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddPassword, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddPassword, startTimeStamp, ex); throw; } } @@ -863,15 +875,16 @@ private async Task AddPasswordCoreAsync(TUser user, string passw /// public virtual async Task ChangePasswordAsync(TUser user, string currentPassword, string newPassword) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await ChangePasswordCoreAsync(user, currentPassword, newPassword).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ChangePassword); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ChangePassword, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ChangePassword, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ChangePassword, startTimeStamp, ex); throw; } } @@ -905,6 +918,7 @@ private async Task ChangePasswordCoreAsync(TUser user, string cu /// public virtual async Task RemovePasswordAsync(TUser user) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -912,11 +926,11 @@ public virtual async Task RemovePasswordAsync(TUser user) ArgumentNullThrowHelper.ThrowIfNull(user); await UpdatePasswordHash(passwordStore, user, null, validatePassword: false).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemovePassword).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemovePassword, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemovePassword, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemovePassword, startTimeStamp, ex); throw; } } @@ -975,6 +989,7 @@ public virtual async Task GetSecurityStampAsync(TUser user) /// public virtual async Task UpdateSecurityStampAsync(TUser user) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -982,11 +997,11 @@ public virtual async Task UpdateSecurityStampAsync(TUser user) ArgumentNullThrowHelper.ThrowIfNull(user); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SecurityStamp).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.UpdateSecurityStamp, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SecurityStamp, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.UpdateSecurityStamp, startTimeStamp, ex); throw; } } @@ -1017,6 +1032,8 @@ public virtual Task GeneratePasswordResetTokenAsync(TUser user) /// public virtual async Task ResetPasswordAsync(TUser user, string token, string newPassword) { + var startTimeStamp = Stopwatch.GetTimestamp(); + ThrowIfDisposed(); ArgumentNullThrowHelper.ThrowIfNull(user); @@ -1024,17 +1041,17 @@ public virtual async Task ResetPasswordAsync(TUser user, string if (!await VerifyUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider, ResetPasswordTokenPurpose, token).ConfigureAwait(false)) { var failureResult = IdentityResult.Failed(ErrorDescriber.InvalidToken()); - _metrics?.UpdateUser(typeof(TUser).FullName!, failureResult, UserUpdateType.ResetPassword); + _metrics?.UpdateUser(typeof(TUser).FullName!, failureResult, UserUpdateType.ResetPassword, startTimeStamp); return failureResult; } var result = await UpdatePasswordHash(user, newPassword, validatePassword: true).ConfigureAwait(false); if (!result.Succeeded) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ResetPassword); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ResetPassword, startTimeStamp); return result; } - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ResetPassword).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ResetPassword, startTimeStamp).ConfigureAwait(false); } /// @@ -1067,6 +1084,7 @@ public virtual async Task ResetPasswordAsync(TUser user, string /// public virtual async Task RemoveLoginAsync(TUser user, string loginProvider, string providerKey) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -1077,11 +1095,11 @@ public virtual async Task RemoveLoginAsync(TUser user, string lo await loginStore.RemoveLoginAsync(user, loginProvider, providerKey, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveLogin).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveLogin, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveLogin, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveLogin, startTimeStamp, ex); throw; } } @@ -1097,15 +1115,16 @@ public virtual async Task RemoveLoginAsync(TUser user, string lo /// public virtual async Task AddLoginAsync(TUser user, UserLoginInfo login) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await AddLoginCoreAsync(user, login).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddLogin); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddLogin, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddLogin, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddLogin, startTimeStamp, ex); throw; } } @@ -1167,6 +1186,7 @@ public virtual Task AddClaimAsync(TUser user, Claim claim) /// public virtual async Task AddClaimsAsync(TUser user, IEnumerable claims) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -1175,11 +1195,11 @@ public virtual async Task AddClaimsAsync(TUser user, IEnumerable ArgumentNullThrowHelper.ThrowIfNull(claims); await claimStore.AddClaimsAsync(user, claims, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.AddClaims).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.AddClaims, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddClaims, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddClaims, startTimeStamp, ex); throw; } } @@ -1196,6 +1216,7 @@ public virtual async Task AddClaimsAsync(TUser user, IEnumerable /// public virtual async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -1205,11 +1226,11 @@ public virtual async Task ReplaceClaimAsync(TUser user, Claim cl ArgumentNullThrowHelper.ThrowIfNull(newClaim); await claimStore.ReplaceClaimAsync(user, claim, newClaim, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ReplaceClaim).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ReplaceClaim, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ReplaceClaim, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ReplaceClaim, startTimeStamp, ex); throw; } } @@ -1239,6 +1260,7 @@ public virtual Task RemoveClaimAsync(TUser user, Claim claim) /// public virtual async Task RemoveClaimsAsync(TUser user, IEnumerable claims) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -1247,11 +1269,11 @@ public virtual async Task RemoveClaimsAsync(TUser user, IEnumera ArgumentNullThrowHelper.ThrowIfNull(claims); await claimStore.RemoveClaimsAsync(user, claims, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveClaims).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveClaims, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveClaims, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveClaims, startTimeStamp, ex); throw; } } @@ -1282,15 +1304,16 @@ public virtual async Task> GetClaimsAsync(TUser user) /// public virtual async Task AddToRoleAsync(TUser user, string role) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await AddToRoleCoreAsync(user, role).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddToRoles); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddToRoles, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddToRoles, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddToRoles, startTimeStamp, ex); throw; } } @@ -1321,15 +1344,16 @@ private async Task AddToRoleCoreAsync(TUser user, string role) /// public virtual async Task AddToRolesAsync(TUser user, IEnumerable roles) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await AddToRolesCoreAsync(user, roles).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddToRoles); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddToRoles, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddToRoles, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddToRoles, startTimeStamp, ex); throw; } } @@ -1364,6 +1388,8 @@ private async Task AddToRolesCoreAsync(TUser user, IEnumerable public virtual async Task RemoveFromRoleAsync(TUser user, string role) { + var startTimeStamp = Stopwatch.GetTimestamp(); + ThrowIfDisposed(); var userRoleStore = GetUserRoleStore(); ArgumentNullThrowHelper.ThrowIfNull(user); @@ -1372,12 +1398,12 @@ public virtual async Task RemoveFromRoleAsync(TUser user, string if (!await userRoleStore.IsInRoleAsync(user, normalizedRole, CancellationToken).ConfigureAwait(false)) { var failureResult = UserNotInRoleError(role); - _metrics?.UpdateUser(typeof(TUser).FullName!, failureResult, UserUpdateType.RemoveFromRoles); + _metrics?.UpdateUser(typeof(TUser).FullName!, failureResult, UserUpdateType.RemoveFromRoles, startTimeStamp); return failureResult; } await userRoleStore.RemoveFromRoleAsync(user, normalizedRole, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveFromRoles).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveFromRoles, startTimeStamp).ConfigureAwait(false); } private IdentityResult UserAlreadyInRoleError(string role) @@ -1409,15 +1435,16 @@ private IdentityResult UserNotInRoleError(string role) /// public virtual async Task RemoveFromRolesAsync(TUser user, IEnumerable roles) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await RemoveFromRolesCoreAsync(user, roles).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RemoveFromRoles); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RemoveFromRoles, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveFromRoles, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveFromRoles, startTimeStamp, ex); throw; } } @@ -1495,6 +1522,7 @@ public virtual async Task IsInRoleAsync(TUser user, string role) /// public virtual async Task SetEmailAsync(TUser user, string? email) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -1504,11 +1532,11 @@ public virtual async Task SetEmailAsync(TUser user, string? emai await store.SetEmailAsync(user, email, CancellationToken).ConfigureAwait(false); await store.SetEmailConfirmedAsync(user, false, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetEmail).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetEmail, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetEmail, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetEmail, startTimeStamp, ex); throw; } } @@ -1591,15 +1619,16 @@ public virtual Task GenerateEmailConfirmationTokenAsync(TUser user) /// public virtual async Task ConfirmEmailAsync(TUser user, string token) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { var result = await ConfirmEmailCoreAsync(user, token).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ConfirmEmail); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ConfirmEmail, startTimeStamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ConfirmEmail, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ConfirmEmail, startTimeStamp, ex); throw; } } @@ -1661,20 +1690,19 @@ public virtual Task GenerateChangeEmailTokenAsync(TUser user, string new /// public virtual async Task ChangeEmailAsync(TUser user, string newEmail, string token) { + var startTimeStamp = Stopwatch.GetTimestamp(); try { - var result = await ChangeEmailCoreAsync(user, newEmail, token).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ChangeEmail); - return result; + return await ChangeEmailCoreAsync(user, newEmail, token, startTimeStamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ChangeEmail, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ChangeEmail, startTimeStamp, ex); throw; } } - private async Task ChangeEmailCoreAsync(TUser user, string newEmail, string token) + private async Task ChangeEmailCoreAsync(TUser user, string newEmail, string token, long startTimestamp) { ThrowIfDisposed(); ArgumentNullThrowHelper.ThrowIfNull(user); @@ -1688,7 +1716,7 @@ private async Task ChangeEmailCoreAsync(TUser user, string newEm await store.SetEmailAsync(user, newEmail, CancellationToken).ConfigureAwait(false); await store.SetEmailConfirmedAsync(user, true, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ChangeEmail).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ChangeEmail, startTimestamp).ConfigureAwait(false); } /// @@ -1715,6 +1743,7 @@ private async Task ChangeEmailCoreAsync(TUser user, string newEm /// public virtual async Task SetPhoneNumberAsync(TUser user, string? phoneNumber) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -1724,11 +1753,11 @@ public virtual async Task SetPhoneNumberAsync(TUser user, string await store.SetPhoneNumberAsync(user, phoneNumber, CancellationToken).ConfigureAwait(false); await store.SetPhoneNumberConfirmedAsync(user, false, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetPhoneNumber).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetPhoneNumber, startTimestamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetPhoneNumber, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetPhoneNumber, startTimestamp, ex); throw; } } @@ -1746,15 +1775,16 @@ public virtual async Task SetPhoneNumberAsync(TUser user, string /// public virtual async Task ChangePhoneNumberAsync(TUser user, string phoneNumber, string token) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await ChangePhoneNumberCoreAsync(user, phoneNumber, token).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ChangePhoneNumber); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ChangePhoneNumber, startTimestamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ChangePhoneNumber, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ChangePhoneNumber, startTimestamp, ex); throw; } } @@ -2031,6 +2061,7 @@ public virtual async Task GetTwoFactorEnabledAsync(TUser user) /// public virtual async Task SetTwoFactorEnabledAsync(TUser user, bool enabled) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -2039,11 +2070,11 @@ public virtual async Task SetTwoFactorEnabledAsync(TUser user, b await store.SetTwoFactorEnabledAsync(user, enabled, CancellationToken).ConfigureAwait(false); await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetTwoFactorEnabled).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetTwoFactorEnabled, startTimestamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetTwoFactorEnabled, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetTwoFactorEnabled, startTimestamp, ex); throw; } } @@ -2081,6 +2112,7 @@ public virtual async Task IsLockedOutAsync(TUser user) /// public virtual async Task SetLockoutEnabledAsync(TUser user, bool enabled) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -2088,11 +2120,11 @@ public virtual async Task SetLockoutEnabledAsync(TUser user, boo ArgumentNullThrowHelper.ThrowIfNull(user); await store.SetLockoutEnabledAsync(user, enabled, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetLockoutEnabled).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetLockoutEnabled, startTimestamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetLockoutEnabled, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetLockoutEnabled, startTimestamp, ex); throw; } } @@ -2136,15 +2168,16 @@ public virtual async Task GetLockoutEnabledAsync(TUser user) /// The that represents the asynchronous operation, containing the of the operation. public virtual async Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await SetLockoutEndDateCoreAsync(user, lockoutEnd).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.SetLockoutEndDate); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.SetLockoutEndDate, startTimestamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetLockoutEndDate, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetLockoutEndDate, startTimestamp, ex); throw; } } @@ -2173,6 +2206,7 @@ private async Task SetLockoutEndDateCoreAsync(TUser user, DateTi /// The that represents the asynchronous operation, containing the of the operation. public virtual async Task AccessFailedAsync(TUser user) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -2183,17 +2217,17 @@ public virtual async Task AccessFailedAsync(TUser user) var count = await store.IncrementAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false); if (count < Options.Lockout.MaxFailedAccessAttempts) { - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.AccessFailed).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.IncrementAccessFailed, startTimestamp).ConfigureAwait(false); } Logger.LogDebug(LoggerEventIds.UserLockedOut, "User is locked out."); await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Lockout.DefaultLockoutTimeSpan), CancellationToken).ConfigureAwait(false); await store.ResetAccessFailedCountAsync(user, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.AccessFailed).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.IncrementAccessFailed, startTimestamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AccessFailed, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.IncrementAccessFailed, startTimestamp, ex); throw; } } @@ -2205,15 +2239,16 @@ await store.SetLockoutEndDateAsync(user, DateTimeOffset.UtcNow.Add(Options.Locko /// The that represents the asynchronous operation, containing the of the operation. public virtual async Task ResetAccessFailedCountAsync(TUser user) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await ResetAccessFailedCountCoreAsync(user).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ResetAccessFailedCount); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.ResetAccessFailedCount, startTimestamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ResetAccessFailedCount, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ResetAccessFailedCount, startTimestamp, ex); throw; } } @@ -2307,6 +2342,7 @@ public virtual Task> GetUsersInRoleAsync(string roleName) /// Whether the user was successfully updated. public virtual async Task SetAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName, string? tokenValue) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -2317,11 +2353,11 @@ public virtual async Task SetAuthenticationTokenAsync(TUser user // REVIEW: should updating any tokens affect the security stamp? await store.SetTokenAsync(user, loginProvider, tokenName, tokenValue, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetAuthenticationToken).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.SetAuthenticationToken, startTimestamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetAuthenticationToken, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.SetAuthenticationToken, startTimestamp, ex); throw; } } @@ -2335,6 +2371,7 @@ public virtual async Task SetAuthenticationTokenAsync(TUser user /// Whether a token was removed. public virtual async Task RemoveAuthenticationTokenAsync(TUser user, string loginProvider, string tokenName) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -2344,11 +2381,11 @@ public virtual async Task RemoveAuthenticationTokenAsync(TUser u ArgumentNullThrowHelper.ThrowIfNull(tokenName); await store.RemoveTokenAsync(user, loginProvider, tokenName, CancellationToken).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveAuthenticationToken).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.RemoveAuthenticationToken, startTimestamp).ConfigureAwait(false); } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveAuthenticationToken, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemoveAuthenticationToken, startTimestamp, ex); throw; } } @@ -2373,12 +2410,21 @@ public virtual async Task RemoveAuthenticationTokenAsync(TUser u /// Whether the user was successfully updated. public virtual async Task ResetAuthenticatorKeyAsync(TUser user) { - ThrowIfDisposed(); - var store = GetAuthenticatorKeyStore(); - ArgumentNullThrowHelper.ThrowIfNull(user); - await store.SetAuthenticatorKeyAsync(user, GenerateNewAuthenticatorKey(), CancellationToken).ConfigureAwait(false); - await UpdateSecurityStampInternal(user).ConfigureAwait(false); - return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ResetAuthenticatorKey).ConfigureAwait(false); + var startTimestamp = Stopwatch.GetTimestamp(); + try + { + ThrowIfDisposed(); + var store = GetAuthenticatorKeyStore(); + ArgumentNullThrowHelper.ThrowIfNull(user); + await store.SetAuthenticatorKeyAsync(user, GenerateNewAuthenticatorKey(), CancellationToken).ConfigureAwait(false); + await UpdateSecurityStampInternal(user).ConfigureAwait(false); + return await UpdateUserAndRecordMetricAsync(user, UserUpdateType.ResetAuthenticatorKey, startTimestamp).ConfigureAwait(false); + } + catch (Exception ex) + { + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.ResetAuthenticatorKey, startTimestamp, ex); + throw; + } } /// @@ -2396,6 +2442,7 @@ public virtual string GenerateNewAuthenticatorKey() /// The new recovery codes for the user. Note: there may be less than number returned, as duplicates will be removed. public virtual async Task?> GenerateNewTwoFactorRecoveryCodesAsync(TUser user, int number) { + var startTimestamp = Stopwatch.GetTimestamp(); try { ThrowIfDisposed(); @@ -2409,7 +2456,7 @@ public virtual string GenerateNewAuthenticatorKey() } await store.ReplaceCodesAsync(user, newCodes.Distinct(), CancellationToken).ConfigureAwait(false); - var update = await UpdateUserAndRecordMetricAsync(user, UserUpdateType.GenerateNewTwoFactorRecoveryCodes).ConfigureAwait(false); + var update = await UpdateUserAndRecordMetricAsync(user, UserUpdateType.GenerateNewTwoFactorRecoveryCodes, startTimestamp).ConfigureAwait(false); if (update.Succeeded) { return newCodes; @@ -2418,7 +2465,7 @@ public virtual string GenerateNewAuthenticatorKey() } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.GenerateNewTwoFactorRecoveryCodes, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.GenerateNewTwoFactorRecoveryCodes, startTimestamp, ex); throw; } } @@ -2508,15 +2555,16 @@ private static char GetRandomRecoveryCodeChar() /// True if the recovery code was found for the user. public virtual async Task RedeemTwoFactorRecoveryCodeAsync(TUser user, string code) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await RedeemTwoFactorRecoveryCodeCoreAsync(user, code).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RedeemTwoFactorRecoveryCode); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RedeemTwoFactorRecoveryCode, startTimestamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RedeemTwoFactorRecoveryCode, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RedeemTwoFactorRecoveryCode, startTimestamp, ex); throw; } } @@ -2557,15 +2605,16 @@ public virtual Task CountRecoveryCodesAsync(TUser user) /// Whether the passkey was successfully set. public virtual async Task AddOrUpdatePasskeyAsync(TUser user, UserPasskeyInfo passkey) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await AddOrUpdatePasskeyCoreAsync(user, passkey).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddOrUpdatePasskey); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.AddOrUpdatePasskey, startTimestamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddOrUpdatePasskey, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.AddOrUpdatePasskey, startTimestamp, ex); throw; } } @@ -2642,15 +2691,16 @@ public virtual Task> GetPasskeysAsync(TUser user) /// public virtual async Task RemovePasskeyAsync(TUser user, byte[] credentialId) { + var startTimestamp = Stopwatch.GetTimestamp(); try { var result = await RemovePasskeyCoreAsync(user, credentialId).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RemovePasskey); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, UserUpdateType.RemovePasskey, startTimestamp); return result; } catch (Exception ex) { - _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemovePasskey, ex); + _metrics?.UpdateUser(typeof(TUser).FullName!, result: null, UserUpdateType.RemovePasskey, startTimestamp, ex); throw; } } @@ -2922,10 +2972,10 @@ protected virtual async Task UpdateUserAsync(TUser user) return await Store.UpdateAsync(user, CancellationToken).ConfigureAwait(false); } - private async Task UpdateUserAndRecordMetricAsync(TUser user, UserUpdateType updateType) + private async Task UpdateUserAndRecordMetricAsync(TUser user, UserUpdateType updateType, long startTimestamp) { var result = await UpdateUserAsync(user).ConfigureAwait(false); - _metrics?.UpdateUser(typeof(TUser).FullName!, result, updateType); + _metrics?.UpdateUser(typeof(TUser).FullName!, result, updateType, startTimestamp); return result; } diff --git a/src/Identity/Extensions.Core/src/UserManagerMetrics.cs b/src/Identity/Extensions.Core/src/UserManagerMetrics.cs index 54c0f9f6a016..b54b24887a2c 100644 --- a/src/Identity/Extensions.Core/src/UserManagerMetrics.cs +++ b/src/Identity/Extensions.Core/src/UserManagerMetrics.cs @@ -6,6 +6,8 @@ using System.Diagnostics.Metrics; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Internal; using static Microsoft.AspNetCore.Identity.UserManagerMetrics; namespace Microsoft.AspNetCore.Identity; @@ -14,35 +16,62 @@ internal sealed class UserManagerMetrics : IDisposable { public const string MeterName = "Microsoft.AspNetCore.Identity"; - public const string CreateCounterName = "aspnetcore.identity.user.create"; - public const string UpdateCounterName = "aspnetcore.identity.user.update"; - public const string DeleteCounterName = "aspnetcore.identity.user.delete"; - public const string CheckPasswordCounterName = "aspnetcore.identity.user.check_password"; - public const string VerifyTokenCounterName = "aspnetcore.identity.user.verify_token"; - public const string GenerateTokenCounterName = "aspnetcore.identity.user.generate_token"; + public const string CreateDurationName = "aspnetcore.identity.user.create.duration"; + public const string UpdateDurationName = "aspnetcore.identity.user.update.duration"; + public const string DeleteDurationName = "aspnetcore.identity.user.delete.duration"; + public const string CheckPasswordAttemptsCounterName = "aspnetcore.identity.user.check_password_attempts"; + public const string VerifyTokenAttemptsCounterName = "aspnetcore.identity.user.verify_token_attempts"; + public const string GenerateTokensCounterName = "aspnetcore.identity.user.generated_tokens"; private readonly Meter _meter; - private readonly Counter _createCounter; - private readonly Counter _updateCounter; - private readonly Counter _deleteCounter; - private readonly Counter _checkPasswordCounter; - private readonly Counter _verifyTokenCounter; - private readonly Counter _generateTokenCounter; + private readonly Histogram _createDuration; + private readonly Histogram _updateDuration; + private readonly Histogram _deleteDuration; + private readonly Counter _checkPasswordAttemptsCounter; + private readonly Counter _verifyTokenAttemptsCounter; + private readonly Counter _generateTokensCounter; public UserManagerMetrics(IMeterFactory meterFactory) { _meter = meterFactory.Create(MeterName); - _createCounter = _meter.CreateCounter(CreateCounterName, "{user}", "The number of users created."); - _updateCounter = _meter.CreateCounter(UpdateCounterName, "{user}", "The number of users updated."); - _deleteCounter = _meter.CreateCounter(DeleteCounterName, "{user}", "The number of users deleted."); - _checkPasswordCounter = _meter.CreateCounter(CheckPasswordCounterName, "{check}", "The number of check password attempts. Only checks whether the password is valid and not whether the user account is in a state that can log in."); - _verifyTokenCounter = _meter.CreateCounter(VerifyTokenCounterName, "{count}", "The number of token verification attempts."); - _generateTokenCounter = _meter.CreateCounter(GenerateTokenCounterName, "{count}", "The number of token generation attempts."); + + _createDuration = _meter.CreateHistogram( + CreateDurationName, + unit: "s", + description: "The duration of user creation operations.", + advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries }); + + _updateDuration = _meter.CreateHistogram( + UpdateDurationName, + unit: "s", + description: "The duration of user update operations.", + advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries }); + + _deleteDuration = _meter.CreateHistogram( + DeleteDurationName, + unit: "s", + description: "The duration of user deletion operations.", + advice: new() { HistogramBucketBoundaries = MetricsConstants.ShortSecondsBucketBoundaries }); + + _checkPasswordAttemptsCounter = _meter.CreateCounter( + CheckPasswordAttemptsCounterName, + unit: "{attempt}", + description: "The total number of check password attempts. Only checks whether the password is valid and not whether the user account is in a state that can log in."); + + _verifyTokenAttemptsCounter = _meter.CreateCounter( + VerifyTokenAttemptsCounterName, + unit: "{attempt}", + description: "The total number of token verification attempts."); + + _generateTokensCounter = _meter.CreateCounter( + GenerateTokensCounterName, + unit: "{count}", + description: "The total number of token generations."); } - internal void CreateUser(string userType, IdentityResult? result, Exception? exception = null) + internal void CreateUser(string userType, IdentityResult? result, long startTimestamp, Exception? exception = null) { - if (!_createCounter.Enabled) + if (!_createDuration.Enabled) { return; } @@ -54,12 +83,13 @@ internal void CreateUser(string userType, IdentityResult? result, Exception? exc AddIdentityResultTags(ref tags, result); AddErrorTag(ref tags, exception, result: result); - _createCounter.Add(1, tags); + var duration = GetElapsedTime(startTimestamp); + _createDuration.Record(duration.TotalSeconds, tags); } - internal void UpdateUser(string userType, IdentityResult? result, UserUpdateType updateType, Exception? exception = null) + internal void UpdateUser(string userType, IdentityResult? result, UserUpdateType updateType, long startTimestamp, Exception? exception = null) { - if (!_updateCounter.Enabled) + if (!_updateDuration.Enabled) { return; } @@ -72,12 +102,13 @@ internal void UpdateUser(string userType, IdentityResult? result, UserUpdateType AddIdentityResultTags(ref tags, result); AddErrorTag(ref tags, exception, result: result); - _updateCounter.Add(1, tags); + var duration = GetElapsedTime(startTimestamp); + _updateDuration.Record(duration.TotalSeconds, tags); } - internal void DeleteUser(string userType, IdentityResult? result, Exception? exception = null) + internal void DeleteUser(string userType, IdentityResult? result, long startTimestamp, Exception? exception = null) { - if (!_deleteCounter.Enabled) + if (!_deleteDuration.Enabled) { return; } @@ -89,12 +120,13 @@ internal void DeleteUser(string userType, IdentityResult? result, Exception? exc AddIdentityResultTags(ref tags, result); AddErrorTag(ref tags, exception, result: result); - _deleteCounter.Add(1, tags); + var duration = GetElapsedTime(startTimestamp); + _deleteDuration.Record(duration.TotalSeconds, tags); } internal void CheckPassword(string userType, bool? userMissing, PasswordVerificationResult? result, Exception? exception = null) { - if (!_checkPasswordCounter.Enabled) + if (!_checkPasswordAttemptsCounter.Enabled) { return; } @@ -109,12 +141,12 @@ internal void CheckPassword(string userType, bool? userMissing, PasswordVerifica } AddErrorTag(ref tags, exception); - _checkPasswordCounter.Add(1, tags); + _checkPasswordAttemptsCounter.Add(1, tags); } internal void VerifyToken(string userType, bool? result, string purpose, Exception? exception = null) { - if (!_verifyTokenCounter.Enabled) + if (!_verifyTokenAttemptsCounter.Enabled) { return; } @@ -130,12 +162,12 @@ internal void VerifyToken(string userType, bool? result, string purpose, Excepti } AddErrorTag(ref tags, exception); - _verifyTokenCounter.Add(1, tags); + _verifyTokenAttemptsCounter.Add(1, tags); } internal void GenerateToken(string userType, string purpose, Exception? exception = null) { - if (!_generateTokenCounter.Enabled) + if (!_generateTokensCounter.Enabled) { return; } @@ -147,7 +179,12 @@ internal void GenerateToken(string userType, string purpose, Exception? exceptio }; AddErrorTag(ref tags, exception); - _generateTokenCounter.Add(1, tags); + _generateTokensCounter.Add(1, tags); + } + + private static TimeSpan GetElapsedTime(long startTimestamp) + { + return ValueStopwatch.GetElapsedTime(startTimestamp, Stopwatch.GetTimestamp()); } private static string GetTokenPurpose(string purpose) @@ -215,10 +252,10 @@ private static string GetUpdateType(UserUpdateType updateType) return updateType switch { UserUpdateType.Update => "update", - UserUpdateType.UserName => "user_name", + UserUpdateType.SetUserName => "set_user_name", UserUpdateType.AddPassword => "add_password", UserUpdateType.ChangePassword => "change_password", - UserUpdateType.SecurityStamp => "security_stamp", + UserUpdateType.UpdateSecurityStamp => "update_security_stamp", UserUpdateType.ResetPassword => "reset_password", UserUpdateType.RemoveLogin => "remove_login", UserUpdateType.AddLogin => "add_login", @@ -237,7 +274,7 @@ private static string GetUpdateType(UserUpdateType updateType) UserUpdateType.SetTwoFactorEnabled => "set_two_factor_enabled", UserUpdateType.SetLockoutEnabled => "set_lockout_enabled", UserUpdateType.SetLockoutEndDate => "set_lockout_end_date", - UserUpdateType.AccessFailed => "access_failed", + UserUpdateType.IncrementAccessFailed => "increment_access_failed", UserUpdateType.ResetAccessFailedCount => "reset_access_failed_count", UserUpdateType.SetAuthenticationToken => "set_authentication_token", UserUpdateType.RemoveAuthenticationToken => "remove_authentication_token", diff --git a/src/Identity/Extensions.Core/src/UserUpdateType.cs b/src/Identity/Extensions.Core/src/UserUpdateType.cs index ed60fe9a1a9f..4e9eeb06baae 100644 --- a/src/Identity/Extensions.Core/src/UserUpdateType.cs +++ b/src/Identity/Extensions.Core/src/UserUpdateType.cs @@ -6,10 +6,10 @@ namespace Microsoft.AspNetCore.Identity; internal enum UserUpdateType { Update, - UserName, + SetUserName, AddPassword, ChangePassword, - SecurityStamp, + UpdateSecurityStamp, ResetPassword, RemoveLogin, AddLogin, @@ -28,7 +28,7 @@ internal enum UserUpdateType SetTwoFactorEnabled, SetLockoutEnabled, SetLockoutEndDate, - AccessFailed, + IncrementAccessFailed, ResetAccessFailedCount, SetAuthenticationToken, RemoveAuthenticationToken, diff --git a/src/Identity/test/Identity.Test/SignInManagerTest.cs b/src/Identity/test/Identity.Test/SignInManagerTest.cs index 2cddbeae36e3..c71662ecc295 100644 --- a/src/Identity/test/Identity.Test/SignInManagerTest.cs +++ b/src/Identity/test/Identity.Test/SignInManagerTest.cs @@ -39,8 +39,8 @@ public async Task PasswordSignInReturnsLockedOutWhenLockedOut() { // Setup var testMeterFactory = new TestMeterFactory(); - using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName); - using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInUserPrincipalCounterName); + using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateDurationName); + using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInsCounterName); var user = new PocoUser { UserName = "Foo" }; var manager = SetupUserManager(user, meterFactory: testMeterFactory); @@ -68,12 +68,12 @@ public async Task PasswordSignInReturnsLockedOutWhenLockedOut() manager.Verify(); Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("aspnetcore.identity.sign_in.type", "password"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "locked_out"), ])); Assert.Empty(signInUserPrincipal.GetMeasurementSnapshot()); @@ -84,7 +84,7 @@ public async Task CheckPasswordSignInReturnsLockedOutWhenLockedOut() { // Setup var testMeterFactory = new TestMeterFactory(); - using var checkPasswordSignIn = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.CheckPasswordCounterName); + using var checkPasswordSignIn = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.CheckPasswordAttemptsCounterName); var user = new PocoUser { UserName = "Foo" }; var manager = SetupUserManager(user, meterFactory: testMeterFactory); @@ -348,8 +348,8 @@ public async Task ExternalSignInRequiresVerificationIfNotBypassed(bool bypass) { // Setup var testMeterFactory = new TestMeterFactory(); - using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName); - using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInUserPrincipalCounterName); + using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateDurationName); + using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInsCounterName); var user = new PocoUser { UserName = "Foo" }; const string loginProvider = "login"; @@ -392,31 +392,31 @@ public async Task ExternalSignInRequiresVerificationIfNotBypassed(bool bypass) if (bypass) { Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("aspnetcore.identity.sign_in.type", "external"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "success"), ])); Assert.Collection(signInUserPrincipal.GetMeasurementSnapshot(), m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), ])); } else { Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("aspnetcore.identity.sign_in.type", "external"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "requires_two_factor"), ])); Assert.Empty(signInUserPrincipal.GetMeasurementSnapshot()); @@ -428,8 +428,8 @@ public async Task CanPasskeySignIn() { // Setup var testMeterFactory = new TestMeterFactory(); - using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName); - using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInUserPrincipalCounterName); + using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateDurationName); + using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInsCounterName); var user = new PocoUser { UserName = "Foo" }; var passkey = new UserPasskeyInfo(null, null, default, 0, null, false, false, false, null, null); @@ -469,20 +469,20 @@ public async Task CanPasskeySignIn() auth.Verify(); Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("aspnetcore.identity.sign_in.type", "passkey"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "success"), ])); Assert.Collection(signInUserPrincipal.GetMeasurementSnapshot(), m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), ])); } @@ -778,7 +778,7 @@ public async Task SignInAsync_Failure() { // Setup var testMeterFactory = new TestMeterFactory(); - using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInUserPrincipalCounterName); + using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInsCounterName); var user = new PocoUser { UserName = "Foo" }; var manager = SetupUserManager(user, meterFactory: testMeterFactory); @@ -800,7 +800,7 @@ public async Task SignInAsync_Failure() m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("error.type", "System.InvalidOperationException"), ])); } @@ -814,7 +814,7 @@ public async Task CanResignIn(bool isPersistent, bool externalLogin) { // Setup var testMeterFactory = new TestMeterFactory(); - using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName); + using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateDurationName); var user = new PocoUser { UserName = "Foo" }; var context = new DefaultHttpContext(); @@ -855,11 +855,11 @@ public async Task CanResignIn(bool isPersistent, bool externalLogin) signInManager.Verify(); Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", isPersistent), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", isPersistent), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "success"), ])); } @@ -868,8 +868,8 @@ public async Task CanResignIn(bool isPersistent, bool externalLogin) public async Task ResignInNoOpsAndLogsErrorIfNotAuthenticated() { var testMeterFactory = new TestMeterFactory(); - using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName); - using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInUserPrincipalCounterName); + using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateDurationName); + using var signInUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignInsCounterName); var user = new PocoUser { UserName = "Foo" }; var context = new DefaultHttpContext(); @@ -892,10 +892,10 @@ public async Task ResignInNoOpsAndLogsErrorIfNotAuthenticated() Times.Never()); Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "failure"), ])); Assert.Empty(signInUserPrincipal.GetMeasurementSnapshot()); @@ -1044,7 +1044,7 @@ public async Task RememberClientStoresUserId() { // Setup var testMeterFactory = new TestMeterFactory(); - using var rememberTwoFactorClient = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.RememberTwoFactorCounterName); + using var rememberTwoFactorClient = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.RememberedTwoFactorCounterName); var user = new PocoUser { UserName = "Foo" }; var manager = SetupUserManager(user, meterFactory: testMeterFactory); @@ -1069,7 +1069,7 @@ public async Task RememberClientStoresUserId() m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.TwoFactorRememberMe"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.TwoFactorRememberMe"), ])); } @@ -1078,7 +1078,7 @@ public async Task ForgetTwoFactorClient() { // Setup var testMeterFactory = new TestMeterFactory(); - using var forgetTwoFactorClient = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.ForgetTwoFactorCounterName); + using var forgetTwoFactorClient = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.ForgottenTwoFactorCounterName); var user = new PocoUser { UserName = "Foo" }; var manager = SetupUserManager(user, meterFactory: testMeterFactory); @@ -1101,7 +1101,7 @@ public async Task ForgetTwoFactorClient() m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.TwoFactorRememberMe"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.TwoFactorRememberMe"), KeyValuePair.Create("error.type", "System.InvalidOperationException"), ])); } @@ -1152,7 +1152,7 @@ public async Task SignOutCallsContextResponseSignOut_Success() { // Setup var testMeterFactory = new TestMeterFactory(); - using var signOutUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignOutUserPrincipalCounterName); + using var signOutUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignOutsCounterName); var manager = MockHelpers.TestUserManager(meterFactory: testMeterFactory); var context = new DefaultHttpContext(); @@ -1172,7 +1172,7 @@ public async Task SignOutCallsContextResponseSignOut_Success() m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), ])); } @@ -1181,7 +1181,7 @@ public async Task SignOutCallsContextResponseSignOut_Failure() { // Setup var testMeterFactory = new TestMeterFactory(); - using var signOutUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignOutUserPrincipalCounterName); + using var signOutUserPrincipal = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.SignOutsCounterName); var manager = MockHelpers.TestUserManager(meterFactory: testMeterFactory); var context = new DefaultHttpContext(); @@ -1197,7 +1197,7 @@ public async Task SignOutCallsContextResponseSignOut_Failure() m => MetricsHelpers.AssertContainsTags(m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), KeyValuePair.Create("error.type", "System.InvalidOperationException"), ])); } @@ -1207,7 +1207,7 @@ public async Task PasswordSignInFailsWithWrongPassword() { // Setup var testMeterFactory = new TestMeterFactory(); - using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateCounterName); + using var authenticate = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", SignInManagerMetrics.AuthenticateDurationName); var user = new PocoUser { UserName = "Foo" }; var manager = SetupUserManager(user, meterFactory: testMeterFactory); @@ -1230,11 +1230,11 @@ public async Task PasswordSignInFailsWithWrongPassword() context.Verify(); Assert.Collection(authenticate.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), - KeyValuePair.Create("aspnetcore.identity.authentication_scheme", "Identity.Application"), - KeyValuePair.Create("aspnetcore.identity.sign_in.is_persistent", false), + KeyValuePair.Create("aspnetcore.authentication.scheme", "Identity.Application"), + KeyValuePair.Create("aspnetcore.authentication.is_persistent", false), KeyValuePair.Create("aspnetcore.identity.sign_in.result", "failure"), KeyValuePair.Create("aspnetcore.identity.sign_in.type", "password"), ])); diff --git a/src/Identity/test/Identity.Test/UserManagerTest.cs b/src/Identity/test/Identity.Test/UserManagerTest.cs index 80a5382e795f..74c18d5c133b 100644 --- a/src/Identity/test/Identity.Test/UserManagerTest.cs +++ b/src/Identity/test/Identity.Test/UserManagerTest.cs @@ -128,7 +128,7 @@ public async Task DeleteCallsStore_Success() { // Setup var testMeterFactory = new TestMeterFactory(); - using var deleteUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.DeleteCounterName); + using var deleteUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.DeleteDurationName); var store = new Mock>(); var user = new PocoUser { UserName = "Foo" }; @@ -143,7 +143,7 @@ public async Task DeleteCallsStore_Success() store.VerifyAll(); Assert.Collection(deleteUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), KeyValuePair.Create("aspnetcore.identity.result", "success") @@ -155,7 +155,7 @@ public async Task DeleteCallsStore_Failure() { // Setup var testMeterFactory = new TestMeterFactory(); - using var deleteUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.DeleteCounterName); + using var deleteUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.DeleteDurationName); var store = new Mock>(); var user = new PocoUser { UserName = "Foo" }; @@ -170,7 +170,7 @@ public async Task DeleteCallsStore_Failure() store.VerifyAll(); Assert.Collection(deleteUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), KeyValuePair.Create("aspnetcore.identity.result", "failure"), @@ -564,7 +564,7 @@ public async Task AddClaimCallsStore() { // Setup var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var store = new Mock>(); var user = new PocoUser { UserName = "Foo" }; @@ -583,7 +583,7 @@ public async Task AddClaimCallsStore() store.VerifyAll(); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "add_claims"), KeyValuePair.Create("aspnetcore.identity.result", "success") @@ -595,7 +595,7 @@ public async Task UpdateClaimCallsStore() { // Setup var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var store = new Mock>(); var user = new PocoUser { UserName = "Foo" }; @@ -615,7 +615,7 @@ public async Task UpdateClaimCallsStore() store.VerifyAll(); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "replace_claim"), KeyValuePair.Create("aspnetcore.identity.result", "success") @@ -627,8 +627,8 @@ public async Task CheckPasswordWillRehashPasswordWhenNeeded() { // Setup var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); - using var checkPassword = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CheckPasswordCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); + using var checkPassword = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CheckPasswordAttemptsCounterName); var store = new Mock>(); var hasher = new Mock>(); @@ -656,7 +656,8 @@ public async Task CheckPasswordWillRehashPasswordWhenNeeded() store.VerifyAll(); hasher.VerifyAll(); - Assert.Collection(updateUser.GetMeasurementSnapshot(), m => MetricsHelpers.AssertContainsTags(m.Tags, + Assert.Collection(updateUser.GetMeasurementSnapshot(), + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "password_rehash"), KeyValuePair.Create("aspnetcore.identity.result", "success") @@ -673,7 +674,7 @@ public async Task CreateFailsWithNullSecurityStamp() { // Setup var testMeterFactory = new TestMeterFactory(); - using var createUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CreateCounterName); + using var createUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CreateDurationName); var store = new Mock>(); var manager = MockHelpers.TestUserManager(store.Object, meterFactory: testMeterFactory); @@ -688,7 +689,7 @@ public async Task CreateFailsWithNullSecurityStamp() store.VerifyAll(); Assert.Collection(createUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user_type", "Microsoft.AspNetCore.Identity.Test.PocoUser"), KeyValuePair.Create("error.type", "System.InvalidOperationException") @@ -700,7 +701,7 @@ public async Task UpdateFailsWithNullSecurityStamp() { // Setup var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var store = new Mock>(); var manager = MockHelpers.TestUserManager(store.Object, meterFactory: testMeterFactory); @@ -715,7 +716,7 @@ public async Task UpdateFailsWithNullSecurityStamp() store.VerifyAll(); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "update"), KeyValuePair.Create("error.type", "System.InvalidOperationException") @@ -727,7 +728,7 @@ public async Task RemoveClaimsCallsStore() { // Setup var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var store = new Mock>(); var user = new PocoUser { UserName = "Foo" }; @@ -746,7 +747,7 @@ public async Task RemoveClaimsCallsStore() store.VerifyAll(); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "remove_claims"), KeyValuePair.Create("aspnetcore.identity.result", "success") @@ -758,7 +759,7 @@ public async Task RemoveClaimCallsStore() { // Setup var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var store = new Mock>(); var user = new PocoUser { UserName = "Foo" }; @@ -777,7 +778,7 @@ public async Task RemoveClaimCallsStore() store.VerifyAll(); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "remove_claims"), KeyValuePair.Create("aspnetcore.identity.result", "success") @@ -863,7 +864,7 @@ public async Task RemovePasskeyAsyncCallsStore() public async Task CheckPasswordWithNullUserReturnsFalse() { var testMeterFactory = new TestMeterFactory(); - using var checkPassword = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CheckPasswordCounterName); + using var checkPassword = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CheckPasswordAttemptsCounterName); var manager = MockHelpers.TestUserManager(new EmptyStore(), meterFactory: testMeterFactory); Assert.False(await manager.CheckPasswordAsync(null, "whatevs")); @@ -887,7 +888,7 @@ public void UsersQueryableFailWhenStoreNotImplemented() public async Task UsersEmailMethodsFailWhenStoreNotImplemented() { var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var manager = MockHelpers.TestUserManager(new NoopUserStore(), meterFactory: testMeterFactory); Assert.False(manager.SupportsUserEmail); @@ -898,12 +899,12 @@ public async Task UsersEmailMethodsFailWhenStoreNotImplemented() await Assert.ThrowsAsync(() => manager.ConfirmEmailAsync(null, null)); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "set_email"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "confirm_email"), KeyValuePair.Create("error.type", "System.NotSupportedException"), @@ -914,7 +915,7 @@ public async Task UsersEmailMethodsFailWhenStoreNotImplemented() public async Task UsersPhoneNumberMethodsFailWhenStoreNotImplemented() { var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var manager = MockHelpers.TestUserManager(new NoopUserStore(), meterFactory: testMeterFactory); Assert.False(manager.SupportsUserPhoneNumber); @@ -923,12 +924,12 @@ public async Task UsersPhoneNumberMethodsFailWhenStoreNotImplemented() await Assert.ThrowsAsync(async () => await manager.GetPhoneNumberAsync(null)); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "set_phone_number"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "change_phone_number"), KeyValuePair.Create("error.type", "System.NotSupportedException"), @@ -939,8 +940,8 @@ public async Task UsersPhoneNumberMethodsFailWhenStoreNotImplemented() public async Task TokenMethodsThrowWithNoTokenProvider() { var testMeterFactory = new TestMeterFactory(); - using var generateToken = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.GenerateTokenCounterName); - using var verifyToken = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.VerifyTokenCounterName); + using var generateToken = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.GenerateTokensCounterName); + using var verifyToken = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.VerifyTokenAttemptsCounterName); var manager = MockHelpers.TestUserManager(new NoopUserStore(), meterFactory: testMeterFactory); var user = new PocoUser(); @@ -967,9 +968,9 @@ await Assert.ThrowsAsync( public async Task PasswordMethodsFailWhenStoreNotImplemented() { var testMeterFactory = new TestMeterFactory(); - using var createUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CreateCounterName); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); - using var checkPassword = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CheckPasswordCounterName); + using var createUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CreateDurationName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); + using var checkPassword = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.CheckPasswordAttemptsCounterName); var manager = MockHelpers.TestUserManager(new NoopUserStore(), meterFactory: testMeterFactory); Assert.False(manager.SupportsUserPassword); @@ -981,22 +982,22 @@ public async Task PasswordMethodsFailWhenStoreNotImplemented() await Assert.ThrowsAsync(() => manager.HasPasswordAsync(null)); Assert.Collection(createUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("error.type", "System.NotSupportedException"), ])); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "change_password"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "add_password"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "remove_password"), KeyValuePair.Create("error.type", "System.NotSupportedException"), @@ -1012,8 +1013,8 @@ public async Task PasswordMethodsFailWhenStoreNotImplemented() public async Task SecurityStampMethodsFailWhenStoreNotImplemented() { var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); - using var generateToken = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.GenerateTokenCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); + using var generateToken = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.GenerateTokensCounterName); var store = new Mock>(); store.Setup(x => x.GetUserIdAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(Guid.NewGuid().ToString())); @@ -1027,9 +1028,9 @@ await Assert.ThrowsAsync( () => manager.GenerateChangePhoneNumberTokenAsync(new PocoUser(), "111-111-1111")); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ - KeyValuePair.Create("aspnetcore.identity.user.update_type", "security_stamp"), + KeyValuePair.Create("aspnetcore.identity.user.update_type", "update_security_stamp"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ])); Assert.Collection(generateToken.GetMeasurementSnapshot(), @@ -1044,7 +1045,7 @@ await Assert.ThrowsAsync( public async Task LoginMethodsFailWhenStoreNotImplemented() { var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var manager = MockHelpers.TestUserManager(new NoopUserStore(), meterFactory: testMeterFactory); Assert.False(manager.SupportsUserLogin); @@ -1054,12 +1055,12 @@ public async Task LoginMethodsFailWhenStoreNotImplemented() await Assert.ThrowsAsync(async () => await manager.FindByLoginAsync(null, null)); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "add_login"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "remove_login"), KeyValuePair.Create("error.type", "System.NotSupportedException"), @@ -1070,7 +1071,7 @@ public async Task LoginMethodsFailWhenStoreNotImplemented() public async Task ClaimMethodsFailWhenStoreNotImplemented() { var testMeterFactory = new TestMeterFactory(); - using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateCounterName); + using var updateUser = new MetricCollector(testMeterFactory, "Microsoft.AspNetCore.Identity", UserManagerMetrics.UpdateDurationName); var manager = MockHelpers.TestUserManager(new NoopUserStore(), meterFactory: testMeterFactory); Assert.False(manager.SupportsUserClaim); @@ -1080,17 +1081,17 @@ public async Task ClaimMethodsFailWhenStoreNotImplemented() await Assert.ThrowsAsync(async () => await manager.GetClaimsAsync(null)); Assert.Collection(updateUser.GetMeasurementSnapshot(), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "add_claims"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "replace_claim"), KeyValuePair.Create("error.type", "System.NotSupportedException"), ]), - m => MetricsHelpers.AssertContainsTags(m.Tags, + m => MetricsHelpers.AssertHasDurationAndContainsTags(m.Value, m.Tags, [ KeyValuePair.Create("aspnetcore.identity.user.update_type", "remove_claims"), KeyValuePair.Create("error.type", "System.NotSupportedException"), diff --git a/src/Identity/test/Shared/MetricsHelpers.cs b/src/Identity/test/Shared/MetricsHelpers.cs index ab86e504eea9..afeff728e06e 100644 --- a/src/Identity/test/Shared/MetricsHelpers.cs +++ b/src/Identity/test/Shared/MetricsHelpers.cs @@ -5,6 +5,12 @@ namespace Microsoft.AspNetCore.Identity.Test; public static class MetricsHelpers { + public static void AssertHasDurationAndContainsTags(double duration, IReadOnlyDictionary tags, List> expectedTags) + { + Assert.True(duration > 0, "Duration should be greater than 0."); + AssertContainsTags(tags, expectedTags); + } + public static void AssertContainsTags(IReadOnlyDictionary tags, List> expectedTags) { var found = 0; diff --git a/src/Shared/Metrics/MetricsConstants.cs b/src/Shared/Metrics/MetricsConstants.cs index d62983f9bba4..6c527315bbf9 100644 --- a/src/Shared/Metrics/MetricsConstants.cs +++ b/src/Shared/Metrics/MetricsConstants.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; + namespace Microsoft.AspNetCore.Http; internal static class MetricsConstants diff --git a/src/Shared/ValueStopwatch/ValueStopwatch.cs b/src/Shared/ValueStopwatch/ValueStopwatch.cs index d96ec70f87bb..7366ee2f2a06 100644 --- a/src/Shared/ValueStopwatch/ValueStopwatch.cs +++ b/src/Shared/ValueStopwatch/ValueStopwatch.cs @@ -23,6 +23,17 @@ private ValueStopwatch(long startTimestamp) public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp()); + public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) + { +#if !NET7_0_OR_GREATER + var timestampDelta = endingTimestamp - startingTimestamp; + var ticks = (long)(TimestampToTicks * timestampDelta); + return new TimeSpan(ticks); +#else + return Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp); +#endif + } + public TimeSpan GetElapsedTime() { // Start timestamp can't be zero in an initialized ValueStopwatch. It would have to be literally the first thing executed when the machine boots to be 0. @@ -34,12 +45,6 @@ public TimeSpan GetElapsedTime() var end = Stopwatch.GetTimestamp(); -#if !NET7_0_OR_GREATER - var timestampDelta = end - _startTimestamp; - var ticks = (long)(TimestampToTicks * timestampDelta); - return new TimeSpan(ticks); -#else - return Stopwatch.GetElapsedTime(_startTimestamp, end); -#endif + return GetElapsedTime(_startTimestamp, end); } }