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.NetworkStack; 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 IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter)121 public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) { 122 super(ctx); 123 mCapacityGetter = capacityGetter; 124 initBuffer(); 125 } 126 IpConnectivityMetrics(Context ctx)127 public IpConnectivityMetrics(Context ctx) { 128 this(ctx, READ_BUFFER_SIZE); 129 } 130 131 @Override onStart()132 public void onStart() { 133 if (DBG) Log.d(TAG, "onStart"); 134 } 135 136 @Override onBootPhase(int phase)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 bufferCapacity()150 public int bufferCapacity() { 151 return mCapacityGetter.applyAsInt(getContext()); 152 } 153 initBuffer()154 private void initBuffer() { 155 synchronized (mLock) { 156 mDropped = 0; 157 mCapacity = bufferCapacity(); 158 mBuffer = new ArrayList<>(mCapacity); 159 } 160 } 161 append(ConnectivityMetricsEvent event)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 isRateLimited(ConnectivityMetricsEvent event)183 private boolean isRateLimited(ConnectivityMetricsEvent event) { 184 TokenBucket tb = mBuckets.get(event.data.getClass()); 185 return (tb != null) && !tb.get(); 186 } 187 flushEncodedOutput()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 */ cmdFlush(PrintWriter pw)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 */ cmdList(PrintWriter pw)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 */ cmdListAsProto(PrintWriter pw)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 */ getEvents()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 // By default any other argument will fall into the default case which is the equivalent 274 // of calling both the "list" and "ipclient" commands. This includes most notably bug 275 // reports collected by dumpsys.cpp with the "-a" argument. 276 static final String CMD_DEFAULT = ""; 277 278 @Override logEvent(ConnectivityMetricsEvent event)279 public int logEvent(ConnectivityMetricsEvent event) { 280 NetworkStack.checkNetworkStackPermission(getContext()); 281 return append(event); 282 } 283 284 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)285 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 286 enforceDumpPermission(); 287 if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args)); 288 final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT; 289 switch (cmd) { 290 case CMD_FLUSH: 291 cmdFlush(pw); 292 return; 293 case CMD_PROTO: 294 cmdListAsProto(pw); 295 return; 296 case CMD_LIST: 297 default: 298 cmdList(pw); 299 return; 300 } 301 } 302 enforceDumpPermission()303 private void enforceDumpPermission() { 304 enforcePermission(android.Manifest.permission.DUMP); 305 } 306 enforcePermission(String what)307 private void enforcePermission(String what) { 308 getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics"); 309 } 310 enforceNetdEventListeningPermission()311 private void enforceNetdEventListeningPermission() { 312 final int uid = Binder.getCallingUid(); 313 if (uid != Process.SYSTEM_UID) { 314 throw new SecurityException(String.format("Uid %d has no permission to listen for" 315 + " netd events.", uid)); 316 } 317 } 318 319 @Override addNetdEventCallback(int callerType, INetdEventCallback callback)320 public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) { 321 enforceNetdEventListeningPermission(); 322 if (mNetdListener == null) { 323 return false; 324 } 325 return mNetdListener.addNetdEventCallback(callerType, callback); 326 } 327 328 @Override removeNetdEventCallback(int callerType)329 public boolean removeNetdEventCallback(int callerType) { 330 enforceNetdEventListeningPermission(); 331 if (mNetdListener == null) { 332 // if the service is null, we aren't registered anyway 333 return true; 334 } 335 return mNetdListener.removeNetdEventCallback(callerType); 336 } 337 }; 338 339 private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> { 340 int size = Settings.Global.getInt(ctx.getContentResolver(), 341 Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); 342 if (size <= 0) { 343 return DEFAULT_BUFFER_SIZE; 344 } 345 return Math.min(size, MAXIMUM_BUFFER_SIZE); 346 }; 347 makeRateLimitingBuckets()348 private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() { 349 ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>(); 350 // one token every minute, 50 tokens max: burst of ~50 events every hour. 351 map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50)); 352 return map; 353 } 354 355 /** Direct non-Binder interface for event producer clients within the system servers. */ 356 public interface Logger { defaultNetworkMetrics()357 DefaultNetworkMetrics defaultNetworkMetrics(); 358 } 359 360 private class LoggerImpl implements Logger { defaultNetworkMetrics()361 public DefaultNetworkMetrics defaultNetworkMetrics() { 362 return mDefaultNetworkMetrics; 363 } 364 } 365 } 366