1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.credentials.metrics;
18 
19 import com.android.server.credentials.MetricUtilities;
20 import com.android.server.credentials.ProviderSession;
21 import com.android.server.credentials.metrics.shared.ResponseCollective;
22 
23 import java.util.LinkedHashMap;
24 import java.util.Map;
25 
26 /**
27  * This will generate most of its data via using the information of {@link CandidatePhaseMetric}
28  * across all the providers. This belongs to the metric flow where the calling app is known. It
29  * also contains {@link BrowsedAuthenticationMetric} data aggregated within.
30  */
31 public class CandidateAggregateMetric {
32 
33     private static final String TAG = "CandidateTotalMetric";
34     // The session id of this provider metric
35     private final int mSessionIdProvider;
36     // Indicates if this provider returned from the candidate query phase,
37     // true if at least one provider returns validly, even if empty, default false
38     private boolean mQueryReturned = false;
39     // For reference, the initial log timestamp when the service started running the API call,
40     // defaults to -1
41     private long mServiceBeganTimeNanoseconds = -1;
42     // Indicates the total number of providers this aggregate captures information for, default 0
43     private int mNumProviders = 0;
44     // Indicates if the authentication entry returned, true if at least one entry returns validly,
45     // even if empty, default false
46     private boolean mAuthReturned = false;
47     // Indicates the total number of authentication entries that were tapped in aggregate, default 0
48     private int mNumAuthEntriesTapped = 0;
49     // The combined aggregate collective across the candidate get/create
50     private ResponseCollective mAggregateCollectiveQuery =
51             new ResponseCollective(Map.of(), Map.of());
52     // The combined aggregate collective across the auth entry info
53     private ResponseCollective mAggregateCollectiveAuth =
54             new ResponseCollective(Map.of(), Map.of());
55     // The minimum of all the providers query start time, defaults to -1
56     private long mMinProviderTimestampNanoseconds = -1;
57     // The maximum of all the providers query finish time, defaults to -1
58     private long mMaxProviderTimestampNanoseconds = -1;
59     // The total number of failures across all the providers, defaults to 0
60     private int mTotalQueryFailures = 0;
61     // The map of all seen framework exceptions and their counts across all providers, default empty
62     private Map<String, Integer> mExceptionCountQuery = new LinkedHashMap<>();
63     // The total number of failures across all auth entries, defaults to 0
64     private int mTotalAuthFailures = 0;
65     // The map of all seen framework exceptions and their counts across auth entries, default empty
66     private Map<String, Integer> mExceptionCountAuth = new LinkedHashMap<>();
67 
CandidateAggregateMetric(int sessionIdTrackOne)68     public CandidateAggregateMetric(int sessionIdTrackOne) {
69         mSessionIdProvider = sessionIdTrackOne;
70     }
71 
getSessionIdProvider()72     public int getSessionIdProvider() {
73         return mSessionIdProvider;
74     }
75 
76     /**
77      * This will take all the candidate data captured and aggregate that information.
78      * @param providers the providers associated with the candidate flow
79      */
collectAverages(Map<String, ProviderSession> providers)80     public void collectAverages(Map<String, ProviderSession> providers) {
81         collectQueryAggregates(providers);
82         collectAuthAggregates(providers);
83     }
84 
collectQueryAggregates(Map<String, ProviderSession> providers)85     private void collectQueryAggregates(Map<String, ProviderSession> providers) {
86         mNumProviders = providers.size();
87         Map<String, Integer> responseCountQuery = new LinkedHashMap<>();
88         Map<EntryEnum, Integer> entryCountQuery = new LinkedHashMap<>();
89         var providerSessions = providers.values();
90         long min_query_start = Long.MAX_VALUE;
91         long max_query_end = Long.MIN_VALUE;
92         for (var session : providerSessions) {
93             var sessionMetric = session.getProviderSessionMetric();
94             var candidateMetric = sessionMetric.getCandidatePhasePerProviderMetric();
95             if (candidateMetric.getCandidateUid() == MetricUtilities.DEFAULT_INT_32) {
96                 mNumProviders--;
97                 continue; // Do not aggregate this one and reduce the size of actual candidates
98             }
99             if (mServiceBeganTimeNanoseconds == -1) {
100                 mServiceBeganTimeNanoseconds = candidateMetric.getServiceBeganTimeNanoseconds();
101             }
102             mQueryReturned = mQueryReturned || candidateMetric.isQueryReturned();
103             ResponseCollective candidateCollective = candidateMetric.getResponseCollective();
104             ResponseCollective.combineTypeCountMaps(responseCountQuery,
105                     candidateCollective.getResponseCountsMap());
106             ResponseCollective.combineTypeCountMaps(entryCountQuery,
107                     candidateCollective.getEntryCountsMap());
108             min_query_start = Math.min(min_query_start,
109                     candidateMetric.getStartQueryTimeNanoseconds());
110             max_query_end = Math.max(max_query_end, candidateMetric
111                     .getQueryFinishTimeNanoseconds());
112             mTotalQueryFailures += (candidateMetric.isHasException() ? 1 : 0);
113             if (!candidateMetric.getFrameworkException().isEmpty()) {
114                 mExceptionCountQuery.put(candidateMetric.getFrameworkException(),
115                         mExceptionCountQuery.getOrDefault(
116                                 candidateMetric.getFrameworkException(), 0) + 1);
117             }
118         }
119         mMinProviderTimestampNanoseconds = min_query_start;
120         mMaxProviderTimestampNanoseconds = max_query_end;
121         mAggregateCollectiveQuery = new ResponseCollective(responseCountQuery, entryCountQuery);
122     }
123 
collectAuthAggregates(Map<String, ProviderSession> providers)124     private void collectAuthAggregates(Map<String, ProviderSession> providers) {
125         Map<String, Integer> responseCountAuth = new LinkedHashMap<>();
126         Map<EntryEnum, Integer> entryCountAuth = new LinkedHashMap<>();
127         var providerSessions = providers.values();
128         for (var session : providerSessions) {
129             var sessionMetric = session.getProviderSessionMetric();
130             var authMetrics = sessionMetric.getBrowsedAuthenticationMetric();
131             for (var authMetric : authMetrics) {
132                 if (authMetric.getProviderUid() == MetricUtilities.DEFAULT_INT_32) {
133                     continue; // skip this unfilled base auth entry
134                 }
135                 mNumAuthEntriesTapped++;
136                 mAuthReturned = mAuthReturned || authMetric.isAuthReturned();
137                 ResponseCollective authCollective = authMetric.getAuthEntryCollective();
138                 ResponseCollective.combineTypeCountMaps(responseCountAuth,
139                         authCollective.getResponseCountsMap());
140                 ResponseCollective.combineTypeCountMaps(entryCountAuth,
141                         authCollective.getEntryCountsMap());
142                 mTotalQueryFailures += (authMetric.isHasException() ? 1 : 0);
143                 if (!authMetric.getFrameworkException().isEmpty()) {
144                     mExceptionCountQuery.put(authMetric.getFrameworkException(),
145                             mExceptionCountQuery.getOrDefault(
146                                     authMetric.getFrameworkException(), 0) + 1);
147                 }
148             }
149         }
150         mAggregateCollectiveAuth = new ResponseCollective(responseCountAuth, entryCountAuth);
151     }
152 
getNumProviders()153     public int getNumProviders() {
154         return mNumProviders;
155     }
156 
isQueryReturned()157     public boolean isQueryReturned() {
158         return mQueryReturned;
159     }
160 
161 
getNumAuthEntriesTapped()162     public int getNumAuthEntriesTapped() {
163         return mNumAuthEntriesTapped;
164     }
165 
getAggregateCollectiveQuery()166     public ResponseCollective getAggregateCollectiveQuery() {
167         return mAggregateCollectiveQuery;
168     }
169 
getAggregateCollectiveAuth()170     public ResponseCollective getAggregateCollectiveAuth() {
171         return mAggregateCollectiveAuth;
172     }
173 
isAuthReturned()174     public boolean isAuthReturned() {
175         return mAuthReturned;
176     }
177 
getMaxProviderTimestampNanoseconds()178     public long getMaxProviderTimestampNanoseconds() {
179         return mMaxProviderTimestampNanoseconds;
180     }
181 
getMinProviderTimestampNanoseconds()182     public long getMinProviderTimestampNanoseconds() {
183         return mMinProviderTimestampNanoseconds;
184     }
185 
getTotalQueryFailures()186     public int getTotalQueryFailures() {
187         return mTotalQueryFailures;
188     }
189 
190     /**
191      * Returns the unique, deduped, exception classtypes for logging associated with this provider.
192      *
193      * @return a string array for deduped exception classtypes
194      */
getUniqueExceptionStringsQuery()195     public String[] getUniqueExceptionStringsQuery() {
196         String[] result = new String[mExceptionCountQuery.keySet().size()];
197         mExceptionCountQuery.keySet().toArray(result);
198         return result;
199     }
200 
201     /**
202      * Returns the unique, deduped, exception classtype counts for logging associated with this
203      * provider.
204      *
205      * @return a string array for deduped classtype exception counts
206      */
getUniqueExceptionCountsQuery()207     public int[] getUniqueExceptionCountsQuery() {
208         return mExceptionCountQuery.values().stream().mapToInt(Integer::intValue).toArray();
209     }
210 
211     /**
212      * Returns the unique, deduped, exception classtypes for logging associated with this provider
213      * for auth entries.
214      *
215      * @return a string array for deduped exception classtypes for auth entries
216      */
getUniqueExceptionStringsAuth()217     public String[] getUniqueExceptionStringsAuth() {
218         String[] result = new String[mExceptionCountAuth.keySet().size()];
219         mExceptionCountAuth.keySet().toArray(result);
220         return result;
221     }
222 
223     /**
224      * Returns the unique, deduped, exception classtype counts for logging associated with this
225      * provider for auth entries.
226      *
227      * @return a string array for deduped classtype exception counts for auth entries
228      */
getUniqueExceptionCountsAuth()229     public int[] getUniqueExceptionCountsAuth() {
230         return mExceptionCountAuth.values().stream().mapToInt(Integer::intValue).toArray();
231     }
232 
getServiceBeganTimeNanoseconds()233     public long getServiceBeganTimeNanoseconds() {
234         return mServiceBeganTimeNanoseconds;
235     }
236 
getTotalAuthFailures()237     public int getTotalAuthFailures() {
238         return mTotalAuthFailures;
239     }
240 }
241