blob: da56a07d2396e601fa4625fa4502a4228f129530 [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 Benichie1c173d2016-10-18 10:36:33 +090037import com.android.internal.util.TokenBucket;
Hugo Benichieab511b2016-09-09 09:23:47 +090038import com.android.server.SystemService;
Hugo Benichi0d4a3982016-11-25 11:24:22 +090039import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
Hugo Benichieab511b2016-09-09 09:23:47 +090040import java.io.FileDescriptor;
41import java.io.IOException;
42import java.io.PrintWriter;
43import java.util.ArrayList;
Hugo Benichi0d4a3982016-11-25 11:24:22 +090044import java.util.List;
Hugo Benichi05686db2016-10-19 11:17:28 +090045import java.util.function.ToIntFunction;
Hugo Benichieab511b2016-09-09 09:23:47 +090046
Hugo Benichieab511b2016-09-09 09:23:47 +090047/** {@hide} */
48final public class IpConnectivityMetrics extends SystemService {
49 private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
50 private static final boolean DBG = false;
51
Hugo Benichid680d4c2016-10-13 13:16:16 +090052 // The logical version numbers of ipconnectivity.proto, corresponding to the
53 // "version" field of IpConnectivityLog.
54 private static final int NYC = 0;
55 private static final int NYC_MR1 = 1;
56 private static final int NYC_MR2 = 2;
57 public static final int VERSION = NYC_MR2;
58
Hugo Benichieab511b2016-09-09 09:23:47 +090059 private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
60
61 // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
62 private static final int DEFAULT_BUFFER_SIZE = 2000;
Hugo Benichi05686db2016-10-19 11:17:28 +090063 // Maximum size of the event buffer.
64 private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
Hugo Benichieab511b2016-09-09 09:23:47 +090065
Hugo Benichi0d4a3982016-11-25 11:24:22 +090066 private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000;
67
Hugo Benichie1c173d2016-10-18 10:36:33 +090068 private static final int ERROR_RATE_LIMITED = -1;
Hugo Benichieab511b2016-09-09 09:23:47 +090069
70 // Lock ensuring that concurrent manipulations of the event buffer are correct.
71 // There are three concurrent operations to synchronize:
72 // - appending events to the buffer.
73 // - iterating throught the buffer.
74 // - flushing the buffer content and replacing it by a new buffer.
75 private final Object mLock = new Object();
76
77 @VisibleForTesting
78 public final Impl impl = new Impl();
Hugo Benichie69608f2016-09-23 14:48:01 +090079 private NetdEventListenerService mNetdListener;
Hugo Benichi00a42d42016-09-13 15:55:09 +090080
Hugo Benichieab511b2016-09-09 09:23:47 +090081 @GuardedBy("mLock")
82 private ArrayList<ConnectivityMetricsEvent> mBuffer;
83 @GuardedBy("mLock")
84 private int mDropped;
85 @GuardedBy("mLock")
86 private int mCapacity;
Hugo Benichie1c173d2016-10-18 10:36:33 +090087 @GuardedBy("mLock")
88 private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
Hugo Benichieab511b2016-09-09 09:23:47 +090089
Hugo Benichi05686db2016-10-19 11:17:28 +090090 private final ToIntFunction<Context> mCapacityGetter;
91
92 public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
Hugo Benichieab511b2016-09-09 09:23:47 +090093 super(ctx);
Hugo Benichi05686db2016-10-19 11:17:28 +090094 mCapacityGetter = capacityGetter;
Hugo Benichieab511b2016-09-09 09:23:47 +090095 initBuffer();
96 }
Hugo Benichieab511b2016-09-09 09:23:47 +090097
98 public IpConnectivityMetrics(Context ctx) {
Hugo Benichi05686db2016-10-19 11:17:28 +090099 this(ctx, READ_BUFFER_SIZE);
Hugo Benichieab511b2016-09-09 09:23:47 +0900100 }
101
102 @Override
103 public void onStart() {
104 if (DBG) Log.d(TAG, "onStart");
105 }
106
107 @Override
108 public void onBootPhase(int phase) {
109 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
110 if (DBG) Log.d(TAG, "onBootPhase");
Hugo Benichie69608f2016-09-23 14:48:01 +0900111 mNetdListener = new NetdEventListenerService(getContext());
Hugo Benichieab511b2016-09-09 09:23:47 +0900112
113 publishBinderService(SERVICE_NAME, impl);
Hugo Benichie69608f2016-09-23 14:48:01 +0900114 publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
Hugo Benichieab511b2016-09-09 09:23:47 +0900115 }
116 }
117
118 @VisibleForTesting
119 public int bufferCapacity() {
Hugo Benichi05686db2016-10-19 11:17:28 +0900120 return mCapacityGetter.applyAsInt(getContext());
Hugo Benichieab511b2016-09-09 09:23:47 +0900121 }
122
123 private void initBuffer() {
124 synchronized (mLock) {
125 mDropped = 0;
126 mCapacity = bufferCapacity();
127 mBuffer = new ArrayList<>(mCapacity);
128 }
129 }
130
131 private int append(ConnectivityMetricsEvent event) {
132 if (DBG) Log.d(TAG, "logEvent: " + event);
133 synchronized (mLock) {
134 final int left = mCapacity - mBuffer.size();
135 if (event == null) {
136 return left;
137 }
Hugo Benichie1c173d2016-10-18 10:36:33 +0900138 if (isRateLimited(event)) {
139 // Do not count as a dropped event. TODO: consider adding separate counter
140 return ERROR_RATE_LIMITED;
141 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900142 if (left == 0) {
143 mDropped++;
144 return 0;
145 }
146 mBuffer.add(event);
147 return left - 1;
148 }
149 }
150
Hugo Benichie1c173d2016-10-18 10:36:33 +0900151 private boolean isRateLimited(ConnectivityMetricsEvent event) {
152 TokenBucket tb = mBuckets.get(event.data.getClass());
153 return (tb != null) && !tb.get();
154 }
155
Hugo Benichieab511b2016-09-09 09:23:47 +0900156 private String flushEncodedOutput() {
157 final ArrayList<ConnectivityMetricsEvent> events;
158 final int dropped;
159 synchronized (mLock) {
160 events = mBuffer;
161 dropped = mDropped;
162 initBuffer();
163 }
164
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900165 final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
166
167 if (mNetdListener != null) {
168 mNetdListener.flushStatistics(protoEvents);
169 }
170
Hugo Benichieab511b2016-09-09 09:23:47 +0900171 final byte[] data;
172 try {
Hugo Benichi0d4a3982016-11-25 11:24:22 +0900173 data = IpConnectivityEventBuilder.serialize(dropped, protoEvents);
Hugo Benichieab511b2016-09-09 09:23:47 +0900174 } catch (IOException e) {
175 Log.e(TAG, "could not serialize events", e);
176 return "";
177 }
178
179 return Base64.encodeToString(data, Base64.DEFAULT);
180 }
181
182 /**
183 * Clears the event buffer and prints its content as a protobuf serialized byte array
184 * inside a base64 encoded string.
185 */
186 private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
187 pw.print(flushEncodedOutput());
188 }
189
190 /**
191 * Prints the content of the event buffer, either using the events ASCII representation
192 * or using protobuf text format.
193 */
194 private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
195 final ArrayList<ConnectivityMetricsEvent> events;
196 synchronized (mLock) {
197 events = new ArrayList(mBuffer);
198 }
199
200 if (args.length > 1 && args[1].equals("proto")) {
201 for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
202 pw.print(ev.toString());
203 }
Hugo Benichia2decca2017-02-22 14:32:27 +0900204 if (mNetdListener != null) {
205 mNetdListener.listAsProtos(pw);
206 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900207 return;
208 }
209
210 for (ConnectivityMetricsEvent ev : events) {
211 pw.println(ev.toString());
212 }
Hugo Benichia2decca2017-02-22 14:32:27 +0900213 if (mNetdListener != null) {
214 mNetdListener.list(pw);
215 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900216 }
217
218 private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
219 synchronized (mLock) {
220 pw.println("Buffered events: " + mBuffer.size());
221 pw.println("Buffer capacity: " + mCapacity);
222 pw.println("Dropped events: " + mDropped);
223 }
Hugo Benichie69608f2016-09-23 14:48:01 +0900224 if (mNetdListener != null) {
225 mNetdListener.dump(pw);
Hugo Benichi00a42d42016-09-13 15:55:09 +0900226 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900227 }
228
229 private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
230 if (args.length == 0) {
231 pw.println("No command");
232 return;
233 }
234 pw.println("Unknown command " + TextUtils.join(" ", args));
235 }
236
237 public final class Impl extends IIpConnectivityMetrics.Stub {
238 static final String CMD_FLUSH = "flush";
239 static final String CMD_LIST = "list";
240 static final String CMD_STATS = "stats";
Hugo Benichi51d14cb2016-10-19 13:48:40 +0900241 static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
Hugo Benichieab511b2016-09-09 09:23:47 +0900242 static final String CMD_DEFAULT = CMD_STATS;
243
244 @Override
245 public int logEvent(ConnectivityMetricsEvent event) {
246 enforceConnectivityInternalPermission();
247 return append(event);
248 }
249
250 @Override
251 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
252 enforceDumpPermission();
253 if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
254 final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
255 switch (cmd) {
256 case CMD_FLUSH:
257 cmdFlush(fd, pw, args);
258 return;
Hugo Benichi51d14cb2016-10-19 13:48:40 +0900259 case CMD_DUMPSYS:
260 // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
Hugo Benichieab511b2016-09-09 09:23:47 +0900261 case CMD_LIST:
262 cmdList(fd, pw, args);
263 return;
264 case CMD_STATS:
265 cmdStats(fd, pw, args);
266 return;
267 default:
268 cmdDefault(fd, pw, args);
269 }
270 }
271
272 private void enforceConnectivityInternalPermission() {
273 enforcePermission(android.Manifest.permission.CONNECTIVITY_INTERNAL);
274 }
275
276 private void enforceDumpPermission() {
277 enforcePermission(android.Manifest.permission.DUMP);
278 }
279
280 private void enforcePermission(String what) {
281 getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
282 }
Michal Karpinskidd9bb4f2016-10-12 14:59:26 +0100283
284 private void enforceNetdEventListeningPermission() {
285 final int uid = Binder.getCallingUid();
286 if (uid != Process.SYSTEM_UID) {
287 throw new SecurityException(String.format("Uid %d has no permission to listen for"
288 + " netd events.", uid));
289 }
290 }
291
292 @Override
293 public boolean registerNetdEventCallback(INetdEventCallback callback) {
294 enforceNetdEventListeningPermission();
295 if (mNetdListener == null) {
296 return false;
297 }
298 return mNetdListener.registerNetdEventCallback(callback);
299 }
300
301 @Override
302 public boolean unregisterNetdEventCallback() {
303 enforceNetdEventListeningPermission();
304 if (mNetdListener == null) {
305 // if the service is null, we aren't registered anyway
306 return true;
307 }
308 return mNetdListener.unregisterNetdEventCallback();
309 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900310 };
Hugo Benichi05686db2016-10-19 11:17:28 +0900311
312 private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
313 int size = Settings.Global.getInt(ctx.getContentResolver(),
314 Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
315 if (size <= 0) {
316 return DEFAULT_BUFFER_SIZE;
317 }
318 return Math.min(size, MAXIMUM_BUFFER_SIZE);
319 };
Hugo Benichie1c173d2016-10-18 10:36:33 +0900320
321 private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
322 ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
323 // one token every minute, 50 tokens max: burst of ~50 events every hour.
324 map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
325 return map;
326 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900327}