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.federatedcompute.services.statsd;
18 
19 import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.EXAMPLE_ITERATOR_NEXT_LATENCY_REPORTED;
20 import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_API_CALLED;
21 import static com.android.federatedcompute.services.stats.FederatedComputeStatsLog.FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED;
22 
23 import com.android.federatedcompute.services.stats.FederatedComputeStatsLog;
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import com.google.common.util.concurrent.RateLimiter;
27 
28 /** Log API stats and client error stats to StatsD. */
29 public class FederatedComputeStatsdLogger {
30     private static volatile FederatedComputeStatsdLogger sFCStatsdLogger = null;
31     private final RateLimiter mRateLimiter;
32 
33     @VisibleForTesting
FederatedComputeStatsdLogger(RateLimiter rateLimiter)34     FederatedComputeStatsdLogger(RateLimiter rateLimiter) {
35         mRateLimiter = rateLimiter;
36     }
37 
38     /** Returns an instance of {@link FederatedComputeStatsdLogger}. */
getInstance()39     public static FederatedComputeStatsdLogger getInstance() {
40         if (sFCStatsdLogger == null) {
41             synchronized (FederatedComputeStatsdLogger.class) {
42                 if (sFCStatsdLogger == null) {
43                     sFCStatsdLogger =
44                             new FederatedComputeStatsdLogger(
45                                     // Android metrics team recommend the atom logging frequency
46                                     // should not exceed once per 10 milliseconds.
47                                     RateLimiter.create(100));
48                 }
49             }
50         }
51         return sFCStatsdLogger;
52     }
53 
54     /** Log API call stats e.g. response code, API name etc. */
logApiCallStats(ApiCallStats apiCallStats)55     public void logApiCallStats(ApiCallStats apiCallStats) {
56         if (mRateLimiter.tryAcquire()) {
57             FederatedComputeStatsLog.write(
58                     FEDERATED_COMPUTE_API_CALLED,
59                     apiCallStats.getApiClass(),
60                     apiCallStats.getApiName(),
61                     apiCallStats.getLatencyMillis(),
62                     apiCallStats.getResponseCode(),
63                     apiCallStats.getSdkPackageName());
64         }
65     }
66 
67     /**
68      * Log FederatedComputeTrainingEventReported to track each stage of federated computation job
69      * execution.
70      */
logTrainingEventReported(TrainingEventReported trainingEvent)71     public void logTrainingEventReported(TrainingEventReported trainingEvent) {
72         if (mRateLimiter.tryAcquire()) {
73             FederatedComputeStatsLog.write(
74                     FEDERATED_COMPUTE_TRAINING_EVENT_REPORTED,
75                     trainingEvent.getClientVersion(),
76                     trainingEvent.getEventKind(),
77                     trainingEvent.getTaskId(),
78                     trainingEvent.getDurationInMillis(),
79                     trainingEvent.getExampleSize(),
80                     trainingEvent.getDataTransferDurationMillis(),
81                     trainingEvent.getBytesUploaded(),
82                     trainingEvent.getBytesDownloaded(),
83                     trainingEvent.getKeyAttestationLatencyMillis(),
84                     trainingEvent.getExampleStoreBindLatencyNanos(),
85                     trainingEvent.getExampleStoreStartQueryLatencyNanos(),
86                     trainingEvent.getPopulationId(),
87                     trainingEvent.getExampleCount(),
88                     trainingEvent.getSdkPackageName());
89         }
90     }
91 
92     /** This method is only used to test if rate limiter is applied when logging to statsd. */
93     @VisibleForTesting
recordExampleIteratorLatencyMetrics(ExampleIteratorLatency iteratorLatency)94     boolean recordExampleIteratorLatencyMetrics(ExampleIteratorLatency iteratorLatency) {
95         if (mRateLimiter.tryAcquire()) {
96             FederatedComputeStatsLog.write(
97                     EXAMPLE_ITERATOR_NEXT_LATENCY_REPORTED,
98                     iteratorLatency.getClientVersion(),
99                     iteratorLatency.getTaskId(),
100                     iteratorLatency.getGetNextLatencyNanos());
101             return true;
102         }
103         return false;
104     }
105 
106     /**
107      * Log ExampleIteratorNextLatencyReported to track the latency of ExampleStoreIterator.next
108      * called.
109      */
logExampleIteratorNextLatencyReported(ExampleIteratorLatency iteratorLatency)110     public void logExampleIteratorNextLatencyReported(ExampleIteratorLatency iteratorLatency) {
111         var unused = recordExampleIteratorLatencyMetrics(iteratorLatency);
112     }
113 }
114