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.modules.utils.BackgroundThread;
30 import com.android.providers.media.fuse.FuseDaemon;
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         BackgroundThread.getExecutor().execute(() -> {
84             storageAccessMetrics.logMimeType(uid, mimeType);
85         });
86     }
87 
88     /**
89      * Logs the storage access and attributes it to the given {@code uid}.
90      *
91      * <p>This is a no-op if it's called from a non-FUSE thread.
92      */
logFileAccessViaFuse(int uid, @NonNull String file)93     public static void logFileAccessViaFuse(int uid, @NonNull String file) {
94         if (!isInitialized) {
95             return;
96         }
97         // Log only if it's a FUSE thread
98         if (!FuseDaemon.native_is_fuse_thread()) {
99             return;
100         }
101         BackgroundThread.getExecutor().execute(() -> {
102             storageAccessMetrics.logAccessViaFuse(uid, file);
103         });
104     }
105 
106     /**
107      * Logs the storage access and attributes it to the given {@code uid}.
108      *
109      * <p>This is a no-op if it's called on a FUSE thread.
110      */
logVolumeAccessViaMediaProvider(int uid, @NonNull String volumeName)111     public static void logVolumeAccessViaMediaProvider(int uid, @NonNull String volumeName) {
112         if (!isInitialized) {
113             return;
114         }
115 
116         // We don't log if it's a FUSE thread because logAccessViaFuse should handle that.
117         if (FuseDaemon.native_is_fuse_thread()) {
118             return;
119         }
120         BackgroundThread.getExecutor().execute(() -> {
121             storageAccessMetrics.logAccessViaMediaProvider(uid, volumeName);
122         });
123     }
124 
125     private static class StatsPullCallbackHandler implements StatsManager.StatsPullAtomCallback {
126         @Override
onPullAtom(int atomTag, List<StatsEvent> data)127         public int onPullAtom(int atomTag, List<StatsEvent> data) {
128             // handle the tags appropriately.
129             List<StatsEvent> events = pullEvents(atomTag);
130             if (events == null) {
131                 return StatsManager.PULL_SKIP;
132             }
133 
134             data.addAll(events);
135             return StatsManager.PULL_SUCCESS;
136         }
137 
pullEvents(int atomTag)138         private List<StatsEvent> pullEvents(int atomTag) {
139             switch (atomTag) {
140                 case TRANSCODING_DATA:
141                     return TranscodeMetrics.pullStatsEvents();
142                 case GENERAL_EXTERNAL_STORAGE_ACCESS_STATS:
143                     return storageAccessMetrics.pullStatsEvents();
144                 default:
145                     return null;
146             }
147         }
148     }
149 }
150