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