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 android.content.Context;
     20 import android.net.ConnectivityMetricsEvent;
     21 import android.net.IIpConnectivityMetrics;
     22 import android.net.INetdEventCallback;
     23 import android.net.ip.IpClient;
     24 import android.net.metrics.ApfProgramEvent;
     25 import android.net.metrics.IpConnectivityLog;
     26 import android.os.Binder;
     27 import android.os.Process;
     28 import android.provider.Settings;
     29 import android.text.TextUtils;
     30 import android.text.format.DateUtils;
     31 import android.util.ArrayMap;
     32 import android.util.Base64;
     33 import android.util.Log;
     34 
     35 import com.android.internal.annotations.GuardedBy;
     36 import com.android.internal.annotations.VisibleForTesting;
     37 import com.android.internal.util.RingBuffer;
     38 import com.android.internal.util.TokenBucket;
     39 import com.android.server.LocalServices;
     40 import com.android.server.SystemService;
     41 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
     42 
     43 import java.io.FileDescriptor;
     44 import java.io.IOException;
     45 import java.io.PrintWriter;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 import java.util.List;
     49 import java.util.function.ToIntFunction;
     50 
     51 /**
     52  * Event buffering service for core networking and connectivity metrics.
     53  *
     54  * {@hide}
     55  */
     56 final public class IpConnectivityMetrics extends SystemService {
     57     private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
     58     private static final boolean DBG = false;
     59 
     60     // The logical version numbers of ipconnectivity.proto, corresponding to the
     61     // "version" field of IpConnectivityLog.
     62     private static final int NYC      = 0;
     63     private static final int NYC_MR1  = 1;
     64     private static final int NYC_MR2  = 2;
     65     public static final int VERSION   = NYC_MR2;
     66 
     67     private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
     68 
     69     // Default size of the event rolling log for bug report dumps.
     70     private static final int DEFAULT_LOG_SIZE = 500;
     71     // Default size of the event buffer for metrics reporting.
     72     // Once the buffer is full, incoming events are dropped.
     73     private static final int DEFAULT_BUFFER_SIZE = 2000;
     74     // Maximum size of the event buffer.
     75     private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
     76 
     77     private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000;
     78 
     79     private static final int ERROR_RATE_LIMITED = -1;
     80 
     81     // Lock ensuring that concurrent manipulations of the event buffers are correct.
     82     // There are three concurrent operations to synchronize:
     83     //  - appending events to the buffer.
     84     //  - iterating throught the buffer.
     85     //  - flushing the buffer content and replacing it by a new buffer.
     86     private final Object mLock = new Object();
     87 
     88     // Implementation instance of IIpConnectivityMetrics.aidl.
     89     @VisibleForTesting
     90     public final Impl impl = new Impl();
     91     // Subservice listening to Netd events via INetdEventListener.aidl.
     92     @VisibleForTesting
     93     NetdEventListenerService mNetdListener;
     94 
     95     // Rolling log of the most recent events. This log is used for dumping
     96     // connectivity events in bug reports.
     97     @GuardedBy("mLock")
     98     private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
     99             new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
    100     // Buffer of connectivity events used for metrics reporting. This buffer
    101     // does not rotate automatically and instead saturates when it becomes full.
    102     // It is flushed at metrics reporting.
    103     @GuardedBy("mLock")
    104     private ArrayList<ConnectivityMetricsEvent> mBuffer;
    105     // Total number of events dropped from mBuffer since last metrics reporting.
    106     @GuardedBy("mLock")
    107     private int mDropped;
    108     // Capacity of mBuffer
    109     @GuardedBy("mLock")
    110     private int mCapacity;
    111     // A list of rate limiting counters keyed by connectivity event types for
    112     // metrics reporting mBuffer.
    113     @GuardedBy("mLock")
    114     private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
    115 
    116     private final ToIntFunction<Context> mCapacityGetter;
    117 
    118     @VisibleForTesting
    119     final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics();
    120 
    121     public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
    122         super(ctx);
    123         mCapacityGetter = capacityGetter;
    124         initBuffer();
    125     }
    126 
    127     public IpConnectivityMetrics(Context ctx) {
    128         this(ctx, READ_BUFFER_SIZE);
    129     }
    130 
    131     @Override
    132     public void onStart() {
    133         if (DBG) Log.d(TAG, "onStart");
    134     }
    135 
    136     @Override
    137     public void onBootPhase(int phase) {
    138         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
    139             if (DBG) Log.d(TAG, "onBootPhase");
    140             mNetdListener = new NetdEventListenerService(getContext());
    141 
    142             publishBinderService(SERVICE_NAME, impl);
    143             publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
    144 
    145             LocalServices.addService(Logger.class, new LoggerImpl());
    146         }
    147     }
    148 
    149     @VisibleForTesting
    150     public int bufferCapacity() {
    151         return mCapacityGetter.applyAsInt(getContext());
    152     }
    153 
    154     private void initBuffer() {
    155         synchronized (mLock) {
    156             mDropped = 0;
    157             mCapacity = bufferCapacity();
    158             mBuffer = new ArrayList<>(mCapacity);
    159         }
    160     }
    161 
    162     private int append(ConnectivityMetricsEvent event) {
    163         if (DBG) Log.d(TAG, "logEvent: " + event);
    164         synchronized (mLock) {
    165             mEventLog.append(event);
    166             final int left = mCapacity - mBuffer.size();
    167             if (event == null) {
    168                 return left;
    169             }
    170             if (isRateLimited(event)) {
    171                 // Do not count as a dropped event. TODO: consider adding separate counter
    172                 return ERROR_RATE_LIMITED;
    173             }
    174             if (left == 0) {
    175                 mDropped++;
    176                 return 0;
    177             }
    178             mBuffer.add(event);
    179             return left - 1;
    180         }
    181     }
    182 
    183     private boolean isRateLimited(ConnectivityMetricsEvent event) {
    184         TokenBucket tb = mBuckets.get(event.data.getClass());
    185         return (tb != null) && !tb.get();
    186     }
    187 
    188     private String flushEncodedOutput() {
    189         final ArrayList<ConnectivityMetricsEvent> events;
    190         final int dropped;
    191         synchronized (mLock) {
    192             events = mBuffer;
    193             dropped = mDropped;
    194             initBuffer();
    195         }
    196 
    197         final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
    198 
    199         mDefaultNetworkMetrics.flushEvents(protoEvents);
    200 
    201         if (mNetdListener != null) {
    202             mNetdListener.flushStatistics(protoEvents);
    203         }
    204 
    205         final byte[] data;
    206         try {
    207             data = IpConnectivityEventBuilder.serialize(dropped, protoEvents);
    208         } catch (IOException e) {
    209             Log.e(TAG, "could not serialize events", e);
    210             return "";
    211         }
    212 
    213         return Base64.encodeToString(data, Base64.DEFAULT);
    214     }
    215 
    216     /**
    217      * Clear the event buffer and prints its content as a protobuf serialized byte array
    218      * inside a base64 encoded string.
    219      */
    220     private void cmdFlush(PrintWriter pw) {
    221         pw.print(flushEncodedOutput());
    222     }
    223 
    224     /**
    225      * Print the content of the rolling event buffer in human readable format.
    226      * Also print network dns/connect statistics and recent default network events.
    227      */
    228     private void cmdList(PrintWriter pw) {
    229         pw.println("metrics events:");
    230         final List<ConnectivityMetricsEvent> events = getEvents();
    231         for (ConnectivityMetricsEvent ev : events) {
    232             pw.println(ev.toString());
    233         }
    234         pw.println("");
    235         if (mNetdListener != null) {
    236             mNetdListener.list(pw);
    237         }
    238         pw.println("");
    239         mDefaultNetworkMetrics.listEvents(pw);
    240     }
    241 
    242     /*
    243      * Print the content of the rolling event buffer in text proto format.
    244      */
    245     private void cmdListAsProto(PrintWriter pw) {
    246         final List<ConnectivityMetricsEvent> events = getEvents();
    247         for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
    248             pw.print(ev.toString());
    249         }
    250         if (mNetdListener != null) {
    251             mNetdListener.listAsProtos(pw);
    252         }
    253         mDefaultNetworkMetrics.listEventsAsProto(pw);
    254     }
    255 
    256     /*
    257      * Return a copy of metrics events stored in buffer for metrics uploading.
    258      */
    259     private List<ConnectivityMetricsEvent> getEvents() {
    260         synchronized (mLock) {
    261             return Arrays.asList(mEventLog.toArray());
    262         }
    263     }
    264 
    265     public final class Impl extends IIpConnectivityMetrics.Stub {
    266         // Dump and flushes the metrics event buffer in base64 encoded serialized proto output.
    267         static final String CMD_FLUSH = "flush";
    268         // Dump the rolling buffer of metrics event in human readable proto text format.
    269         static final String CMD_PROTO = "proto";
    270         // Dump the rolling buffer of metrics event and pretty print events using a human readable
    271         // format. Also print network dns/connect statistics and default network event time series.
    272         static final String CMD_LIST = "list";
    273         // Dump all IpClient logs ("ipclient").
    274         static final String CMD_IPCLIENT = IpClient.DUMP_ARG;
    275         // By default any other argument will fall into the default case which is the equivalent
    276         // of calling both the "list" and "ipclient" commands. This includes most notably bug
    277         // reports collected by dumpsys.cpp with the "-a" argument.
    278         static final String CMD_DEFAULT = "";
    279 
    280         @Override
    281         public int logEvent(ConnectivityMetricsEvent event) {
    282             enforceConnectivityInternalPermission();
    283             return append(event);
    284         }
    285 
    286         @Override
    287         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    288             enforceDumpPermission();
    289             if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
    290             final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
    291             switch (cmd) {
    292                 case CMD_FLUSH:
    293                     cmdFlush(pw);
    294                     return;
    295                 case CMD_PROTO:
    296                     cmdListAsProto(pw);
    297                     return;
    298                 case CMD_IPCLIENT: {
    299                     final String[] ipclientArgs = ((args != null) && (args.length > 1))
    300                             ? Arrays.copyOfRange(args, 1, args.length)
    301                             : null;
    302                     IpClient.dumpAllLogs(pw, ipclientArgs);
    303                     return;
    304                 }
    305                 case CMD_LIST:
    306                     cmdList(pw);
    307                     return;
    308                 default:
    309                     cmdList(pw);
    310                     pw.println("");
    311                     IpClient.dumpAllLogs(pw, null);
    312                     return;
    313             }
    314         }
    315 
    316         private void enforceConnectivityInternalPermission() {
    317             enforcePermission(android.Manifest.permission.CONNECTIVITY_INTERNAL);
    318         }
    319 
    320         private void enforceDumpPermission() {
    321             enforcePermission(android.Manifest.permission.DUMP);
    322         }
    323 
    324         private void enforcePermission(String what) {
    325             getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
    326         }
    327 
    328         private void enforceNetdEventListeningPermission() {
    329             final int uid = Binder.getCallingUid();
    330             if (uid != Process.SYSTEM_UID) {
    331                 throw new SecurityException(String.format("Uid %d has no permission to listen for"
    332                         + " netd events.", uid));
    333             }
    334         }
    335 
    336         @Override
    337         public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
    338             enforceNetdEventListeningPermission();
    339             if (mNetdListener == null) {
    340                 return false;
    341             }
    342             return mNetdListener.addNetdEventCallback(callerType, callback);
    343         }
    344 
    345         @Override
    346         public boolean removeNetdEventCallback(int callerType) {
    347             enforceNetdEventListeningPermission();
    348             if (mNetdListener == null) {
    349                 // if the service is null, we aren't registered anyway
    350                 return true;
    351             }
    352             return mNetdListener.removeNetdEventCallback(callerType);
    353         }
    354     };
    355 
    356     private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
    357         int size = Settings.Global.getInt(ctx.getContentResolver(),
    358                 Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
    359         if (size <= 0) {
    360             return DEFAULT_BUFFER_SIZE;
    361         }
    362         return Math.min(size, MAXIMUM_BUFFER_SIZE);
    363     };
    364 
    365     private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
    366         ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
    367         // one token every minute, 50 tokens max: burst of ~50 events every hour.
    368         map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
    369         return map;
    370     }
    371 
    372     /** Direct non-Binder interface for event producer clients within the system servers. */
    373     public interface Logger {
    374         DefaultNetworkMetrics defaultNetworkMetrics();
    375     }
    376 
    377     private class LoggerImpl implements Logger {
    378         public DefaultNetworkMetrics defaultNetworkMetrics() {
    379             return mDefaultNetworkMetrics;
    380         }
    381     }
    382 }
    383