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. *