1 /* 2 * Copyright (C) 2019 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.providers.media.util; 18 19 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_CONTENT_DELETED; 20 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_IDLE_MAINTENANCE_FINISHED; 21 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_REQUESTED; 22 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_DENIED; 23 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_GRANTED; 24 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED; 25 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_OTHER; 26 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_PRIMARY; 27 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__INTERNAL; 28 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__UNKNOWN; 29 import static com.android.providers.media.MediaProviderStatsLog.MEDIA_PROVIDER_SCHEMA_CHANGED; 30 import static com.android.providers.media.scan.MediaScanner.REASON_DEMAND; 31 import static com.android.providers.media.scan.MediaScanner.REASON_IDLE; 32 import static com.android.providers.media.scan.MediaScanner.REASON_MOUNTED; 33 import static com.android.providers.media.scan.MediaScanner.REASON_UNKNOWN; 34 35 import android.provider.MediaStore; 36 37 import androidx.annotation.NonNull; 38 39 import com.android.providers.media.MediaProviderStatsLog; 40 41 /** 42 * Class that emits common metrics to both remote and local endpoints to aid in 43 * regression investigations and bug triage. 44 */ 45 public class Metrics { logScan(@onNull String volumeName, int reason, long itemCount, long durationMillis, int insertCount, int updateCount, int deleteCount)46 public static void logScan(@NonNull String volumeName, int reason, long itemCount, 47 long durationMillis, int insertCount, int updateCount, int deleteCount) { 48 Logging.logPersistent(String.format( 49 "Scanned %s due to %s, found %d items in %dms, %d inserts %d updates %d deletes", 50 volumeName, translateReason(reason), itemCount, durationMillis, insertCount, 51 updateCount, deleteCount)); 52 53 final float normalizedDurationMillis = ((float) durationMillis) / itemCount; 54 final float normalizedInsertCount = ((float) insertCount) / itemCount; 55 final float normalizedUpdateCount = ((float) updateCount) / itemCount; 56 final float normalizedDeleteCount = ((float) deleteCount) / itemCount; 57 58 MediaProviderStatsLog.write(MEDIA_PROVIDER_SCAN_OCCURRED, 59 translateVolumeName(volumeName), reason, itemCount, normalizedDurationMillis, 60 normalizedInsertCount, normalizedUpdateCount, normalizedDeleteCount); 61 } 62 63 /** 64 * Logs persistent deletion logs on-device. 65 */ logDeletionPersistent(@onNull String volumeName, String reason, int[] countPerMediaType)66 public static void logDeletionPersistent(@NonNull String volumeName, String reason, 67 int[] countPerMediaType) { 68 final StringBuilder builder = new StringBuilder("Deleted "); 69 for (int count: countPerMediaType) { 70 builder.append(count).append(' '); 71 } 72 builder.append("items on ") 73 .append(volumeName) 74 .append(" due to ") 75 .append(reason); 76 77 Logging.logPersistent(builder.toString()); 78 } 79 80 /** 81 * Logs persistent deletion logs on-device and stats metrics. Count of items per-media-type 82 * are not uploaded to MediaProviderStats logs. 83 */ logDeletion(@onNull String volumeName, int uid, String packageName, int itemCount, int[] countPerMediaType)84 public static void logDeletion(@NonNull String volumeName, int uid, String packageName, 85 int itemCount, int[] countPerMediaType) { 86 logDeletionPersistent(volumeName, packageName, countPerMediaType); 87 MediaProviderStatsLog.write(MEDIA_CONTENT_DELETED, 88 translateVolumeName(volumeName), uid, itemCount); 89 } 90 logPermissionGranted(@onNull String volumeName, int uid, String packageName, int itemCount)91 public static void logPermissionGranted(@NonNull String volumeName, int uid, String packageName, 92 int itemCount) { 93 Logging.logPersistent(String.format( 94 "Granted permission to %3$d items on %1$s to %2$s", 95 volumeName, packageName, itemCount)); 96 97 MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_REQUESTED, 98 translateVolumeName(volumeName), uid, itemCount, 99 MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_GRANTED); 100 } 101 logPermissionDenied(@onNull String volumeName, int uid, String packageName, int itemCount)102 public static void logPermissionDenied(@NonNull String volumeName, int uid, String packageName, 103 int itemCount) { 104 Logging.logPersistent(String.format( 105 "Denied permission to %3$d items on %1$s to %2$s", 106 volumeName, packageName, itemCount)); 107 108 MediaProviderStatsLog.write(MEDIA_PROVIDER_PERMISSION_REQUESTED, 109 translateVolumeName(volumeName), uid, itemCount, 110 MEDIA_PROVIDER_PERMISSION_REQUESTED__RESULT__USER_DENIED); 111 } 112 logSchemaChange(@onNull String volumeName, int versionFrom, int versionTo, long itemCount, long durationMillis, @NonNull String databaseUuid)113 public static void logSchemaChange(@NonNull String volumeName, int versionFrom, int versionTo, 114 long itemCount, long durationMillis, @NonNull String databaseUuid) { 115 Logging.logPersistent(String.format( 116 "Changed schema version on %s from %d to %d, %d items taking %dms UUID %s", 117 volumeName, versionFrom, versionTo, itemCount, durationMillis, databaseUuid)); 118 119 final float normalizedDurationMillis = ((float) durationMillis) / itemCount; 120 121 MediaProviderStatsLog.write(MEDIA_PROVIDER_SCHEMA_CHANGED, 122 translateVolumeName(volumeName), versionFrom, versionTo, itemCount, 123 normalizedDurationMillis); 124 } 125 logIdleMaintenance(@onNull String volumeName, long itemCount, long durationMillis, int staleThumbnails, int expiredMedia)126 public static void logIdleMaintenance(@NonNull String volumeName, long itemCount, 127 long durationMillis, int staleThumbnails, int expiredMedia) { 128 Logging.logPersistent(String.format( 129 "Idle maintenance on %s, %d items taking %dms, %d stale, %d expired", 130 volumeName, itemCount, durationMillis, staleThumbnails, expiredMedia)); 131 132 final float normalizedDurationMillis = ((float) durationMillis) / itemCount; 133 final float normalizedStaleThumbnails = ((float) staleThumbnails) / itemCount; 134 final float normalizedExpiredMedia = ((float) expiredMedia) / itemCount; 135 136 MediaProviderStatsLog.write(MEDIA_PROVIDER_IDLE_MAINTENANCE_FINISHED, 137 translateVolumeName(volumeName), itemCount, normalizedDurationMillis, 138 normalizedStaleThumbnails, normalizedExpiredMedia); 139 } 140 translateReason(int reason)141 public static String translateReason(int reason) { 142 switch (reason) { 143 case REASON_UNKNOWN: return "REASON_UNKNOWN"; 144 case REASON_MOUNTED: return "REASON_MOUNTED"; 145 case REASON_DEMAND: return "REASON_DEMAND"; 146 case REASON_IDLE: return "REASON_IDLE"; 147 default: return String.valueOf(reason); 148 } 149 } 150 translateVolumeName(@onNull String volumeName)151 private static int translateVolumeName(@NonNull String volumeName) { 152 switch (volumeName) { 153 case MediaStore.VOLUME_INTERNAL: 154 return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__INTERNAL; 155 case MediaStore.VOLUME_EXTERNAL: 156 // Callers using generic "external" volume name end up applying 157 // to all external volumes, so we can't tell which volumes were 158 // actually changed 159 return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__UNKNOWN; 160 case MediaStore.VOLUME_EXTERNAL_PRIMARY: 161 return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_PRIMARY; 162 default: 163 return MEDIA_PROVIDER_SCAN_OCCURRED__VOLUME_TYPE__EXTERNAL_OTHER; 164 } 165 } 166 } 167