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