blob: 961a451778973b8e8c07573bf25949227a429e24 [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;
Lorenzo Colittid3e4a1e2018-01-19 01:12:04 +090020import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
Lorenzo Colittiada23ed2018-01-19 01:05:20 +090021import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
Stephen Chen25147872016-10-21 12:44:26 -070022import static android.net.NetworkStats.METERED_NO;
23import static android.net.NetworkStats.METERED_YES;
Jeff Davidson1f7e05e2016-03-10 13:21:38 -080024import static android.net.NetworkStats.ROAMING_NO;
25import static android.net.NetworkStats.ROAMING_YES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080026import static android.net.NetworkStats.SET_ALL;
27import static android.net.NetworkStats.SET_DEFAULT;
28import static android.net.NetworkStats.TAG_NONE;
29import static android.net.NetworkStats.UID_ALL;
30import static android.net.TrafficStats.UID_REMOVED;
Jeff Sharkey55a442e2014-11-18 18:22:21 -080031import static android.text.format.DateUtils.WEEK_IN_MILLIS;
Jeff Sharkey63abc372012-01-11 18:38:16 -080032
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060033import static com.android.server.net.NetworkStatsService.TAG;
34
Jeff Sharkey63abc372012-01-11 18:38:16 -080035import android.net.NetworkIdentity;
36import android.net.NetworkStats;
37import android.net.NetworkStatsHistory;
38import android.net.NetworkTemplate;
39import android.net.TrafficStats;
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +000040import android.os.Binder;
Makoto Onukida65a522017-01-13 10:23:30 -080041import android.service.NetworkStatsCollectionKeyProto;
42import android.service.NetworkStatsCollectionProto;
43import android.service.NetworkStatsCollectionStatsProto;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060044import android.telephony.SubscriptionPlan;
Jeff Sharkey55a442e2014-11-18 18:22:21 -080045import android.util.ArrayMap;
Dianne Hackborn39606a02012-07-31 17:54:35 -070046import android.util.AtomicFile;
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +000047import android.util.IntArray;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060048import android.util.Pair;
49import android.util.Slog;
Makoto Onukida65a522017-01-13 10:23:30 -080050import android.util.proto.ProtoOutputStream;
Jeff Sharkey63abc372012-01-11 18:38:16 -080051
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060052import com.android.internal.annotations.VisibleForTesting;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -070053import com.android.internal.util.ArrayUtils;
Jeff Sharkey63abc372012-01-11 18:38:16 -080054import com.android.internal.util.FileRotator;
55import com.android.internal.util.IndentingPrintWriter;
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +000056
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060057import libcore.io.IoUtils;
58
Jeff Sharkey63abc372012-01-11 18:38:16 -080059import com.google.android.collect.Lists;
60import com.google.android.collect.Maps;
61
62import java.io.BufferedInputStream;
63import java.io.DataInputStream;
64import java.io.DataOutputStream;
65import java.io.File;
66import java.io.FileNotFoundException;
67import java.io.IOException;
68import java.io.InputStream;
Jeff Sharkey55a442e2014-11-18 18:22:21 -080069import java.io.PrintWriter;
Jeff Sharkey63abc372012-01-11 18:38:16 -080070import java.net.ProtocolException;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060071import java.time.ZonedDateTime;
Jeff Sharkey63abc372012-01-11 18:38:16 -080072import java.util.ArrayList;
73import java.util.Collections;
74import java.util.HashMap;
Jeff Sharkeyf4de2942017-08-29 15:32:13 -060075import java.util.Iterator;
Kenny Roote6585b32013-12-13 12:00:26 -080076import java.util.Objects;
Jeff Sharkey63abc372012-01-11 18:38:16 -080077
Jeff Sharkey63abc372012-01-11 18:38:16 -080078/**
79 * Collection of {@link NetworkStatsHistory}, stored based on combined key of
80 * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
81 */
82public class NetworkStatsCollection implements FileRotator.Reader {
Jeff Sharkey63abc372012-01-11 18:38:16 -080083 /** File header magic number: "ANET" */
84 private static final int FILE_MAGIC = 0x414E4554;
85
86 private static final int VERSION_NETWORK_INIT = 1;
87
88 private static final int VERSION_UID_INIT = 1;
89 private static final int VERSION_UID_WITH_IDENT = 2;
90 private static final int VERSION_UID_WITH_TAG = 3;
91 private static final int VERSION_UID_WITH_SET = 4;
92
93 private static final int VERSION_UNIFIED_INIT = 16;
94
Jeff Sharkey55a442e2014-11-18 18:22:21 -080095 private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>();
Jeff Sharkey63abc372012-01-11 18:38:16 -080096
Jeff Sharkey70c70532012-05-16 14:51:19 -070097 private final long mBucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080098
99 private long mStartMillis;
100 private long mEndMillis;
101 private long mTotalBytes;
102 private boolean mDirty;
103
104 public NetworkStatsCollection(long bucketDuration) {
105 mBucketDuration = bucketDuration;
106 reset();
107 }
108
109 public void reset() {
110 mStats.clear();
111 mStartMillis = Long.MAX_VALUE;
112 mEndMillis = Long.MIN_VALUE;
113 mTotalBytes = 0;
114 mDirty = false;
115 }
116
117 public long getStartMillis() {
118 return mStartMillis;
119 }
120
Jeff Sharkey70c70532012-05-16 14:51:19 -0700121 /**
122 * Return first atomic bucket in this collection, which is more conservative
123 * than {@link #mStartMillis}.
124 */
125 public long getFirstAtomicBucketMillis() {
126 if (mStartMillis == Long.MAX_VALUE) {
127 return Long.MAX_VALUE;
128 } else {
129 return mStartMillis + mBucketDuration;
130 }
131 }
132
Jeff Sharkey63abc372012-01-11 18:38:16 -0800133 public long getEndMillis() {
134 return mEndMillis;
135 }
136
137 public long getTotalBytes() {
138 return mTotalBytes;
139 }
140
141 public boolean isDirty() {
142 return mDirty;
143 }
144
145 public void clearDirty() {
146 mDirty = false;
147 }
148
149 public boolean isEmpty() {
150 return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
151 }
152
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600153 @VisibleForTesting
154 public long roundUp(long time) {
155 if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
156 || time == SubscriptionPlan.TIME_UNKNOWN) {
157 return time;
158 } else {
159 final long mod = time % mBucketDuration;
160 if (mod > 0) {
161 time -= mod;
162 time += mBucketDuration;
163 }
164 return time;
165 }
166 }
167
168 @VisibleForTesting
169 public long roundDown(long time) {
170 if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
171 || time == SubscriptionPlan.TIME_UNKNOWN) {
172 return time;
173 } else {
174 final long mod = time % mBucketDuration;
175 if (mod > 0) {
176 time -= mod;
177 }
178 return time;
179 }
180 }
181
Jeff Sharkeyd405d052017-09-01 12:51:51 -0600182 /**
183 * Safely multiple a value by a rational.
184 * <p>
185 * Internally it uses integer-based math whenever possible, but switches
186 * over to double-based math if values would overflow.
187 */
188 @VisibleForTesting
189 public static long multiplySafe(long value, long num, long den) {
190 long x = value;
191 long y = num;
192
193 // Logic shamelessly borrowed from Math.multiplyExact()
194 long r = x * y;
195 long ax = Math.abs(x);
196 long ay = Math.abs(y);
197 if (((ax | ay) >>> 31 != 0)) {
198 // Some bits greater than 2^31 that might cause overflow
199 // Check the result using the divide operator
200 // and check for the special case of Long.MIN_VALUE * -1
201 if (((y != 0) && (r / y != x)) ||
202 (x == Long.MIN_VALUE && y == -1)) {
203 // Use double math to avoid overflowing
204 return (long) (((double) num / den) * value);
205 }
206 }
207 return r / den;
208 }
209
Jeff Davidson1efb1332015-12-09 18:04:50 -0800210 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800211 return getRelevantUids(accessLevel, Binder.getCallingUid());
212 }
213
214 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
215 final int callerUid) {
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +0000216 IntArray uids = new IntArray();
217 for (int i = 0; i < mStats.size(); i++) {
218 final Key key = mStats.keyAt(i);
Jeff Davidson1efb1332015-12-09 18:04:50 -0800219 if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +0000220 int j = uids.binarySearch(key.uid);
221
222 if (j < 0) {
223 j = ~j;
224 uids.add(j, key.uid);
225 }
226 }
227 }
228 return uids.toArray();
229 }
230
Jeff Sharkey63abc372012-01-11 18:38:16 -0800231 /**
232 * Combine all {@link NetworkStatsHistory} in this collection which match
233 * the requested parameters.
234 */
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600235 public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
236 int uid, int set, int tag, int fields, long start, long end,
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800237 @NetworkStatsAccess.Level int accessLevel, int callerUid) {
Jeff Davidson1efb1332015-12-09 18:04:50 -0800238 if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +0000239 throw new SecurityException("Network stats history of uid " + uid
240 + " is forbidden for caller " + callerUid);
241 }
242
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600243 final int bucketEstimate = (int) ((end - start) / mBucketDuration);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800244 final NetworkStatsHistory combined = new NetworkStatsHistory(
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600245 mBucketDuration, bucketEstimate, fields);
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +0000246
247 // shortcut when we know stats will be empty
248 if (start == end) return combined;
249
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600250 // Figure out the window of time that we should be augmenting (if any)
251 long augmentStart = SubscriptionPlan.TIME_UNKNOWN;
252 long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime()
253 : SubscriptionPlan.TIME_UNKNOWN;
254 // And if augmenting, we might need to collect more data to adjust with
255 long collectStart = start;
256 long collectEnd = end;
257
258 if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) {
259 final Iterator<Pair<ZonedDateTime, ZonedDateTime>> it = augmentPlan.cycleIterator();
260 while (it.hasNext()) {
261 final Pair<ZonedDateTime, ZonedDateTime> cycle = it.next();
262 final long cycleStart = cycle.first.toInstant().toEpochMilli();
263 final long cycleEnd = cycle.second.toInstant().toEpochMilli();
264 if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) {
265 augmentStart = cycleStart;
266 collectStart = Long.min(collectStart, augmentStart);
267 collectEnd = Long.max(collectEnd, augmentEnd);
268 break;
269 }
270 }
271 }
272
273 if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
274 // Shrink augmentation window so we don't risk undercounting.
275 augmentStart = roundUp(augmentStart);
276 augmentEnd = roundDown(augmentEnd);
277 // Grow collection window so we get all the stats needed.
278 collectStart = roundDown(collectStart);
279 collectEnd = roundUp(collectEnd);
280 }
281
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800282 for (int i = 0; i < mStats.size(); i++) {
283 final Key key = mStats.keyAt(i);
Wenchao Tong98170b02015-03-17 16:14:23 -0700284 if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
Jeff Sharkey63abc372012-01-11 18:38:16 -0800285 && templateMatches(template, key.ident)) {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800286 final NetworkStatsHistory value = mStats.valueAt(i);
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600287 combined.recordHistory(value, collectStart, collectEnd);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800288 }
289 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800290
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600291 if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
292 final NetworkStatsHistory.Entry entry = combined.getValues(
293 augmentStart, augmentEnd, null);
294
295 // If we don't have any recorded data for this time period, give
296 // ourselves something to scale with.
297 if (entry.rxBytes == 0 || entry.txBytes == 0) {
298 combined.recordData(augmentStart, augmentEnd,
299 new NetworkStats.Entry(1, 0, 1, 0, 0));
300 combined.getValues(augmentStart, augmentEnd, entry);
301 }
302
303 final long rawBytes = entry.rxBytes + entry.txBytes;
304 final long rawRxBytes = entry.rxBytes;
305 final long rawTxBytes = entry.txBytes;
306 final long targetBytes = augmentPlan.getDataUsageBytes();
Jeff Sharkeyd405d052017-09-01 12:51:51 -0600307 final long targetRxBytes = multiplySafe(targetBytes, rawRxBytes, rawBytes);
308 final long targetTxBytes = multiplySafe(targetBytes, rawTxBytes, rawBytes);
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600309
310 // Scale all matching buckets to reach anchor target
311 final long beforeTotal = combined.getTotalBytes();
312 for (int i = 0; i < combined.size(); i++) {
313 combined.getValues(i, entry);
314 if (entry.bucketStart >= augmentStart
315 && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
Jeff Sharkeyd405d052017-09-01 12:51:51 -0600316 entry.rxBytes = multiplySafe(targetRxBytes, entry.rxBytes, rawRxBytes);
317 entry.txBytes = multiplySafe(targetTxBytes, entry.txBytes, rawTxBytes);
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600318 // We purposefully clear out packet counters to indicate
319 // that this data has been augmented.
320 entry.rxPackets = 0;
321 entry.txPackets = 0;
322 combined.setValues(i, entry);
323 }
324 }
325
326 final long deltaTotal = combined.getTotalBytes() - beforeTotal;
327 if (deltaTotal != 0) {
328 Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
329 }
330
331 // Finally we can slice data as originally requested
332 final NetworkStatsHistory sliced = new NetworkStatsHistory(
333 mBucketDuration, bucketEstimate, fields);
334 sliced.recordHistory(combined, start, end);
335 return sliced;
336 } else {
337 return combined;
338 }
Antonio Cansadocd42acd2016-02-17 13:03:38 -0800339 }
340
341 /**
342 * Summarize all {@link NetworkStatsHistory} in this collection which match
343 * the requested parameters.
344 */
345 public NetworkStats getSummary(NetworkTemplate template, long start, long end,
346 @NetworkStatsAccess.Level int accessLevel, int callerUid) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800347 final long now = System.currentTimeMillis();
348
349 final NetworkStats stats = new NetworkStats(end - start, 24);
Jeff Sharkeyf4de2942017-08-29 15:32:13 -0600350
Jeff Sharkey70c70532012-05-16 14:51:19 -0700351 // shortcut when we know stats will be empty
352 if (start == end) return stats;
353
Zoltan Szatmary-Ban9c5dfa52015-02-23 17:20:20 +0000354 final NetworkStats.Entry entry = new NetworkStats.Entry();
355 NetworkStatsHistory.Entry historyEntry = null;
356
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800357 for (int i = 0; i < mStats.size(); i++) {
358 final Key key = mStats.keyAt(i);
Jeff Davidson1efb1332015-12-09 18:04:50 -0800359 if (templateMatches(template, key.ident)
360 && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
Wenchao Tong98170b02015-03-17 16:14:23 -0700361 && key.set < NetworkStats.SET_DEBUG_START) {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800362 final NetworkStatsHistory value = mStats.valueAt(i);
363 historyEntry = value.getValues(start, end, now, historyEntry);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800364
365 entry.iface = IFACE_ALL;
366 entry.uid = key.uid;
367 entry.set = key.set;
368 entry.tag = key.tag;
Lorenzo Colittid3e4a1e2018-01-19 01:12:04 +0900369 entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ?
370 DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
Stephen Chen25147872016-10-21 12:44:26 -0700371 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
Jeff Davidson1f7e05e2016-03-10 13:21:38 -0800372 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800373 entry.rxBytes = historyEntry.rxBytes;
374 entry.rxPackets = historyEntry.rxPackets;
375 entry.txBytes = historyEntry.txBytes;
376 entry.txPackets = historyEntry.txPackets;
377 entry.operations = historyEntry.operations;
378
379 if (!entry.isEmpty()) {
380 stats.combineValues(entry);
381 }
382 }
383 }
384
385 return stats;
386 }
387
388 /**
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700389 * Record given {@link android.net.NetworkStats.Entry} into this collection.
Jeff Sharkey63abc372012-01-11 18:38:16 -0800390 */
391 public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
392 long end, NetworkStats.Entry entry) {
Jeff Sharkey70c70532012-05-16 14:51:19 -0700393 final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
394 history.recordData(start, end, entry);
395 noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800396 }
397
398 /**
399 * Record given {@link NetworkStatsHistory} into this collection.
400 */
401 private void recordHistory(Key key, NetworkStatsHistory history) {
402 if (history.size() == 0) return;
403 noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
404
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700405 NetworkStatsHistory target = mStats.get(key);
406 if (target == null) {
407 target = new NetworkStatsHistory(history.getBucketDuration());
408 mStats.put(key, target);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800409 }
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700410 target.recordEntireHistory(history);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800411 }
412
413 /**
414 * Record all {@link NetworkStatsHistory} contained in the given collection
415 * into this collection.
416 */
417 public void recordCollection(NetworkStatsCollection another) {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800418 for (int i = 0; i < another.mStats.size(); i++) {
419 final Key key = another.mStats.keyAt(i);
420 final NetworkStatsHistory value = another.mStats.valueAt(i);
421 recordHistory(key, value);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800422 }
423 }
424
425 private NetworkStatsHistory findOrCreateHistory(
426 NetworkIdentitySet ident, int uid, int set, int tag) {
427 final Key key = new Key(ident, uid, set, tag);
428 final NetworkStatsHistory existing = mStats.get(key);
429
430 // update when no existing, or when bucket duration changed
431 NetworkStatsHistory updated = null;
432 if (existing == null) {
433 updated = new NetworkStatsHistory(mBucketDuration, 10);
434 } else if (existing.getBucketDuration() != mBucketDuration) {
435 updated = new NetworkStatsHistory(existing, mBucketDuration);
436 }
437
438 if (updated != null) {
439 mStats.put(key, updated);
440 return updated;
441 } else {
442 return existing;
443 }
444 }
445
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700446 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800447 public void read(InputStream in) throws IOException {
448 read(new DataInputStream(in));
449 }
450
451 public void read(DataInputStream in) throws IOException {
452 // verify file magic header intact
453 final int magic = in.readInt();
454 if (magic != FILE_MAGIC) {
455 throw new ProtocolException("unexpected magic: " + magic);
456 }
457
458 final int version = in.readInt();
459 switch (version) {
460 case VERSION_UNIFIED_INIT: {
461 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
462 final int identSize = in.readInt();
463 for (int i = 0; i < identSize; i++) {
464 final NetworkIdentitySet ident = new NetworkIdentitySet(in);
465
466 final int size = in.readInt();
467 for (int j = 0; j < size; j++) {
468 final int uid = in.readInt();
469 final int set = in.readInt();
470 final int tag = in.readInt();
471
472 final Key key = new Key(ident, uid, set, tag);
473 final NetworkStatsHistory history = new NetworkStatsHistory(in);
474 recordHistory(key, history);
475 }
476 }
477 break;
478 }
479 default: {
480 throw new ProtocolException("unexpected version: " + version);
481 }
482 }
483 }
484
485 public void write(DataOutputStream out) throws IOException {
486 // cluster key lists grouped by ident
487 final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
488 for (Key key : mStats.keySet()) {
489 ArrayList<Key> keys = keysByIdent.get(key.ident);
490 if (keys == null) {
491 keys = Lists.newArrayList();
492 keysByIdent.put(key.ident, keys);
493 }
494 keys.add(key);
495 }
496
497 out.writeInt(FILE_MAGIC);
498 out.writeInt(VERSION_UNIFIED_INIT);
499
500 out.writeInt(keysByIdent.size());
501 for (NetworkIdentitySet ident : keysByIdent.keySet()) {
502 final ArrayList<Key> keys = keysByIdent.get(ident);
503 ident.writeToStream(out);
504
505 out.writeInt(keys.size());
506 for (Key key : keys) {
507 final NetworkStatsHistory history = mStats.get(key);
508 out.writeInt(key.uid);
509 out.writeInt(key.set);
510 out.writeInt(key.tag);
511 history.writeToStream(out);
512 }
513 }
514
515 out.flush();
516 }
517
518 @Deprecated
519 public void readLegacyNetwork(File file) throws IOException {
520 final AtomicFile inputFile = new AtomicFile(file);
521
522 DataInputStream in = null;
523 try {
524 in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
525
526 // verify file magic header intact
527 final int magic = in.readInt();
528 if (magic != FILE_MAGIC) {
529 throw new ProtocolException("unexpected magic: " + magic);
530 }
531
532 final int version = in.readInt();
533 switch (version) {
534 case VERSION_NETWORK_INIT: {
535 // network := size *(NetworkIdentitySet NetworkStatsHistory)
536 final int size = in.readInt();
537 for (int i = 0; i < size; i++) {
538 final NetworkIdentitySet ident = new NetworkIdentitySet(in);
539 final NetworkStatsHistory history = new NetworkStatsHistory(in);
540
541 final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
542 recordHistory(key, history);
543 }
544 break;
545 }
546 default: {
547 throw new ProtocolException("unexpected version: " + version);
548 }
549 }
550 } catch (FileNotFoundException e) {
551 // missing stats is okay, probably first boot
552 } finally {
553 IoUtils.closeQuietly(in);
554 }
555 }
556
557 @Deprecated
558 public void readLegacyUid(File file, boolean onlyTags) throws IOException {
559 final AtomicFile inputFile = new AtomicFile(file);
560
561 DataInputStream in = null;
562 try {
563 in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
564
565 // verify file magic header intact
566 final int magic = in.readInt();
567 if (magic != FILE_MAGIC) {
568 throw new ProtocolException("unexpected magic: " + magic);
569 }
570
571 final int version = in.readInt();
572 switch (version) {
573 case VERSION_UID_INIT: {
574 // uid := size *(UID NetworkStatsHistory)
575
576 // drop this data version, since we don't have a good
577 // mapping into NetworkIdentitySet.
578 break;
579 }
580 case VERSION_UID_WITH_IDENT: {
581 // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
582
583 // drop this data version, since this version only existed
584 // for a short time.
585 break;
586 }
587 case VERSION_UID_WITH_TAG:
588 case VERSION_UID_WITH_SET: {
589 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
590 final int identSize = in.readInt();
591 for (int i = 0; i < identSize; i++) {
592 final NetworkIdentitySet ident = new NetworkIdentitySet(in);
593
594 final int size = in.readInt();
595 for (int j = 0; j < size; j++) {
596 final int uid = in.readInt();
597 final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
598 : SET_DEFAULT;
599 final int tag = in.readInt();
600
601 final Key key = new Key(ident, uid, set, tag);
602 final NetworkStatsHistory history = new NetworkStatsHistory(in);
603
604 if ((tag == TAG_NONE) != onlyTags) {
605 recordHistory(key, history);
606 }
607 }
608 }
609 break;
610 }
611 default: {
612 throw new ProtocolException("unexpected version: " + version);
613 }
614 }
615 } catch (FileNotFoundException e) {
616 // missing stats is okay, probably first boot
617 } finally {
618 IoUtils.closeQuietly(in);
619 }
620 }
621
622 /**
623 * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
624 * moving any {@link NetworkStats#TAG_NONE} series to
625 * {@link TrafficStats#UID_REMOVED}.
626 */
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700627 public void removeUids(int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800628 final ArrayList<Key> knownKeys = Lists.newArrayList();
629 knownKeys.addAll(mStats.keySet());
630
631 // migrate all UID stats into special "removed" bucket
632 for (Key key : knownKeys) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700633 if (ArrayUtils.contains(uids, key.uid)) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800634 // only migrate combined TAG_NONE history
635 if (key.tag == TAG_NONE) {
636 final NetworkStatsHistory uidHistory = mStats.get(key);
637 final NetworkStatsHistory removedHistory = findOrCreateHistory(
638 key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
639 removedHistory.recordEntireHistory(uidHistory);
640 }
641 mStats.remove(key);
642 mDirty = true;
643 }
644 }
645 }
646
647 private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
648 if (startMillis < mStartMillis) mStartMillis = startMillis;
649 if (endMillis > mEndMillis) mEndMillis = endMillis;
650 mTotalBytes += totalBytes;
651 mDirty = true;
652 }
653
654 private int estimateBuckets() {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800655 return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5)
Jeff Sharkey63abc372012-01-11 18:38:16 -0800656 / mBucketDuration);
657 }
658
Makoto Onukida65a522017-01-13 10:23:30 -0800659 private ArrayList<Key> getSortedKeys() {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800660 final ArrayList<Key> keys = Lists.newArrayList();
661 keys.addAll(mStats.keySet());
662 Collections.sort(keys);
Makoto Onukida65a522017-01-13 10:23:30 -0800663 return keys;
664 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800665
Makoto Onukida65a522017-01-13 10:23:30 -0800666 public void dump(IndentingPrintWriter pw) {
667 for (Key key : getSortedKeys()) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800668 pw.print("ident="); pw.print(key.ident.toString());
669 pw.print(" uid="); pw.print(key.uid);
670 pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
671 pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
672
673 final NetworkStatsHistory history = mStats.get(key);
674 pw.increaseIndent();
675 history.dump(pw, true);
676 pw.decreaseIndent();
677 }
678 }
679
Makoto Onukida65a522017-01-13 10:23:30 -0800680 public void writeToProto(ProtoOutputStream proto, long tag) {
681 final long start = proto.start(tag);
682
683 for (Key key : getSortedKeys()) {
684 final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
685
686 // Key
687 final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
688 key.ident.writeToProto(proto, NetworkStatsCollectionKeyProto.IDENTITY);
689 proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
690 proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
691 proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
692 proto.end(startKey);
693
694 // Value
695 final NetworkStatsHistory history = mStats.get(key);
696 history.writeToProto(proto, NetworkStatsCollectionStatsProto.HISTORY);
697 proto.end(startStats);
698 }
699
700 proto.end(start);
701 }
702
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800703 public void dumpCheckin(PrintWriter pw, long start, long end) {
704 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
705 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
706 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth");
707 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt");
708 }
709
710 /**
711 * Dump all contained stats that match requested parameters, but group
712 * together all matching {@link NetworkTemplate} under a single prefix.
713 */
714 private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate,
715 String groupPrefix) {
716 final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>();
717
718 // Walk through all history, grouping by matching network templates
719 for (int i = 0; i < mStats.size(); i++) {
720 final Key key = mStats.keyAt(i);
721 final NetworkStatsHistory value = mStats.valueAt(i);
722
723 if (!templateMatches(groupTemplate, key.ident)) continue;
Wenchao Tong98170b02015-03-17 16:14:23 -0700724 if (key.set >= NetworkStats.SET_DEBUG_START) continue;
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800725
726 final Key groupKey = new Key(null, key.uid, key.set, key.tag);
727 NetworkStatsHistory groupHistory = grouped.get(groupKey);
728 if (groupHistory == null) {
729 groupHistory = new NetworkStatsHistory(value.getBucketDuration());
730 grouped.put(groupKey, groupHistory);
731 }
732 groupHistory.recordHistory(value, start, end);
733 }
734
735 for (int i = 0; i < grouped.size(); i++) {
736 final Key key = grouped.keyAt(i);
737 final NetworkStatsHistory value = grouped.valueAt(i);
738
739 if (value.size() == 0) continue;
740
741 pw.print("c,");
742 pw.print(groupPrefix); pw.print(',');
743 pw.print(key.uid); pw.print(',');
744 pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(',');
745 pw.print(key.tag);
746 pw.println();
747
748 value.dumpCheckin(pw);
749 }
750 }
751
Jeff Sharkey63abc372012-01-11 18:38:16 -0800752 /**
753 * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
754 * in the given {@link NetworkIdentitySet}.
755 */
756 private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
757 for (NetworkIdentity ident : identSet) {
758 if (template.matches(ident)) {
759 return true;
760 }
761 }
762 return false;
763 }
764
765 private static class Key implements Comparable<Key> {
766 public final NetworkIdentitySet ident;
767 public final int uid;
768 public final int set;
769 public final int tag;
770
771 private final int hashCode;
772
773 public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
774 this.ident = ident;
775 this.uid = uid;
776 this.set = set;
777 this.tag = tag;
Kenny Roote6585b32013-12-13 12:00:26 -0800778 hashCode = Objects.hash(ident, uid, set, tag);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800779 }
780
781 @Override
782 public int hashCode() {
783 return hashCode;
784 }
785
786 @Override
787 public boolean equals(Object obj) {
788 if (obj instanceof Key) {
789 final Key key = (Key) obj;
790 return uid == key.uid && set == key.set && tag == key.tag
Kenny Roote6585b32013-12-13 12:00:26 -0800791 && Objects.equals(ident, key.ident);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800792 }
793 return false;
794 }
795
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700796 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800797 public int compareTo(Key another) {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800798 int res = 0;
799 if (ident != null && another.ident != null) {
800 res = ident.compareTo(another.ident);
801 }
802 if (res == 0) {
803 res = Integer.compare(uid, another.uid);
804 }
805 if (res == 0) {
806 res = Integer.compare(set, another.set);
807 }
808 if (res == 0) {
809 res = Integer.compare(tag, another.tag);
810 }
811 return res;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800812 }
813 }
814}