MetricsLoggerService.java revision 4b4c31de80aa3c518669db4b2cad43b5677a9bdb
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 com.android.server.SystemService;
20
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.pm.PackageManager;
24import android.net.ConnectivityMetricsEvent;
25import android.net.ConnectivityMetricsLogger;
26import android.net.IConnectivityMetricsLogger;
27import android.os.Binder;
28import android.os.Parcel;
29import android.text.format.DateUtils;
30import android.util.Log;
31
32import java.io.FileDescriptor;
33import java.io.PrintWriter;
34import java.util.ArrayDeque;
35import java.util.ArrayList;
36
37/** {@hide} */
38public 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            boolean dumpDebugInfo = false;
148            for (String arg : args) {
149                switch (arg) {
150                    case "--debug":
151                        dumpDebugInfo = true;
152                        break;
153
154                    case "--events":
155                        dumpEvents = true;
156                        break;
157
158                    case "--size":
159                        dumpSerializedSize = true;
160                        break;
161
162                    case "--all":
163                        dumpDebugInfo = true;
164                        dumpEvents = true;
165                        dumpSerializedSize = true;
166                        break;
167                }
168            }
169
170            synchronized (mEvents) {
171                pw.println("Number of events: " + mEvents.size());
172                pw.println("Counter: " + mEventCounter);
173                if (mEvents.size() > 0) {
174                    pw.println("Time span: " +
175                            DateUtils.formatElapsedTime(
176                                    (System.currentTimeMillis() - mEvents.peekFirst().timestamp)
177                                            / 1000));
178                }
179
180                if (dumpSerializedSize) {
181                    Parcel p = Parcel.obtain();
182                    for (ConnectivityMetricsEvent e : mEvents) {
183                        p.writeParcelable(e, 0);
184                    }
185                    pw.println("Serialized data size: " + p.dataSize());
186                    p.recycle();
187                }
188
189                if (dumpEvents) {
190                    pw.println();
191                    pw.println("Events:");
192                    for (ConnectivityMetricsEvent e : mEvents) {
193                        pw.println(e.toString());
194                    }
195                }
196            }
197
198            if (dumpDebugInfo) {
199                synchronized (mThrottlingCounters) {
200                    pw.println();
201                    for (int i = 0; i < ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS; i++) {
202                        if (mThrottlingCounters[i] > 0) {
203                            pw.println("Throttling Counter #" + i + ": " + mThrottlingCounters[i]);
204                        }
205                    }
206                    pw.println("Throttling Time Remaining: " +
207                            DateUtils.formatElapsedTime(
208                                    (mThrottlingIntervalBoundaryMillis - System.currentTimeMillis())
209                                            / 1000));
210                }
211            }
212
213            synchronized (mPendingIntents) {
214                if (!mPendingIntents.isEmpty()) {
215                    pw.println();
216                    pw.println("Pending intents:");
217                    for (PendingIntent pi : mPendingIntents) {
218                        pw.println(pi.toString());
219                    }
220                }
221            }
222
223            pw.println();
224            mDnsListener.dump(pw);
225        }
226
227        public long logEvent(ConnectivityMetricsEvent event) {
228            ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event};
229            return logEvents(events);
230        }
231
232        /**
233         * @param events
234         *
235         * Note: All events must belong to the same component.
236         *
237         * @return 0 on success
238         *        <0 if error happened
239         *        >0 timestamp after which new events will be accepted
240         */
241        public long logEvents(ConnectivityMetricsEvent[] events) {
242            enforceConnectivityInternalPermission();
243
244            if (events == null || events.length == 0) {
245                Log.wtf(TAG, "No events passed to logEvents()");
246                return -1;
247            }
248
249            int componentTag = events[0].componentTag;
250            if (componentTag < 0 ||
251                    componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) {
252                Log.wtf(TAG, "Unexpected tag: " + componentTag);
253                return -1;
254            }
255
256            synchronized (mThrottlingCounters) {
257                long currentTimeMillis = System.currentTimeMillis();
258                if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) {
259                    resetThrottlingCounters(currentTimeMillis);
260                }
261
262                mThrottlingCounters[componentTag] += events.length;
263
264                if (mThrottlingCounters[componentTag] >
265                        THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) {
266                    Log.w(TAG, "Too many events from #" + componentTag +
267                            ". Block until " + mThrottlingIntervalBoundaryMillis);
268
269                    return mThrottlingIntervalBoundaryMillis;
270                }
271            }
272
273            boolean sendPendingIntents = false;
274
275            synchronized (mEvents) {
276                for (ConnectivityMetricsEvent e : events) {
277                    if (e.componentTag != componentTag) {
278                        Log.wtf(TAG, "Unexpected tag: " + e.componentTag);
279                        return -1;
280                    }
281
282                    addEvent(e);
283                }
284
285                mLastEventReference += events.length;
286
287                mEventCounter += events.length;
288                if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) {
289                    mEventCounter = 0;
290                    sendPendingIntents = true;
291                }
292            }
293
294            if (sendPendingIntents) {
295                synchronized (mPendingIntents) {
296                    for (PendingIntent pi : mPendingIntents) {
297                        if (VDBG) Log.v(TAG, "Send pending intent");
298                        try {
299                            pi.send(getContext(), 0, null, null, null);
300                        } catch (PendingIntent.CanceledException e) {
301                            Log.e(TAG, "Pending intent canceled: " + pi);
302                            mPendingIntents.remove(pi);
303                        }
304                    }
305                }
306            }
307
308            return 0;
309        }
310
311        /**
312         * Retrieve events
313         *
314         * @param reference of the last event previously returned. The function will return
315         *                  events following it.
316         *                  If 0 then all events will be returned.
317         *                  After the function call it will contain reference of the
318         *                  last returned event.
319         * @return events
320         */
321        public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
322            enforceDumpPermission();
323            long ref = reference.getValue();
324            if (VDBG) Log.v(TAG, "getEvents(" + ref + ")");
325
326            ConnectivityMetricsEvent[] result;
327            synchronized (mEvents) {
328                if (ref > mLastEventReference) {
329                    Log.e(TAG, "Invalid reference");
330                    reference.setValue(mLastEventReference);
331                    return null;
332                }
333                if (ref < mLastEventReference - mEvents.size()) {
334                    ref = mLastEventReference - mEvents.size();
335                }
336
337                int numEventsToSkip =
338                        mEvents.size() // Total number of events
339                        - (int)(mLastEventReference - ref); // Number of events to return
340
341                result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip];
342                int i = 0;
343                for (ConnectivityMetricsEvent e : mEvents) {
344                    if (numEventsToSkip > 0) {
345                        numEventsToSkip--;
346                    } else {
347                        result[i++] = e;
348                    }
349                }
350
351                reference.setValue(mLastEventReference);
352            }
353
354            return result;
355        }
356
357        public boolean register(PendingIntent newEventsIntent) {
358            enforceDumpPermission();
359            if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")");
360
361            synchronized (mPendingIntents) {
362                if (mPendingIntents.remove(newEventsIntent)) {
363                    Log.w(TAG, "Replacing registered pending intent");
364                }
365                mPendingIntents.add(newEventsIntent);
366            }
367
368            return true;
369        }
370
371        public void unregister(PendingIntent newEventsIntent) {
372            enforceDumpPermission();
373            if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")");
374
375            synchronized (mPendingIntents) {
376                if (!mPendingIntents.remove(newEventsIntent)) {
377                    Log.e(TAG, "Pending intent is not registered");
378                }
379            }
380        }
381    };
382}
383