From 9d77683ff3e5782fc23ee856d318674de5657206 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Tue, 2 May 2023 00:41:55 +0000 Subject: [PATCH 1/5] Completely emitting aggr candidate+auth metrics This finalizes the aggregate candidate to fully combine the information from authentication entries and candidate entries, and emits the combined flow. Clean up is still required. Bug: 271135048 Test: Build + Won't Submit without E2E Test Change-Id: I72140e75ccec92779a7803524cc2746b9eed2ab5 --- .../server/credentials/MetricUtilities.java | 19 ++-- .../metrics/BrowsedAuthenticationMetric.java | 3 +- .../metrics/CandidateAggregateMetric.java | 104 +++++++++++++++++- .../metrics/ProviderSessionMetric.java | 17 +-- 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 749d6b6246d0b..25400394816e9 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -103,6 +103,10 @@ public class MetricUtilities { if (t2 - t1 > Integer.MAX_VALUE) { throw new ArithmeticException("Input timestamps are too far apart and unsupported"); } + if (t2 < t1) { + Slog.i(TAG, "The timestamps aren't in expected order, falling back to default int"); + return DEFAULT_INT_32; + } return (int) ((t2 - t1) / 1000); } @@ -466,11 +470,11 @@ public class MetricUtilities { candidateAggregateMetric.getAggregateCollectiveQuery() .getUniqueEntryCounts(), /*query_total_candidate_failure*/ - DEFAULT_INT_32, + candidateAggregateMetric.getTotalQueryFailures(), /*query_framework_exception_unique_classtypes*/ - DEFAULT_REPEATED_STR, + candidateAggregateMetric.getUniqueExceptionStringsQuery(), /*query_per_exception_classtype_counts*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getUniqueExceptionCountsQuery(), /*auth_response_unique_classtypes*/ candidateAggregateMetric.getAggregateCollectiveAuth() .getUniqueResponseStrings(), @@ -484,14 +488,15 @@ public class MetricUtilities { candidateAggregateMetric.getAggregateCollectiveAuth() .getUniqueEntryCounts(), /*auth_total_candidate_failure*/ - DEFAULT_INT_32, + candidateAggregateMetric.getTotalAuthFailures(), /*auth_framework_exception_unique_classtypes*/ - DEFAULT_REPEATED_STR, + candidateAggregateMetric.getUniqueExceptionStringsAuth(), /*auth_per_exception_classtype_counts*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getUniqueExceptionCountsAuth(), /*num_auth_clicks*/ candidateAggregateMetric.getNumAuthEntriesTapped(), - /*auth_returned*/ false + /*auth_returned*/ + candidateAggregateMetric.isAuthReturned() ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during metric logging: " + e); diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java index 98ea5b977a925..e24d7994a7978 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -25,10 +25,9 @@ import java.util.Map; * Contains information about what was collected from the authentication entry output. */ public class BrowsedAuthenticationMetric { - private static final String TAG = "BrowsedAuthenticationMetric"; + private static final String TAG = "AuthenticationMetric"; // The session id of this provider known flow related metric private final int mSessionIdProvider; - // The provider associated with the press, defaults to -1 private int mProviderUid = -1; diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java index 05d048d0e79ec..5a2a07994e505 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -24,7 +24,8 @@ import java.util.Map; /** * This will generate most of its data via using the information of {@link CandidatePhaseMetric} - * across all the providers. This belongs to the metric flow where the calling app is known. + * across all the providers. This belongs to the metric flow where the calling app is known. It + * also contains {@link BrowsedAuthenticationMetric} data aggregated within. */ public class CandidateAggregateMetric { @@ -34,6 +35,9 @@ public class CandidateAggregateMetric { // Indicates if this provider returned from the candidate query phase, // true if at least one provider returns validly, even if empty, default false private boolean mQueryReturned = false; + // For reference, the initial log timestamp when the service started running the API call, + // defaults to -1 + private long mServiceBeganTimeNanoseconds = -1; // Indicates the total number of providers this aggregate captures information for, default 0 private int mNumProviders = 0; // Indicates if the authentication entry returned, true if at least one entry returns validly, @@ -47,6 +51,18 @@ public class CandidateAggregateMetric { // The combined aggregate collective across the auth entry info private ResponseCollective mAggregateCollectiveAuth = new ResponseCollective(Map.of(), Map.of()); + // The minimum of all the providers query start time, defaults to -1 + private long mMinProviderTimestampNanoseconds = -1; + // The maximum of all the providers query finish time, defaults to -1 + private long mMaxProviderTimestampsNanoseconds = -1; + // The total number of failures across all the providers, defaults to -1 + private int mTotalQueryFailures = -1; + // The map of all seen framework exceptions and their counts across all providers, default empty + private Map mExceptionCountQuery = new LinkedHashMap<>(); + // The total number of failures across all auth entries, defaults to -1 + private int mTotalAuthFailures = -1; + // The map of all seen framework exceptions and their counts across auth entries, default empty + private Map mExceptionCountAuth = new LinkedHashMap<>(); public CandidateAggregateMetric(int sessionIdTrackOne) { mSessionIdProvider = sessionIdTrackOne; @@ -72,16 +88,33 @@ public class CandidateAggregateMetric { Map responseCountQuery = new LinkedHashMap<>(); Map entryCountQuery = new LinkedHashMap<>(); var providerSessions = providers.values(); + long min_query_start = Integer.MAX_VALUE; + long max_query_end = Integer.MIN_VALUE; for (var session : providerSessions) { var sessionMetric = session.getProviderSessionMetric(); var candidateMetric = sessionMetric.getCandidatePhasePerProviderMetric(); + if (mServiceBeganTimeNanoseconds == -1) { + mServiceBeganTimeNanoseconds = candidateMetric.getServiceBeganTimeNanoseconds(); + } mQueryReturned = mQueryReturned || candidateMetric.isQueryReturned(); ResponseCollective candidateCollective = candidateMetric.getResponseCollective(); ResponseCollective.combineTypeCountMaps(responseCountQuery, candidateCollective.getResponseCountsMap()); ResponseCollective.combineTypeCountMaps(entryCountQuery, candidateCollective.getEntryCountsMap()); + min_query_start = Math.min(min_query_start, + candidateMetric.getStartQueryTimeNanoseconds()); + max_query_end = Math.max(max_query_end, candidateMetric + .getQueryFinishTimeNanoseconds()); + mTotalQueryFailures += (candidateMetric.isHasException() ? 1 : 0); + if (!candidateMetric.getFrameworkException().isEmpty()) { + mExceptionCountQuery.put(candidateMetric.getFrameworkException(), + mExceptionCountQuery.getOrDefault( + candidateMetric.getFrameworkException(), 0) + 1); + } } + mMinProviderTimestampNanoseconds = min_query_start; + mMaxProviderTimestampsNanoseconds = max_query_end; mAggregateCollectiveQuery = new ResponseCollective(responseCountQuery, entryCountQuery); } @@ -101,6 +134,12 @@ public class CandidateAggregateMetric { authCollective.getResponseCountsMap()); ResponseCollective.combineTypeCountMaps(entryCountAuth, authCollective.getEntryCountsMap()); + mTotalQueryFailures += (authMetric.isHasException() ? 1 : 0); + if (!authMetric.getFrameworkException().isEmpty()) { + mExceptionCountQuery.put(authMetric.getFrameworkException(), + mExceptionCountQuery.getOrDefault( + authMetric.getFrameworkException(), 0) + 1); + } } } mAggregateCollectiveAuth = new ResponseCollective(responseCountAuth, entryCountAuth); @@ -130,4 +169,67 @@ public class CandidateAggregateMetric { public boolean isAuthReturned() { return mAuthReturned; } + + public long getMaxProviderTimestampsNanoseconds() { + return mMaxProviderTimestampsNanoseconds; + } + + public long getMinProviderTimestampNanoseconds() { + return mMinProviderTimestampNanoseconds; + } + + public int getTotalQueryFailures() { + return mTotalQueryFailures; + } + + /** + * Returns the unique, deduped, exception classtypes for logging associated with this provider. + * + * @return a string array for deduped exception classtypes + */ + public String[] getUniqueExceptionStringsQuery() { + String[] result = new String[mExceptionCountQuery.keySet().size()]; + mExceptionCountQuery.keySet().toArray(result); + return result; + } + + /** + * Returns the unique, deduped, exception classtype counts for logging associated with this + * provider. + * + * @return a string array for deduped classtype exception counts + */ + public int[] getUniqueExceptionCountsQuery() { + return mExceptionCountQuery.values().stream().mapToInt(Integer::intValue).toArray(); + } + + /** + * Returns the unique, deduped, exception classtypes for logging associated with this provider + * for auth entries. + * + * @return a string array for deduped exception classtypes for auth entries + */ + public String[] getUniqueExceptionStringsAuth() { + String[] result = new String[mExceptionCountAuth.keySet().size()]; + mExceptionCountAuth.keySet().toArray(result); + return result; + } + + /** + * Returns the unique, deduped, exception classtype counts for logging associated with this + * provider for auth entries. + * + * @return a string array for deduped classtype exception counts for auth entries + */ + public int[] getUniqueExceptionCountsAuth() { + return mExceptionCountAuth.values().stream().mapToInt(Integer::intValue).toArray(); + } + + public long getServiceBeganTimeNanoseconds() { + return mServiceBeganTimeNanoseconds; + } + + public int getTotalAuthFailures() { + return mTotalAuthFailures; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java index 629b2dfe55878..da386fd762b1f 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -100,8 +100,8 @@ public class ProviderSessionMetric { */ public void collectAuthenticationExceptionStatus(boolean hasException) { try { - var mostRecentAuthenticationMetric = mBrowsedAuthenticationMetric - .get(mBrowsedAuthenticationMetric.size() - 1); + BrowsedAuthenticationMetric mostRecentAuthenticationMetric = + getUsedAuthenticationMetric(); mostRecentAuthenticationMetric.setHasException(hasException); } catch (Exception e) { Slog.i(TAG, "Error while setting authentication metric exception " + e); @@ -122,13 +122,9 @@ public class ProviderSessionMetric { private void collectAuthEntryUpdate(boolean isFailureStatus, boolean isCompletionStatus, int providerSessionUid) { - // TODO(b/271135048) - Mimic typical candidate update, but with authentication metric - // Collect the final timestamps (and start timestamp), status, exceptions and the provider - // uid. This occurs typically *after* the collection is complete. - var mostRecentAuthenticationMetric = mBrowsedAuthenticationMetric - .get(mBrowsedAuthenticationMetric.size() - 1); + BrowsedAuthenticationMetric mostRecentAuthenticationMetric = + getUsedAuthenticationMetric(); mostRecentAuthenticationMetric.setProviderUid(providerSessionUid); - // TODO(immediately) - add timestamps (no longer needed!!) but also update below values! if (isFailureStatus) { mostRecentAuthenticationMetric.setQueryReturned(false); mostRecentAuthenticationMetric.setProviderStatus( @@ -142,6 +138,11 @@ public class ProviderSessionMetric { } } + private BrowsedAuthenticationMetric getUsedAuthenticationMetric() { + return mBrowsedAuthenticationMetric + .get(mBrowsedAuthenticationMetric.size() - 1); + } + /** * Used to collect metrics at the update stage when a candidate provider gives back an update. * From e68cc2f42b3653f7f513e8e53f35b39b81215318 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Tue, 2 May 2023 20:55:20 +0000 Subject: [PATCH 2/5] Setting up the emit of the aggregate phase This finds a proper spot to emit the aggregate phase of track 1. Since we emit final phases of both track 1 and 2 using the same information right afterwards, the aggregate phase can go right before that. That's because it needs to fully collect the authentication entry clicks, which may loop quite a while after the candidate finishes. Bug: 271135048 Test: Build and won't submit without E2E Test Change-Id: I94e95ead6e558fcb1f6be10d617f311b65eed7c3 --- .../server/credentials/MetricUtilities.java | 10 +++++++--- .../server/credentials/RequestSession.java | 2 ++ .../metrics/BrowsedAuthenticationMetric.java | 10 +++++----- .../metrics/CandidateAggregateMetric.java | 18 +++++++++--------- .../metrics/ProviderSessionMetric.java | 4 ++-- .../metrics/RequestSessionMetric.java | 2 ++ 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 25400394816e9..a86974a47d2ab 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -226,7 +226,7 @@ public class MetricUtilities { /* auth_provider_status */ authenticationMetric.getProviderStatus(), /* query_returned */ - authenticationMetric.isQueryReturned() + authenticationMetric.isAuthReturned() ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e); @@ -454,9 +454,13 @@ public class MetricUtilities { /*query_returned*/ candidateAggregateMetric.isQueryReturned(), /*num_query_providers*/ candidateAggregateMetric.getNumProviders(), /*min_query_start_timestamp_microseconds*/ - DEFAULT_INT_32, + getMetricTimestampDifferenceMicroseconds( + candidateAggregateMetric.getMinProviderTimestampNanoseconds(), + candidateAggregateMetric.getServiceBeganTimeNanoseconds()), /*max_query_end_timestamp_microseconds*/ - DEFAULT_INT_32, + getMetricTimestampDifferenceMicroseconds( + candidateAggregateMetric.getMaxProviderTimestampNanoseconds(), + candidateAggregateMetric.getServiceBeganTimeNanoseconds()), /*query_response_unique_classtypes*/ candidateAggregateMetric.getAggregateCollectiveQuery() .getUniqueResponseStrings(), diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index e48534c9c0413..38372ad551784 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -304,6 +304,7 @@ abstract class RequestSession implements CredentialManagerUi.Credential * @param response the response associated with the API call that just completed */ protected void respondToClientWithResponseAndFinish(V response) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false, ProviderStatusForMetrics.FINAL_SUCCESS); if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { @@ -337,6 +338,7 @@ abstract class RequestSession implements CredentialManagerUi.Credential * @param errorMsg the error message given back in the flow */ protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { + mRequestSessionMetric.logCandidateAggregateMetrics(mProviders); mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java index e24d7994a7978..62b8f24eb2013 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -41,7 +41,7 @@ public class BrowsedAuthenticationMetric { // The status of this particular provider private int mProviderStatus = -1; // Indicates if this provider returned from the authentication entry query, default false - private boolean mQueryReturned = false; + private boolean mAuthReturned = false; // TODO(b/271135048) - Match the atom and provide a clean per provider session metric // encapsulation. @@ -83,12 +83,12 @@ public class BrowsedAuthenticationMetric { mProviderStatus = providerStatus; } - public void setQueryReturned(boolean queryReturned) { - mQueryReturned = queryReturned; + public void setAuthReturned(boolean authReturned) { + mAuthReturned = authReturned; } - public boolean isQueryReturned() { - return mQueryReturned; + public boolean isAuthReturned() { + return mAuthReturned; } public int getProviderStatus() { diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java index 5a2a07994e505..339c221b0cccb 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -54,13 +54,13 @@ public class CandidateAggregateMetric { // The minimum of all the providers query start time, defaults to -1 private long mMinProviderTimestampNanoseconds = -1; // The maximum of all the providers query finish time, defaults to -1 - private long mMaxProviderTimestampsNanoseconds = -1; - // The total number of failures across all the providers, defaults to -1 - private int mTotalQueryFailures = -1; + private long mMaxProviderTimestampNanoseconds = -1; + // The total number of failures across all the providers, defaults to 0 + private int mTotalQueryFailures = 0; // The map of all seen framework exceptions and their counts across all providers, default empty private Map mExceptionCountQuery = new LinkedHashMap<>(); - // The total number of failures across all auth entries, defaults to -1 - private int mTotalAuthFailures = -1; + // The total number of failures across all auth entries, defaults to 0 + private int mTotalAuthFailures = 0; // The map of all seen framework exceptions and their counts across auth entries, default empty private Map mExceptionCountAuth = new LinkedHashMap<>(); @@ -114,7 +114,7 @@ public class CandidateAggregateMetric { } } mMinProviderTimestampNanoseconds = min_query_start; - mMaxProviderTimestampsNanoseconds = max_query_end; + mMaxProviderTimestampNanoseconds = max_query_end; mAggregateCollectiveQuery = new ResponseCollective(responseCountQuery, entryCountQuery); } @@ -128,7 +128,7 @@ public class CandidateAggregateMetric { var authMetrics = sessionMetric.getBrowsedAuthenticationMetric(); mNumAuthEntriesTapped += authMetrics.size(); for (var authMetric : authMetrics) { - mAuthReturned = mAuthReturned || authMetric.isQueryReturned(); + mAuthReturned = mAuthReturned || authMetric.isAuthReturned(); ResponseCollective authCollective = authMetric.getAuthEntryCollective(); ResponseCollective.combineTypeCountMaps(responseCountAuth, authCollective.getResponseCountsMap()); @@ -170,8 +170,8 @@ public class CandidateAggregateMetric { return mAuthReturned; } - public long getMaxProviderTimestampsNanoseconds() { - return mMaxProviderTimestampsNanoseconds; + public long getMaxProviderTimestampNanoseconds() { + return mMaxProviderTimestampNanoseconds; } public long getMinProviderTimestampNanoseconds() { diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java index da386fd762b1f..99181c80bbd4f 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -126,12 +126,12 @@ public class ProviderSessionMetric { getUsedAuthenticationMetric(); mostRecentAuthenticationMetric.setProviderUid(providerSessionUid); if (isFailureStatus) { - mostRecentAuthenticationMetric.setQueryReturned(false); + mostRecentAuthenticationMetric.setAuthReturned(false); mostRecentAuthenticationMetric.setProviderStatus( ProviderStatusForMetrics.QUERY_FAILURE .getMetricCode()); } else if (isCompletionStatus) { - mostRecentAuthenticationMetric.setQueryReturned(true); + mostRecentAuthenticationMetric.setAuthReturned(true); mostRecentAuthenticationMetric.setProviderStatus( ProviderStatusForMetrics.QUERY_SUCCESS .getMetricCode()); diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index 009cfa852bf40..a9e241fc7fce9 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -20,6 +20,7 @@ import static com.android.server.credentials.MetricUtilities.DEFAULT_INT_32; import static com.android.server.credentials.MetricUtilities.DELTA_EXCEPTION_CUT; import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; +import static com.android.server.credentials.MetricUtilities.logApiCalledAggregateCandidate; import static com.android.server.credentials.MetricUtilities.logApiCalledAuthenticationMetric; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidateGetMetric; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase; @@ -357,6 +358,7 @@ public class RequestSessionMetric { public void logCandidateAggregateMetrics(Map providers) { try { mCandidateAggregateMetric.collectAverages(providers); + logApiCalledAggregateCandidate(mCandidateAggregateMetric, ++mSequenceCounter); } catch (Exception e) { Slog.i(TAG, "Unexpected error during aggregate candidate logging " + e); } From 8fad04bb1df55614cf71c886f47801a7d51c0838 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Thu, 4 May 2023 20:02:28 +0000 Subject: [PATCH 3/5] Adding isPrimary to Final Phase Metrics This captures, for any API flows, if the chosen provider was the primary provider. It's the first of a few changes that capture these additional details and smaller API metrics. Bug: 271135048 Test: Build and Won't Submit without E2E Test Change-Id: Ib3870fb1f1f0ce870dd05bc1fc3901b5ef9e8200 --- .../credentials/ClearRequestSession.java | 10 ++----- .../credentials/CreateRequestSession.java | 9 ++----- .../server/credentials/GetRequestSession.java | 10 ++----- .../server/credentials/MetricUtilities.java | 4 +-- .../server/credentials/RequestSession.java | 12 +++++++++ .../ChosenProviderFinalPhaseMetric.java | 10 +++++++ .../metrics/RequestSessionMetric.java | 26 ++++++++++++++++++- 7 files changed, 55 insertions(+), 26 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index e3d4c224f1fda..fe8a8c8979f7c 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -30,8 +30,6 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; -import com.android.server.credentials.metrics.ProviderSessionMetric; - import java.util.ArrayList; import java.util.Set; @@ -95,12 +93,8 @@ public final class ClearRequestSession extends RequestSession implements CredentialManagerUi.Credential mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled); finishSession(/*propagateCancellation=*/false); } + + /** + * Reveals if a certain provider is primary after ensuring it exists at all in the designated + * provider info. + * + * @param componentName used to identify the provider we want to check primary status for + */ + protected boolean isPrimaryProviderViaProviderInfo(ComponentName componentName) { + var chosenProviderSession = mProviders.get(componentName.flattenToString()); + return chosenProviderSession != null && chosenProviderSession.mProviderInfo != null + && chosenProviderSession.mProviderInfo.isPrimary(); + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java index e8af86012aaf9..468d3c80707c7 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java @@ -76,6 +76,8 @@ public class ChosenProviderFinalPhaseMetric { // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); + // Indicates if this chosen provider was the primary provider, false by default + private boolean mIsPrimary = false; public ChosenProviderFinalPhaseMetric(int sessionIdCaller, int sessionIdProvider) { @@ -292,4 +294,12 @@ public class ChosenProviderFinalPhaseMetric { public int getSessionIdCaller() { return mSessionIdCaller; } + + public void setPrimary(boolean primary) { + mIsPrimary = primary; + } + + public boolean isPrimary() { + return mIsPrimary; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java index a9e241fc7fce9..281f3cc705be3 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -28,6 +28,7 @@ import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPh import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal; import android.annotation.NonNull; +import android.content.ComponentName; import android.credentials.GetCredentialRequest; import android.credentials.ui.UserSelectionDialogResult; import android.util.Slog; @@ -280,6 +281,26 @@ public class RequestSessionMetric { } } + /** + * Used to update metrics when a response is received in a RequestSession. + * + * @param componentName the component name associated with the provider the response is for + */ + public void updateMetricsOnResponseReceived(Map providers, + ComponentName componentName, boolean isPrimary) { + try { + var chosenProviderSession = providers.get(componentName.flattenToString()); + if (chosenProviderSession != null) { + ProviderSessionMetric providerSessionMetric = + chosenProviderSession.getProviderSessionMetric(); + collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric(), isPrimary); + } + } catch (Exception e) { + Slog.i(TAG, "Exception upon candidate to chosen metric transfer: " + e); + } + } + /** * Called by RequestSessions upon chosen metric determination. It's expected that most bits * are transferred here. However, certain new information, such as the selected provider's final @@ -289,10 +310,13 @@ public class RequestSessionMetric { * {@link com.android.internal.util.FrameworkStatsLog} metric generation. * * @param candidatePhaseMetric the componentName to associate with a provider + * @param isPrimary indicates that this chosen provider is the primary provider (or not) */ - public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) { + public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric, + boolean isPrimary) { try { mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid()); + mChosenProviderFinalPhaseMetric.setPrimary(isPrimary); mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds( candidatePhaseMetric.getQueryLatencyMicroseconds()); From 9a18aa5070d387ecf5b425e0b9a06a75be53d8c8 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Thu, 4 May 2023 21:54:10 +0000 Subject: [PATCH 4/5] Primary Info in Candidate Metric This adds a bit to indicate which candidate provider is a candidate in the candidate metric, within the flow where we know who the candidates are, but not who the calling app is. Bug: 271135048 Test: Build Test Change-Id: I531c1be0ed67c29ebd01c6d8a52d08ec83144421 --- .../android/server/credentials/MetricUtilities.java | 4 +++- .../android/server/credentials/ProviderSession.java | 4 +++- .../credentials/metrics/CandidatePhaseMetric.java | 10 ++++++++++ .../credentials/metrics/ProviderSessionMetric.java | 5 ++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 35fbc9f899145..64a73c92ca1f0 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -307,6 +307,7 @@ public class MetricUtilities { int[] candidateAuthEntryCountList = new int[providerSize]; int[] candidateRemoteEntryCountList = new int[providerSize]; String[] frameworkExceptionList = new String[providerSize]; + boolean[] candidatePrimaryProviderList = new boolean[providerSize]; int index = 0; for (var session : providerSessions) { CandidatePhaseMetric metric = session.mProviderSessionMetric @@ -339,6 +340,7 @@ public class MetricUtilities { candidateRemoteEntryCountList[index] = metric.getResponseCollective() .getCountForEntry(EntryEnum.REMOTE_ENTRY); frameworkExceptionList[index] = metric.getFrameworkException(); + candidatePrimaryProviderList[index] = metric.isPrimary(); index++; } FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_CANDIDATE_PHASE_REPORTED, @@ -372,7 +374,7 @@ public class MetricUtilities { /* api_name */ initialPhaseMetric.getApiName(), /* primary_candidates_indicated */ - DEFAULT_REPEATED_BOOL + candidatePrimaryProviderList ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e); diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 068ca79281171..83f21afa1a324 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -214,9 +214,11 @@ public abstract class ProviderSession protected void updateStatusAndInvokeCallback(@NonNull Status status, CredentialsSource source) { setStatus(status); + boolean isPrimary = mProviderInfo != null && mProviderInfo.isPrimary(); mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status), isCompletionStatus(status), mProviderSessionUid, - source == CredentialsSource.AUTH_ENTRY); + /*isAuthEntry*/source == CredentialsSource.AUTH_ENTRY, + /*isPrimary*/isPrimary); mCallbacks.onProviderStatusChanged(status, mComponentName, source); } /** Common method that transfers metrics from the init phase to candidates */ diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java index d9bf4a134adb9..56140f367263a 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -59,6 +59,8 @@ public class CandidatePhaseMetric { // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); + // Indicates if this candidate is a primary provider, false by default + private boolean mIsPrimary = false; public CandidatePhaseMetric(int sessionIdTrackTwo) { mSessionIdProvider = sessionIdTrackTwo; @@ -185,4 +187,12 @@ public class CandidatePhaseMetric { public String getFrameworkException() { return mFrameworkException; } + + public void setPrimary(boolean primary) { + mIsPrimary = primary; + } + + public boolean isPrimary() { + return mIsPrimary; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java index 99181c80bbd4f..44d845eaaf43e 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -149,14 +149,17 @@ public class ProviderSessionMetric { * @param isFailureStatus indicates the candidate provider sent back a terminated response * @param isCompletionStatus indicates the candidate provider sent back a completion response * @param providerSessionUid the uid of the provider + * @param isPrimary indicates if this candidate provider was the primary provider */ public void collectCandidateMetricUpdate(boolean isFailureStatus, - boolean isCompletionStatus, int providerSessionUid, boolean isAuthEntry) { + boolean isCompletionStatus, int providerSessionUid, boolean isAuthEntry, + boolean isPrimary) { try { if (isAuthEntry) { collectAuthEntryUpdate(isFailureStatus, isCompletionStatus, providerSessionUid); return; } + mCandidatePhasePerProviderMetric.setPrimary(isPrimary); mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid); mCandidatePhasePerProviderMetric .setQueryFinishTimeNanoseconds(System.nanoTime()); From c362799798d3afa86c4a6a46af2cef4e6a3825bd Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Thu, 4 May 2023 22:22:53 +0000 Subject: [PATCH 5/5] Emit Simple Metrics for Set/GetCredProvider APIs This adds our 'quick metric' to check the status of our other API methods. Final changes to come soon. Bug: 271135048 Test: Build Test Change-Id: I52efc7fdceb03df1e57d494c516425cd77679703 --- .../credentials/CredentialManagerService.java | 30 +++++++++++++++++-- .../server/credentials/metrics/ApiName.java | 17 +++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 86dbe11d5ddce..cadee6f06aad9 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -706,11 +706,18 @@ public final class CredentialManagerService public void setEnabledProviders( List primaryProviders, List providers, int userId, ISetEnabledProvidersCallback callback) { + final int callingUid = Binder.getCallingUid(); if (!hasWriteSecureSettingsPermission()) { try { + MetricUtilities.logApiCalledSimpleV2( + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); callback.onError( PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); } catch (RemoteException e) { + MetricUtilities.logApiCalledSimpleV2( + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking response: ", e); } return; @@ -744,10 +751,16 @@ public final class CredentialManagerService if (!writeEnabledStatus || !writePrimaryStatus) { Slog.e(TAG, "Failed to store setting containing enabled or primary providers"); try { + MetricUtilities.logApiCalledSimpleV2( + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); callback.onError( "failed_setting_store", "Failed to store setting containing enabled or primary providers"); } catch (RemoteException e) { + MetricUtilities.logApiCalledSimpleV2( + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking error response: ", e); return; } @@ -755,8 +768,14 @@ public final class CredentialManagerService // Call the callback. try { + MetricUtilities.logApiCalledSimpleV2( + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.SUCCESS, callingUid); callback.onResponse(); } catch (RemoteException e) { + MetricUtilities.logApiCalledSimpleV2( + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking response: ", e); // TODO: Propagate failure } @@ -805,10 +824,15 @@ public final class CredentialManagerService public List getCredentialProviderServices( int userId, int providerFilter) { verifyGetProvidersPermission(); + final int callingUid = Binder.getCallingUid(); + MetricUtilities.logApiCalledSimpleV2( + ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, + ApiStatus.SUCCESS, callingUid); + return CredentialProviderInfoFactory + .getCredentialProviderServices( + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), + getPrimaryProvidersForUserId(mContext, userId)); - return CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, userId, providerFilter, getEnabledProvidersForUser(userId), - getPrimaryProvidersForUserId(mContext, userId)); } @Override diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java index fd497965b5b1c..cb6a5d06e35f5 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ApiName.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ApiName.java @@ -24,8 +24,11 @@ import static android.credentials.ui.RequestInfo.TYPE_UNDEFINED; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CREATE_CREDENTIAL; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_PROVIDER_SERVICES; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_VIA_REGISTRY; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_REGISTER_CREDENTIAL_DESCRIPTION; +import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_SET_ENABLED_PROVIDERS; import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_UNKNOWN; import android.credentials.ui.RequestInfo; @@ -45,6 +48,20 @@ CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_VIA CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_CLEAR_CREDENTIAL), IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE( CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE + ), + + SET_ENABLED_PROVIDERS( + CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_SET_ENABLED_PROVIDERS), + + GET_CREDENTIAL_PROVIDER_SERVICES( + CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_GET_CREDENTIAL_PROVIDER_SERVICES), + + REGISTER_CREDENTIAL_DESCRIPTION( + CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_REGISTER_CREDENTIAL_DESCRIPTION + ), + + UNREGISTER_CREDENTIAL_DESCRIPTION( + CREDENTIAL_MANAGER_INITIAL_PHASE_REPORTED__API_NAME__API_NAME_REGISTER_CREDENTIAL_DESCRIPTION ); private static final String TAG = "ApiName";