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