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; + } }