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