StatsManager.java revision 4f71629002ae1da22ca1c07ce11b9cca9b272d97
1/* 2 * Copyright 2017 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 */ 16package android.app; 17 18import android.Manifest; 19import android.annotation.Nullable; 20import android.annotation.RequiresPermission; 21import android.annotation.SystemApi; 22import android.os.IBinder; 23import android.os.IStatsManager; 24import android.os.RemoteException; 25import android.os.ServiceManager; 26import android.util.AndroidException; 27import android.util.Slog; 28 29/** 30 * API for statsd clients to send configurations and retrieve data. 31 * 32 * @hide 33 */ 34@SystemApi 35public final class StatsManager { 36 IStatsManager mService; 37 private static final String TAG = "StatsManager"; 38 private static final boolean DEBUG = false; 39 40 /** 41 * Long extra of uid that added the relevant stats config. 42 */ 43 public static final String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID"; 44 /** 45 * Long extra of the relevant stats config's configKey. 46 */ 47 public static final String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY"; 48 /** 49 * Long extra of the relevant statsd_config.proto's Subscription.id. 50 */ 51 public static final String EXTRA_STATS_SUBSCRIPTION_ID = 52 "android.app.extra.STATS_SUBSCRIPTION_ID"; 53 /** 54 * Long extra of the relevant statsd_config.proto's Subscription.rule_id. 55 */ 56 public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID = 57 "android.app.extra.STATS_SUBSCRIPTION_RULE_ID"; 58 /** 59 * List<String> of the relevant statsd_config.proto's BroadcastSubscriberDetails.cookie. 60 * Obtain using {@link android.content.Intent#getStringArrayListExtra(String)}. 61 */ 62 public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = 63 "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES"; 64 /** 65 * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value 66 * information. 67 */ 68 public static final String EXTRA_STATS_DIMENSIONS_VALUE = 69 "android.app.extra.STATS_DIMENSIONS_VALUE"; 70 71 /** 72 * Broadcast Action: Statsd has started. 73 * Configurations and PendingIntents can now be sent to it. 74 */ 75 public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED"; 76 77 /** 78 * Constructor for StatsManagerClient. 79 * 80 * @hide 81 */ 82 public StatsManager() { 83 } 84 85 /** 86 * Adds the given configuration and associates it with the given configKey. If a config with the 87 * given configKey already exists for the caller's uid, it is replaced with the new one. 88 * 89 * @param configKey An arbitrary integer that allows clients to track the configuration. 90 * @param config Wire-encoded StatsdConfig proto that specifies metrics (and all 91 * dependencies eg, conditions and matchers). 92 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 93 * @throws IllegalArgumentException if config is not a wire-encoded StatsdConfig proto 94 */ 95 @RequiresPermission(Manifest.permission.DUMP) 96 public void addConfig(long configKey, byte[] config) throws StatsUnavailableException { 97 synchronized (this) { 98 try { 99 IStatsManager service = getIStatsManagerLocked(); 100 service.addConfiguration(configKey, config); // can throw IllegalArgumentException 101 } catch (RemoteException e) { 102 Slog.e(TAG, "Failed to connect to statsd when adding configuration"); 103 throw new StatsUnavailableException("could not connect", e); 104 } 105 } 106 } 107 108 /** 109 * TODO: Temporary for backwards compatibility. Remove. 110 */ 111 @RequiresPermission(Manifest.permission.DUMP) 112 public boolean addConfiguration(long configKey, byte[] config) { 113 try { 114 addConfig(configKey, config); 115 return true; 116 } catch (StatsUnavailableException | IllegalArgumentException e) { 117 return false; 118 } 119 } 120 121 /** 122 * Remove a configuration from logging. 123 * 124 * @param configKey Configuration key to remove. 125 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 126 */ 127 @RequiresPermission(Manifest.permission.DUMP) 128 public void removeConfig(long configKey) throws StatsUnavailableException { 129 synchronized (this) { 130 try { 131 IStatsManager service = getIStatsManagerLocked(); 132 service.removeConfiguration(configKey); 133 } catch (RemoteException e) { 134 Slog.e(TAG, "Failed to connect to statsd when removing configuration"); 135 throw new StatsUnavailableException("could not connect", e); 136 } 137 } 138 } 139 140 /** 141 * TODO: Temporary for backwards compatibility. Remove. 142 */ 143 @RequiresPermission(Manifest.permission.DUMP) 144 public boolean removeConfiguration(long configKey) { 145 try { 146 removeConfig(configKey); 147 return true; 148 } catch (StatsUnavailableException e) { 149 return false; 150 } 151 } 152 153 /** 154 * Set the PendingIntent to be used when broadcasting subscriber information to the given 155 * subscriberId within the given config. 156 * <p> 157 * Suppose that the calling uid has added a config with key configKey, and that in this config 158 * it is specified that when a particular anomaly is detected, a broadcast should be sent to 159 * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with 160 * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast 161 * when the anomaly is detected. 162 * <p> 163 * When statsd sends the broadcast, the PendingIntent will used to send an intent with 164 * information of 165 * {@link #EXTRA_STATS_CONFIG_UID}, 166 * {@link #EXTRA_STATS_CONFIG_KEY}, 167 * {@link #EXTRA_STATS_SUBSCRIPTION_ID}, 168 * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, 169 * {@link #EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES}, and 170 * {@link #EXTRA_STATS_DIMENSIONS_VALUE}. 171 * <p> 172 * This function can only be called by the owner (uid) of the config. It must be called each 173 * time statsd starts. The config must have been added first (via {@link #addConfig}). 174 * 175 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber 176 * associated with the given subscriberId. May be null, in which case 177 * it undoes any previous setting of this subscriberId. 178 * @param configKey The integer naming the config to which this subscriber is attached. 179 * @param subscriberId ID of the subscriber, as used in the config. 180 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 181 */ 182 @RequiresPermission(Manifest.permission.DUMP) 183 public void setBroadcastSubscriber( 184 PendingIntent pendingIntent, long configKey, long subscriberId) 185 throws StatsUnavailableException { 186 synchronized (this) { 187 try { 188 IStatsManager service = getIStatsManagerLocked(); 189 if (pendingIntent != null) { 190 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. 191 IBinder intentSender = pendingIntent.getTarget().asBinder(); 192 service.setBroadcastSubscriber(configKey, subscriberId, intentSender); 193 } else { 194 service.unsetBroadcastSubscriber(configKey, subscriberId); 195 } 196 } catch (RemoteException e) { 197 Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e); 198 throw new StatsUnavailableException("could not connect", e); 199 } 200 } 201 } 202 203 /** 204 * TODO: Temporary for backwards compatibility. Remove. 205 */ 206 @RequiresPermission(Manifest.permission.DUMP) 207 public boolean setBroadcastSubscriber( 208 long configKey, long subscriberId, PendingIntent pendingIntent) { 209 try { 210 setBroadcastSubscriber(pendingIntent, configKey, subscriberId); 211 return true; 212 } catch (StatsUnavailableException e) { 213 return false; 214 } 215 } 216 217 /** 218 * Registers the operation that is called to retrieve the metrics data. This must be called 219 * each time statsd starts. The config must have been added first (via {@link #addConfig}, 220 * although addConfig could have been called on a previous boot). This operation allows 221 * statsd to send metrics data whenever statsd determines that the metrics in memory are 222 * approaching the memory limits. The fetch operation should call {@link #getReports} to fetch 223 * the data, which also deletes the retrieved metrics from statsd's memory. 224 * 225 * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber 226 * associated with the given subscriberId. May be null, in which case 227 * it removes any associated pending intent with this configKey. 228 * @param configKey The integer naming the config to which this operation is attached. 229 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 230 */ 231 @RequiresPermission(Manifest.permission.DUMP) 232 public void setFetchReportsOperation(PendingIntent pendingIntent, long configKey) 233 throws StatsUnavailableException { 234 synchronized (this) { 235 try { 236 IStatsManager service = getIStatsManagerLocked(); 237 if (pendingIntent == null) { 238 service.removeDataFetchOperation(configKey); 239 } else { 240 // Extracts IIntentSender from the PendingIntent and turns it into an IBinder. 241 IBinder intentSender = pendingIntent.getTarget().asBinder(); 242 service.setDataFetchOperation(configKey, intentSender); 243 } 244 245 } catch (RemoteException e) { 246 Slog.e(TAG, "Failed to connect to statsd when registering data listener."); 247 throw new StatsUnavailableException("could not connect", e); 248 } 249 } 250 } 251 252 /** 253 * TODO: Temporary for backwards compatibility. Remove. 254 */ 255 @RequiresPermission(Manifest.permission.DUMP) 256 public boolean setDataFetchOperation(long configKey, PendingIntent pendingIntent) { 257 try { 258 setFetchReportsOperation(pendingIntent, configKey); 259 return true; 260 } catch (StatsUnavailableException e) { 261 return false; 262 } 263 } 264 265 /** 266 * Request the data collected for the given configKey. 267 * This getter is destructive - it also clears the retrieved metrics from statsd's memory. 268 * 269 * @param configKey Configuration key to retrieve data from. 270 * @return Serialized ConfigMetricsReportList proto. 271 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 272 */ 273 @RequiresPermission(Manifest.permission.DUMP) 274 public byte[] getReports(long configKey) throws StatsUnavailableException { 275 synchronized (this) { 276 try { 277 IStatsManager service = getIStatsManagerLocked(); 278 return service.getData(configKey); 279 } catch (RemoteException e) { 280 Slog.e(TAG, "Failed to connect to statsd when getting data"); 281 throw new StatsUnavailableException("could not connect", e); 282 } 283 } 284 } 285 286 /** 287 * TODO: Temporary for backwards compatibility. Remove. 288 */ 289 @RequiresPermission(Manifest.permission.DUMP) 290 public @Nullable byte[] getData(long configKey) { 291 try { 292 return getReports(configKey); 293 } catch (StatsUnavailableException e) { 294 return null; 295 } 296 } 297 298 /** 299 * Clients can request metadata for statsd. Will contain stats across all configurations but not 300 * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}. 301 * This getter is not destructive and will not reset any metrics/counters. 302 * 303 * @return Serialized StatsdStatsReport proto. 304 * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service 305 */ 306 @RequiresPermission(Manifest.permission.DUMP) 307 public byte[] getStatsMetadata() throws StatsUnavailableException { 308 synchronized (this) { 309 try { 310 IStatsManager service = getIStatsManagerLocked(); 311 return service.getMetadata(); 312 } catch (RemoteException e) { 313 Slog.e(TAG, "Failed to connect to statsd when getting metadata"); 314 throw new StatsUnavailableException("could not connect", e); 315 } 316 } 317 } 318 319 /** 320 * Clients can request metadata for statsd. Will contain stats across all configurations but not 321 * the actual metrics themselves (metrics must be collected via {@link #getReports(long)}. 322 * This getter is not destructive and will not reset any metrics/counters. 323 * 324 * @return Serialized StatsdStatsReport proto. Returns null on failure (eg, if statsd crashed). 325 */ 326 @RequiresPermission(Manifest.permission.DUMP) 327 public @Nullable byte[] getMetadata() { 328 try { 329 return getStatsMetadata(); 330 } catch (StatsUnavailableException e) { 331 return null; 332 } 333 } 334 335 private class StatsdDeathRecipient implements IBinder.DeathRecipient { 336 @Override 337 public void binderDied() { 338 synchronized (this) { 339 mService = null; 340 } 341 } 342 } 343 344 private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException { 345 if (mService != null) { 346 return mService; 347 } 348 mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); 349 if (mService == null) { 350 throw new StatsUnavailableException("could not be found"); 351 } 352 try { 353 mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); 354 } catch (RemoteException e) { 355 throw new StatsUnavailableException("could not connect when linkToDeath", e); 356 } 357 return mService; 358 } 359 360 /** 361 * Exception thrown when communication with the stats service fails (eg if it is not available). 362 * This might be thrown early during boot before the stats service has started or if it crashed. 363 */ 364 public static class StatsUnavailableException extends AndroidException { 365 public StatsUnavailableException(String reason) { 366 super("Failed to connect to statsd: " + reason); 367 } 368 369 public StatsUnavailableException(String reason, Throwable e) { 370 super("Failed to connect to statsd: " + reason, e); 371 } 372 } 373} 374