MetricsLoggerService.java revision 9b907e440cdff222481abf16def6e03870678c20
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            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            synchronized (mPendingIntents) {
196                if (!mPendingIntents.isEmpty()) {
197                    pw.println();
198                    pw.println("Pending intents:");
199                    for (PendingIntent pi : mPendingIntents) {
200                        pw.println(pi.toString());
201                    }
202                }
203            }
204
205            pw.println();
206            mDnsListener.dump(pw);
207        }
208
209        public long logEvent(ConnectivityMetricsEvent event) {
210            ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event};
211            return logEvents(events);
212        }
213
214        /**
215         * @param events
216         *
217         * Note: All events must belong to the same component.
218         *
219         * @return 0 on success
220         *        <0 if error happened
221         *        >0 timestamp after which new events will be accepted
222         */
223        public long logEvents(ConnectivityMetricsEvent[] events) {
224            enforceConnectivityInternalPermission();
225
226            if (events == null || events.length == 0) {
227                Log.wtf(TAG, "No events passed to logEvents()");
228                return -1;
229            }
230
231            int componentTag = events[0].componentTag;
232            if (componentTag < 0 ||
233                    componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) {
234                Log.wtf(TAG, "Unexpected tag: " + componentTag);
235                return -1;
236            }
237
238            synchronized (mThrottlingCounters) {
239                long currentTimeMillis = System.currentTimeMillis();
240                if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) {
241                    resetThrottlingCounters(currentTimeMillis);
242                }
243
244                mThrottlingCounters[componentTag] += events.length;
245
246                if (mThrottlingCounters[componentTag] >
247                        THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) {
248                    Log.w(TAG, "Too many events from #" + componentTag +
249                            ". Block until " + mThrottlingIntervalBoundaryMillis);
250
251                    return mThrottlingIntervalBoundaryMillis;
252                }
253            }
254
255            boolean sendPendingIntents = false;
256
257            synchronized (mEvents) {
258                for (ConnectivityMetricsEvent e : events) {
259                    if (e.componentTag != componentTag) {
260                        Log.wtf(TAG, "Unexpected tag: " + e.componentTag);
261                        return -1;
262                    }
263
264                    addEvent(e);
265                }
266
267                mLastEventReference += events.length;
268
269                mEventCounter += events.length;
270                if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) {
271                    mEventCounter = 0;
272                    sendPendingIntents = true;
273                }
274            }
275
276            if (sendPendingIntents) {
277                synchronized (mPendingIntents) {
278                    for (PendingIntent pi : mPendingIntents) {
279                        if (VDBG) Log.v(TAG, "Send pending intent");
280                        try {
281                            pi.send(getContext(), 0, null, null, null);
282                        } catch (PendingIntent.CanceledException e) {
283                            Log.e(TAG, "Pending intent canceled: " + pi);
284                            mPendingIntents.remove(pi);
285                        }
286                    }
287                }
288            }
289
290            return 0;
291        }
292
293        /**
294         * Retrieve events
295         *
296         * @param reference of the last event previously returned. The function will return
297         *                  events following it.
298         *                  If 0 then all events will be returned.
299         *                  After the function call it will contain reference of the
300         *                  last returned event.
301         * @return events
302         */
303        public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
304            enforceDumpPermission();
305            long ref = reference.getValue();
306            if (VDBG) Log.v(TAG, "getEvents(" + ref + ")");
307
308            ConnectivityMetricsEvent[] result;
309            synchronized (mEvents) {
310                if (ref > mLastEventReference) {
311                    Log.e(TAG, "Invalid reference");
312                    reference.setValue(mLastEventReference);
313                    return null;
314                }
315                if (ref < mLastEventReference - mEvents.size()) {
316                    ref = mLastEventReference - mEvents.size();
317                }
318
319                int numEventsToSkip =
320                        mEvents.size() // Total number of events
321                        - (int)(mLastEventReference - ref); // Number of events to return
322
323                result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip];
324                int i = 0;
325                for (ConnectivityMetricsEvent e : mEvents) {
326                    if (numEventsToSkip > 0) {
327                        numEventsToSkip--;
328                    } else {
329                        result[i++] = e;
330                    }
331                }
332
333                reference.setValue(mLastEventReference);
334            }
335
336            return result;
337        }
338
339        public boolean register(PendingIntent newEventsIntent) {
340            enforceDumpPermission();
341            if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")");
342
343            synchronized (mPendingIntents) {
344                if (mPendingIntents.remove(newEventsIntent)) {
345                    Log.w(TAG, "Replacing registered pending intent");
346                }
347                mPendingIntents.add(newEventsIntent);
348            }
349
350            return true;
351        }
352
353        public void unregister(PendingIntent newEventsIntent) {
354            enforceDumpPermission();
355            if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")");
356
357            synchronized (mPendingIntents) {
358                if (!mPendingIntents.remove(newEventsIntent)) {
359                    Log.e(TAG, "Pending intent is not registered");
360                }
361            }
362        }
363    };
364}
365