blob: d5d76676309e0cefcc94cba6725ccba4cfeb44f2 [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.TAG_NONE;
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070020import static android.net.TrafficStats.KB_IN_BYTES;
21import static android.net.TrafficStats.MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080022import static com.android.internal.util.Preconditions.checkNotNull;
23
24import android.net.NetworkStats;
25import android.net.NetworkStats.NonMonotonicObserver;
26import android.net.NetworkStatsHistory;
27import android.net.NetworkTemplate;
28import android.net.TrafficStats;
Jeff Sharkey6de357e2012-05-09 13:33:52 -070029import android.os.DropBoxManager;
Jeff Sharkey63abc372012-01-11 18:38:16 -080030import android.util.Log;
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070031import android.util.MathUtils;
Jeff Sharkey63abc372012-01-11 18:38:16 -080032import android.util.Slog;
33
34import com.android.internal.util.FileRotator;
35import com.android.internal.util.IndentingPrintWriter;
36import com.google.android.collect.Sets;
37
Jeff Sharkey6de357e2012-05-09 13:33:52 -070038import java.io.ByteArrayOutputStream;
Jeff Sharkey63abc372012-01-11 18:38:16 -080039import java.io.DataOutputStream;
40import java.io.File;
41import java.io.IOException;
42import java.io.InputStream;
43import java.io.OutputStream;
Jeff Sharkey55a442e2014-11-18 18:22:21 -080044import java.io.PrintWriter;
Jeff Sharkey63abc372012-01-11 18:38:16 -080045import java.lang.ref.WeakReference;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -070046import java.util.Arrays;
Jeff Sharkey63abc372012-01-11 18:38:16 -080047import java.util.HashSet;
48import java.util.Map;
49
Jeff Sharkey6de357e2012-05-09 13:33:52 -070050import libcore.io.IoUtils;
51
Jeff Sharkey63abc372012-01-11 18:38:16 -080052/**
53 * Logic to record deltas between periodic {@link NetworkStats} snapshots into
54 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
55 * Keeps pending changes in memory until they pass a specific threshold, in
56 * bytes. Uses {@link FileRotator} for persistence logic.
57 * <p>
58 * Not inherently thread safe.
59 */
60public class NetworkStatsRecorder {
61 private static final String TAG = "NetworkStatsRecorder";
Jeff Sharkeye7bb71d2012-02-28 15:13:08 -080062 private static final boolean LOGD = false;
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -080063 private static final boolean LOGV = false;
Jeff Sharkey63abc372012-01-11 18:38:16 -080064
Jeff Sharkey6de357e2012-05-09 13:33:52 -070065 private static final String TAG_NETSTATS_DUMP = "netstats_dump";
66
67 /** Dump before deleting in {@link #recoverFromWtf()}. */
68 private static final boolean DUMP_BEFORE_DELETE = true;
69
Jeff Sharkey63abc372012-01-11 18:38:16 -080070 private final FileRotator mRotator;
71 private final NonMonotonicObserver<String> mObserver;
Jeff Sharkey6de357e2012-05-09 13:33:52 -070072 private final DropBoxManager mDropBox;
Jeff Sharkey63abc372012-01-11 18:38:16 -080073 private final String mCookie;
74
75 private final long mBucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080076 private final boolean mOnlyTags;
77
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070078 private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080079 private NetworkStats mLastSnapshot;
80
81 private final NetworkStatsCollection mPending;
82 private final NetworkStatsCollection mSinceBoot;
83
84 private final CombiningRewriter mPendingRewriter;
85
86 private WeakReference<NetworkStatsCollection> mComplete;
87
88 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
Jeff Sharkey6de357e2012-05-09 13:33:52 -070089 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
Jeff Sharkey63abc372012-01-11 18:38:16 -080090 mRotator = checkNotNull(rotator, "missing FileRotator");
91 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
Jeff Sharkey6de357e2012-05-09 13:33:52 -070092 mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
Jeff Sharkey63abc372012-01-11 18:38:16 -080093 mCookie = cookie;
94
95 mBucketDuration = bucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080096 mOnlyTags = onlyTags;
97
98 mPending = new NetworkStatsCollection(bucketDuration);
99 mSinceBoot = new NetworkStatsCollection(bucketDuration);
100
101 mPendingRewriter = new CombiningRewriter(mPending);
102 }
103
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700104 public void setPersistThreshold(long thresholdBytes) {
105 if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
106 mPersistThresholdBytes = MathUtils.constrain(
107 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
108 }
109
Jeff Sharkey63abc372012-01-11 18:38:16 -0800110 public void resetLocked() {
111 mLastSnapshot = null;
112 mPending.reset();
113 mSinceBoot.reset();
114 mComplete.clear();
115 }
116
117 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
118 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
119 }
120
121 /**
122 * Load complete history represented by {@link FileRotator}. Caches
123 * internally as a {@link WeakReference}, and updated with future
124 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
125 * as reference is valid.
126 */
127 public NetworkStatsCollection getOrLoadCompleteLocked() {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800128 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
129 if (res == null) {
130 res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
131 mComplete = new WeakReference<NetworkStatsCollection>(res);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800132 }
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800133 return res;
134 }
135
136 public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
137 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
138 if (res == null) {
139 res = loadLocked(start, end);
140 }
141 return res;
142 }
143
144 private NetworkStatsCollection loadLocked(long start, long end) {
145 if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie);
146 final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
147 try {
148 mRotator.readMatching(res, start, end);
149 res.recordCollection(mPending);
150 } catch (IOException e) {
151 Log.wtf(TAG, "problem completely reading network stats", e);
152 recoverFromWtf();
153 } catch (OutOfMemoryError e) {
154 Log.wtf(TAG, "problem completely reading network stats", e);
155 recoverFromWtf();
156 }
157 return res;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800158 }
159
160 /**
161 * Record any delta that occurred since last {@link NetworkStats} snapshot,
162 * using the given {@link Map} to identify network interfaces. First
163 * snapshot is considered bootstrap, and is not counted as delta.
164 */
165 public void recordSnapshotLocked(NetworkStats snapshot,
166 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
167 final HashSet<String> unknownIfaces = Sets.newHashSet();
168
Jeff Sharkeye8914c32012-05-01 16:26:09 -0700169 // skip recording when snapshot missing
170 if (snapshot == null) return;
171
Jeff Sharkey63abc372012-01-11 18:38:16 -0800172 // assume first snapshot is bootstrap and don't record
173 if (mLastSnapshot == null) {
174 mLastSnapshot = snapshot;
175 return;
176 }
177
178 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
179
180 final NetworkStats delta = NetworkStats.subtract(
181 snapshot, mLastSnapshot, mObserver, mCookie);
182 final long end = currentTimeMillis;
183 final long start = end - delta.getElapsedRealtime();
184
185 NetworkStats.Entry entry = null;
186 for (int i = 0; i < delta.size(); i++) {
187 entry = delta.getValues(i, entry);
188 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
189 if (ident == null) {
190 unknownIfaces.add(entry.iface);
191 continue;
192 }
193
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700194 // skip when no delta occurred
Jeff Sharkey63abc372012-01-11 18:38:16 -0800195 if (entry.isEmpty()) continue;
196
197 // only record tag data when requested
198 if ((entry.tag == TAG_NONE) != mOnlyTags) {
199 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
200
201 // also record against boot stats when present
202 if (mSinceBoot != null) {
203 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
204 }
205
206 // also record against complete dataset when present
207 if (complete != null) {
208 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
209 }
210 }
211 }
212
213 mLastSnapshot = snapshot;
214
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800215 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800216 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
217 }
218 }
219
220 /**
221 * Consider persisting any pending deltas, if they are beyond
222 * {@link #mPersistThresholdBytes}.
223 */
224 public void maybePersistLocked(long currentTimeMillis) {
225 final long pendingBytes = mPending.getTotalBytes();
226 if (pendingBytes >= mPersistThresholdBytes) {
227 forcePersistLocked(currentTimeMillis);
228 } else {
229 mRotator.maybeRotate(currentTimeMillis);
230 }
231 }
232
233 /**
234 * Force persisting any pending deltas.
235 */
236 public void forcePersistLocked(long currentTimeMillis) {
237 if (mPending.isDirty()) {
238 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
239 try {
240 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
241 mRotator.maybeRotate(currentTimeMillis);
242 mPending.reset();
243 } catch (IOException e) {
244 Log.wtf(TAG, "problem persisting pending stats", e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700245 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700246 } catch (OutOfMemoryError e) {
247 Log.wtf(TAG, "problem persisting pending stats", e);
248 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800249 }
250 }
251 }
252
253 /**
254 * Remove the given UID from all {@link FileRotator} history, migrating it
255 * to {@link TrafficStats#UID_REMOVED}.
256 */
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700257 public void removeUidsLocked(int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800258 try {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700259 // Rewrite all persisted data to migrate UID stats
260 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
Jeff Sharkey63abc372012-01-11 18:38:16 -0800261 } catch (IOException e) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700262 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700263 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700264 } catch (OutOfMemoryError e) {
265 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
266 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800267 }
268
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700269 // Remove any pending stats
270 mPending.removeUids(uids);
271 mSinceBoot.removeUids(uids);
272
273 // Clear UID from current stats snapshot
Jeff Sharkey63abc372012-01-11 18:38:16 -0800274 if (mLastSnapshot != null) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700275 mLastSnapshot = mLastSnapshot.withoutUids(uids);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800276 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700277
278 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
279 if (complete != null) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700280 complete.removeUids(uids);
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700281 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800282 }
283
284 /**
285 * Rewriter that will combine current {@link NetworkStatsCollection} values
286 * with anything read from disk, and write combined set to disk. Clears the
287 * original {@link NetworkStatsCollection} when finished writing.
288 */
289 private static class CombiningRewriter implements FileRotator.Rewriter {
290 private final NetworkStatsCollection mCollection;
291
292 public CombiningRewriter(NetworkStatsCollection collection) {
293 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
294 }
295
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700296 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800297 public void reset() {
298 // ignored
299 }
300
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700301 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800302 public void read(InputStream in) throws IOException {
303 mCollection.read(in);
304 }
305
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700306 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800307 public boolean shouldWrite() {
308 return true;
309 }
310
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700311 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800312 public void write(OutputStream out) throws IOException {
313 mCollection.write(new DataOutputStream(out));
314 mCollection.reset();
315 }
316 }
317
318 /**
319 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
320 * the requested UID, only writing data back when modified.
321 */
322 public static class RemoveUidRewriter implements FileRotator.Rewriter {
323 private final NetworkStatsCollection mTemp;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700324 private final int[] mUids;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800325
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700326 public RemoveUidRewriter(long bucketDuration, int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800327 mTemp = new NetworkStatsCollection(bucketDuration);
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700328 mUids = uids;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800329 }
330
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700331 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800332 public void reset() {
333 mTemp.reset();
334 }
335
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700336 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800337 public void read(InputStream in) throws IOException {
338 mTemp.read(in);
339 mTemp.clearDirty();
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700340 mTemp.removeUids(mUids);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800341 }
342
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700343 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800344 public boolean shouldWrite() {
345 return mTemp.isDirty();
346 }
347
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700348 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800349 public void write(OutputStream out) throws IOException {
350 mTemp.write(new DataOutputStream(out));
351 }
352 }
353
354 public void importLegacyNetworkLocked(File file) throws IOException {
355 // legacy file still exists; start empty to avoid double importing
356 mRotator.deleteAll();
357
358 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
359 collection.readLegacyNetwork(file);
360
361 final long startMillis = collection.getStartMillis();
362 final long endMillis = collection.getEndMillis();
363
364 if (!collection.isEmpty()) {
365 // process legacy data, creating active file at starting time, then
366 // using end time to possibly trigger rotation.
367 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
368 mRotator.maybeRotate(endMillis);
369 }
370 }
371
372 public void importLegacyUidLocked(File file) throws IOException {
373 // legacy file still exists; start empty to avoid double importing
374 mRotator.deleteAll();
375
376 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
377 collection.readLegacyUid(file, mOnlyTags);
378
379 final long startMillis = collection.getStartMillis();
380 final long endMillis = collection.getEndMillis();
381
382 if (!collection.isEmpty()) {
383 // process legacy data, creating active file at starting time, then
384 // using end time to possibly trigger rotation.
385 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
386 mRotator.maybeRotate(endMillis);
387 }
388 }
389
390 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
391 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
392 if (fullHistory) {
393 pw.println("Complete history:");
394 getOrLoadCompleteLocked().dump(pw);
395 } else {
396 pw.println("History since boot:");
397 mSinceBoot.dump(pw);
398 }
399 }
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700400
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800401 public void dumpCheckin(PrintWriter pw, long start, long end) {
402 // Only load and dump stats from the requested window
403 getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end);
404 }
405
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700406 /**
407 * Recover from {@link FileRotator} failure by dumping state to
408 * {@link DropBoxManager} and deleting contents.
409 */
410 private void recoverFromWtf() {
411 if (DUMP_BEFORE_DELETE) {
412 final ByteArrayOutputStream os = new ByteArrayOutputStream();
413 try {
414 mRotator.dumpAll(os);
415 } catch (IOException e) {
416 // ignore partial contents
417 os.reset();
418 } finally {
419 IoUtils.closeQuietly(os);
420 }
421 mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
422 }
423
424 mRotator.deleteAll();
425 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800426}