NetworkStatsService.java revision 104344e507610be42fb70c7deda3c422c543bfcb
1/*
2 * Copyright (C) 2011 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.net;
18
19import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
20import static android.Manifest.permission.DUMP;
21import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
22import static android.content.Intent.ACTION_SHUTDOWN;
23import static android.content.Intent.ACTION_UID_REMOVED;
24import static android.content.Intent.EXTRA_UID;
25import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
26import static android.net.NetworkStats.IFACE_ALL;
27import static android.net.NetworkStats.TAG_NONE;
28import static android.net.NetworkStats.UID_ALL;
29import static android.net.TrafficStats.UID_REMOVED;
30import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
31import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
32import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
33import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
34import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY;
35import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
36import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
37import static android.text.format.DateUtils.DAY_IN_MILLIS;
38import static android.text.format.DateUtils.HOUR_IN_MILLIS;
39import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
40import static com.android.internal.util.Preconditions.checkNotNull;
41
42import android.app.AlarmManager;
43import android.app.IAlarmManager;
44import android.app.PendingIntent;
45import android.content.BroadcastReceiver;
46import android.content.ContentResolver;
47import android.content.Context;
48import android.content.Intent;
49import android.content.IntentFilter;
50import android.content.pm.ApplicationInfo;
51import android.net.IConnectivityManager;
52import android.net.INetworkStatsService;
53import android.net.NetworkIdentity;
54import android.net.NetworkInfo;
55import android.net.NetworkState;
56import android.net.NetworkStats;
57import android.net.NetworkStatsHistory;
58import android.net.NetworkTemplate;
59import android.os.Environment;
60import android.os.Handler;
61import android.os.HandlerThread;
62import android.os.INetworkManagementService;
63import android.os.RemoteException;
64import android.os.SystemClock;
65import android.provider.Settings;
66import android.telephony.TelephonyManager;
67import android.util.LongSparseArray;
68import android.util.NtpTrustedTime;
69import android.util.Slog;
70import android.util.TrustedTime;
71
72import com.android.internal.os.AtomicFile;
73import com.google.android.collect.Maps;
74import com.google.android.collect.Sets;
75
76import java.io.DataInputStream;
77import java.io.DataOutputStream;
78import java.io.File;
79import java.io.FileDescriptor;
80import java.io.FileInputStream;
81import java.io.FileNotFoundException;
82import java.io.FileOutputStream;
83import java.io.IOException;
84import java.io.PrintWriter;
85import java.net.ProtocolException;
86import java.util.HashMap;
87import java.util.HashSet;
88import java.util.List;
89
90import libcore.io.IoUtils;
91
92/**
93 * Collect and persist detailed network statistics, and provide this data to
94 * other system services.
95 */
96public class NetworkStatsService extends INetworkStatsService.Stub {
97    private static final String TAG = "NetworkStats";
98    private static final boolean LOGD = true;
99    private static final boolean LOGV = false;
100
101    /** File header magic number: "ANET" */
102    private static final int FILE_MAGIC = 0x414E4554;
103    private static final int VERSION_NETWORK_INIT = 1;
104    private static final int VERSION_UID_INIT = 1;
105    private static final int VERSION_UID_WITH_IDENT = 2;
106    private static final int VERSION_UID_WITH_TAG = 3;
107
108    private final Context mContext;
109    private final INetworkManagementService mNetworkManager;
110    private final IAlarmManager mAlarmManager;
111    private final TrustedTime mTime;
112    private final NetworkStatsSettings mSettings;
113
114    private IConnectivityManager mConnManager;
115
116    // @VisibleForTesting
117    public static final String ACTION_NETWORK_STATS_POLL =
118            "com.android.server.action.NETWORK_STATS_POLL";
119    public static final String ACTION_NETWORK_STATS_UPDATED =
120            "com.android.server.action.NETWORK_STATS_UPDATED";
121
122    private PendingIntent mPollIntent;
123
124    // TODO: listen for kernel push events through netd instead of polling
125    // TODO: trim empty history objects entirely
126
127    private static final long KB_IN_BYTES = 1024;
128    private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
129    private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
130
131    /**
132     * Settings that can be changed externally.
133     */
134    public interface NetworkStatsSettings {
135        public long getPollInterval();
136        public long getPersistThreshold();
137        public long getNetworkBucketDuration();
138        public long getNetworkMaxHistory();
139        public long getUidBucketDuration();
140        public long getUidMaxHistory();
141        public long getTagMaxHistory();
142        public long getTimeCacheMaxAge();
143    }
144
145    private final Object mStatsLock = new Object();
146
147    /** Set of currently active ifaces. */
148    private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
149    /** Set of historical stats for known networks. */
150    private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkStats = Maps.newHashMap();
151    /** Set of historical stats for known UIDs. */
152    private HashMap<NetworkIdentitySet, LongSparseArray<NetworkStatsHistory>> mUidStats =
153            Maps.newHashMap();
154
155    /** Flag if {@link #mUidStats} have been loaded from disk. */
156    private boolean mUidStatsLoaded = false;
157
158    private NetworkStats mLastNetworkSnapshot;
159    private NetworkStats mLastPersistNetworkSnapshot;
160
161    private NetworkStats mLastUidSnapshot;
162
163    private final HandlerThread mHandlerThread;
164    private final Handler mHandler;
165
166    private final AtomicFile mNetworkFile;
167    private final AtomicFile mUidFile;
168
169    // TODO: collect detailed uid stats, storing tag-granularity data until next
170    // dropbox, and uid summary for a specific bucket count.
171
172    // TODO: periodically compile statistics and send to dropbox.
173
174    public NetworkStatsService(
175            Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
176        this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
177                getSystemDir(), new DefaultNetworkStatsSettings(context));
178    }
179
180    private static File getSystemDir() {
181        return new File(Environment.getDataDirectory(), "system");
182    }
183
184    public NetworkStatsService(Context context, INetworkManagementService networkManager,
185            IAlarmManager alarmManager, TrustedTime time, File systemDir,
186            NetworkStatsSettings settings) {
187        mContext = checkNotNull(context, "missing Context");
188        mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
189        mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager");
190        mTime = checkNotNull(time, "missing TrustedTime");
191        mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
192
193        mHandlerThread = new HandlerThread(TAG);
194        mHandlerThread.start();
195        mHandler = new Handler(mHandlerThread.getLooper());
196
197        mNetworkFile = new AtomicFile(new File(systemDir, "netstats.bin"));
198        mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin"));
199    }
200
201    public void bindConnectivityManager(IConnectivityManager connManager) {
202        mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
203    }
204
205    public void systemReady() {
206        synchronized (mStatsLock) {
207            // read historical network stats from disk, since policy service
208            // might need them right away. we delay loading detailed UID stats
209            // until actually needed.
210            readNetworkStatsLocked();
211        }
212
213        // watch for network interfaces to be claimed
214        final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION);
215        mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
216
217        // listen for periodic polling events
218        final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
219        mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
220
221        // listen for uid removal to clean stats
222        final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
223        mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
224
225        // persist stats during clean shutdown
226        final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
227        mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
228
229        try {
230            registerPollAlarmLocked();
231        } catch (RemoteException e) {
232            Slog.w(TAG, "unable to register poll alarm");
233        }
234    }
235
236    private void shutdownLocked() {
237        mContext.unregisterReceiver(mConnReceiver);
238        mContext.unregisterReceiver(mPollReceiver);
239        mContext.unregisterReceiver(mRemovedReceiver);
240        mContext.unregisterReceiver(mShutdownReceiver);
241
242        writeNetworkStatsLocked();
243        if (mUidStatsLoaded) {
244            writeUidStatsLocked();
245        }
246        mNetworkStats.clear();
247        mUidStats.clear();
248        mUidStatsLoaded = false;
249    }
250
251    /**
252     * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
253     * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
254     */
255    private void registerPollAlarmLocked() throws RemoteException {
256        if (mPollIntent != null) {
257            mAlarmManager.remove(mPollIntent);
258        }
259
260        mPollIntent = PendingIntent.getBroadcast(
261                mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
262
263        final long currentRealtime = SystemClock.elapsedRealtime();
264        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
265                mSettings.getPollInterval(), mPollIntent);
266    }
267
268    @Override
269    public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template) {
270        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
271
272        synchronized (mStatsLock) {
273            // combine all interfaces that match template
274            final NetworkStatsHistory combined = new NetworkStatsHistory(
275                    mSettings.getNetworkBucketDuration(), estimateNetworkBuckets());
276            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
277                if (templateMatches(template, ident)) {
278                    final NetworkStatsHistory history = mNetworkStats.get(ident);
279                    if (history != null) {
280                        combined.recordEntireHistory(history);
281                    }
282                }
283            }
284            return combined;
285        }
286    }
287
288    @Override
289    public NetworkStatsHistory getHistoryForUid(NetworkTemplate template, int uid, int tag) {
290        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
291
292        synchronized (mStatsLock) {
293            ensureUidStatsLoadedLocked();
294            final long packed = packUidAndTag(uid, tag);
295
296            // combine all interfaces that match template
297            final NetworkStatsHistory combined = new NetworkStatsHistory(
298                    mSettings.getUidBucketDuration(), estimateUidBuckets());
299            for (NetworkIdentitySet ident : mUidStats.keySet()) {
300                if (templateMatches(template, ident)) {
301                    final NetworkStatsHistory history = mUidStats.get(ident).get(packed);
302                    if (history != null) {
303                        combined.recordEntireHistory(history);
304                    }
305                }
306            }
307            return combined;
308        }
309    }
310
311    @Override
312    public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
313        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
314
315        synchronized (mStatsLock) {
316            long rx = 0;
317            long tx = 0;
318            long[] networkTotal = new long[2];
319
320            // combine total from all interfaces that match template
321            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
322                if (templateMatches(template, ident)) {
323                    final NetworkStatsHistory history = mNetworkStats.get(ident);
324                    networkTotal = history.getTotalData(start, end, networkTotal);
325                    rx += networkTotal[0];
326                    tx += networkTotal[1];
327                }
328            }
329
330            final NetworkStats stats = new NetworkStats(end - start, 1);
331            stats.addEntry(IFACE_ALL, UID_ALL, TAG_NONE, rx, tx);
332            return stats;
333        }
334    }
335
336    @Override
337    public NetworkStats getSummaryForAllUid(
338            NetworkTemplate template, long start, long end, boolean includeTags) {
339        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
340
341        synchronized (mStatsLock) {
342            ensureUidStatsLoadedLocked();
343
344            final NetworkStats stats = new NetworkStats(end - start, 24);
345            long[] total = new long[2];
346
347            for (NetworkIdentitySet ident : mUidStats.keySet()) {
348                if (templateMatches(template, ident)) {
349                    final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
350                    for (int i = 0; i < uidStats.size(); i++) {
351                        final long packed = uidStats.keyAt(i);
352                        final int uid = unpackUid(packed);
353                        final int tag = unpackTag(packed);
354
355                        // always include summary under TAG_NONE, and include
356                        // other tags when requested.
357                        if (tag == TAG_NONE || includeTags) {
358                            final NetworkStatsHistory history = uidStats.valueAt(i);
359                            total = history.getTotalData(start, end, total);
360                            final long rx = total[0];
361                            final long tx = total[1];
362                            if (rx > 0 || tx > 0) {
363                                stats.combineEntry(IFACE_ALL, uid, tag, rx, tx);
364                            }
365                        }
366                    }
367                }
368            }
369
370            return stats;
371        }
372    }
373
374    @Override
375    public void forceUpdate() {
376        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
377
378        synchronized (mStatsLock) {
379            performPollLocked(true, false);
380        }
381    }
382
383    /**
384     * Receiver that watches for {@link IConnectivityManager} to claim network
385     * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
386     * with mobile interfaces.
387     */
388    private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
389        @Override
390        public void onReceive(Context context, Intent intent) {
391            // on background handler thread, and verified CONNECTIVITY_INTERNAL
392            // permission above.
393            synchronized (mStatsLock) {
394                updateIfacesLocked();
395            }
396        }
397    };
398
399    private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
400        @Override
401        public void onReceive(Context context, Intent intent) {
402            // on background handler thread, and verified UPDATE_DEVICE_STATS
403            // permission above.
404            synchronized (mStatsLock) {
405                // TODO: acquire wakelock while performing poll
406                performPollLocked(true, false);
407            }
408        }
409    };
410
411    private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
412        @Override
413        public void onReceive(Context context, Intent intent) {
414            // on background handler thread, and UID_REMOVED is protected
415            // broadcast.
416            final int uid = intent.getIntExtra(EXTRA_UID, 0);
417            synchronized (mStatsLock) {
418                removeUidLocked(uid);
419            }
420        }
421    };
422
423    private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
424        @Override
425        public void onReceive(Context context, Intent intent) {
426            // SHUTDOWN is protected broadcast.
427            synchronized (mStatsLock) {
428                shutdownLocked();
429            }
430        }
431    };
432
433    /**
434     * Inspect all current {@link NetworkState} to derive mapping from {@code
435     * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
436     * are active on a single {@code iface}, they are combined under a single
437     * {@link NetworkIdentitySet}.
438     */
439    private void updateIfacesLocked() {
440        if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
441
442        // take one last stats snapshot before updating iface mapping. this
443        // isn't perfect, since the kernel may already be counting traffic from
444        // the updated network.
445        performPollLocked(false, false);
446
447        final NetworkState[] states;
448        try {
449            states = mConnManager.getAllNetworkState();
450        } catch (RemoteException e) {
451            Slog.w(TAG, "problem reading network state");
452            return;
453        }
454
455        // rebuild active interfaces based on connected networks
456        mActiveIfaces.clear();
457
458        for (NetworkState state : states) {
459            if (state.networkInfo.isConnected()) {
460                // collect networks under their parent interfaces
461                final String iface = state.linkProperties.getInterfaceName();
462
463                NetworkIdentitySet ident = mActiveIfaces.get(iface);
464                if (ident == null) {
465                    ident = new NetworkIdentitySet();
466                    mActiveIfaces.put(iface, ident);
467                }
468
469                ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state));
470            }
471        }
472    }
473
474    /**
475     * Periodic poll operation, reading current statistics and recording into
476     * {@link NetworkStatsHistory}.
477     *
478     * @param detailedPoll Indicate if detailed UID stats should be collected
479     *            during this poll operation.
480     */
481    private void performPollLocked(boolean detailedPoll, boolean forcePersist) {
482        if (LOGV) Slog.v(TAG, "performPollLocked()");
483
484        // try refreshing time source when stale
485        if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
486            mTime.forceRefresh();
487        }
488
489        // TODO: consider marking "untrusted" times in historical stats
490        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
491                : System.currentTimeMillis();
492
493        final NetworkStats networkSnapshot;
494        final NetworkStats uidSnapshot;
495        try {
496            networkSnapshot = mNetworkManager.getNetworkStatsSummary();
497            uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
498        } catch (IllegalStateException e) {
499            Slog.w(TAG, "problem reading network stats: " + e);
500            return;
501        } catch (RemoteException e) {
502            Slog.w(TAG, "problem reading network stats: " + e);
503            return;
504        }
505
506        performNetworkPollLocked(networkSnapshot, currentTime);
507        if (detailedPoll) {
508            performUidPollLocked(uidSnapshot, currentTime);
509        }
510
511        // decide if enough has changed to trigger persist
512        final NetworkStats persistDelta = computeStatsDelta(
513                mLastPersistNetworkSnapshot, networkSnapshot);
514        final long persistThreshold = mSettings.getPersistThreshold();
515        for (String iface : persistDelta.getUniqueIfaces()) {
516            final int index = persistDelta.findIndex(iface, UID_ALL, TAG_NONE);
517            if (forcePersist || persistDelta.rx[index] > persistThreshold
518                    || persistDelta.tx[index] > persistThreshold) {
519                writeNetworkStatsLocked();
520                if (mUidStatsLoaded) {
521                    writeUidStatsLocked();
522                }
523                mLastPersistNetworkSnapshot = networkSnapshot;
524                break;
525            }
526        }
527
528        // finally, dispatch updated event to any listeners
529        final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
530        updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
531        mContext.sendBroadcast(updatedIntent, READ_NETWORK_USAGE_HISTORY);
532    }
533
534    /**
535     * Update {@link #mNetworkStats} historical usage.
536     */
537    private void performNetworkPollLocked(NetworkStats networkSnapshot, long currentTime) {
538        final HashSet<String> unknownIface = Sets.newHashSet();
539
540        final NetworkStats delta = computeStatsDelta(mLastNetworkSnapshot, networkSnapshot);
541        final long timeStart = currentTime - delta.elapsedRealtime;
542        for (int i = 0; i < delta.size; i++) {
543            final String iface = delta.iface[i];
544            final NetworkIdentitySet ident = mActiveIfaces.get(iface);
545            if (ident == null) {
546                unknownIface.add(iface);
547                continue;
548            }
549
550            final long rx = delta.rx[i];
551            final long tx = delta.tx[i];
552
553            final NetworkStatsHistory history = findOrCreateNetworkStatsLocked(ident);
554            history.recordData(timeStart, currentTime, rx, tx);
555        }
556
557        // trim any history beyond max
558        final long maxHistory = mSettings.getNetworkMaxHistory();
559        for (NetworkStatsHistory history : mNetworkStats.values()) {
560            history.removeBucketsBefore(currentTime - maxHistory);
561        }
562
563        mLastNetworkSnapshot = networkSnapshot;
564
565        if (LOGD && unknownIface.size() > 0) {
566            Slog.w(TAG, "unknown interfaces " + unknownIface.toString() + ", ignoring those stats");
567        }
568    }
569
570    /**
571     * Update {@link #mUidStats} historical usage.
572     */
573    private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
574        ensureUidStatsLoadedLocked();
575
576        final NetworkStats delta = computeStatsDelta(mLastUidSnapshot, uidSnapshot);
577        final long timeStart = currentTime - delta.elapsedRealtime;
578
579        for (int i = 0; i < delta.size; i++) {
580            final String iface = delta.iface[i];
581            final NetworkIdentitySet ident = mActiveIfaces.get(iface);
582            if (ident == null) {
583                continue;
584            }
585
586            final int uid = delta.uid[i];
587            final int tag = delta.tag[i];
588            final long rx = delta.rx[i];
589            final long tx = delta.tx[i];
590
591            final NetworkStatsHistory history = findOrCreateUidStatsLocked(ident, uid, tag);
592            history.recordData(timeStart, currentTime, rx, tx);
593        }
594
595        // trim any history beyond max
596        final long maxUidHistory = mSettings.getUidMaxHistory();
597        final long maxTagHistory = mSettings.getTagMaxHistory();
598        for (LongSparseArray<NetworkStatsHistory> uidStats : mUidStats.values()) {
599            for (int i = 0; i < uidStats.size(); i++) {
600                final long packed = uidStats.keyAt(i);
601                final NetworkStatsHistory history = uidStats.valueAt(i);
602
603                // detailed tags are trimmed sooner than summary in TAG_NONE
604                if (unpackTag(packed) == TAG_NONE) {
605                    history.removeBucketsBefore(currentTime - maxUidHistory);
606                } else {
607                    history.removeBucketsBefore(currentTime - maxTagHistory);
608                }
609            }
610        }
611
612        mLastUidSnapshot = uidSnapshot;
613    }
614
615    /**
616     * Clean up {@link #mUidStats} after UID is removed.
617     */
618    private void removeUidLocked(int uid) {
619        ensureUidStatsLoadedLocked();
620
621        // migrate all UID stats into special "removed" bucket
622        for (NetworkIdentitySet ident : mUidStats.keySet()) {
623            final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
624            for (int i = 0; i < uidStats.size(); i++) {
625                final long packed = uidStats.keyAt(i);
626                if (unpackUid(packed) == uid) {
627                    // only migrate combined TAG_NONE history
628                    if (unpackTag(packed) == TAG_NONE) {
629                        final NetworkStatsHistory uidHistory = uidStats.valueAt(i);
630                        final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
631                                ident, UID_REMOVED, TAG_NONE);
632                        removedHistory.recordEntireHistory(uidHistory);
633                    }
634                    uidStats.remove(packed);
635                }
636            }
637        }
638
639        // TODO: push kernel event to wipe stats for UID, otherwise we risk
640        // picking them up again during next poll.
641
642        // since this was radical rewrite, push to disk
643        writeUidStatsLocked();
644    }
645
646    private NetworkStatsHistory findOrCreateNetworkStatsLocked(NetworkIdentitySet ident) {
647        final NetworkStatsHistory existing = mNetworkStats.get(ident);
648
649        // update when no existing, or when bucket duration changed
650        final long bucketDuration = mSettings.getNetworkBucketDuration();
651        NetworkStatsHistory updated = null;
652        if (existing == null) {
653            updated = new NetworkStatsHistory(bucketDuration, 10);
654        } else if (existing.bucketDuration != bucketDuration) {
655            updated = new NetworkStatsHistory(
656                    bucketDuration, estimateResizeBuckets(existing, bucketDuration));
657            updated.recordEntireHistory(existing);
658        }
659
660        if (updated != null) {
661            mNetworkStats.put(ident, updated);
662            return updated;
663        } else {
664            return existing;
665        }
666    }
667
668    private NetworkStatsHistory findOrCreateUidStatsLocked(
669            NetworkIdentitySet ident, int uid, int tag) {
670        ensureUidStatsLoadedLocked();
671
672        LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
673        if (uidStats == null) {
674            uidStats = new LongSparseArray<NetworkStatsHistory>();
675            mUidStats.put(ident, uidStats);
676        }
677
678        final long packed = packUidAndTag(uid, tag);
679        final NetworkStatsHistory existing = uidStats.get(packed);
680
681        // update when no existing, or when bucket duration changed
682        final long bucketDuration = mSettings.getUidBucketDuration();
683        NetworkStatsHistory updated = null;
684        if (existing == null) {
685            updated = new NetworkStatsHistory(bucketDuration, 10);
686        } else if (existing.bucketDuration != bucketDuration) {
687            updated = new NetworkStatsHistory(
688                    bucketDuration, estimateResizeBuckets(existing, bucketDuration));
689            updated.recordEntireHistory(existing);
690        }
691
692        if (updated != null) {
693            uidStats.put(packed, updated);
694            return updated;
695        } else {
696            return existing;
697        }
698    }
699
700    private void readNetworkStatsLocked() {
701        if (LOGV) Slog.v(TAG, "readNetworkStatsLocked()");
702
703        // clear any existing stats and read from disk
704        mNetworkStats.clear();
705
706        FileInputStream fis = null;
707        try {
708            fis = mNetworkFile.openRead();
709            final DataInputStream in = new DataInputStream(fis);
710
711            // verify file magic header intact
712            final int magic = in.readInt();
713            if (magic != FILE_MAGIC) {
714                throw new ProtocolException("unexpected magic: " + magic);
715            }
716
717            final int version = in.readInt();
718            switch (version) {
719                case VERSION_NETWORK_INIT: {
720                    // network := size *(NetworkIdentitySet NetworkStatsHistory)
721                    final int size = in.readInt();
722                    for (int i = 0; i < size; i++) {
723                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
724                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
725                        mNetworkStats.put(ident, history);
726                    }
727                    break;
728                }
729                default: {
730                    throw new ProtocolException("unexpected version: " + version);
731                }
732            }
733        } catch (FileNotFoundException e) {
734            // missing stats is okay, probably first boot
735        } catch (IOException e) {
736            Slog.e(TAG, "problem reading network stats", e);
737        } finally {
738            IoUtils.closeQuietly(fis);
739        }
740    }
741
742    private void ensureUidStatsLoadedLocked() {
743        if (!mUidStatsLoaded) {
744            readUidStatsLocked();
745            mUidStatsLoaded = true;
746        }
747    }
748
749    private void readUidStatsLocked() {
750        if (LOGV) Slog.v(TAG, "readUidStatsLocked()");
751
752        // clear any existing stats and read from disk
753        mUidStats.clear();
754
755        FileInputStream fis = null;
756        try {
757            fis = mUidFile.openRead();
758            final DataInputStream in = new DataInputStream(fis);
759
760            // verify file magic header intact
761            final int magic = in.readInt();
762            if (magic != FILE_MAGIC) {
763                throw new ProtocolException("unexpected magic: " + magic);
764            }
765
766            final int version = in.readInt();
767            switch (version) {
768                case VERSION_UID_INIT: {
769                    // uid := size *(UID NetworkStatsHistory)
770
771                    // drop this data version, since we don't have a good
772                    // mapping into NetworkIdentitySet.
773                    break;
774                }
775                case VERSION_UID_WITH_IDENT: {
776                    // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
777
778                    // drop this data version, since this version only existed
779                    // for a short time.
780                    break;
781                }
782                case VERSION_UID_WITH_TAG: {
783                    // uid := size *(NetworkIdentitySet size *(UID tag NetworkStatsHistory))
784                    final int ifaceSize = in.readInt();
785                    for (int i = 0; i < ifaceSize; i++) {
786                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
787
788                        final int childSize = in.readInt();
789                        final LongSparseArray<NetworkStatsHistory> uidStats = new LongSparseArray<
790                                NetworkStatsHistory>(childSize);
791                        for (int j = 0; j < childSize; j++) {
792                            final int uid = in.readInt();
793                            final int tag = in.readInt();
794                            final long packed = packUidAndTag(uid, tag);
795
796                            final NetworkStatsHistory history = new NetworkStatsHistory(in);
797                            uidStats.put(packed, history);
798                        }
799
800                        mUidStats.put(ident, uidStats);
801                    }
802                    break;
803                }
804                default: {
805                    throw new ProtocolException("unexpected version: " + version);
806                }
807            }
808        } catch (FileNotFoundException e) {
809            // missing stats is okay, probably first boot
810        } catch (IOException e) {
811            Slog.e(TAG, "problem reading uid stats", e);
812        } finally {
813            IoUtils.closeQuietly(fis);
814        }
815    }
816
817    private void writeNetworkStatsLocked() {
818        if (LOGV) Slog.v(TAG, "writeNetworkStatsLocked()");
819
820        // TODO: consider duplicating stats and releasing lock while writing
821
822        FileOutputStream fos = null;
823        try {
824            fos = mNetworkFile.startWrite();
825            final DataOutputStream out = new DataOutputStream(fos);
826
827            out.writeInt(FILE_MAGIC);
828            out.writeInt(VERSION_NETWORK_INIT);
829
830            out.writeInt(mNetworkStats.size());
831            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
832                final NetworkStatsHistory history = mNetworkStats.get(ident);
833                ident.writeToStream(out);
834                history.writeToStream(out);
835            }
836
837            mNetworkFile.finishWrite(fos);
838        } catch (IOException e) {
839            if (fos != null) {
840                mNetworkFile.failWrite(fos);
841            }
842        }
843    }
844
845    private void writeUidStatsLocked() {
846        if (LOGV) Slog.v(TAG, "writeUidStatsLocked()");
847
848        if (!mUidStatsLoaded) {
849            Slog.w(TAG, "asked to write UID stats when not loaded; skipping");
850            return;
851        }
852
853        // TODO: consider duplicating stats and releasing lock while writing
854
855        FileOutputStream fos = null;
856        try {
857            fos = mUidFile.startWrite();
858            final DataOutputStream out = new DataOutputStream(fos);
859
860            out.writeInt(FILE_MAGIC);
861            out.writeInt(VERSION_UID_WITH_TAG);
862
863            final int size = mUidStats.size();
864            out.writeInt(size);
865            for (NetworkIdentitySet ident : mUidStats.keySet()) {
866                final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
867                ident.writeToStream(out);
868
869                final int childSize = uidStats.size();
870                out.writeInt(childSize);
871                for (int i = 0; i < childSize; i++) {
872                    final long packed = uidStats.keyAt(i);
873                    final int uid = unpackUid(packed);
874                    final int tag = unpackTag(packed);
875                    final NetworkStatsHistory history = uidStats.valueAt(i);
876                    out.writeInt(uid);
877                    out.writeInt(tag);
878                    history.writeToStream(out);
879                }
880            }
881
882            mUidFile.finishWrite(fos);
883        } catch (IOException e) {
884            if (fos != null) {
885                mUidFile.failWrite(fos);
886            }
887        }
888    }
889
890    @Override
891    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
892        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
893
894        final HashSet<String> argSet = new HashSet<String>();
895        for (String arg : args) {
896            argSet.add(arg);
897        }
898
899        final boolean fullHistory = argSet.contains("full");
900
901        synchronized (mStatsLock) {
902            // TODO: remove this testing code, since it corrupts stats
903            if (argSet.contains("generate")) {
904                generateRandomLocked();
905                pw.println("Generated stub stats");
906                return;
907            }
908
909            if (argSet.contains("poll")) {
910                performPollLocked(true, true);
911                pw.println("Forced poll");
912                return;
913            }
914
915            pw.println("Active interfaces:");
916            for (String iface : mActiveIfaces.keySet()) {
917                final NetworkIdentitySet ident = mActiveIfaces.get(iface);
918                pw.print("  iface="); pw.print(iface);
919                pw.print(" ident="); pw.println(ident.toString());
920            }
921
922            pw.println("Known historical stats:");
923            for (NetworkIdentitySet ident : mNetworkStats.keySet()) {
924                final NetworkStatsHistory history = mNetworkStats.get(ident);
925                pw.print("  ident="); pw.println(ident.toString());
926                history.dump("  ", pw, fullHistory);
927            }
928
929            if (argSet.contains("detail")) {
930                // since explicitly requested with argument, we're okay to load
931                // from disk if not already in memory.
932                ensureUidStatsLoadedLocked();
933
934                pw.println("Detailed UID stats:");
935                for (NetworkIdentitySet ident : mUidStats.keySet()) {
936                    pw.print("  ident="); pw.println(ident.toString());
937
938                    final LongSparseArray<NetworkStatsHistory> uidStats = mUidStats.get(ident);
939                    for (int i = 0; i < uidStats.size(); i++) {
940                        final long packed = uidStats.keyAt(i);
941                        final int uid = unpackUid(packed);
942                        final int tag = unpackTag(packed);
943                        final NetworkStatsHistory history = uidStats.valueAt(i);
944                        pw.print("    UID="); pw.print(uid);
945                        pw.print(" tag="); pw.println(tag);
946                        history.dump("    ", pw, fullHistory);
947                    }
948                }
949            }
950        }
951    }
952
953    /**
954     * @deprecated only for temporary testing
955     */
956    @Deprecated
957    private void generateRandomLocked() {
958        long networkEnd = System.currentTimeMillis();
959        long networkStart = networkEnd - mSettings.getNetworkMaxHistory();
960        long networkRx = 3 * GB_IN_BYTES;
961        long networkTx = 2 * GB_IN_BYTES;
962
963        long uidEnd = System.currentTimeMillis();
964        long uidStart = uidEnd - mSettings.getUidMaxHistory();
965        long uidRx = 500 * MB_IN_BYTES;
966        long uidTx = 100 * MB_IN_BYTES;
967
968        final List<ApplicationInfo> installedApps = mContext
969                .getPackageManager().getInstalledApplications(0);
970
971        mNetworkStats.clear();
972        mUidStats.clear();
973        for (NetworkIdentitySet ident : mActiveIfaces.values()) {
974            findOrCreateNetworkStatsLocked(ident).generateRandom(
975                    networkStart, networkEnd, networkRx, networkTx);
976
977            for (ApplicationInfo info : installedApps) {
978                final int uid = info.uid;
979                findOrCreateUidStatsLocked(ident, uid, TAG_NONE).generateRandom(
980                        uidStart, uidEnd, uidRx, uidTx);
981            }
982        }
983    }
984
985    /**
986     * Return the delta between two {@link NetworkStats} snapshots, where {@code
987     * before} can be {@code null}.
988     */
989    private static NetworkStats computeStatsDelta(NetworkStats before, NetworkStats current) {
990        if (before != null) {
991            return current.subtractClamped(before);
992        } else {
993            return current;
994        }
995    }
996
997    private int estimateNetworkBuckets() {
998        return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
999    }
1000
1001    private int estimateUidBuckets() {
1002        return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration());
1003    }
1004
1005    private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) {
1006        return (int) (existing.bucketCount * existing.bucketDuration / newBucketDuration);
1007    }
1008
1009    // @VisibleForTesting
1010    public static long packUidAndTag(int uid, int tag) {
1011        final long uidLong = uid;
1012        final long tagLong = tag;
1013        return (uidLong << 32) | (tagLong & 0xFFFFFFFFL);
1014    }
1015
1016    // @VisibleForTesting
1017    public static int unpackUid(long packed) {
1018        return (int) (packed >> 32);
1019    }
1020
1021    // @VisibleForTesting
1022    public static int unpackTag(long packed) {
1023        return (int) (packed & 0xFFFFFFFFL);
1024    }
1025
1026    /**
1027     * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
1028     * in the given {@link NetworkIdentitySet}.
1029     */
1030    private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
1031        for (NetworkIdentity ident : identSet) {
1032            if (template.matches(ident)) {
1033                return true;
1034            }
1035        }
1036        return false;
1037    }
1038
1039    /**
1040     * Default external settings that read from {@link Settings.Secure}.
1041     */
1042    private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
1043        private final ContentResolver mResolver;
1044
1045        public DefaultNetworkStatsSettings(Context context) {
1046            mResolver = checkNotNull(context.getContentResolver());
1047            // TODO: adjust these timings for production builds
1048        }
1049
1050        private long getSecureLong(String name, long def) {
1051            return Settings.Secure.getLong(mResolver, name, def);
1052        }
1053
1054        public long getPollInterval() {
1055            return getSecureLong(NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS);
1056        }
1057        public long getPersistThreshold() {
1058            return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 16 * KB_IN_BYTES);
1059        }
1060        public long getNetworkBucketDuration() {
1061            return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS);
1062        }
1063        public long getNetworkMaxHistory() {
1064            return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS);
1065        }
1066        public long getUidBucketDuration() {
1067            return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS);
1068        }
1069        public long getUidMaxHistory() {
1070            return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS);
1071        }
1072        public long getTagMaxHistory() {
1073            return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS);
1074        }
1075        public long getTimeCacheMaxAge() {
1076            return DAY_IN_MILLIS;
1077        }
1078    }
1079
1080}
1081