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