From 39942cfa7e3ce501f15b9df881051c8d68093fb0 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Mon, 1 May 2023 02:07:56 +0000 Subject: [PATCH 1/4] Extends the CandidateAverageMetric with Auth Entry This adds the auth entries response collective into the candidate average metric, thereby creating a full aggregate for the response information. It also sets up the Get emit logic, and is a step towards final completion of the collection system. The threaded timed get emit system was removed in favor of an immediate function-based buffer/queue logging system that will be added once all changes are in. Bug: 271135048 Test: Build and Won't Submit without E2E Test Change-Id: I17ac83de23dee1486a10d7ce02bfc334ceef5581 --- .../server/credentials/MetricUtilities.java | 67 +++++++++++++++++-- .../metrics/BrowsedAuthenticationMetric.java | 27 ++++++++ .../metrics/CandidateAggregateMetric.java | 50 +++++++++++++- .../metrics/ProviderSessionMetric.java | 21 +++++- .../metrics/RequestSessionMetric.java | 2 + .../metrics/shared/ResponseCollective.java | 59 ++++++++++++++++ 6 files changed, 217 insertions(+), 9 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index f9c44a94f89bc..2de57b95787b5 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,6 +24,7 @@ import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.BrowsedAuthenticationMetric; import com.android.server.credentials.metrics.CandidateAggregateMetric; import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric; import com.android.server.credentials.metrics.CandidatePhaseMetric; @@ -45,6 +46,7 @@ public class MetricUtilities { private static final String TAG = "MetricUtilities"; public static final String USER_CANCELED_SUBSTRING = "TYPE_USER_CANCELED"; + public static final int MIN_EMIT_WAIT_TIME_MS = 10; public static final int DEFAULT_INT_32 = -1; public static final String DEFAULT_STRING = ""; @@ -117,7 +119,8 @@ public class MetricUtilities { } /** - * A logging utility used primarily for the final phase of the current metric setup. + * A logging utility used primarily for the final phase of the current metric setup, focused on + * track 2, where the provider uid is known. * * @param finalPhaseMetric the coalesced data of the chosen provider * @param browsingPhaseMetrics the coalesced data of the browsing phase @@ -188,6 +191,56 @@ public class MetricUtilities { } } + /** + * This emits the authentication entry metrics for track 2, where the provider uid is known. + * + * @param authenticationMetric the authentication metric collection to emit with + */ + public static void logApiCalledAuthenticationMetric( + BrowsedAuthenticationMetric authenticationMetric) { + // TODO(immediately) - Add in this emit + } + + /** + * A logging utility used primarily for the candidate phase's get responses in the current + * metric setup. This helps avoid nested proto-files. This is primarily focused on track 2, + * where the provider uid is known. It ensures to run in a separate thread while emitting + * the multiple atoms to work with expected emit limits. + * + * @param providers a map with known providers and their held metric objects + * @param emitSequenceId an emitted sequence id for the current session, that matches the + * candidate emit value, as these metrics belong with the candidates + */ + public static void logApiCalledCandidateGetMetric(Map providers, + int emitSequenceId) { + try { + // TODO(immediately) - Modify to a Static Queue of Ordered Functions and emit from + // queue to adhere to 10 second limit (thread removed given android safe-calling). + var sessions = providers.values(); + for (var session : sessions) { + try { + var metric = session.getProviderSessionMetric() + .getCandidatePhasePerProviderMetric(); + FrameworkStatsLog.write( + FrameworkStatsLog.CREDENTIAL_MANAGER_GET_REPORTED, + /* session_id */ metric.getSessionIdProvider(), + /* sequence_num */ emitSequenceId, + /* candidate_provider_uid */ metric.getCandidateUid(), + /* response_unique_classtypes */ + metric.getResponseCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + metric.getResponseCollective().getUniqueResponseCounts() + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected exception during get metric logging" + e); + } + } + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e); + } + } + + /** * A logging utility used primarily for the candidate phase of the current metric setup. This * will primarily focus on track 2, where the session id is associated with known providers, @@ -369,13 +422,17 @@ public class MetricUtilities { /*max_query_end_timestamp_microseconds*/ DEFAULT_INT_32, /*query_response_unique_classtypes*/ - DEFAULT_REPEATED_STR, + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueResponseStrings(), /*query_per_classtype_counts*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueResponseCounts(), /*query_unique_entries*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueEntries(), /*query_per_entry_counts*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueEntryCounts(), /*query_total_candidate_failure*/ DEFAULT_INT_32, /*query_framework_exception_unique_classtypes*/ 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 51e86d51acdfd..c3192ddbb16c4 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -16,13 +16,23 @@ package com.android.server.credentials.metrics; +import com.android.server.credentials.metrics.shared.ResponseCollective; + +import java.util.Map; + /** * Encapsulates an authentication entry click atom, as a part of track 2. * Contains information about what was collected from the authentication entry output. */ public class BrowsedAuthenticationMetric { + private static final String TAG = "BrowsedAuthenticationMetric"; // 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; + + private ResponseCollective mAuthEntryCollective = new ResponseCollective(Map.of(), Map.of()); // TODO(b/271135048) - Match the atom and provide a clean per provider session metric // encapsulation. @@ -33,4 +43,21 @@ public class BrowsedAuthenticationMetric { public int getSessionIdProvider() { return mSessionIdProvider; } + + public void setProviderUid(int providerUid) { + mProviderUid = providerUid; + } + + public int getProviderUid() { + return mProviderUid; + } + + public void setAuthEntryCollective( + ResponseCollective authEntryCollective) { + this.mAuthEntryCollective = authEntryCollective; + } + + public ResponseCollective getAuthEntryCollective() { + return mAuthEntryCollective; + } } 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 08e75837a2748..c254374d4d465 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -17,7 +17,9 @@ package com.android.server.credentials.metrics; import com.android.server.credentials.ProviderSession; +import com.android.server.credentials.metrics.shared.ResponseCollective; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -35,6 +37,12 @@ public class CandidateAggregateMetric { private int mNumProviders = 0; // Indicates the total number of authentication entries that were tapped in aggregate, default 0 private int mNumAuthEntriesTapped = 0; + // The combined aggregate collective across the candidate get/create + private ResponseCollective mAggregateCollectiveQuery = + new ResponseCollective(Map.of(), Map.of()); + // The combined aggregate collective across the auth entry info + private ResponseCollective mAggregateCollectiveAuth = + new ResponseCollective(Map.of(), Map.of()); public CandidateAggregateMetric(int sessionIdTrackOne) { mSessionIdProvider = sessionIdTrackOne; @@ -52,13 +60,44 @@ public class CandidateAggregateMetric { */ public void collectAverages(Map providers) { // TODO(b/271135048) : Complete this method + collectQueryAggregates(providers); + collectAuthAggregates(providers); + } + + private void collectQueryAggregates(Map providers) { mNumProviders = providers.size(); + Map responseCountQuery = new LinkedHashMap<>(); + Map entryCountQuery = new LinkedHashMap<>(); var providerSessions = providers.values(); for (var session : providerSessions) { - var metric = session.getProviderSessionMetric(); - mQueryReturned = mQueryReturned || metric - .mCandidatePhasePerProviderMetric.isQueryReturned(); + var sessionMetric = session.getProviderSessionMetric(); + var candidateMetric = sessionMetric.getCandidatePhasePerProviderMetric(); + mQueryReturned = mQueryReturned || candidateMetric.isQueryReturned(); + ResponseCollective candidateCollective = candidateMetric.getResponseCollective(); + ResponseCollective.combineTypeCountMaps(responseCountQuery, + candidateCollective.getResponseCountsMap()); + ResponseCollective.combineTypeCountMaps(entryCountQuery, + candidateCollective.getEntryCountsMap()); } + mAggregateCollectiveQuery = new ResponseCollective(responseCountQuery, entryCountQuery); + } + + private void collectAuthAggregates(Map providers) { + mNumProviders = providers.size(); + Map responseCountAuth = new LinkedHashMap<>(); + Map entryCountAuth = new LinkedHashMap<>(); + var providerSessions = providers.values(); + for (var session : providerSessions) { + var sessionMetric = session.getProviderSessionMetric(); + var authMetric = sessionMetric.getBrowsedAuthenticationMetric(); + mQueryReturned = mQueryReturned; // TODO add auth info + ResponseCollective authCollective = authMetric.getAuthEntryCollective(); + ResponseCollective.combineTypeCountMaps(responseCountAuth, + authCollective.getResponseCountsMap()); + ResponseCollective.combineTypeCountMaps(entryCountAuth, + authCollective.getEntryCountsMap()); + } + mAggregateCollectiveAuth = new ResponseCollective(responseCountAuth, entryCountAuth); } public int getNumProviders() { @@ -69,7 +108,12 @@ public class CandidateAggregateMetric { return mQueryReturned; } + public int getNumAuthEntriesTapped() { return mNumAuthEntriesTapped; } + + public ResponseCollective getAggregateCollectiveQuery() { + return mAggregateCollectiveQuery; + } } 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 47db8f59ff355..56a7482372827 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -63,6 +63,12 @@ public class ProviderSessionMetric { return mCandidatePhasePerProviderMetric; } + /** + * Retrieves the authentication clicked metric information. + */ + public BrowsedAuthenticationMetric getBrowsedAuthenticationMetric() { + return mBrowsedAuthenticationMetric; + } /** * This collects for ProviderSessions, with respect to the candidate providers, whether @@ -91,6 +97,19 @@ public class ProviderSessionMetric { // 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. + mBrowsedAuthenticationMetric.setProviderUid(providerSessionUid); + // TODO(immediately) - add timestamps + if (isFailureStatus) { + mCandidatePhasePerProviderMetric.setQueryReturned(false); + mCandidatePhasePerProviderMetric.setProviderQueryStatus( + ProviderStatusForMetrics.QUERY_FAILURE + .getMetricCode()); + } else if (isCompletionStatus) { + mCandidatePhasePerProviderMetric.setQueryReturned(true); + mCandidatePhasePerProviderMetric.setProviderQueryStatus( + ProviderStatusForMetrics.QUERY_SUCCESS + .getMetricCode()); + } } /** @@ -240,7 +259,7 @@ public class ProviderSessionMetric { if (!isAuthEntry) { mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); } else { - // TODO(b/immediately) - Add the auth entry get logic + mBrowsedAuthenticationMetric.setAuthEntryCollective(responseCollective); } } } 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 03ffe23f98866..8846f2ddb92e5 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -19,6 +19,7 @@ package com.android.server.credentials.metrics; 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.logApiCalledCandidateGetMetric; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase; import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase; import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal; @@ -343,6 +344,7 @@ public class RequestSessionMetric { public void logCandidatePhaseMetrics(Map providers) { try { logApiCalledCandidatePhase(providers, ++mSequenceCounter, mInitialPhaseMetric); + logApiCalledCandidateGetMetric(providers, mSequenceCounter); } catch (Exception e) { Slog.i(TAG, "Unexpected error during candidate metric emit: " + e); } diff --git a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java index fd785c2f4dfcd..0958a841fe7ee 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java +++ b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import com.android.server.credentials.metrics.EntryEnum; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -69,6 +70,24 @@ public class ResponseCollective { return result; } + /** + * Returns an unmodifiable map of the entry counts, safe under the immutability of the + * class the original map is held within. + * @return an unmodifiable map of the entry : counts + */ + public Map getEntryCountsMap() { + return Collections.unmodifiableMap(mEntryCounts); + } + + /** + * Returns an unmodifiable map of the response counts, safe under the immutability of the + * class the original map is held within. + * @return an unmodifiable map of the response : counts + */ + public Map getResponseCountsMap() { + return Collections.unmodifiableMap(mResponseCounts); + } + /** * Returns the unique, deduped, response classtype counts for logging associated with this * provider. @@ -112,4 +131,44 @@ public class ResponseCollective { public int getNumEntriesTotal() { return mEntryCounts.values().stream().mapToInt(Integer::intValue).sum(); } + + /** + * This combines the current collective with another collective, only if that other + * collective is indeed a differing one in memory. + * @param other the other response collective to combine with + * @return a combined {@link ResponseCollective} object + */ + public ResponseCollective combineCollectives(ResponseCollective other) { + if (this == other) { + return this; + } + + Map responseCounts = new LinkedHashMap<>(other.mResponseCounts); + for (String response : mResponseCounts.keySet()) { + responseCounts.merge(response, mResponseCounts.get(response), Integer::sum); + } + + Map entryCounts = new LinkedHashMap<>(other.mEntryCounts); + for (EntryEnum entry : mEntryCounts.keySet()) { + entryCounts.merge(entry, mEntryCounts.get(entry), Integer::sum); + } + + return new ResponseCollective(responseCounts, entryCounts); + } + + /** + * Given two maps of type : counts, this combines the second into the first, to get an aggregate + * deduped type:count output. + * @param first the first map of some type to counts used as the base + * @param second the second map of some type to counts that mixies with the first + * @param The type of the object we are mix-deduping - i.e. responses or entries. + * @return the first map updated with the second map's information for type:counts + */ + public static Map combineTypeCountMaps(Map first, + Map second) { + for (T response : second.keySet()) { + first.merge(response, first.getOrDefault(response, 0), Integer::sum); + } + return first; + } } From 9cfa89f4ede2c88a155ad204abe08ea09c19ff84 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Wed, 3 May 2023 18:54:59 +0000 Subject: [PATCH 2/4] Handling sequential auth entries in metrics Since auth entries could repeat multiple times, maybe across multiple providers, this ensures that the aggregate and single case properly handle such sequential amounts of calls and emit them accordingly. Bug: 271135048 Test: Build and Won't Submit without E2E Test Change-Id: If4b77e0865761c22bd2e63b333bd46647f25d976 --- .../server/credentials/MetricUtilities.java | 19 ++++++++------ .../metrics/CandidateAggregateMetric.java | 22 ++++++++++------ .../metrics/ProviderSessionMetric.java | 26 ++++++++++++++----- .../metrics/RequestSessionMetric.java | 3 --- 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 2de57b95787b5..58c6fa9a9bf1a 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -214,8 +214,7 @@ public class MetricUtilities { public static void logApiCalledCandidateGetMetric(Map providers, int emitSequenceId) { try { - // TODO(immediately) - Modify to a Static Queue of Ordered Functions and emit from - // queue to adhere to 10 second limit (thread removed given android safe-calling). + // TODO(b/future) - Switch to Log format var sessions = providers.values(); for (var session : sessions) { try { @@ -416,7 +415,7 @@ public class MetricUtilities { /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), /*sequence_num*/ sequenceNum, /*query_returned*/ candidateAggregateMetric.isQueryReturned(), - /*num_providers*/ candidateAggregateMetric.getNumProviders(), + /*num_query_providers*/ candidateAggregateMetric.getNumProviders(), /*min_query_start_timestamp_microseconds*/ DEFAULT_INT_32, /*max_query_end_timestamp_microseconds*/ @@ -440,13 +439,17 @@ public class MetricUtilities { /*query_per_exception_classtype_counts*/ DEFAULT_REPEATED_INT_32, /*auth_response_unique_classtypes*/ - DEFAULT_REPEATED_STR, + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueResponseStrings(), /*auth_per_classtype_counts*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueResponseCounts(), /*auth_unique_entries*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueEntries(), /*auth_per_entry_counts*/ - DEFAULT_REPEATED_INT_32, + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueEntryCounts(), /*auth_total_candidate_failure*/ DEFAULT_INT_32, /*auth_framework_exception_unique_classtypes*/ @@ -454,7 +457,7 @@ public class MetricUtilities { /*auth_per_exception_classtype_counts*/ DEFAULT_REPEATED_INT_32, /*num_auth_clicks*/ - DEFAULT_INT_32, + candidateAggregateMetric.getNumAuthEntriesTapped(), /*auth_returned*/ false ); } 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 c254374d4d465..06905c15ef29c 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -59,7 +59,6 @@ public class CandidateAggregateMetric { * @param providers the providers associated with the candidate flow */ public void collectAverages(Map providers) { - // TODO(b/271135048) : Complete this method collectQueryAggregates(providers); collectAuthAggregates(providers); } @@ -89,13 +88,16 @@ public class CandidateAggregateMetric { var providerSessions = providers.values(); for (var session : providerSessions) { var sessionMetric = session.getProviderSessionMetric(); - var authMetric = sessionMetric.getBrowsedAuthenticationMetric(); - mQueryReturned = mQueryReturned; // TODO add auth info - ResponseCollective authCollective = authMetric.getAuthEntryCollective(); - ResponseCollective.combineTypeCountMaps(responseCountAuth, - authCollective.getResponseCountsMap()); - ResponseCollective.combineTypeCountMaps(entryCountAuth, - authCollective.getEntryCountsMap()); + var authMetrics = sessionMetric.getBrowsedAuthenticationMetric(); + mQueryReturned = mQueryReturned; // TODO add rest of auth info + mNumAuthEntriesTapped += authMetrics.size(); + for (var authMetric : authMetrics) { + ResponseCollective authCollective = authMetric.getAuthEntryCollective(); + ResponseCollective.combineTypeCountMaps(responseCountAuth, + authCollective.getResponseCountsMap()); + ResponseCollective.combineTypeCountMaps(entryCountAuth, + authCollective.getEntryCountsMap()); + } } mAggregateCollectiveAuth = new ResponseCollective(responseCountAuth, entryCountAuth); } @@ -116,4 +118,8 @@ public class CandidateAggregateMetric { public ResponseCollective getAggregateCollectiveQuery() { return mAggregateCollectiveQuery; } + + public ResponseCollective getAggregateCollectiveAuth() { + return mAggregateCollectiveAuth; + } } 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 56a7482372827..e0407a996ff5c 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -28,6 +28,7 @@ import android.util.Slog; import com.android.server.credentials.MetricUtilities; import com.android.server.credentials.metrics.shared.ResponseCollective; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -47,13 +48,17 @@ public class ProviderSessionMetric { protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric; // IFF there was an authentication entry clicked, this stores all required information for - // that event. This is for the 'get' flow. + // that event. This is for the 'get' flow. Notice these flows may be repetitive. + // Thus each provider stores a list of authentication metrics. The time between emits + // of these metrics should exceed 10 ms (given human reaction time is ~ 100's of ms), so emits + // will never collide. However, for aggregation, this will store information accordingly. @NonNull - protected final BrowsedAuthenticationMetric mBrowsedAuthenticationMetric; + protected final List mBrowsedAuthenticationMetric = + new ArrayList<>(); public ProviderSessionMetric(int sessionIdTrackTwo) { mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(sessionIdTrackTwo); - mBrowsedAuthenticationMetric = new BrowsedAuthenticationMetric(sessionIdTrackTwo); + mBrowsedAuthenticationMetric.add(new BrowsedAuthenticationMetric(sessionIdTrackTwo)); } /** @@ -66,7 +71,7 @@ public class ProviderSessionMetric { /** * Retrieves the authentication clicked metric information. */ - public BrowsedAuthenticationMetric getBrowsedAuthenticationMetric() { + public List getBrowsedAuthenticationMetric() { return mBrowsedAuthenticationMetric; } @@ -97,8 +102,10 @@ public class ProviderSessionMetric { // 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. - mBrowsedAuthenticationMetric.setProviderUid(providerSessionUid); - // TODO(immediately) - add timestamps + var mostRecentAuthenticationMetric = mBrowsedAuthenticationMetric + .get(mBrowsedAuthenticationMetric.size() - 1); + mostRecentAuthenticationMetric.setProviderUid(providerSessionUid); + // TODO(immediately) - add timestamps (no longer needed!!) but also update below values! if (isFailureStatus) { mCandidatePhasePerProviderMetric.setQueryReturned(false); mCandidatePhasePerProviderMetric.setProviderQueryStatus( @@ -259,7 +266,12 @@ public class ProviderSessionMetric { if (!isAuthEntry) { mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); } else { - mBrowsedAuthenticationMetric.setAuthEntryCollective(responseCollective); + BrowsedAuthenticationMetric browsedAuthenticationMetric = + new BrowsedAuthenticationMetric(mCandidatePhasePerProviderMetric + .getSessionIdProvider()); + // to receive an auth entry, the candidate phase must have succeeded + browsedAuthenticationMetric.setAuthEntryCollective(responseCollective); + mBrowsedAuthenticationMetric.add(browsedAuthenticationMetric); } } } 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 8846f2ddb92e5..3c8d2e58f265f 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -53,7 +53,6 @@ public class RequestSessionMetric { protected final InitialPhaseMetric mInitialPhaseMetric; protected final ChosenProviderFinalPhaseMetric mChosenProviderFinalPhaseMetric; - // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4) protected List mCandidateBrowsingPhaseMetric = new ArrayList<>(); // Specific aggregate candidate provider metric for the provider this session handles @NonNull @@ -216,8 +215,6 @@ public class RequestSessionMetric { /** * During browsing, where multiple entries can be selected, this collects the browsing phase * metric information. - * TODO(b/271135048) - modify asap to account for a new metric emit per browse response to - * framework. * * @param selection contains the selected entry key type * @param selectedProviderPhaseMetric contains the utility information of the selected provider From 6ca8ce8ab289ef0777862bd7f0a52df55808391f Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Mon, 1 May 2023 23:11:05 +0000 Subject: [PATCH 3/4] Sets up Emit for Auth Entry This CL focuses on setting up the emit for the authentication entry metrics, by identifying where in the API flow this may occur. Further CLs will add more details. Bug: 271135048 Test: Build + Won't Submit before E2E Test Change-Id: I82c2f7477f585b0c43a6cf06e7a1f4982cbf5907 --- .../server/credentials/MetricUtilities.java | 135 +++++++++++------- .../server/credentials/RequestSession.java | 11 ++ .../metrics/RequestSessionMetric.java | 27 +++- 3 files changed, 118 insertions(+), 55 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index 58c6fa9a9bf1a..ed0c88709aff2 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -195,10 +195,37 @@ public class MetricUtilities { * This emits the authentication entry metrics for track 2, where the provider uid is known. * * @param authenticationMetric the authentication metric collection to emit with + * @param emitSequenceId an emitted sequence id for the current session */ public static void logApiCalledAuthenticationMetric( - BrowsedAuthenticationMetric authenticationMetric) { - // TODO(immediately) - Add in this emit + BrowsedAuthenticationMetric authenticationMetric, + int emitSequenceId) { + try { + if (!LOG_FLAG) { + return; + } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_AUTH_CLICK_REPORTED, + /* session_id */ authenticationMetric.getSessionIdProvider(), + /* sequence_num */ emitSequenceId, + /* chosen_provider_uid */ authenticationMetric.getProviderUid(), + /* unique_response_classtypes */ + authenticationMetric.getAuthEntryCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + authenticationMetric.getAuthEntryCollective().getUniqueResponseCounts(), + /* unique_entries */ + authenticationMetric.getAuthEntryCollective().getUniqueEntries(), + /* auth_per_entry_counts */ + authenticationMetric.getAuthEntryCollective().getUniqueEntryCounts(), + /* framework_exception_unique_classtype */ + DEFAULT_STRING, + /* exception_specified */ false, + /* auth_provider_status TODO(immediately) change */ DEFAULT_INT_32, + /* query_returned */ + false + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e); + } } /** @@ -214,7 +241,10 @@ public class MetricUtilities { public static void logApiCalledCandidateGetMetric(Map providers, int emitSequenceId) { try { - // TODO(b/future) - Switch to Log format + // TODO(b/274954697) : To queue format in future optimizations (metrics POC support) + if (!LOG_FLAG) { + return; + } var sessions = providers.values(); for (var session : sessions) { try { @@ -411,56 +441,57 @@ public class MetricUtilities { int sequenceNum) { try { if (!LOG_FLAG) { - FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, - /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), - /*sequence_num*/ sequenceNum, - /*query_returned*/ candidateAggregateMetric.isQueryReturned(), - /*num_query_providers*/ candidateAggregateMetric.getNumProviders(), - /*min_query_start_timestamp_microseconds*/ - DEFAULT_INT_32, - /*max_query_end_timestamp_microseconds*/ - DEFAULT_INT_32, - /*query_response_unique_classtypes*/ - candidateAggregateMetric.getAggregateCollectiveQuery() - .getUniqueResponseStrings(), - /*query_per_classtype_counts*/ - candidateAggregateMetric.getAggregateCollectiveQuery() - .getUniqueResponseCounts(), - /*query_unique_entries*/ - candidateAggregateMetric.getAggregateCollectiveQuery() - .getUniqueEntries(), - /*query_per_entry_counts*/ - candidateAggregateMetric.getAggregateCollectiveQuery() - .getUniqueEntryCounts(), - /*query_total_candidate_failure*/ - DEFAULT_INT_32, - /*query_framework_exception_unique_classtypes*/ - DEFAULT_REPEATED_STR, - /*query_per_exception_classtype_counts*/ - DEFAULT_REPEATED_INT_32, - /*auth_response_unique_classtypes*/ - candidateAggregateMetric.getAggregateCollectiveAuth() - .getUniqueResponseStrings(), - /*auth_per_classtype_counts*/ - candidateAggregateMetric.getAggregateCollectiveAuth() - .getUniqueResponseCounts(), - /*auth_unique_entries*/ - candidateAggregateMetric.getAggregateCollectiveAuth() - .getUniqueEntries(), - /*auth_per_entry_counts*/ - candidateAggregateMetric.getAggregateCollectiveAuth() - .getUniqueEntryCounts(), - /*auth_total_candidate_failure*/ - DEFAULT_INT_32, - /*auth_framework_exception_unique_classtypes*/ - DEFAULT_REPEATED_STR, - /*auth_per_exception_classtype_counts*/ - DEFAULT_REPEATED_INT_32, - /*num_auth_clicks*/ - candidateAggregateMetric.getNumAuthEntriesTapped(), - /*auth_returned*/ false - ); + return; } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, + /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), + /*sequence_num*/ sequenceNum, + /*query_returned*/ candidateAggregateMetric.isQueryReturned(), + /*num_query_providers*/ candidateAggregateMetric.getNumProviders(), + /*min_query_start_timestamp_microseconds*/ + DEFAULT_INT_32, + /*max_query_end_timestamp_microseconds*/ + DEFAULT_INT_32, + /*query_response_unique_classtypes*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueResponseStrings(), + /*query_per_classtype_counts*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueResponseCounts(), + /*query_unique_entries*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueEntries(), + /*query_per_entry_counts*/ + candidateAggregateMetric.getAggregateCollectiveQuery() + .getUniqueEntryCounts(), + /*query_total_candidate_failure*/ + DEFAULT_INT_32, + /*query_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_response_unique_classtypes*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueResponseStrings(), + /*auth_per_classtype_counts*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueResponseCounts(), + /*auth_unique_entries*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueEntries(), + /*auth_per_entry_counts*/ + candidateAggregateMetric.getAggregateCollectiveAuth() + .getUniqueEntryCounts(), + /*auth_total_candidate_failure*/ + DEFAULT_INT_32, + /*auth_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*num_auth_clicks*/ + candidateAggregateMetric.getNumAuthEntriesTapped(), + /*auth_returned*/ false + ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during metric logging: " + e); } diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index a41b5713ee145..c332c426e3315 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -38,6 +38,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import com.android.server.credentials.metrics.RequestSessionMetric; @@ -199,10 +200,20 @@ abstract class RequestSession implements CredentialManagerUi.Credential Slog.w(TAG, "providerSession not found in onUiSelection. This is strange."); return; } + ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric; + int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric() + .size(); mRequestSessionMetric.collectMetricPerBrowsingSelect(selection, providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric()); providerSession.onUiEntrySelected(selection.getEntryKey(), selection.getEntrySubkey(), selection.getPendingIntentProviderResponse()); + int numAuthPerProvider = providerSessionMetric.getBrowsedAuthenticationMetric().size(); + boolean authMetricLogged = (numAuthPerProvider - initialAuthMetricsProvider) == 1; + if (authMetricLogged) { + mRequestSessionMetric.logAuthEntry( + providerSession.mProviderSessionMetric.getBrowsedAuthenticationMetric() + .get(numAuthPerProvider - 1)); + } } protected void finishSession(boolean propagateCancellation) { 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 3c8d2e58f265f..4fb2d207b112b 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -16,9 +16,11 @@ package com.android.server.credentials.metrics; +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.logApiCalledAuthenticationMetric; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidateGetMetric; import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase; import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase; @@ -100,8 +102,6 @@ public class RequestSessionMetric { * @param timestampStarted the timestamp the service begins at * @param mCallingUid the calling process's uid * @param metricCode typically pulled from {@link ApiName} - * @param callingAppFlowUniqueInt the unique integer used as the session id for the calling app - * known flow */ public void collectInitialPhaseMetricInfo(long timestampStarted, int mCallingUid, int metricCode) { @@ -214,7 +214,8 @@ public class RequestSessionMetric { /** * During browsing, where multiple entries can be selected, this collects the browsing phase - * metric information. + * metric information. This is emitted together with the final phase, and the recursive path + * with authentication entries, which may occur in rare circumstances, are captured. * * @param selection contains the selected entry key type * @param selectedProviderPhaseMetric contains the utility information of the selected provider @@ -361,6 +362,26 @@ public class RequestSessionMetric { } } + /** + * This logs the authentication entry when browsed. Combined with the known browsed clicks + * in the {@link ChosenProviderFinalPhaseMetric}, this fully captures the authentication entry + * logic for multiple loops. + * + * @param browsedAuthenticationMetric the authentication metric information to emit + */ + public void logAuthEntry(BrowsedAuthenticationMetric browsedAuthenticationMetric) { + try { + if (browsedAuthenticationMetric.getProviderUid() == DEFAULT_INT_32) { + Slog.v(TAG, "An authentication entry was not clicked"); + return; + } + logApiCalledAuthenticationMetric(browsedAuthenticationMetric, ++mSequenceCounter); + } catch (Exception e) { + Slog.i(TAG, "Unexpected error during metric logging: " + e); + } + + } + /** * Handles the final logging for RequestSession context for the final phase. * From 35509db0089a8dd468ddd23b3f74e631e32dcba0 Mon Sep 17 00:00:00 2001 From: Arpan Kaphle Date: Mon, 1 May 2023 23:46:19 +0000 Subject: [PATCH 4/4] Auth Entry Metric Exceptions and Status This collects status and exception information for the auth entry metric and continues the combination of this metric with the total candidate metric. Bug: 271135048 Test: Build and Won't Submit without E2E Test Change-Id: Ib3db0b2723189d02f4af84b3101d0d45e24cd936 --- .../server/credentials/MetricUtilities.java | 9 ++-- .../credentials/ProviderGetSession.java | 2 + .../metrics/BrowsedAuthenticationMetric.java | 43 +++++++++++++++ .../metrics/CandidateAggregateMetric.java | 12 ++++- .../metrics/ProviderSessionMetric.java | 52 +++++++++++++++---- .../metrics/RequestSessionMetric.java | 4 +- 6 files changed, 105 insertions(+), 17 deletions(-) diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index ed0c88709aff2..749d6b6246d0b 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -217,11 +217,12 @@ public class MetricUtilities { /* auth_per_entry_counts */ authenticationMetric.getAuthEntryCollective().getUniqueEntryCounts(), /* framework_exception_unique_classtype */ - DEFAULT_STRING, - /* exception_specified */ false, - /* auth_provider_status TODO(immediately) change */ DEFAULT_INT_32, + authenticationMetric.getFrameworkException(), + /* exception_specified */ authenticationMetric.isHasException(), + /* auth_provider_status */ + authenticationMetric.getProviderStatus(), /* query_returned */ - false + authenticationMetric.isQueryReturned() ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e); diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 51af25b589924..14260f0e1ad43 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -269,6 +269,7 @@ public final class ProviderGetSession extends ProviderSession entryCounts = new LinkedHashMap<>(); @@ -266,12 +300,10 @@ public class ProviderSessionMetric { if (!isAuthEntry) { mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); } else { - BrowsedAuthenticationMetric browsedAuthenticationMetric = - new BrowsedAuthenticationMetric(mCandidatePhasePerProviderMetric - .getSessionIdProvider()); - // to receive an auth entry, the candidate phase must have succeeded + // The most recent auth entry must be created already + var browsedAuthenticationMetric = + mBrowsedAuthenticationMetric.get(mBrowsedAuthenticationMetric.size() - 1); browsedAuthenticationMetric.setAuthEntryCollective(responseCollective); - mBrowsedAuthenticationMetric.add(browsedAuthenticationMetric); } } } 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 4fb2d207b112b..009cfa852bf40 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -365,7 +365,9 @@ public class RequestSessionMetric { /** * This logs the authentication entry when browsed. Combined with the known browsed clicks * in the {@link ChosenProviderFinalPhaseMetric}, this fully captures the authentication entry - * logic for multiple loops. + * logic for multiple loops. An auth entry may have default or missing data, but if a provider + * was never assigned to an auth entry, this indicates an auth entry was never clicked. + * This case is handled in this emit. * * @param browsedAuthenticationMetric the authentication metric information to emit */