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