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