blob: f2445fa360065da5b895fdaef906e69248b83120 [file] [log] [blame]
Hugo Benichieab511b2016-09-09 09:23:47 +09001/*
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
17package com.android.server.connectivity;
18
19import android.content.Context;
20import android.net.ConnectivityMetricsEvent;
21import android.net.IIpConnectivityMetrics;
Michal Karpinskidd9bb4f2016-10-12 14:59:26 +010022import android.net.INetdEventCallback;
Hugo Benichie1c173d2016-10-18 10:36:33 +090023import android.net.metrics.ApfProgramEvent;
Hugo Benichieab511b2016-09-09 09:23:47 +090024import android.net.metrics.IpConnectivityLog;
Michal Karpinskidd9bb4f2016-10-12 14:59:26 +010025import android.os.Binder;
Hugo Benichieab511b2016-09-09 09:23:47 +090026import android.os.IBinder;
27import android.os.Parcelable;
Michal Karpinskidd9bb4f2016-10-12 14:59:26 +010028import android.os.Process;
Hugo Benichi05686db2016-10-19 11:17:28 +090029import android.provider.Settings;
Hugo Benichieab511b2016-09-09 09:23:47 +090030import android.text.TextUtils;
Hugo Benichie1c173d2016-10-18 10:36:33 +090031import android.text.format.DateUtils;
32import android.util.ArrayMap;
Hugo Benichieab511b2016-09-09 09:23:47 +090033import android.util.Base64;
34import android.util.Log;
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.annotations.VisibleForTesting;
Hugo Benichi1198ba12017-09-15 14:18:57 +090037import com.android.internal.util.RingBuffer;
Hugo Benichie1c173d2016-10-18 10:36:33 +090038import com.android.internal.util.TokenBucket;
Hugo Benichieab511b2016-09-09 09:23:47 +090039import com.android.server.SystemService;
Hugo Benichi0d4a3982016-11-25 11:24:22 +090040import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
Hugo Benichieab511b2016-09-09 09:23:47 +090041import java.io.FileDescriptor;
42import java.io.IOException;
43import java.io.PrintWriter;
44import java.util.ArrayList;
Hugo Benichi0d4a3982016-11-25 11:24:22 +090045import java.util.List;
Hugo Benichi05686db2016-10-19 11:17:28 +090046import java.util.function.ToIntFunction;
Hugo Benichieab511b2016-09-09 09:23:47 +090047
Hugo Benichi67c5e032017-09-14 16:31:38 +090048/**
49 * Event buffering service for core networking and connectivity metrics.
50 *
51 * {@hide}
52 */
Hugo Benichieab511b2016-09-09 09:23:47 +090053final public class IpConnectivityMetrics extends SystemService {
54 private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
55 private static final boolean DBG = false;
56
Hugo Benichid680d4c2016-10-13 13:16:16 +090057 // The logical version numbers of ipconnectivity.proto, corresponding to the
58 // "version" field of IpConnectivityLog.
59 private static final int NYC = 0;
60 private static final int NYC_MR1 = 1;
61 private static final int NYC_MR2 = 2;
62 public static final int VERSION = NYC_MR2;
63
Hugo Benichieab511b2016-09-09 09:23:47 +090064 private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
65
Hugo Benichi1198ba12017-09-15 14:18:57 +090066 // Default size of the event rolling log for bug report dumps.
67 private static final int DEFAULT_LOG_SIZE = 500;
68 // Default size of the event buffer for metrics reporting.
69 // Once the buffer is full, incoming events are dropped.
Hugo Benichieab511b2016-09-09 09:23:47 +090070 private static final int DEFAULT_BUFFER_SIZE = 2000;
Hugo Benichi05686db2016-10-19 11:17:28 +090071 // Maximum size of the event buffer.
72 private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
Hugo Benichieab511b2016-09-09 09:23:47 +090073
Hugo Benichi0d4a3982016-11-25 11:24:22 +090074 private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000;
75
Hugo Benichie1c173d2016-10-18 10:36:33 +090076 private static final int ERROR_RATE_LIMITED = -1;
Hugo Benichieab511b2016-09-09 09:23:47 +090077
Hugo Benichi1198ba12017-09-15 14:18:57 +090078 // Lock ensuring that concurrent manipulations of the event buffers are correct.
Hugo Benichieab511b2016-09-09 09:23:47 +090079 // There are three concurrent operations to synchronize:
80 // - appending events to the buffer.
81 // - iterating throught the buffer.
82 // - flushing the buffer content and replacing it by a new buffer.
83 private final Object mLock = new Object();
84
Hugo Benichi1198ba12017-09-15 14:18:57 +090085 // Implementation instance of IIpConnectivityMetrics.aidl.
Hugo Benichieab511b2016-09-09 09:23:47 +090086 @VisibleForTesting
87 public final Impl impl = new Impl();
Hugo Benichi1198ba12017-09-15 14:18:57 +090088 // Subservice listening to Netd events via INetdEventListener.aidl.
Hugo Benichi2a5cfb92017-03-22 22:21:44 +090089 @VisibleForTesting
90 NetdEventListenerService mNetdListener;
Hugo Benichi00a42d42016-09-13 15:55:09 +090091
Hugo Benichi1198ba12017-09-15 14:18:57 +090092 // Rolling log of the most recent events. This log is used for dumping
93 // connectivity events in bug reports.
94 @GuardedBy("mLock")
95 private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
96 new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
97 // Buffer of connectivity events used for metrics reporting. This buffer
98 // does not rotate automatically and instead saturates when it becomes full.
99 // It is flushed at metrics reporting.
Hugo Benichieab511b2016-09-09 09:23:47 +0900100 @GuardedBy("mLock")
101 private ArrayList<ConnectivityMetricsEvent> mBuffer;
Hugo Benichi1198ba12017-09-15 14:18:57 +0900102 // Total number of events dropped from mBuffer since last metrics reporting.
Hugo Benichieab511b2016-09-09 09:23:47 +0900103 @GuardedBy("mLock")
104 private int mDropped;
Hugo Benichi1198ba12017-09-15 14:18:57 +0900105 // Capacity of mBuffer
Hugo Benichieab511b2016-09-09 09:23:47 +0900106 @GuardedBy("mLock")
107 private int mCapacity;
Hugo Benichi1198ba12017-09-15 14:18:57 +0900108 // A list of rate limiting counters keyed by connectivity event types for
109 // metrics reporting mBuffer.
Hugo Benichie1c173d2016-10-18 10:36:33 +0900110 @GuardedBy("mLock")
111 private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
Hugo Benichieab511b2016-09-09 09:23:47 +0900112
Hugo Benichi05686db2016-10-19 11:17:28 +0900113 private final ToIntFunction<Context> mCapacityGetter;
114
115 public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
Hugo Benichieab511b2016-09-09 09:23:47 +0900116 super(ctx);
Hugo Benichi05686db2016-10-19 11:17:28 +0900117 mCapacityGetter = capacityGetter;
Hugo Benichieab511b2016-09-09 09:23:47 +0900118 initBuffer();
119 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900120
121 public IpConnectivityMetrics(Context ctx) {
Hugo Benichi05686db2016-10-19 11:17:28 +0900122 this(ctx, READ_BUFFER_SIZE);
Hugo Benichieab511b2016-09-09 09:23:47 +0900123 }
124
125 @Override
126 public void onStart() {
127 if (DBG) Log.d(TAG, "onStart");
128 }
129
130 @Override
131 public void onBootPhase(int phase) {
132 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
133 if (DBG) Log.d(TAG, "onBootPhase");
Hugo Benichie69608f2016-09-23 14:48:01 +0900134 mNetdListener = new NetdEventListenerService(getContext());
Hugo Benichieab511b2016-09-09 09:23:47 +0900135
136 publishBinderService(SERVICE_NAME, impl);
Hugo Benichie69608f2016-09-23 14:48:01 +0900137 publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
Hugo Benichieab511b2016-09-09 09:23:47 +0900138 }
139 }
140
141 @VisibleForTesting
142 public int bufferCapacity() {
Hugo Benichi05686db2016-10-19 11:17:28 +0900143 return mCapacityGetter.applyAsInt(getContext());
Hugo Benichieab511b2016-09-09 09:23:47 +0900144 }
145
146 private void initBuffer() {
147 synchronized (mLock) {
148 mDropped = 0;
149 mCapacity = bufferCapacity();
150 mBuffer = new ArrayList<>(mCapacity);
151 }
152 }
153
154 private int append(ConnectivityMetricsEvent event) {
155 if (DBG) Log.d(TAG, "logEvent: " + event);
156 synchronized (mLock) {
Hugo Benichi1198ba12017-09-15 14:18:57 +0900157 mEventLog.append(event);
Hugo Benichieab511b2016-09-09 09:23:47 +0900158 final int left = mCapacity - mBuffer.size();
159 if (event == null) {
160 return left;
161 }
Hugo Benichie1c173d2016-10-18 10:36:33 +0900162 if (isRateLimited(event)) {
163 // Do not count as a dropped event. TODO: consider adding separate counter
164 return ERROR_RATE_LIMITED;
165 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900166 if (left == 0) {
167 mDropped++;
168 return 0;
169 }
170 mBuffer.add(event);
171 return left - 1;
172 }
173 }
174
Hugo Benichie1c173d2016-10-18 10:36:33 +0900175 private boolean isRateLimited(ConnectivityMetricsEvent event) {
176 TokenBucket tb = mBuckets.get(event.data.getClass());
177 return (tb != null) && !tb.get();
178 }
179
Hugo Benichieab511b2016-09-09 09:23:47 +0900180 private String flushEncodedOutput() {
181 final ArrayList<ConnectivityMetricsEvent> events;
182 final int dropped;
183 synchronized (mLock) {
184 events = mBuffer;
185 dropped = mDropped;
186 initBuffer();
187 }
188
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900189 final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
190
191 if (mNetdListener != null) {
192 mNetdListener.flushStatistics(protoEvents);
193 }
194
Hugo Benichieab511b2016-09-09 09:23:47 +0900195 final byte[] data;
196 try {
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900197 data = IpConnectivityEventBuilder.serialize(dropped, protoEvents);
Hugo Benichieab511b2016-09-09 09:23:47 +0900198 } catch (IOException e) {
199 Log.e(TAG, "could not serialize events", e);
200 return "";
201 }
202
203 return Base64.encodeToString(data, Base64.DEFAULT);
204 }
205
206 /**
207 * Clears the event buffer and prints its content as a protobuf serialized byte array
208 * inside a base64 encoded string.
209 */
210 private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
211 pw.print(flushEncodedOutput());
212 }
213
214 /**
215 * Prints the content of the event buffer, either using the events ASCII representation
216 * or using protobuf text format.
217 */
218 private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
219 final ArrayList<ConnectivityMetricsEvent> events;
220 synchronized (mLock) {
221 events = new ArrayList(mBuffer);
222 }
223
224 if (args.length > 1 && args[1].equals("proto")) {
225 for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
226 pw.print(ev.toString());
227 }
Hugo Benichia2decca2017-02-22 14:32:27 +0900228 if (mNetdListener != null) {
229 mNetdListener.listAsProtos(pw);
230 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900231 return;
232 }
233
234 for (ConnectivityMetricsEvent ev : events) {
235 pw.println(ev.toString());
236 }
Hugo Benichia2decca2017-02-22 14:32:27 +0900237 if (mNetdListener != null) {
238 mNetdListener.list(pw);
239 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900240 }
241
Hugo Benichi1198ba12017-09-15 14:18:57 +0900242 /**
243 * Prints for bug reports the content of the rolling event log and the
244 * content of Netd event listener.
245 */
246 private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
247 final ConnectivityMetricsEvent[] events;
248 synchronized (mLock) {
249 events = mEventLog.toArray();
250 }
251 for (ConnectivityMetricsEvent ev : events) {
252 pw.println(ev.toString());
253 }
254 if (mNetdListener != null) {
255 mNetdListener.list(pw);
256 }
257 }
258
Hugo Benichieab511b2016-09-09 09:23:47 +0900259 private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
260 synchronized (mLock) {
261 pw.println("Buffered events: " + mBuffer.size());
262 pw.println("Buffer capacity: " + mCapacity);
263 pw.println("Dropped events: " + mDropped);
264 }
Hugo Benichie69608f2016-09-23 14:48:01 +0900265 if (mNetdListener != null) {
266 mNetdListener.dump(pw);
Hugo Benichi00a42d42016-09-13 15:55:09 +0900267 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900268 }
269
270 private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
271 if (args.length == 0) {
272 pw.println("No command");
273 return;
274 }
275 pw.println("Unknown command " + TextUtils.join(" ", args));
276 }
277
278 public final class Impl extends IIpConnectivityMetrics.Stub {
279 static final String CMD_FLUSH = "flush";
280 static final String CMD_LIST = "list";
281 static final String CMD_STATS = "stats";
Hugo Benichi51d14cb2016-10-19 13:48:40 +0900282 static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
Hugo Benichieab511b2016-09-09 09:23:47 +0900283 static final String CMD_DEFAULT = CMD_STATS;
284
285 @Override
286 public int logEvent(ConnectivityMetricsEvent event) {
287 enforceConnectivityInternalPermission();
288 return append(event);
289 }
290
291 @Override
292 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
293 enforceDumpPermission();
294 if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
295 final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
296 switch (cmd) {
297 case CMD_FLUSH:
298 cmdFlush(fd, pw, args);
299 return;
Hugo Benichi51d14cb2016-10-19 13:48:40 +0900300 case CMD_DUMPSYS:
Hugo Benichi1198ba12017-09-15 14:18:57 +0900301 cmdDumpsys(fd, pw, args);
302 return;
Hugo Benichieab511b2016-09-09 09:23:47 +0900303 case CMD_LIST:
304 cmdList(fd, pw, args);
305 return;
306 case CMD_STATS:
307 cmdStats(fd, pw, args);
308 return;
309 default:
310 cmdDefault(fd, pw, args);
311 }
312 }
313
314 private void enforceConnectivityInternalPermission() {
315 enforcePermission(android.Manifest.permission.CONNECTIVITY_INTERNAL);
316 }
317
318 private void enforceDumpPermission() {
319 enforcePermission(android.Manifest.permission.DUMP);
320 }
321
322 private void enforcePermission(String what) {
323 getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
324 }
Michal Karpinskidd9bb4f2016-10-12 14:59:26 +0100325
326 private void enforceNetdEventListeningPermission() {
327 final int uid = Binder.getCallingUid();
328 if (uid != Process.SYSTEM_UID) {
329 throw new SecurityException(String.format("Uid %d has no permission to listen for"
330 + " netd events.", uid));
331 }
332 }
333
334 @Override
335 public boolean registerNetdEventCallback(INetdEventCallback callback) {
336 enforceNetdEventListeningPermission();
337 if (mNetdListener == null) {
338 return false;
339 }
340 return mNetdListener.registerNetdEventCallback(callback);
341 }
342
343 @Override
344 public boolean unregisterNetdEventCallback() {
345 enforceNetdEventListeningPermission();
346 if (mNetdListener == null) {
347 // if the service is null, we aren't registered anyway
348 return true;
349 }
350 return mNetdListener.unregisterNetdEventCallback();
351 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900352 };
Hugo Benichi05686db2016-10-19 11:17:28 +0900353
354 private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
355 int size = Settings.Global.getInt(ctx.getContentResolver(),
356 Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
357 if (size <= 0) {
358 return DEFAULT_BUFFER_SIZE;
359 }
360 return Math.min(size, MAXIMUM_BUFFER_SIZE);
361 };
Hugo Benichie1c173d2016-10-18 10:36:33 +0900362
363 private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
364 ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
365 // one token every minute, 50 tokens max: burst of ~50 events every hour.
366 map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
367 return map;
368 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900369}