blob: 85c4303e926d10906188161ddc74f1c5a20e4e46 [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;
22import android.net.metrics.IpConnectivityLog;
23import android.os.IBinder;
24import android.os.Parcelable;
25import android.text.TextUtils;
26import android.util.Base64;
27import android.util.Log;
28import com.android.internal.annotations.GuardedBy;
29import com.android.internal.annotations.VisibleForTesting;
30import com.android.server.SystemService;
31import java.io.FileDescriptor;
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35
36import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
37
38/** {@hide} */
39final public class IpConnectivityMetrics extends SystemService {
40 private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
41 private static final boolean DBG = false;
42
Hugo Benichid680d4c2016-10-13 13:16:16 +090043 // The logical version numbers of ipconnectivity.proto, corresponding to the
44 // "version" field of IpConnectivityLog.
45 private static final int NYC = 0;
46 private static final int NYC_MR1 = 1;
47 private static final int NYC_MR2 = 2;
48 public static final int VERSION = NYC_MR2;
49
Hugo Benichieab511b2016-09-09 09:23:47 +090050 private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
51
52 // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
53 private static final int DEFAULT_BUFFER_SIZE = 2000;
54
55 // Lock ensuring that concurrent manipulations of the event buffer are correct.
56 // There are three concurrent operations to synchronize:
57 // - appending events to the buffer.
58 // - iterating throught the buffer.
59 // - flushing the buffer content and replacing it by a new buffer.
60 private final Object mLock = new Object();
61
62 @VisibleForTesting
63 public final Impl impl = new Impl();
Hugo Benichie69608f2016-09-23 14:48:01 +090064 private NetdEventListenerService mNetdListener;
Hugo Benichi00a42d42016-09-13 15:55:09 +090065
Hugo Benichieab511b2016-09-09 09:23:47 +090066 @GuardedBy("mLock")
67 private ArrayList<ConnectivityMetricsEvent> mBuffer;
68 @GuardedBy("mLock")
69 private int mDropped;
70 @GuardedBy("mLock")
71 private int mCapacity;
72
73 public IpConnectivityMetrics(Context ctx) {
74 super(ctx);
75 initBuffer();
76 }
77
78 @Override
79 public void onStart() {
80 if (DBG) Log.d(TAG, "onStart");
81 }
82
83 @Override
84 public void onBootPhase(int phase) {
85 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
86 if (DBG) Log.d(TAG, "onBootPhase");
Hugo Benichie69608f2016-09-23 14:48:01 +090087 mNetdListener = new NetdEventListenerService(getContext());
Hugo Benichieab511b2016-09-09 09:23:47 +090088
89 publishBinderService(SERVICE_NAME, impl);
Hugo Benichie69608f2016-09-23 14:48:01 +090090 publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
Hugo Benichieab511b2016-09-09 09:23:47 +090091 }
92 }
93
94 @VisibleForTesting
95 public int bufferCapacity() {
96 return DEFAULT_BUFFER_SIZE; // TODO: read from config
97 }
98
99 private void initBuffer() {
100 synchronized (mLock) {
101 mDropped = 0;
102 mCapacity = bufferCapacity();
103 mBuffer = new ArrayList<>(mCapacity);
104 }
105 }
106
107 private int append(ConnectivityMetricsEvent event) {
108 if (DBG) Log.d(TAG, "logEvent: " + event);
109 synchronized (mLock) {
110 final int left = mCapacity - mBuffer.size();
111 if (event == null) {
112 return left;
113 }
114 if (left == 0) {
115 mDropped++;
116 return 0;
117 }
118 mBuffer.add(event);
119 return left - 1;
120 }
121 }
122
123 private String flushEncodedOutput() {
124 final ArrayList<ConnectivityMetricsEvent> events;
125 final int dropped;
126 synchronized (mLock) {
127 events = mBuffer;
128 dropped = mDropped;
129 initBuffer();
130 }
131
132 final byte[] data;
133 try {
134 data = IpConnectivityEventBuilder.serialize(dropped, events);
135 } catch (IOException e) {
136 Log.e(TAG, "could not serialize events", e);
137 return "";
138 }
139
140 return Base64.encodeToString(data, Base64.DEFAULT);
141 }
142
143 /**
144 * Clears the event buffer and prints its content as a protobuf serialized byte array
145 * inside a base64 encoded string.
146 */
147 private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
148 pw.print(flushEncodedOutput());
149 }
150
151 /**
152 * Prints the content of the event buffer, either using the events ASCII representation
153 * or using protobuf text format.
154 */
155 private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
156 final ArrayList<ConnectivityMetricsEvent> events;
157 synchronized (mLock) {
158 events = new ArrayList(mBuffer);
159 }
160
161 if (args.length > 1 && args[1].equals("proto")) {
162 for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
163 pw.print(ev.toString());
164 }
165 return;
166 }
167
168 for (ConnectivityMetricsEvent ev : events) {
169 pw.println(ev.toString());
170 }
171 }
172
173 private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
174 synchronized (mLock) {
175 pw.println("Buffered events: " + mBuffer.size());
176 pw.println("Buffer capacity: " + mCapacity);
177 pw.println("Dropped events: " + mDropped);
178 }
Hugo Benichie69608f2016-09-23 14:48:01 +0900179 if (mNetdListener != null) {
180 mNetdListener.dump(pw);
Hugo Benichi00a42d42016-09-13 15:55:09 +0900181 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900182 }
183
184 private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
185 if (args.length == 0) {
186 pw.println("No command");
187 return;
188 }
189 pw.println("Unknown command " + TextUtils.join(" ", args));
190 }
191
192 public final class Impl extends IIpConnectivityMetrics.Stub {
193 static final String CMD_FLUSH = "flush";
194 static final String CMD_LIST = "list";
195 static final String CMD_STATS = "stats";
196 static final String CMD_DEFAULT = CMD_STATS;
197
198 @Override
199 public int logEvent(ConnectivityMetricsEvent event) {
200 enforceConnectivityInternalPermission();
201 return append(event);
202 }
203
204 @Override
205 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
206 enforceDumpPermission();
207 if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
208 final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
209 switch (cmd) {
210 case CMD_FLUSH:
211 cmdFlush(fd, pw, args);
212 return;
213 case CMD_LIST:
214 cmdList(fd, pw, args);
215 return;
216 case CMD_STATS:
217 cmdStats(fd, pw, args);
218 return;
219 default:
220 cmdDefault(fd, pw, args);
221 }
222 }
223
224 private void enforceConnectivityInternalPermission() {
225 enforcePermission(android.Manifest.permission.CONNECTIVITY_INTERNAL);
226 }
227
228 private void enforceDumpPermission() {
229 enforcePermission(android.Manifest.permission.DUMP);
230 }
231
232 private void enforcePermission(String what) {
233 getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
234 }
235 };
236}