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