blob: c3ecf549513255fa7c15e6a0e2ca07ee863b155f [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;
44import java.lang.ref.WeakReference;
45import java.util.HashSet;
46import java.util.Map;
47
Jeff Sharkey6de357e2012-05-09 13:33:52 -070048import libcore.io.IoUtils;
49
Jeff Sharkey63abc372012-01-11 18:38:16 -080050/**
51 * Logic to record deltas between periodic {@link NetworkStats} snapshots into
52 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
53 * Keeps pending changes in memory until they pass a specific threshold, in
54 * bytes. Uses {@link FileRotator} for persistence logic.
55 * <p>
56 * Not inherently thread safe.
57 */
58public class NetworkStatsRecorder {
59 private static final String TAG = "NetworkStatsRecorder";
Jeff Sharkeye7bb71d2012-02-28 15:13:08 -080060 private static final boolean LOGD = false;
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -080061 private static final boolean LOGV = false;
Jeff Sharkey63abc372012-01-11 18:38:16 -080062
Jeff Sharkey6de357e2012-05-09 13:33:52 -070063 private static final String TAG_NETSTATS_DUMP = "netstats_dump";
64
65 /** Dump before deleting in {@link #recoverFromWtf()}. */
66 private static final boolean DUMP_BEFORE_DELETE = true;
67
Jeff Sharkey63abc372012-01-11 18:38:16 -080068 private final FileRotator mRotator;
69 private final NonMonotonicObserver<String> mObserver;
Jeff Sharkey6de357e2012-05-09 13:33:52 -070070 private final DropBoxManager mDropBox;
Jeff Sharkey63abc372012-01-11 18:38:16 -080071 private final String mCookie;
72
73 private final long mBucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080074 private final boolean mOnlyTags;
75
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070076 private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080077 private NetworkStats mLastSnapshot;
78
79 private final NetworkStatsCollection mPending;
80 private final NetworkStatsCollection mSinceBoot;
81
82 private final CombiningRewriter mPendingRewriter;
83
84 private WeakReference<NetworkStatsCollection> mComplete;
85
86 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
Jeff Sharkey6de357e2012-05-09 13:33:52 -070087 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
Jeff Sharkey63abc372012-01-11 18:38:16 -080088 mRotator = checkNotNull(rotator, "missing FileRotator");
89 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
Jeff Sharkey6de357e2012-05-09 13:33:52 -070090 mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
Jeff Sharkey63abc372012-01-11 18:38:16 -080091 mCookie = cookie;
92
93 mBucketDuration = bucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080094 mOnlyTags = onlyTags;
95
96 mPending = new NetworkStatsCollection(bucketDuration);
97 mSinceBoot = new NetworkStatsCollection(bucketDuration);
98
99 mPendingRewriter = new CombiningRewriter(mPending);
100 }
101
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700102 public void setPersistThreshold(long thresholdBytes) {
103 if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
104 mPersistThresholdBytes = MathUtils.constrain(
105 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
106 }
107
Jeff Sharkey63abc372012-01-11 18:38:16 -0800108 public void resetLocked() {
109 mLastSnapshot = null;
110 mPending.reset();
111 mSinceBoot.reset();
112 mComplete.clear();
113 }
114
115 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
116 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
117 }
118
119 /**
120 * Load complete history represented by {@link FileRotator}. Caches
121 * internally as a {@link WeakReference}, and updated with future
122 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
123 * as reference is valid.
124 */
125 public NetworkStatsCollection getOrLoadCompleteLocked() {
126 NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
127 if (complete == null) {
128 if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
129 try {
130 complete = new NetworkStatsCollection(mBucketDuration);
131 mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
132 complete.recordCollection(mPending);
133 mComplete = new WeakReference<NetworkStatsCollection>(complete);
134 } catch (IOException e) {
135 Log.wtf(TAG, "problem completely reading network stats", e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700136 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800137 }
138 }
139 return complete;
140 }
141
142 /**
143 * Record any delta that occurred since last {@link NetworkStats} snapshot,
144 * using the given {@link Map} to identify network interfaces. First
145 * snapshot is considered bootstrap, and is not counted as delta.
146 */
147 public void recordSnapshotLocked(NetworkStats snapshot,
148 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
149 final HashSet<String> unknownIfaces = Sets.newHashSet();
150
Jeff Sharkeye8914c32012-05-01 16:26:09 -0700151 // skip recording when snapshot missing
152 if (snapshot == null) return;
153
Jeff Sharkey63abc372012-01-11 18:38:16 -0800154 // assume first snapshot is bootstrap and don't record
155 if (mLastSnapshot == null) {
156 mLastSnapshot = snapshot;
157 return;
158 }
159
160 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
161
162 final NetworkStats delta = NetworkStats.subtract(
163 snapshot, mLastSnapshot, mObserver, mCookie);
164 final long end = currentTimeMillis;
165 final long start = end - delta.getElapsedRealtime();
166
167 NetworkStats.Entry entry = null;
168 for (int i = 0; i < delta.size(); i++) {
169 entry = delta.getValues(i, entry);
170 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
171 if (ident == null) {
172 unknownIfaces.add(entry.iface);
173 continue;
174 }
175
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700176 // skip when no delta occurred
Jeff Sharkey63abc372012-01-11 18:38:16 -0800177 if (entry.isEmpty()) continue;
178
179 // only record tag data when requested
180 if ((entry.tag == TAG_NONE) != mOnlyTags) {
181 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
182
183 // also record against boot stats when present
184 if (mSinceBoot != null) {
185 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
186 }
187
188 // also record against complete dataset when present
189 if (complete != null) {
190 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
191 }
192 }
193 }
194
195 mLastSnapshot = snapshot;
196
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800197 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800198 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
199 }
200 }
201
202 /**
203 * Consider persisting any pending deltas, if they are beyond
204 * {@link #mPersistThresholdBytes}.
205 */
206 public void maybePersistLocked(long currentTimeMillis) {
207 final long pendingBytes = mPending.getTotalBytes();
208 if (pendingBytes >= mPersistThresholdBytes) {
209 forcePersistLocked(currentTimeMillis);
210 } else {
211 mRotator.maybeRotate(currentTimeMillis);
212 }
213 }
214
215 /**
216 * Force persisting any pending deltas.
217 */
218 public void forcePersistLocked(long currentTimeMillis) {
219 if (mPending.isDirty()) {
220 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
221 try {
222 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
223 mRotator.maybeRotate(currentTimeMillis);
224 mPending.reset();
225 } catch (IOException e) {
226 Log.wtf(TAG, "problem persisting pending stats", e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700227 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800228 }
229 }
230 }
231
232 /**
233 * Remove the given UID from all {@link FileRotator} history, migrating it
234 * to {@link TrafficStats#UID_REMOVED}.
235 */
236 public void removeUidLocked(int uid) {
237 try {
238 // process all existing data to migrate uid
239 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
240 } catch (IOException e) {
241 Log.wtf(TAG, "problem removing UID " + uid, e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700242 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800243 }
244
245 // clear UID from current stats snapshot
246 if (mLastSnapshot != null) {
247 mLastSnapshot = mLastSnapshot.withoutUid(uid);
248 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700249
250 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
251 if (complete != null) {
252 complete.removeUid(uid);
253 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800254 }
255
256 /**
257 * Rewriter that will combine current {@link NetworkStatsCollection} values
258 * with anything read from disk, and write combined set to disk. Clears the
259 * original {@link NetworkStatsCollection} when finished writing.
260 */
261 private static class CombiningRewriter implements FileRotator.Rewriter {
262 private final NetworkStatsCollection mCollection;
263
264 public CombiningRewriter(NetworkStatsCollection collection) {
265 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
266 }
267
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700268 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800269 public void reset() {
270 // ignored
271 }
272
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700273 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800274 public void read(InputStream in) throws IOException {
275 mCollection.read(in);
276 }
277
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700278 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800279 public boolean shouldWrite() {
280 return true;
281 }
282
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700283 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800284 public void write(OutputStream out) throws IOException {
285 mCollection.write(new DataOutputStream(out));
286 mCollection.reset();
287 }
288 }
289
290 /**
291 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
292 * the requested UID, only writing data back when modified.
293 */
294 public static class RemoveUidRewriter implements FileRotator.Rewriter {
295 private final NetworkStatsCollection mTemp;
296 private final int mUid;
297
298 public RemoveUidRewriter(long bucketDuration, int uid) {
299 mTemp = new NetworkStatsCollection(bucketDuration);
300 mUid = uid;
301 }
302
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700303 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800304 public void reset() {
305 mTemp.reset();
306 }
307
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700308 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800309 public void read(InputStream in) throws IOException {
310 mTemp.read(in);
311 mTemp.clearDirty();
312 mTemp.removeUid(mUid);
313 }
314
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700315 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800316 public boolean shouldWrite() {
317 return mTemp.isDirty();
318 }
319
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700320 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800321 public void write(OutputStream out) throws IOException {
322 mTemp.write(new DataOutputStream(out));
323 }
324 }
325
326 public void importLegacyNetworkLocked(File file) throws IOException {
327 // legacy file still exists; start empty to avoid double importing
328 mRotator.deleteAll();
329
330 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
331 collection.readLegacyNetwork(file);
332
333 final long startMillis = collection.getStartMillis();
334 final long endMillis = collection.getEndMillis();
335
336 if (!collection.isEmpty()) {
337 // process legacy data, creating active file at starting time, then
338 // using end time to possibly trigger rotation.
339 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
340 mRotator.maybeRotate(endMillis);
341 }
342 }
343
344 public void importLegacyUidLocked(File file) throws IOException {
345 // legacy file still exists; start empty to avoid double importing
346 mRotator.deleteAll();
347
348 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
349 collection.readLegacyUid(file, mOnlyTags);
350
351 final long startMillis = collection.getStartMillis();
352 final long endMillis = collection.getEndMillis();
353
354 if (!collection.isEmpty()) {
355 // process legacy data, creating active file at starting time, then
356 // using end time to possibly trigger rotation.
357 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
358 mRotator.maybeRotate(endMillis);
359 }
360 }
361
362 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
363 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
364 if (fullHistory) {
365 pw.println("Complete history:");
366 getOrLoadCompleteLocked().dump(pw);
367 } else {
368 pw.println("History since boot:");
369 mSinceBoot.dump(pw);
370 }
371 }
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700372
373 /**
374 * Recover from {@link FileRotator} failure by dumping state to
375 * {@link DropBoxManager} and deleting contents.
376 */
377 private void recoverFromWtf() {
378 if (DUMP_BEFORE_DELETE) {
379 final ByteArrayOutputStream os = new ByteArrayOutputStream();
380 try {
381 mRotator.dumpAll(os);
382 } catch (IOException e) {
383 // ignore partial contents
384 os.reset();
385 } finally {
386 IoUtils.closeQuietly(os);
387 }
388 mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
389 }
390
391 mRotator.deleteAll();
392 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800393}