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         }
     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