Home | History | Annotate | Download | only in connectivity
      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             mDnsListener = new DnsEventListenerService(getContext());
     60             publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener);
     61         }
     62     }
     63 
     64     // TODO: read these constants from system property
     65     private final int EVENTS_NOTIFICATION_THRESHOLD                   = 300;
     66     private final int MAX_NUMBER_OF_EVENTS                            = 1000;
     67     private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000;
     68     private final long THROTTLING_TIME_INTERVAL_MILLIS                = DateUtils.HOUR_IN_MILLIS;
     69 
     70     private int mEventCounter = 0;
     71 
     72     /**
     73      * Reference of the last event in the list of cached events.
     74      *
     75      * When client of this service retrieves events by calling getEvents, it is passing
     76      * ConnectivityMetricsEvent.Reference object. After getEvents returns, that object will
     77      * contain this reference. The client can save it and use next time it calls getEvents.
     78      * This way only new events will be returned.
     79      */
     80     private long mLastEventReference = 0;
     81 
     82     private final int mThrottlingCounters[] =
     83             new int[ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS];
     84 
     85     private long mThrottlingIntervalBoundaryMillis;
     86 
     87     private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>();
     88 
     89     private DnsEventListenerService mDnsListener;
     90 
     91     private void enforceConnectivityInternalPermission() {
     92         getContext().enforceCallingOrSelfPermission(
     93                 android.Manifest.permission.CONNECTIVITY_INTERNAL,
     94                 "MetricsLoggerService");
     95     }
     96 
     97     private void enforceDumpPermission() {
     98         getContext().enforceCallingOrSelfPermission(
     99                 android.Manifest.permission.DUMP,
    100                 "MetricsLoggerService");
    101     }
    102 
    103     private void resetThrottlingCounters(long currentTimeMillis) {
    104         synchronized (mThrottlingCounters) {
    105             for (int i = 0; i < mThrottlingCounters.length; i++) {
    106                 mThrottlingCounters[i] = 0;
    107             }
    108             mThrottlingIntervalBoundaryMillis =
    109                     currentTimeMillis + THROTTLING_TIME_INTERVAL_MILLIS;
    110         }
    111     }
    112 
    113     private void addEvent(ConnectivityMetricsEvent e) {
    114         if (VDBG) {
    115             Log.v(TAG, "writeEvent(" + e.toString() + ")");
    116         }
    117 
    118         while (mEvents.size() >= MAX_NUMBER_OF_EVENTS) {
    119             mEvents.removeFirst();
    120         }
    121 
    122         mEvents.addLast(e);
    123     }
    124 
    125     @VisibleForTesting
    126     final MetricsLoggerImpl mBinder = new MetricsLoggerImpl();
    127 
    128     /**
    129      * Implementation of the IConnectivityMetricsLogger interface.
    130      */
    131     final class MetricsLoggerImpl extends IConnectivityMetricsLogger.Stub {
    132 
    133         private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>();
    134 
    135         @Override
    136         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    137             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
    138                     != PackageManager.PERMISSION_GRANTED) {
    139                 pw.println("Permission Denial: can't dump ConnectivityMetricsLoggerService " +
    140                         "from from pid=" + Binder.getCallingPid() + ", uid=" +
    141                         Binder.getCallingUid());
    142                 return;
    143             }
    144 
    145             boolean dumpSerializedSize = false;
    146             boolean dumpEvents = false;
    147             boolean dumpDebugInfo = false;
    148             for (String arg : args) {
    149                 switch (arg) {
    150                     case "--debug":
    151                         dumpDebugInfo = true;
    152                         break;
    153 
    154                     case "--events":
    155                         dumpEvents = true;
    156                         break;
    157 
    158                     case "--size":
    159                         dumpSerializedSize = true;
    160                         break;
    161 
    162                     case "--all":
    163                         dumpDebugInfo = true;
    164                         dumpEvents = true;
    165                         dumpSerializedSize = true;
    166                         break;
    167                 }
    168             }
    169 
    170             synchronized (mEvents) {
    171                 pw.println("Number of events: " + mEvents.size());
    172                 pw.println("Counter: " + mEventCounter);
    173                 if (mEvents.size() > 0) {
    174                     pw.println("Time span: " +
    175                             DateUtils.formatElapsedTime(
    176                                     (System.currentTimeMillis() - mEvents.peekFirst().timestamp)
    177                                             / 1000));
    178                 }
    179 
    180                 if (dumpSerializedSize) {
    181                     Parcel p = Parcel.obtain();
    182                     for (ConnectivityMetricsEvent e : mEvents) {
    183                         p.writeParcelable(e, 0);
    184                     }
    185                     pw.println("Serialized data size: " + p.dataSize());
    186                     p.recycle();
    187                 }
    188 
    189                 if (dumpEvents) {
    190                     pw.println();
    191                     pw.println("Events:");
    192                     for (ConnectivityMetricsEvent e : mEvents) {
    193                         pw.println(e.toString());
    194                     }
    195                 }
    196             }
    197 
    198             if (dumpDebugInfo) {
    199                 synchronized (mThrottlingCounters) {
    200                     pw.println();
    201                     for (int i = 0; i < ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS; i++) {
    202                         if (mThrottlingCounters[i] > 0) {
    203                             pw.println("Throttling Counter #" + i + ": " + mThrottlingCounters[i]);
    204                         }
    205                     }
    206                     pw.println("Throttling Time Remaining: " +
    207                             DateUtils.formatElapsedTime(
    208                                     (mThrottlingIntervalBoundaryMillis - System.currentTimeMillis())
    209                                             / 1000));
    210                 }
    211             }
    212 
    213             synchronized (mPendingIntents) {
    214                 if (!mPendingIntents.isEmpty()) {
    215                     pw.println();
    216                     pw.println("Pending intents:");
    217                     for (PendingIntent pi : mPendingIntents) {
    218                         pw.println(pi.toString());
    219                     }
    220                 }
    221             }
    222 
    223             pw.println();
    224             if (mDnsListener != null) {
    225                 mDnsListener.dump(pw);
    226             }
    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