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