• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal.logging;
17 
18 import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
19 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
20 
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 
25 import com.google.android.libraries.mobiledatadownload.Flags;
26 import com.google.android.libraries.mobiledatadownload.Logger;
27 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFluentFuture;
28 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
29 import com.google.common.base.Optional;
30 import com.google.common.util.concurrent.AsyncCallable;
31 import com.google.common.util.concurrent.FutureCallback;
32 import com.google.common.util.concurrent.ListenableFuture;
33 import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent;
34 import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult;
35 import com.google.mobiledatadownload.LogProto.AndroidClientInfo;
36 import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats;
37 import com.google.mobiledatadownload.LogProto.MddDeviceInfo;
38 import com.google.mobiledatadownload.LogProto.MddDownloadLatency;
39 import com.google.mobiledatadownload.LogProto.MddDownloadResultLog;
40 import com.google.mobiledatadownload.LogProto.MddLibApiResultLog;
41 import com.google.mobiledatadownload.LogProto.MddLogData;
42 import com.google.mobiledatadownload.LogProto.MddNetworkStats;
43 import com.google.mobiledatadownload.LogProto.MddStorageStats;
44 import com.google.mobiledatadownload.LogProto.StableSamplingInfo;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.List;
49 
50 /** Assembles data and logs them with underlying {@link Logger}. */
51 public final class MddEventLogger implements EventLogger {
52 
53     private static final String TAG = "MddEventLogger";
54 
55     private final Context context;
56     private final Logger logger;
57     // A process that has mdi download module loaded will get restarted if a new module version is
58     // installed.
59     private final int moduleVersion;
60     private final String hostPackageName;
61     private final Flags flags;
62     private final LogSampler logSampler;
63 
64     private Optional<LoggingStateStore> loggingStateStore = Optional.absent();
65 
MddEventLogger( Context context, Logger logger, int moduleVersion, LogSampler logSampler, Flags flags)66     public MddEventLogger(
67             Context context, Logger logger, int moduleVersion, LogSampler logSampler, Flags flags) {
68         this.context = context;
69         this.logger = logger;
70         this.moduleVersion = moduleVersion;
71         this.hostPackageName = context.getPackageName();
72         this.logSampler = logSampler;
73         this.flags = flags;
74     }
75 
76     /**
77      * This should be called before MddEventLogger is used. If it is not called before
78      * MddEventLogger
79      * is used, stable sampling will not be used.
80      *
81      * <p>Note(rohitsat): this is required because LoggingStateStore is constructed with a PDS in
82      * the
83      * MainMddLibModule. MddEventLogger is required to construct the MainMddLibModule.
84      *
85      * @param loggingStateStore the LoggingStateStore that contains the persisted random number for
86      *                          stable sampling.
87      */
setLoggingStateStore(LoggingStateStore loggingStateStore)88     public void setLoggingStateStore(LoggingStateStore loggingStateStore) {
89         this.loggingStateStore = Optional.of(loggingStateStore);
90     }
91 
92     @Override
logEventSampled(MddClientEvent.Code eventCode)93     public void logEventSampled(MddClientEvent.Code eventCode) {
94         sampleAndSendLogEvent(eventCode, MddLogData.newBuilder(), flags.mddDefaultSampleInterval());
95     }
96 
97     @Override
logEventSampled( MddClientEvent.Code eventCode, String fileGroupName, int fileGroupVersionNumber, long buildId, String variantId)98     public void logEventSampled(
99             MddClientEvent.Code eventCode,
100             String fileGroupName,
101             int fileGroupVersionNumber,
102             long buildId,
103             String variantId) {
104 
105         DataDownloadFileGroupStats dataDownloadFileGroupStats =
106                 DataDownloadFileGroupStats.newBuilder()
107                         .setFileGroupName(fileGroupName)
108                         .setFileGroupVersionNumber(fileGroupVersionNumber)
109                         .setBuildId(buildId)
110                         .setVariantId(variantId)
111                         .build();
112 
113         sampleAndSendLogEvent(
114                 eventCode,
115                 MddLogData.newBuilder().setDataDownloadFileGroupStats(dataDownloadFileGroupStats),
116                 flags.mddDefaultSampleInterval());
117     }
118 
119     @Override
logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval)120     public void logEventAfterSample(MddClientEvent.Code eventCode, int sampleInterval) {
121         // TODO(b/138392640): delete this method once the pds migration is complete. If it's
122         //  necessary
123         // for other use cases, we can establish a pattern where this class is still responsible for
124         // sampling.
125         MddLogData.Builder logData = MddLogData.newBuilder();
126         processAndSendEventWithoutStableSampling(eventCode, logData, sampleInterval);
127     }
128 
129     @Override
logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats)130     public void logMddApiCallStats(DataDownloadFileGroupStats fileGroupDetails, Void apiCallStats) {
131         // TODO(b/144684763): update this to use stable sampling. Leaving it as is for now since
132         //  it is
133         // fairly high volume.
134         long sampleInterval = flags.apiLoggingSampleInterval();
135         if (!LogUtil.shouldSampleInterval(sampleInterval)) {
136             return;
137         }
138         MddLogData.Builder logData =
139                 MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails);
140         processAndSendEventWithoutStableSampling(
141                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, sampleInterval);
142     }
143 
144     @Override
logMddLibApiResultLog(MddLibApiResultLog mddLibApiResultLog)145     public void logMddLibApiResultLog(MddLibApiResultLog mddLibApiResultLog) {
146         MddLogData.Builder logData = MddLogData.newBuilder().setMddLibApiResultLog(
147                 mddLibApiResultLog);
148 
149         sampleAndSendLogEvent(
150                 MddClientEvent.Code.DATA_DOWNLOAD_LIB_API_RESULT,
151                 logData,
152                 flags.apiLoggingSampleInterval());
153     }
154 
155     @Override
logMddFileGroupStats( AsyncCallable<List<EventLogger.FileGroupStatusWithDetails>> buildFileGroupStats)156     public ListenableFuture<Void> logMddFileGroupStats(
157             AsyncCallable<List<EventLogger.FileGroupStatusWithDetails>> buildFileGroupStats) {
158         return lazySampleAndSendLogEvent(
159                 MddClientEvent.Code.DATA_DOWNLOAD_FILE_GROUP_STATUS,
160                 () ->
161                         PropagatedFutures.transform(
162                                 buildFileGroupStats.call(),
163                                 fileGroupStatusAndDetailsList -> {
164                                     List<MddLogData> allMddLogData = new ArrayList<>();
165 
166                                     for (FileGroupStatusWithDetails fileGroupStatusAndDetails :
167                                             fileGroupStatusAndDetailsList) {
168                                         allMddLogData.add(
169                                                 MddLogData.newBuilder()
170                                                         .setMddFileGroupStatus(
171                                                                 fileGroupStatusAndDetails.fileGroupStatus())
172                                                         .setDataDownloadFileGroupStats(
173                                                                 fileGroupStatusAndDetails.fileGroupDetails())
174                                                         .build());
175                                     }
176                                     return allMddLogData;
177                                 },
178                                 directExecutor()),
179                 flags.groupStatsLoggingSampleInterval());
180     }
181 
182     @Override
logMddStorageStats( AsyncCallable<MddStorageStats> buildStorageStats)183     public ListenableFuture<Void> logMddStorageStats(
184             AsyncCallable<MddStorageStats> buildStorageStats) {
185         return lazySampleAndSendLogEvent(
186                 MddClientEvent.Code.DATA_DOWNLOAD_STORAGE_STATS,
187                 () ->
188                         PropagatedFutures.transform(
189                                 buildStorageStats.call(),
190                                 storageStats ->
191                                         Arrays.asList(MddLogData.newBuilder().setMddStorageStats(
192                                                 storageStats).build()),
193                                 directExecutor()),
194                 flags.storageStatsLoggingSampleInterval());
195     }
196 
197     @Override
logMddNetworkStats( AsyncCallable<MddNetworkStats> buildNetworkStats)198     public ListenableFuture<Void> logMddNetworkStats(
199             AsyncCallable<MddNetworkStats> buildNetworkStats) {
200         return lazySampleAndSendLogEvent(
201                 MddClientEvent.Code.DATA_DOWNLOAD_NETWORK_STATS,
202                 () ->
203                         PropagatedFutures.transform(
204                                 buildNetworkStats.call(),
205                                 networkStats ->
206                                         Arrays.asList(MddLogData.newBuilder().setMddNetworkStats(
207                                                 networkStats).build()),
208                                 directExecutor()),
209                 flags.networkStatsLoggingSampleInterval());
210     }
211 
212     @Override
logMddDataDownloadFileExpirationEvent(int eventCode, int count)213     public void logMddDataDownloadFileExpirationEvent(int eventCode, int count) {
214         MddLogData.Builder logData = MddLogData.newBuilder();
215         sampleAndSendLogEvent(
216                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData,
217                 flags.mddDefaultSampleInterval());
218     }
219 
220     @Override
logMddNetworkSavings( DataDownloadFileGroupStats fileGroupDetails, int code, long fullFileSize, long downloadedFileSize, String fileId, int deltaIndex)221     public void logMddNetworkSavings(
222             DataDownloadFileGroupStats fileGroupDetails,
223             int code,
224             long fullFileSize,
225             long downloadedFileSize,
226             String fileId,
227             int deltaIndex) {
228         MddLogData.Builder logData = MddLogData.newBuilder();
229 
230         sampleAndSendLogEvent(
231                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData,
232                 flags.mddDefaultSampleInterval());
233     }
234 
235     @Override
logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails)236     public void logMddQueryStats(DataDownloadFileGroupStats fileGroupDetails) {
237         MddLogData.Builder logData = MddLogData.newBuilder();
238 
239         sampleAndSendLogEvent(
240                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData,
241                 flags.mddDefaultSampleInterval());
242     }
243 
244     @Override
logMddDownloadLatency( DataDownloadFileGroupStats fileGroupDetails, MddDownloadLatency downloadLatency)245     public void logMddDownloadLatency(
246             DataDownloadFileGroupStats fileGroupDetails, MddDownloadLatency downloadLatency) {
247         MddLogData.Builder logData =
248                 MddLogData.newBuilder()
249                         .setMddDownloadLatency(downloadLatency)
250                         .setDataDownloadFileGroupStats(fileGroupDetails);
251 
252         sampleAndSendLogEvent(
253                 MddClientEvent.Code.DATA_DOWNLOAD_LATENCY_LOG, logData,
254                 flags.mddDefaultSampleInterval());
255     }
256 
257     @Override
logMddDownloadResult( MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails)258     public void logMddDownloadResult(
259             MddDownloadResult.Code code, DataDownloadFileGroupStats fileGroupDetails) {
260         MddLogData.Builder logData =
261                 MddLogData.newBuilder()
262                         .setMddDownloadResultLog(
263                                 MddDownloadResultLog.newBuilder()
264                                         .setResult(code)
265                                         .setDataDownloadFileGroupStats(fileGroupDetails));
266 
267         sampleAndSendLogEvent(
268                 MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG, logData,
269                 flags.mddDefaultSampleInterval());
270     }
271 
272     @Override
logMddAndroidSharingLog(Void event)273     public void logMddAndroidSharingLog(Void event) {
274         // TODO(b/144684763): consider moving this to stable sampling depending on frequency of
275         //  events.
276         long sampleInterval = flags.mddAndroidSharingSampleInterval();
277         if (!LogUtil.shouldSampleInterval(sampleInterval)) {
278             return;
279         }
280         MddLogData.Builder logData = MddLogData.newBuilder();
281         processAndSendEventWithoutStableSampling(
282                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData, sampleInterval);
283     }
284 
285     @Override
logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog)286     public void logMddUsageEvent(DataDownloadFileGroupStats fileGroupDetails, Void usageEventLog) {
287         MddLogData.Builder logData =
288                 MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails);
289 
290         sampleAndSendLogEvent(
291                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData,
292                 flags.mddDefaultSampleInterval());
293     }
294 
295     @Override
logNewConfigReceived( DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo)296     public void logNewConfigReceived(
297             DataDownloadFileGroupStats fileGroupDetails, Void newConfigReceivedInfo) {
298         MddLogData.Builder logData =
299                 MddLogData.newBuilder().setDataDownloadFileGroupStats(fileGroupDetails);
300         sampleAndSendLogEvent(
301                 MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, logData,
302                 flags.mddDefaultSampleInterval());
303     }
304 
305     /**
306      * Determines whether the log event will be a part of the sample, and if so calls {@code
307      * buildStats} to construct the log event. This is like {@link sampleAndSendLogEvent} but
308      * constructs the log event lazy. This is useful if constructing the log event is expensive.
309      */
lazySampleAndSendLogEvent( MddClientEvent.Code eventCode, AsyncCallable<List<MddLogData>> buildStats, int sampleInterval)310     private ListenableFuture<Void> lazySampleAndSendLogEvent(
311             MddClientEvent.Code eventCode,
312             AsyncCallable<List<MddLogData>> buildStats,
313             int sampleInterval) {
314         return PropagatedFutures.transformAsync(
315                 logSampler.shouldLog(sampleInterval, loggingStateStore),
316                 samplingInfoOptional -> {
317                     if (!samplingInfoOptional.isPresent()) {
318                         return immediateVoidFuture();
319                     }
320 
321                     return PropagatedFluentFuture.from(buildStats.call())
322                             .transform(
323                                     icingLogDataList -> {
324                                         if (icingLogDataList != null) {
325                                             for (MddLogData icingLogData : icingLogDataList) {
326                                                 processAndSendEvent(
327                                                         eventCode,
328                                                         icingLogData.toBuilder(),
329                                                         sampleInterval,
330                                                         samplingInfoOptional.get());
331                                             }
332                                         }
333                                         return null;
334                                     },
335                                     directExecutor());
336                 },
337                 directExecutor());
338     }
339 
340     private void sampleAndSendLogEvent(
341             MddClientEvent.Code eventCode, MddLogData.Builder logData, long sampleInterval) {
342         // NOTE: When using a single-threaded executor, logging may be delayed since other
343         // work will come before the log sampler check.
344         PropagatedFutures.addCallback(
345                 logSampler.shouldLog(sampleInterval, loggingStateStore),
346                 new FutureCallback<Optional<StableSamplingInfo>>() {
347                     @Override
348                     public void onSuccess(Optional<StableSamplingInfo> stableSamplingInfo) {
349                         if (stableSamplingInfo.isPresent()) {
350                             processAndSendEvent(eventCode, logData, sampleInterval,
351                                     stableSamplingInfo.get());
352                         }
353                     }
354 
355                     @Override
356                     public void onFailure(Throwable t) {
357                         LogUtil.e(t, "%s: failure when sampling log!", TAG);
358                     }
359                 },
360                 directExecutor());
361     }
362 
363     /** Adds all transforms common to all logs and sends the event to Logger. */
364     private void processAndSendEventWithoutStableSampling(
365             MddClientEvent.Code eventCode, MddLogData.Builder logData, long sampleInterval) {
366         processAndSendEvent(
367                 eventCode,
368                 logData,
369                 sampleInterval,
370                 StableSamplingInfo.newBuilder().setStableSamplingUsed(false).build());
371     }
372 
373     /** Adds all transforms common to all logs and sends the event to Logger. */
374     private void processAndSendEvent(
375             MddClientEvent.Code eventCode,
376             MddLogData.Builder logData,
377             long sampleInterval,
378             StableSamplingInfo stableSamplingInfo) {
379         if (eventCode.equals(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED)) {
380             LogUtil.e("%s: unspecified code used, skipping event log", TAG);
381             // return early for unspecified codes.
382             return;
383         }
384 
385         logData
386                 .setSamplingInterval(sampleInterval)
387                 .setDeviceInfo(
388                         MddDeviceInfo.newBuilder().setDeviceStorageLow(isDeviceStorageLow(context)))
389                 .setAndroidClientInfo(
390                         AndroidClientInfo.newBuilder()
391                                 .setHostPackageName(hostPackageName)
392                                 .setModuleVersion(moduleVersion))
393                 .setStableSamplingInfo(stableSamplingInfo);
394         logger.log(logData.build(), eventCode.getNumber());
395     }
396 
397     /** Returns whether the device is in low storage state. */
398     private static boolean isDeviceStorageLow(Context context) {
399         // Check if the system says storage is low, by reading the sticky intent.
400         return context.registerReceiver(null, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW))
401                 != null;
402     }
403 }
404