NetworkStatsCollection.java revision 63abc37356728c0575d6a62a203102ae6d97953b
1/*
2 * Copyright (C) 2012 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.net.NetworkStats.IFACE_ALL;
20import static android.net.NetworkStats.SET_ALL;
21import static android.net.NetworkStats.SET_DEFAULT;
22import static android.net.NetworkStats.TAG_NONE;
23import static android.net.NetworkStats.UID_ALL;
24import static android.net.TrafficStats.UID_REMOVED;
25
26import android.net.NetworkIdentity;
27import android.net.NetworkStats;
28import android.net.NetworkStatsHistory;
29import android.net.NetworkTemplate;
30import android.net.TrafficStats;
31import android.text.format.DateUtils;
32
33import com.android.internal.os.AtomicFile;
34import com.android.internal.util.FileRotator;
35import com.android.internal.util.IndentingPrintWriter;
36import com.android.internal.util.Objects;
37import com.google.android.collect.Lists;
38import com.google.android.collect.Maps;
39
40import java.io.BufferedInputStream;
41import java.io.DataInputStream;
42import java.io.DataOutputStream;
43import java.io.File;
44import java.io.FileNotFoundException;
45import java.io.IOException;
46import java.io.InputStream;
47import java.net.ProtocolException;
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.HashMap;
51import java.util.Map;
52
53import libcore.io.IoUtils;
54
55/**
56 * Collection of {@link NetworkStatsHistory}, stored based on combined key of
57 * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
58 */
59public class NetworkStatsCollection implements FileRotator.Reader {
60    private static final String TAG = "NetworkStatsCollection";
61
62    /** File header magic number: "ANET" */
63    private static final int FILE_MAGIC = 0x414E4554;
64
65    private static final int VERSION_NETWORK_INIT = 1;
66
67    private static final int VERSION_UID_INIT = 1;
68    private static final int VERSION_UID_WITH_IDENT = 2;
69    private static final int VERSION_UID_WITH_TAG = 3;
70    private static final int VERSION_UID_WITH_SET = 4;
71
72    private static final int VERSION_UNIFIED_INIT = 16;
73
74    private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
75
76    private long mBucketDuration;
77
78    private long mStartMillis;
79    private long mEndMillis;
80    private long mTotalBytes;
81    private boolean mDirty;
82
83    public NetworkStatsCollection(long bucketDuration) {
84        mBucketDuration = bucketDuration;
85        reset();
86    }
87
88    public void reset() {
89        mStats.clear();
90        mStartMillis = Long.MAX_VALUE;
91        mEndMillis = Long.MIN_VALUE;
92        mTotalBytes = 0;
93        mDirty = false;
94    }
95
96    public long getStartMillis() {
97        return mStartMillis;
98    }
99
100    public long getEndMillis() {
101        return mEndMillis;
102    }
103
104    public long getTotalBytes() {
105        return mTotalBytes;
106    }
107
108    public boolean isDirty() {
109        return mDirty;
110    }
111
112    public void clearDirty() {
113        mDirty = false;
114    }
115
116    public boolean isEmpty() {
117        return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
118    }
119
120    /**
121     * Combine all {@link NetworkStatsHistory} in this collection which match
122     * the requested parameters.
123     */
124    public NetworkStatsHistory getHistory(
125            NetworkTemplate template, int uid, int set, int tag, int fields) {
126        final NetworkStatsHistory combined = new NetworkStatsHistory(
127                mBucketDuration, estimateBuckets(), fields);
128        for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
129            final Key key = entry.getKey();
130            final boolean setMatches = set == SET_ALL || key.set == set;
131            if (key.uid == uid && setMatches && key.tag == tag
132                    && templateMatches(template, key.ident)) {
133                combined.recordEntireHistory(entry.getValue());
134            }
135        }
136        return combined;
137    }
138
139    /**
140     * Summarize all {@link NetworkStatsHistory} in this collection which match
141     * the requested parameters.
142     */
143    public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
144        final long now = System.currentTimeMillis();
145
146        final NetworkStats stats = new NetworkStats(end - start, 24);
147        final NetworkStats.Entry entry = new NetworkStats.Entry();
148        NetworkStatsHistory.Entry historyEntry = null;
149
150        for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
151            final Key key = mapEntry.getKey();
152            if (templateMatches(template, key.ident)) {
153                final NetworkStatsHistory history = mapEntry.getValue();
154                historyEntry = history.getValues(start, end, now, historyEntry);
155
156                entry.iface = IFACE_ALL;
157                entry.uid = key.uid;
158                entry.set = key.set;
159                entry.tag = key.tag;
160                entry.rxBytes = historyEntry.rxBytes;
161                entry.rxPackets = historyEntry.rxPackets;
162                entry.txBytes = historyEntry.txBytes;
163                entry.txPackets = historyEntry.txPackets;
164                entry.operations = historyEntry.operations;
165
166                if (!entry.isEmpty()) {
167                    stats.combineValues(entry);
168                }
169            }
170        }
171
172        return stats;
173    }
174
175    /**
176     * Record given {@link NetworkStats.Entry} into this collection.
177     */
178    public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
179            long end, NetworkStats.Entry entry) {
180        noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes);
181        findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry);
182    }
183
184    /**
185     * Record given {@link NetworkStatsHistory} into this collection.
186     */
187    private void recordHistory(Key key, NetworkStatsHistory history) {
188        if (history.size() == 0) return;
189        noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
190
191        final NetworkStatsHistory existing = mStats.get(key);
192        if (existing != null) {
193            existing.recordEntireHistory(history);
194        } else {
195            mStats.put(key, history);
196        }
197    }
198
199    /**
200     * Record all {@link NetworkStatsHistory} contained in the given collection
201     * into this collection.
202     */
203    public void recordCollection(NetworkStatsCollection another) {
204        for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) {
205            recordHistory(entry.getKey(), entry.getValue());
206        }
207    }
208
209    private NetworkStatsHistory findOrCreateHistory(
210            NetworkIdentitySet ident, int uid, int set, int tag) {
211        final Key key = new Key(ident, uid, set, tag);
212        final NetworkStatsHistory existing = mStats.get(key);
213
214        // update when no existing, or when bucket duration changed
215        NetworkStatsHistory updated = null;
216        if (existing == null) {
217            updated = new NetworkStatsHistory(mBucketDuration, 10);
218        } else if (existing.getBucketDuration() != mBucketDuration) {
219            updated = new NetworkStatsHistory(existing, mBucketDuration);
220        }
221
222        if (updated != null) {
223            mStats.put(key, updated);
224            return updated;
225        } else {
226            return existing;
227        }
228    }
229
230    /** {@inheritDoc} */
231    public void read(InputStream in) throws IOException {
232        read(new DataInputStream(in));
233    }
234
235    public void read(DataInputStream in) throws IOException {
236        // verify file magic header intact
237        final int magic = in.readInt();
238        if (magic != FILE_MAGIC) {
239            throw new ProtocolException("unexpected magic: " + magic);
240        }
241
242        final int version = in.readInt();
243        switch (version) {
244            case VERSION_UNIFIED_INIT: {
245                // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
246                final int identSize = in.readInt();
247                for (int i = 0; i < identSize; i++) {
248                    final NetworkIdentitySet ident = new NetworkIdentitySet(in);
249
250                    final int size = in.readInt();
251                    for (int j = 0; j < size; j++) {
252                        final int uid = in.readInt();
253                        final int set = in.readInt();
254                        final int tag = in.readInt();
255
256                        final Key key = new Key(ident, uid, set, tag);
257                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
258                        recordHistory(key, history);
259                    }
260                }
261                break;
262            }
263            default: {
264                throw new ProtocolException("unexpected version: " + version);
265            }
266        }
267    }
268
269    public void write(DataOutputStream out) throws IOException {
270        // cluster key lists grouped by ident
271        final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
272        for (Key key : mStats.keySet()) {
273            ArrayList<Key> keys = keysByIdent.get(key.ident);
274            if (keys == null) {
275                keys = Lists.newArrayList();
276                keysByIdent.put(key.ident, keys);
277            }
278            keys.add(key);
279        }
280
281        out.writeInt(FILE_MAGIC);
282        out.writeInt(VERSION_UNIFIED_INIT);
283
284        out.writeInt(keysByIdent.size());
285        for (NetworkIdentitySet ident : keysByIdent.keySet()) {
286            final ArrayList<Key> keys = keysByIdent.get(ident);
287            ident.writeToStream(out);
288
289            out.writeInt(keys.size());
290            for (Key key : keys) {
291                final NetworkStatsHistory history = mStats.get(key);
292                out.writeInt(key.uid);
293                out.writeInt(key.set);
294                out.writeInt(key.tag);
295                history.writeToStream(out);
296            }
297        }
298
299        out.flush();
300    }
301
302    @Deprecated
303    public void readLegacyNetwork(File file) throws IOException {
304        final AtomicFile inputFile = new AtomicFile(file);
305
306        DataInputStream in = null;
307        try {
308            in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
309
310            // verify file magic header intact
311            final int magic = in.readInt();
312            if (magic != FILE_MAGIC) {
313                throw new ProtocolException("unexpected magic: " + magic);
314            }
315
316            final int version = in.readInt();
317            switch (version) {
318                case VERSION_NETWORK_INIT: {
319                    // network := size *(NetworkIdentitySet NetworkStatsHistory)
320                    final int size = in.readInt();
321                    for (int i = 0; i < size; i++) {
322                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
323                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
324
325                        final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
326                        recordHistory(key, history);
327                    }
328                    break;
329                }
330                default: {
331                    throw new ProtocolException("unexpected version: " + version);
332                }
333            }
334        } catch (FileNotFoundException e) {
335            // missing stats is okay, probably first boot
336        } finally {
337            IoUtils.closeQuietly(in);
338        }
339    }
340
341    @Deprecated
342    public void readLegacyUid(File file, boolean onlyTags) throws IOException {
343        final AtomicFile inputFile = new AtomicFile(file);
344
345        DataInputStream in = null;
346        try {
347            in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
348
349            // verify file magic header intact
350            final int magic = in.readInt();
351            if (magic != FILE_MAGIC) {
352                throw new ProtocolException("unexpected magic: " + magic);
353            }
354
355            final int version = in.readInt();
356            switch (version) {
357                case VERSION_UID_INIT: {
358                    // uid := size *(UID NetworkStatsHistory)
359
360                    // drop this data version, since we don't have a good
361                    // mapping into NetworkIdentitySet.
362                    break;
363                }
364                case VERSION_UID_WITH_IDENT: {
365                    // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
366
367                    // drop this data version, since this version only existed
368                    // for a short time.
369                    break;
370                }
371                case VERSION_UID_WITH_TAG:
372                case VERSION_UID_WITH_SET: {
373                    // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
374                    final int identSize = in.readInt();
375                    for (int i = 0; i < identSize; i++) {
376                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
377
378                        final int size = in.readInt();
379                        for (int j = 0; j < size; j++) {
380                            final int uid = in.readInt();
381                            final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
382                                    : SET_DEFAULT;
383                            final int tag = in.readInt();
384
385                            final Key key = new Key(ident, uid, set, tag);
386                            final NetworkStatsHistory history = new NetworkStatsHistory(in);
387
388                            if ((tag == TAG_NONE) != onlyTags) {
389                                recordHistory(key, history);
390                            }
391                        }
392                    }
393                    break;
394                }
395                default: {
396                    throw new ProtocolException("unexpected version: " + version);
397                }
398            }
399        } catch (FileNotFoundException e) {
400            // missing stats is okay, probably first boot
401        } finally {
402            IoUtils.closeQuietly(in);
403        }
404    }
405
406    /**
407     * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
408     * moving any {@link NetworkStats#TAG_NONE} series to
409     * {@link TrafficStats#UID_REMOVED}.
410     */
411    public void removeUid(int uid) {
412        final ArrayList<Key> knownKeys = Lists.newArrayList();
413        knownKeys.addAll(mStats.keySet());
414
415        // migrate all UID stats into special "removed" bucket
416        for (Key key : knownKeys) {
417            if (key.uid == uid) {
418                // only migrate combined TAG_NONE history
419                if (key.tag == TAG_NONE) {
420                    final NetworkStatsHistory uidHistory = mStats.get(key);
421                    final NetworkStatsHistory removedHistory = findOrCreateHistory(
422                            key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
423                    removedHistory.recordEntireHistory(uidHistory);
424                }
425                mStats.remove(key);
426                mDirty = true;
427            }
428        }
429    }
430
431    private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
432        if (startMillis < mStartMillis) mStartMillis = startMillis;
433        if (endMillis > mEndMillis) mEndMillis = endMillis;
434        mTotalBytes += totalBytes;
435        mDirty = true;
436    }
437
438    private int estimateBuckets() {
439        return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5)
440                / mBucketDuration);
441    }
442
443    public void dump(IndentingPrintWriter pw) {
444        final ArrayList<Key> keys = Lists.newArrayList();
445        keys.addAll(mStats.keySet());
446        Collections.sort(keys);
447
448        for (Key key : keys) {
449            pw.print("ident="); pw.print(key.ident.toString());
450            pw.print(" uid="); pw.print(key.uid);
451            pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
452            pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
453
454            final NetworkStatsHistory history = mStats.get(key);
455            pw.increaseIndent();
456            history.dump(pw, true);
457            pw.decreaseIndent();
458        }
459    }
460
461    /**
462     * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
463     * in the given {@link NetworkIdentitySet}.
464     */
465    private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
466        for (NetworkIdentity ident : identSet) {
467            if (template.matches(ident)) {
468                return true;
469            }
470        }
471        return false;
472    }
473
474    private static class Key implements Comparable<Key> {
475        public final NetworkIdentitySet ident;
476        public final int uid;
477        public final int set;
478        public final int tag;
479
480        private final int hashCode;
481
482        public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
483            this.ident = ident;
484            this.uid = uid;
485            this.set = set;
486            this.tag = tag;
487            hashCode = Objects.hashCode(ident, uid, set, tag);
488        }
489
490        @Override
491        public int hashCode() {
492            return hashCode;
493        }
494
495        @Override
496        public boolean equals(Object obj) {
497            if (obj instanceof Key) {
498                final Key key = (Key) obj;
499                return uid == key.uid && set == key.set && tag == key.tag
500                        && Objects.equal(ident, key.ident);
501            }
502            return false;
503        }
504
505        /** {@inheritDoc} */
506        public int compareTo(Key another) {
507            return Integer.compare(uid, another.uid);
508        }
509    }
510}
511