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 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(); 57 private DnsEventListenerService mDnsListener; 58 59 @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"); 80 mDnsListener = new DnsEventListenerService(getContext()); 81 82 publishBinderService(SERVICE_NAME, impl); 83 publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener); 84 } 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 } 172 if (mDnsListener != null) { 173 mDnsListener.dump(pw); 174 } 175 } 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} 230