OSDN Git Service

Metrics logging for DNS queries.
[android-x86/frameworks-base.git] / services / core / java / com / android / server / connectivity / MetricsLoggerService.java
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
17 package com.android.server.connectivity;
18
19 import com.android.server.SystemService;
20
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.net.ConnectivityMetricsEvent;
25 import android.net.ConnectivityMetricsLogger;
26 import android.net.IConnectivityMetricsLogger;
27 import android.os.Binder;
28 import android.os.Parcel;
29 import android.text.format.DateUtils;
30 import android.util.Log;
31
32 import java.io.FileDescriptor;
33 import java.io.PrintWriter;
34 import java.util.ArrayDeque;
35 import java.util.ArrayList;
36
37 /** {@hide} */
38 public class MetricsLoggerService extends SystemService {
39     private static String TAG = "ConnectivityMetricsLoggerService";
40     private static final boolean DBG = true;
41     private static final boolean VDBG = false;
42
43     public MetricsLoggerService(Context context) {
44         super(context);
45     }
46
47     @Override
48     public void onStart() {
49         resetThrottlingCounters(System.currentTimeMillis());
50     }
51
52     @Override
53     public void onBootPhase(int phase) {
54         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
55             if (DBG) Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
56             publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE,
57                     mBinder);
58             mDnsListener = new DnsEventListenerService(getContext());
59             publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener);
60         }
61     }
62
63     // TODO: read from system property
64     private final int MAX_NUMBER_OF_EVENTS = 1000;
65
66     // TODO: read from system property
67     private final int EVENTS_NOTIFICATION_THRESHOLD = 300;
68
69     // TODO: read from system property
70     private final int THROTTLING_TIME_INTERVAL_MILLIS = 60 * 60 * 1000; // 1 hour
71
72     // TODO: read from system property
73     private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000;
74
75     private int mEventCounter = 0;
76
77     /**
78      * Reference of the last event in the list of cached events.
79      *
80      * When client of this service retrieves events by calling getEvents, it is passing
81      * ConnectivityMetricsEvent.Reference object. After getEvents returns, that object will
82      * contain this reference. The client can save it and use next time it calls getEvents.
83      * This way only new events will be returned.
84      */
85     private long mLastEventReference = 0;
86
87     private final int mThrottlingCounters[] =
88             new int[ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS];
89
90     private long mThrottlingIntervalBoundaryMillis;
91
92     private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>();
93
94     private DnsEventListenerService mDnsListener;
95
96     private void enforceConnectivityInternalPermission() {
97         getContext().enforceCallingOrSelfPermission(
98                 android.Manifest.permission.CONNECTIVITY_INTERNAL,
99                 "MetricsLoggerService");
100     }
101
102     private void enforceDumpPermission() {
103         getContext().enforceCallingOrSelfPermission(
104                 android.Manifest.permission.DUMP,
105                 "MetricsLoggerService");
106     }
107
108     private void resetThrottlingCounters(long currentTimeMillis) {
109         for (int i = 0; i < mThrottlingCounters.length; i++) {
110             mThrottlingCounters[i] = 0;
111         }
112         mThrottlingIntervalBoundaryMillis =
113                 currentTimeMillis + THROTTLING_TIME_INTERVAL_MILLIS;
114     }
115
116     private void addEvent(ConnectivityMetricsEvent e) {
117         if (VDBG) {
118             Log.v(TAG, "writeEvent(" + e.toString() + ")");
119         }
120
121         while (mEvents.size() >= MAX_NUMBER_OF_EVENTS) {
122             mEvents.removeFirst();
123         }
124
125         mEvents.addLast(e);
126     }
127
128     /**
129      * Implementation of the IConnectivityMetricsLogger interface.
130      */
131     private final IConnectivityMetricsLogger.Stub mBinder = new IConnectivityMetricsLogger.Stub() {
132
133         private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>();
134
135         @Override
136         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
137             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
138                     != PackageManager.PERMISSION_GRANTED) {
139                 pw.println("Permission Denial: can't dump ConnectivityMetricsLoggerService " +
140                         "from from pid=" + Binder.getCallingPid() + ", uid=" +
141                         Binder.getCallingUid());
142                 return;
143             }
144
145             boolean dumpSerializedSize = false;
146             boolean dumpEvents = false;
147             for (String arg : args) {
148                 switch (arg) {
149                     case "--events":
150                         dumpEvents = true;
151                         break;
152
153                     case "--size":
154                         dumpSerializedSize = true;
155                         break;
156
157                     case "--all":
158                         dumpEvents = true;
159                         dumpSerializedSize = true;
160                         break;
161                 }
162             }
163
164             synchronized (mEvents) {
165                 pw.println("Number of events: " + mEvents.size());
166                 if (mEvents.size() > 0) {
167                     pw.println("Time span: " +
168                             DateUtils.formatElapsedTime(
169                                     (System.currentTimeMillis() - mEvents.peekFirst().timestamp)
170                                             / 1000));
171                 }
172
173                 if (dumpSerializedSize) {
174                     long dataSize = 0;
175                     Parcel p = Parcel.obtain();
176                     for (ConnectivityMetricsEvent e : mEvents) {
177                         dataSize += 16; // timestamp and 2 stamps
178
179                         p.writeParcelable(e.data, 0);
180                     }
181                     dataSize += p.dataSize();
182                     p.recycle();
183                     pw.println("Serialized data size: " + dataSize);
184                 }
185
186                 if (dumpEvents) {
187                     pw.println();
188                     pw.println("Events:");
189                     for (ConnectivityMetricsEvent e : mEvents) {
190                         pw.println(e.toString());
191                     }
192                 }
193             }
194
195             if (!mPendingIntents.isEmpty()) {
196                 pw.println();
197                 pw.println("Pending intents:");
198                 for (PendingIntent pi : mPendingIntents) {
199                     pw.println(pi.toString());
200                 }
201             }
202
203             pw.println();
204             mDnsListener.dump(pw);
205         }
206
207         public long logEvent(ConnectivityMetricsEvent event) {
208             ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event};
209             return logEvents(events);
210         }
211
212         /**
213          * @param events
214          *
215          * Note: All events must belong to the same component.
216          *
217          * @return 0 on success
218          *        <0 if error happened
219          *        >0 timestamp after which new events will be accepted
220          */
221         public long logEvents(ConnectivityMetricsEvent[] events) {
222             enforceConnectivityInternalPermission();
223
224             if (events == null || events.length == 0) {
225                 Log.wtf(TAG, "No events passed to logEvents()");
226                 return -1;
227             }
228
229             int componentTag = events[0].componentTag;
230             if (componentTag < 0 ||
231                     componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) {
232                 Log.wtf(TAG, "Unexpected tag: " + componentTag);
233                 return -1;
234             }
235
236             synchronized (mThrottlingCounters) {
237                 long currentTimeMillis = System.currentTimeMillis();
238                 if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) {
239                     resetThrottlingCounters(currentTimeMillis);
240                 }
241
242                 mThrottlingCounters[componentTag] += events.length;
243
244                 if (mThrottlingCounters[componentTag] >
245                         THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) {
246                     Log.w(TAG, "Too many events from #" + componentTag +
247                             ". Block until " + mThrottlingIntervalBoundaryMillis);
248
249                     return mThrottlingIntervalBoundaryMillis;
250                 }
251             }
252
253             boolean sendPendingIntents = false;
254
255             synchronized (mEvents) {
256                 for (ConnectivityMetricsEvent e : events) {
257                     if (e.componentTag != componentTag) {
258                         Log.wtf(TAG, "Unexpected tag: " + e.componentTag);
259                         return -1;
260                     }
261
262                     addEvent(e);
263                 }
264
265                 mLastEventReference += events.length;
266
267                 mEventCounter += events.length;
268                 if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) {
269                     mEventCounter = 0;
270                     sendPendingIntents = true;
271                 }
272             }
273
274             if (sendPendingIntents) {
275                 synchronized (mPendingIntents) {
276                     for (PendingIntent pi : mPendingIntents) {
277                         if (VDBG) Log.v(TAG, "Send pending intent");
278                         try {
279                             pi.send(getContext(), 0, null, null, null);
280                         } catch (PendingIntent.CanceledException e) {
281                             Log.e(TAG, "Pending intent canceled: " + pi);
282                             mPendingIntents.remove(pi);
283                         }
284                     }
285                 }
286             }
287
288             return 0;
289         }
290
291         /**
292          * Retrieve events
293          *
294          * @param reference of the last event previously returned. The function will return
295          *                  events following it.
296          *                  If 0 then all events will be returned.
297          *                  After the function call it will contain reference of the
298          *                  last returned event.
299          * @return events
300          */
301         public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
302             enforceDumpPermission();
303             long ref = reference.value;
304             if (VDBG) Log.v(TAG, "getEvents(" + ref + ")");
305
306             ConnectivityMetricsEvent[] result;
307             synchronized (mEvents) {
308                 if (ref > mLastEventReference) {
309                     Log.e(TAG, "Invalid reference");
310                     reference.value = mLastEventReference;
311                     return null;
312                 }
313                 if (ref < mLastEventReference - mEvents.size()) {
314                     ref = mLastEventReference - mEvents.size();
315                 }
316
317                 int numEventsToSkip =
318                         mEvents.size() // Total number of events
319                         - (int)(mLastEventReference - ref); // Number of events to return
320
321                 result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip];
322                 int i = 0;
323                 for (ConnectivityMetricsEvent e : mEvents) {
324                     if (numEventsToSkip > 0) {
325                         numEventsToSkip--;
326                     } else {
327                         result[i++] = e;
328                     }
329                 }
330             }
331
332             reference.value = mLastEventReference;
333
334             return result;
335         }
336
337         public boolean register(PendingIntent newEventsIntent) {
338             enforceDumpPermission();
339             if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")");
340
341             synchronized (mPendingIntents) {
342                 if (mPendingIntents.remove(newEventsIntent)) {
343                     Log.w(TAG, "Replacing registered pending intent");
344                 }
345                 mPendingIntents.add(newEventsIntent);
346             }
347
348             return true;
349         }
350
351         public void unregister(PendingIntent newEventsIntent) {
352             enforceDumpPermission();
353             if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")");
354
355             synchronized (mPendingIntents) {
356                 if (!mPendingIntents.remove(newEventsIntent)) {
357                     Log.e(TAG, "Pending intent is not registered");
358                 }
359             }
360         }
361     };
362 }