1 /*
2  * Copyright (C) 2021 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.metrics;
18 
19 import static com.android.providers.media.MediaProviderStatsLog.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS;
20 import static com.android.providers.media.MediaProviderStatsLog.TRANSCODING_DATA;
21 
22 import android.app.StatsManager;
23 import android.content.Context;
24 import android.util.Log;
25 import android.util.StatsEvent;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.providers.media.fuse.FuseDaemon;
30 import com.android.providers.media.util.BackgroundThread;
31 
32 import java.util.List;
33 
34 /** A class to initialise and log metrics pulled by statsd. */
35 public class PulledMetrics {
36     private static final String TAG = "PulledMetrics";
37 
38     private static final StatsPullCallbackHandler STATS_PULL_CALLBACK_HANDLER =
39             new StatsPullCallbackHandler();
40 
41     private static final StorageAccessMetrics storageAccessMetrics = new StorageAccessMetrics();
42 
43     private static boolean isInitialized = false;
44 
initialize(Context context)45     public static void initialize(Context context) {
46         if (isInitialized) {
47             return;
48         }
49 
50         final StatsManager statsManager = context.getSystemService(StatsManager.class);
51         if (statsManager == null) {
52             Log.e(TAG, "Error retrieving StatsManager. Cannot initialize PulledMetrics.");
53         } else {
54             Log.d(TAG, "Registering callback with StatsManager");
55 
56             try {
57                 // use the same callback handler for registering for all the tags.
58                 statsManager.setPullAtomCallback(TRANSCODING_DATA, null /* metadata */,
59                         BackgroundThread.getExecutor(),
60                         STATS_PULL_CALLBACK_HANDLER);
61                 statsManager.setPullAtomCallback(
62                         GENERAL_EXTERNAL_STORAGE_ACCESS_STATS,
63                         /*metadata*/null,
64                         BackgroundThread.getExecutor(),
65                         STATS_PULL_CALLBACK_HANDLER);
66                 isInitialized = true;
67             } catch (NullPointerException e) {
68                 Log.w(TAG, "Pulled metrics not supported. Could not register.", e);
69             }
70         }
71     }
72 
73     // Storage Access Metrics log functions
74 
75     /**
76      * Logs the mime type that was accessed by the given {@code uid}.
77      * Does nothing if the stats puller is not initialized.
78      */
logMimeTypeAccess(int uid, @NonNull String mimeType)79     public static void logMimeTypeAccess(int uid, @NonNull String mimeType) {
80         if (!isInitialized) {
81             return;
82         }
83 
84         storageAccessMetrics.logMimeType(uid, mimeType);
85     }
86 
87     /**
88      * Logs the storage access and attributes it to the given {@code uid}.
89      *
90      * <p>Should only be called from a FUSE thread.
91      */
logFileAccessViaFuse(int uid, @NonNull String file)92     public static void logFileAccessViaFuse(int uid, @NonNull String file) {
93         if (!isInitialized) {
94             return;
95         }
96 
97         storageAccessMetrics.logAccessViaFuse(uid, file);
98     }
99 
100     /**
101      * Logs the storage access and attributes it to the given {@code uid}.
102      *
103      * <p>This is a no-op if it's called on a FUSE thread.
104      */
logVolumeAccessViaMediaProvider(int uid, @NonNull String volumeName)105     public static void logVolumeAccessViaMediaProvider(int uid, @NonNull String volumeName) {
106         if (!isInitialized) {
107             return;
108         }
109 
110         // We don't log if it's a FUSE thread because logAccessViaFuse should handle that.
111         if (FuseDaemon.native_is_fuse_thread()) {
112             return;
113         }
114         storageAccessMetrics.logAccessViaMediaProvider(uid, volumeName);
115     }
116 
117     private static class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
118         @Override
onPullAtom(int atomTag, List<StatsEvent> data)119         public int onPullAtom(int atomTag, List<StatsEvent> data) {
120             // handle the tags appropriately.
121             List<StatsEvent> events = pullEvents(atomTag);
122             if (events == null) {
123                 return StatsManager.PULL_SKIP;
124             }
125 
126             data.addAll(events);
127             return StatsManager.PULL_SUCCESS;
128         }
129 
pullEvents(int atomTag)130         private List<StatsEvent> pullEvents(int atomTag) {
131             switch (atomTag) {
132                 case TRANSCODING_DATA:
133                     return TranscodeMetrics.pullStatsEvents();
134                 case GENERAL_EXTERNAL_STORAGE_ACCESS_STATS:
135                     return storageAccessMetrics.pullStatsEvents();
136                 default:
137                     return null;
138             }
139         }
140     }
141 }
142