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.immediateFuture; 19 20 import com.google.android.libraries.mobiledatadownload.internal.FileGroupManager; 21 import com.google.android.libraries.mobiledatadownload.internal.FileGroupManager.GroupDownloadStatus; 22 import com.google.android.libraries.mobiledatadownload.internal.FileGroupsMetadata; 23 import com.google.android.libraries.mobiledatadownload.internal.annotations.SequentialControlExecutor; 24 import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup; 25 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil; 26 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 27 import com.google.common.util.concurrent.Futures; 28 import com.google.common.util.concurrent.ListenableFuture; 29 import com.google.mobiledatadownload.LogEnumsProto.MddFileGroupDownloadStatus; 30 import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; 31 import com.google.mobiledatadownload.LogProto.MddFileGroupStatus; 32 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; 33 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.concurrent.Executor; 37 import javax.inject.Inject; 38 39 /** 40 * Log MDD file group stats. For each file group, it will log the file group details along with the 41 * current state of the file group (pending, downloaded or stale). 42 */ 43 public class FileGroupStatsLogger { 44 45 private static final String TAG = "FileGroupStatsLogger"; 46 private final FileGroupManager fileGroupManager; 47 private final FileGroupsMetadata fileGroupsMetadata; 48 private final EventLogger eventLogger; 49 private final Executor sequentialControlExecutor; 50 51 @Inject FileGroupStatsLogger( FileGroupManager fileGroupManager, FileGroupsMetadata fileGroupsMetadata, EventLogger eventLogger, @SequentialControlExecutor Executor sequentialControlExecutor)52 public FileGroupStatsLogger( 53 FileGroupManager fileGroupManager, 54 FileGroupsMetadata fileGroupsMetadata, 55 EventLogger eventLogger, 56 @SequentialControlExecutor Executor sequentialControlExecutor) { 57 this.fileGroupManager = fileGroupManager; 58 this.fileGroupsMetadata = fileGroupsMetadata; 59 this.eventLogger = eventLogger; 60 this.sequentialControlExecutor = sequentialControlExecutor; 61 } 62 63 // TODO(b/73490689): Also log stats about stale groups. log(int daysSinceLastLog)64 public ListenableFuture<Void> log(int daysSinceLastLog) { 65 return eventLogger.logMddFileGroupStats(() -> buildFileGroupStatusList(daysSinceLastLog)); 66 } 67 buildFileGroupStatusList( int daysSinceLastLog)68 private ListenableFuture<List<EventLogger.FileGroupStatusWithDetails>> buildFileGroupStatusList( 69 int daysSinceLastLog) { 70 return PropagatedFutures.transformAsync( 71 fileGroupsMetadata.getAllFreshGroups(), 72 downloadedAndPendingGroups -> { 73 List<ListenableFuture<EventLogger.FileGroupStatusWithDetails>> futures = 74 new ArrayList<>(); 75 for (GroupKeyAndGroup pair : downloadedAndPendingGroups) { 76 GroupKey groupKey = pair.groupKey(); 77 DataFileGroupInternal dataFileGroup = pair.dataFileGroup(); 78 if (dataFileGroup == null) { 79 continue; 80 } 81 82 DataDownloadFileGroupStats fileGroupDetails = 83 DataDownloadFileGroupStats.newBuilder() 84 .setFileGroupName(groupKey.getGroupName()) 85 .setOwnerPackage(groupKey.getOwnerPackage()) 86 .setFileGroupVersionNumber(dataFileGroup.getFileGroupVersionNumber()) 87 .setFileCount(dataFileGroup.getFileCount()) 88 .setInlineFileCount(FileGroupUtil.getInlineFileCount(dataFileGroup)) 89 .setHasAccount(!groupKey.getAccount().isEmpty()) 90 .setBuildId(dataFileGroup.getBuildId()) 91 .setVariantId(dataFileGroup.getVariantId()) 92 .build(); 93 94 futures.add( 95 PropagatedFutures.transform( 96 buildFileGroupStatus(dataFileGroup, groupKey, daysSinceLastLog), 97 fileGroupStatus -> 98 EventLogger.FileGroupStatusWithDetails.create( 99 fileGroupStatus, fileGroupDetails), 100 sequentialControlExecutor)); 101 } 102 return Futures.allAsList(futures); 103 }, 104 sequentialControlExecutor); 105 } 106 buildFileGroupStatus( DataFileGroupInternal dataFileGroup, GroupKey groupKey, int daysSinceLastLog)107 private ListenableFuture<MddFileGroupStatus> buildFileGroupStatus( 108 DataFileGroupInternal dataFileGroup, GroupKey groupKey, int daysSinceLastLog) { 109 MddFileGroupStatus.Builder fileGroupStatus = 110 MddFileGroupStatus.newBuilder().setDaysSinceLastLog(daysSinceLastLog); 111 if (dataFileGroup.getBookkeeping().hasGroupNewFilesReceivedTimestamp()) { 112 fileGroupStatus.setGroupAddedTimestampInSeconds( 113 dataFileGroup.getBookkeeping().getGroupNewFilesReceivedTimestamp() / 1000); 114 } else { 115 fileGroupStatus.setGroupAddedTimestampInSeconds(-1); 116 } 117 118 if (groupKey.getDownloaded()) { 119 fileGroupStatus.setFileGroupDownloadStatus(MddFileGroupDownloadStatus.Code.COMPLETE); 120 if (dataFileGroup.getBookkeeping().hasGroupDownloadedTimestampInMillis()) { 121 fileGroupStatus.setGroupDownloadedTimestampInSeconds( 122 dataFileGroup.getBookkeeping().getGroupDownloadedTimestampInMillis() / 1000); 123 } else { 124 fileGroupStatus.setGroupDownloadedTimestampInSeconds(-1); 125 } 126 return immediateFuture(fileGroupStatus.build()); 127 } else { 128 fileGroupStatus.setGroupDownloadedTimestampInSeconds(-1); 129 return PropagatedFutures.transform( 130 fileGroupManager.getFileGroupDownloadStatus(dataFileGroup), 131 status -> { 132 if (status == GroupDownloadStatus.DOWNLOADED || status == GroupDownloadStatus.PENDING) { 133 // Log pending even if verify returns downloaded, as it will be marked as 134 // completed in the next periodic task. 135 fileGroupStatus.setFileGroupDownloadStatus(MddFileGroupDownloadStatus.Code.PENDING); 136 } else { 137 // TODO(b/73490689): Log the reason for failure along with this. 138 fileGroupStatus.setFileGroupDownloadStatus(MddFileGroupDownloadStatus.Code.FAILED); 139 } 140 return fileGroupStatus.build(); 141 }, 142 sequentialControlExecutor); 143 } 144 } 145 } 146