blob: bcbcf54b03d28fe65c41c4227afb9a5380c63bf7 [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
43 private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
44
45 // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
46 private static final int DEFAULT_BUFFER_SIZE = 2000;
47
48 // Lock ensuring that concurrent manipulations of the event buffer are correct.
49 // There are three concurrent operations to synchronize:
50 // - appending events to the buffer.
51 // - iterating throught the buffer.
52 // - flushing the buffer content and replacing it by a new buffer.
53 private final Object mLock = new Object();
54
55 @VisibleForTesting
56 public final Impl impl = new Impl();
Hugo Benichi00a42d42016-09-13 15:55:09 +090057 private DnsEventListenerService mDnsListener;
58
Hugo Benichieab511b2016-09-09 09:23:47 +090059 @GuardedBy("mLock")
60 private ArrayList<ConnectivityMetricsEvent> mBuffer;
61 @GuardedBy("mLock")
62 private int mDropped;
63 @GuardedBy("mLock")
64 private int mCapacity;
65
66 public IpConnectivityMetrics(Context ctx) {
67 super(ctx);
68 initBuffer();
69 }
70
71 @Override
72 public void onStart() {
73 if (DBG) Log.d(TAG, "onStart");
74 }
75
76 @Override
77 public void onBootPhase(int phase) {
78 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
79 if (DBG) Log.d(TAG, "onBootPhase");
Hugo Benichi00a42d42016-09-13 15:55:09 +090080 mDnsListener = new DnsEventListenerService(getContext());
Hugo Benichieab511b2016-09-09 09:23:47 +090081
82 publishBinderService(SERVICE_NAME, impl);
Hugo Benichi00a42d42016-09-13 15:55:09 +090083 publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener);
Hugo Benichieab511b2016-09-09 09:23:47 +090084 }
85 }
86
87 @VisibleForTesting
88 public int bufferCapacity() {
89 return DEFAULT_BUFFER_SIZE; // TODO: read from config
90 }
91
92 private void initBuffer() {
93 synchronized (mLock) {
94 mDropped = 0;
95 mCapacity = bufferCapacity();
96 mBuffer = new ArrayList<>(mCapacity);
97 }
98 }
99
100 private int append(ConnectivityMetricsEvent event) {
101 if (DBG) Log.d(TAG, "logEvent: " + event);
102 synchronized (mLock) {
103 final int left = mCapacity - mBuffer.size();
104 if (event == null) {
105 return left;
106 }
107 if (left == 0) {
108 mDropped++;
109 return 0;
110 }
111 mBuffer.add(event);
112 return left - 1;
113 }
114 }
115
116 private String flushEncodedOutput() {
117 final ArrayList<ConnectivityMetricsEvent> events;
118 final int dropped;
119 synchronized (mLock) {
120 events = mBuffer;
121 dropped = mDropped;
122 initBuffer();
123 }
124
125 final byte[] data;
126 try {
127 data = IpConnectivityEventBuilder.serialize(dropped, events);
128 } catch (IOException e) {
129 Log.e(TAG, "could not serialize events", e);
130 return "";
131 }
132
133 return Base64.encodeToString(data, Base64.DEFAULT);
134 }
135
136 /**
137 * Clears the event buffer and prints its content as a protobuf serialized byte array
138 * inside a base64 encoded string.
139 */
140 private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
141 pw.print(flushEncodedOutput());
142 }
143
144 /**
145 * Prints the content of the event buffer, either using the events ASCII representation
146 * or using protobuf text format.
147 */
148 private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
149 final ArrayList<ConnectivityMetricsEvent> events;
150 synchronized (mLock) {
151 events = new ArrayList(mBuffer);
152 }
153
154 if (args.length > 1 && args[1].equals("proto")) {
155 for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
156 pw.print(ev.toString());
157 }
158 return;
159 }
160
161 for (ConnectivityMetricsEvent ev : events) {
162 pw.println(ev.toString());
163 }
164 }
165
166 private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
167 synchronized (mLock) {
168 pw.println("Buffered events: " + mBuffer.size());
169 pw.println("Buffer capacity: " + mCapacity);
170 pw.println("Dropped events: " + mDropped);
171 }
Hugo Benichi00a42d42016-09-13 15:55:09 +0900172 if (mDnsListener != null) {
173 mDnsListener.dump(pw);
174 }
Hugo Benichieab511b2016-09-09 09:23:47 +0900175 }
176
177 private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
178 if (args.length == 0) {
179 pw.println("No command");
180 return;
181 }
182 pw.println("Unknown command " + TextUtils.join(" ", args));
183 }
184
185 public final class Impl extends IIpConnectivityMetrics.Stub {
186 static final String CMD_FLUSH = "flush";
187 static final String CMD_LIST = "list";
188 static final String CMD_STATS = "stats";
189 static final String CMD_DEFAULT = CMD_STATS;
190
191 @Override
192 public int logEvent(ConnectivityMetricsEvent event) {
193 enforceConnectivityInternalPermission();
194 return append(event);
195 }
196
197 @Override
198 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
199 enforceDumpPermission();
200 if (DBG) Log.d(TAG, "dumpsys " + TextUtils.join(" ", args));
201 final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
202 switch (cmd) {
203 case CMD_FLUSH:
204 cmdFlush(fd, pw, args);
205 return;
206 case CMD_LIST:
207 cmdList(fd, pw, args);
208 return;
209 case CMD_STATS:
210 cmdStats(fd, pw, args);
211 return;
212 default:
213 cmdDefault(fd, pw, args);
214 }
215 }
216
217 private void enforceConnectivityInternalPermission() {
218 enforcePermission(android.Manifest.permission.CONNECTIVITY_INTERNAL);
219 }
220
221 private void enforceDumpPermission() {
222 enforcePermission(android.Manifest.permission.DUMP);
223 }
224
225 private void enforcePermission(String what) {
226 getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
227 }
228 };
229}