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