1 /* 2 * Copyright (C) 2016 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.server.connectivity; 18 19 import com.android.internal.annotations.VisibleForTesting; 20 import com.android.server.SystemService; 21 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.content.pm.PackageManager; 25 import android.net.ConnectivityMetricsEvent; 26 import android.net.ConnectivityMetricsLogger; 27 import android.net.IConnectivityMetricsLogger; 28 import android.os.Binder; 29 import android.os.Parcel; 30 import android.text.format.DateUtils; 31 import android.util.Log; 32 33 import java.io.FileDescriptor; 34 import java.io.PrintWriter; 35 import java.util.ArrayDeque; 36 import java.util.ArrayList; 37 38 /** {@hide} */ 39 public class MetricsLoggerService extends SystemService { 40 private static String TAG = "ConnectivityMetricsLoggerService"; 41 private static final boolean DBG = true; 42 private static final boolean VDBG = false; 43 44 public MetricsLoggerService(Context context) { 45 super(context); 46 } 47 48 @Override 49 public void onStart() { 50 resetThrottlingCounters(System.currentTimeMillis()); 51 } 52 53 @Override 54 public void onBootPhase(int phase) { 55 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 56 if (DBG) Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY"); 57 publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE, 58 mBinder); 59 } 60 } 61 62 // TODO: read these constants from system property 63 private final int EVENTS_NOTIFICATION_THRESHOLD = 300; 64 private final int MAX_NUMBER_OF_EVENTS = 1000; 65 private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000; 66 private final long THROTTLING_TIME_INTERVAL_MILLIS = DateUtils.HOUR_IN_MILLIS; 67 68 private int mEventCounter = 0; 69 70 /** 71 * Reference of the last event in the list of cached events. 72 * 73 * When client of this service retrieves events by calling getEvents, it is passing 74 * ConnectivityMetricsEvent.Reference object. After getEvents returns, that object will 75 * contain this reference. The client can save it and use next time it calls getEvents. 76 * This way only new events will be returned. 77 */ 78 private long mLastEventReference = 0; 79 80 private final int mThrottlingCounters[] = 81 new int[ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS]; 82 83 private long mThrottlingIntervalBoundaryMillis; 84 85 private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>(); 86 87 private void enforceConnectivityInternalPermission() { 88 getContext().enforceCallingOrSelfPermission( 89 android.Manifest.permission.CONNECTIVITY_INTERNAL, 90 "MetricsLoggerService"); 91 } 92 93 private void enforceDumpPermission() { 94 getContext().enforceCallingOrSelfPermission( 95 android.Manifest.permission.DUMP, 96 "MetricsLoggerService"); 97 } 98 99 private void resetThrottlingCounters(long currentTimeMillis) { 100 synchronized (mThrottlingCounters) { 101 for (int i = 0; i < mThrottlingCounters.length; i++) { 102 mThrottlingCounters[i] = 0; 103 } 104 mThrottlingIntervalBoundaryMillis = 105 currentTimeMillis + THROTTLING_TIME_INTERVAL_MILLIS; 106 } 107 } 108 109 private void addEvent(ConnectivityMetricsEvent e) { 110 if (VDBG) { 111 Log.v(TAG, "writeEvent(" + e.toString() + ")"); 112 } 113 114 while (mEvents.size() >= MAX_NUMBER_OF_EVENTS) { 115 mEvents.removeFirst(); 116 } 117 118 mEvents.addLast(e); 119 } 120 121 @VisibleForTesting 122 final MetricsLoggerImpl mBinder = new MetricsLoggerImpl(); 123 124 /** 125 * Implementation of the IConnectivityMetricsLogger interface. 126 */ 127 final class MetricsLoggerImpl extends IConnectivityMetricsLogger.Stub { 128 129 private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>(); 130 131 @Override 132 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 133 if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 134 != PackageManager.PERMISSION_GRANTED) { 135 pw.println("Permission Denial: can't dump ConnectivityMetricsLoggerService " + 136 "from from pid=" + Binder.getCallingPid() + ", uid=" + 137 Binder.getCallingUid()); 138 return; 139 } 140 141 boolean dumpSerializedSize = false; 142 boolean dumpEvents = false; 143 boolean dumpDebugInfo = false; 144 for (String arg : args) { 145 switch (arg) { 146 case "--debug": 147 dumpDebugInfo = true; 148 break; 149 150 case "--events": 151 dumpEvents = true; 152 break; 153 154 case "--size": 155 dumpSerializedSize = true; 156 break; 157 158 case "--all": 159 dumpDebugInfo = true; 160 dumpEvents = true; 161 dumpSerializedSize = true; 162 break; 163 } 164 } 165 166 synchronized (mEvents) { 167 pw.println("Number of events: " + mEvents.size()); 168 pw.println("Counter: " + mEventCounter); 169 if (mEvents.size() > 0) { 170 pw.println("Time span: " + 171 DateUtils.formatElapsedTime( 172 (System.currentTimeMillis() - mEvents.peekFirst().timestamp) 173 / 1000)); 174 } 175 176 if (dumpSerializedSize) { 177 Parcel p = Parcel.obtain(); 178 for (ConnectivityMetricsEvent e : mEvents) { 179 p.writeParcelable(e, 0); 180 } 181 pw.println("Serialized data size: " + p.dataSize()); 182 p.recycle(); 183 } 184 185 if (dumpEvents) { 186 pw.println(); 187 pw.println("Events:"); 188 for (ConnectivityMetricsEvent e : mEvents) { 189 pw.println(e.toString()); 190 } 191 } 192 } 193 194 if (dumpDebugInfo) { 195 synchronized (mThrottlingCounters) { 196 pw.println(); 197 for (int i = 0; i < ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS; i++) { 198 if (mThrottlingCounters[i] > 0) { 199 pw.println("Throttling Counter #" + i + ": " + mThrottlingCounters[i]); 200 } 201 } 202 pw.println("Throttling Time Remaining: " + 203 DateUtils.formatElapsedTime( 204 (mThrottlingIntervalBoundaryMillis - System.currentTimeMillis()) 205 / 1000)); 206 } 207 } 208 209 synchronized (mPendingIntents) { 210 if (!mPendingIntents.isEmpty()) { 211 pw.println(); 212 pw.println("Pending intents:"); 213 for (PendingIntent pi : mPendingIntents) { 214 pw.println(pi.toString()); 215 } 216 } 217 } 218 } 219 220 public long logEvent(ConnectivityMetricsEvent event) { 221 ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event}; 222 return logEvents(events); 223 } 224 225 /** 226 * @param events 227 * 228 * Note: All events must belong to the same component. 229 * 230 * @return 0 on success 231 * <0 if error happened 232 * >0 timestamp after which new events will be accepted 233 */ 234 public long logEvents(ConnectivityMetricsEvent[] events) { 235 enforceConnectivityInternalPermission(); 236 237 if (events == null || events.length == 0) { 238 Log.wtf(TAG, "No events passed to logEvents()"); 239 return -1; 240 } 241 242 int componentTag = events[0].componentTag; 243 if (componentTag < 0 || 244 componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) { 245 Log.wtf(TAG, "Unexpected tag: " + componentTag); 246 return -1; 247 } 248 249 synchronized (mThrottlingCounters) { 250 long currentTimeMillis = System.currentTimeMillis(); 251 if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) { 252 resetThrottlingCounters(currentTimeMillis); 253 } 254 255 mThrottlingCounters[componentTag] += events.length; 256 257 if (mThrottlingCounters[componentTag] > 258 THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) { 259 Log.w(TAG, "Too many events from #" + componentTag + 260 ". Block until " + mThrottlingIntervalBoundaryMillis); 261 262 return mThrottlingIntervalBoundaryMillis; 263 } 264 } 265 266 boolean sendPendingIntents = false; 267 268 synchronized (mEvents) { 269 for (ConnectivityMetricsEvent e : events) { 270 if (e.componentTag != componentTag) { 271 Log.wtf(TAG, "Unexpected tag: " + e.componentTag); 272 return -1; 273 } 274 275 addEvent(e); 276 } 277 278 mLastEventReference += events.length; 279 280 mEventCounter += events.length; 281 if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) { 282 mEventCounter = 0; 283 sendPendingIntents = true; 284 } 285 } 286 287 if (sendPendingIntents) { 288 synchronized (mPendingIntents) { 289 for (PendingIntent pi : mPendingIntents) { 290 if (VDBG) Log.v(TAG, "Send pending intent"); 291 try { 292 pi.send(getContext(), 0, null, null, null); 293 } catch (PendingIntent.CanceledException e) { 294 Log.e(TAG, "Pending intent canceled: " + pi); 295 mPendingIntents.remove(pi); 296 } 297 } 298 } 299 } 300 301 return 0; 302 } 303 304 /** 305 * Retrieve events 306 * 307 * @param reference of the last event previously returned. The function will return 308 * events following it. 309 * If 0 then all events will be returned. 310 * After the function call it will contain reference of the 311 * last returned event. 312 * @return events 313 */ 314 public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) { 315 enforceDumpPermission(); 316 long ref = reference.getValue(); 317 if (VDBG) Log.v(TAG, "getEvents(" + ref + ")"); 318 319 ConnectivityMetricsEvent[] result; 320 synchronized (mEvents) { 321 if (ref > mLastEventReference) { 322 Log.e(TAG, "Invalid reference"); 323 reference.setValue(mLastEventReference); 324 return null; 325 } 326 if (ref < mLastEventReference - mEvents.size()) { 327 ref = mLastEventReference - mEvents.size(); 328 } 329 330 int numEventsToSkip = 331 mEvents.size() // Total number of events 332 - (int)(mLastEventReference - ref); // Number of events to return 333 334 result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip]; 335 int i = 0; 336 for (ConnectivityMetricsEvent e : mEvents) { 337 if (numEventsToSkip > 0) { 338 numEventsToSkip--; 339 } else { 340 result[i++] = e; 341 } 342 } 343 344 reference.setValue(mLastEventReference); 345 } 346 347 return result; 348 } 349 350 public boolean register(PendingIntent newEventsIntent) { 351 enforceDumpPermission(); 352 if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")"); 353 354 synchronized (mPendingIntents) { 355 if (mPendingIntents.remove(newEventsIntent)) { 356 Log.w(TAG, "Replacing registered pending intent"); 357 } 358 mPendingIntents.add(newEventsIntent); 359 } 360 361 return true; 362 } 363 364 public void unregister(PendingIntent newEventsIntent) { 365 enforceDumpPermission(); 366 if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")"); 367 368 synchronized (mPendingIntents) { 369 if (!mPendingIntents.remove(newEventsIntent)) { 370 Log.e(TAG, "Pending intent is not registered"); 371 } 372 } 373 } 374 }; 375 } 376