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