blob: 2ce7771135560cd25f39acebf0dbb661b519e64e [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;
29import android.util.Log;
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070030import android.util.MathUtils;
Jeff Sharkey63abc372012-01-11 18:38:16 -080031import android.util.Slog;
32
33import com.android.internal.util.FileRotator;
34import com.android.internal.util.IndentingPrintWriter;
35import com.google.android.collect.Sets;
36
37import java.io.DataOutputStream;
38import java.io.File;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.OutputStream;
42import java.lang.ref.WeakReference;
43import java.util.HashSet;
44import java.util.Map;
45
46/**
47 * Logic to record deltas between periodic {@link NetworkStats} snapshots into
48 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
49 * Keeps pending changes in memory until they pass a specific threshold, in
50 * bytes. Uses {@link FileRotator} for persistence logic.
51 * <p>
52 * Not inherently thread safe.
53 */
54public class NetworkStatsRecorder {
55 private static final String TAG = "NetworkStatsRecorder";
Jeff Sharkeye7bb71d2012-02-28 15:13:08 -080056 private static final boolean LOGD = false;
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -080057 private static final boolean LOGV = false;
Jeff Sharkey63abc372012-01-11 18:38:16 -080058
59 private final FileRotator mRotator;
60 private final NonMonotonicObserver<String> mObserver;
61 private final String mCookie;
62
63 private final long mBucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080064 private final boolean mOnlyTags;
65
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070066 private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080067 private NetworkStats mLastSnapshot;
68
69 private final NetworkStatsCollection mPending;
70 private final NetworkStatsCollection mSinceBoot;
71
72 private final CombiningRewriter mPendingRewriter;
73
74 private WeakReference<NetworkStatsCollection> mComplete;
75
76 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070077 String cookie, long bucketDuration, boolean onlyTags) {
Jeff Sharkey63abc372012-01-11 18:38:16 -080078 mRotator = checkNotNull(rotator, "missing FileRotator");
79 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
80 mCookie = cookie;
81
82 mBucketDuration = bucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080083 mOnlyTags = onlyTags;
84
85 mPending = new NetworkStatsCollection(bucketDuration);
86 mSinceBoot = new NetworkStatsCollection(bucketDuration);
87
88 mPendingRewriter = new CombiningRewriter(mPending);
89 }
90
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070091 public void setPersistThreshold(long thresholdBytes) {
92 if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
93 mPersistThresholdBytes = MathUtils.constrain(
94 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
95 }
96
Jeff Sharkey63abc372012-01-11 18:38:16 -080097 public void resetLocked() {
98 mLastSnapshot = null;
99 mPending.reset();
100 mSinceBoot.reset();
101 mComplete.clear();
102 }
103
104 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
105 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
106 }
107
108 /**
109 * Load complete history represented by {@link FileRotator}. Caches
110 * internally as a {@link WeakReference}, and updated with future
111 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
112 * as reference is valid.
113 */
114 public NetworkStatsCollection getOrLoadCompleteLocked() {
115 NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
116 if (complete == null) {
117 if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
118 try {
119 complete = new NetworkStatsCollection(mBucketDuration);
120 mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
121 complete.recordCollection(mPending);
122 mComplete = new WeakReference<NetworkStatsCollection>(complete);
123 } catch (IOException e) {
124 Log.wtf(TAG, "problem completely reading network stats", e);
125 }
126 }
127 return complete;
128 }
129
130 /**
131 * Record any delta that occurred since last {@link NetworkStats} snapshot,
132 * using the given {@link Map} to identify network interfaces. First
133 * snapshot is considered bootstrap, and is not counted as delta.
134 */
135 public void recordSnapshotLocked(NetworkStats snapshot,
136 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
137 final HashSet<String> unknownIfaces = Sets.newHashSet();
138
Jeff Sharkeye8914c32012-05-01 16:26:09 -0700139 // skip recording when snapshot missing
140 if (snapshot == null) return;
141
Jeff Sharkey63abc372012-01-11 18:38:16 -0800142 // assume first snapshot is bootstrap and don't record
143 if (mLastSnapshot == null) {
144 mLastSnapshot = snapshot;
145 return;
146 }
147
148 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
149
150 final NetworkStats delta = NetworkStats.subtract(
151 snapshot, mLastSnapshot, mObserver, mCookie);
152 final long end = currentTimeMillis;
153 final long start = end - delta.getElapsedRealtime();
154
155 NetworkStats.Entry entry = null;
156 for (int i = 0; i < delta.size(); i++) {
157 entry = delta.getValues(i, entry);
158 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
159 if (ident == null) {
160 unknownIfaces.add(entry.iface);
161 continue;
162 }
163
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700164 // skip when no delta occurred
Jeff Sharkey63abc372012-01-11 18:38:16 -0800165 if (entry.isEmpty()) continue;
166
167 // only record tag data when requested
168 if ((entry.tag == TAG_NONE) != mOnlyTags) {
169 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
170
171 // also record against boot stats when present
172 if (mSinceBoot != null) {
173 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
174 }
175
176 // also record against complete dataset when present
177 if (complete != null) {
178 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
179 }
180 }
181 }
182
183 mLastSnapshot = snapshot;
184
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800185 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800186 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
187 }
188 }
189
190 /**
191 * Consider persisting any pending deltas, if they are beyond
192 * {@link #mPersistThresholdBytes}.
193 */
194 public void maybePersistLocked(long currentTimeMillis) {
195 final long pendingBytes = mPending.getTotalBytes();
196 if (pendingBytes >= mPersistThresholdBytes) {
197 forcePersistLocked(currentTimeMillis);
198 } else {
199 mRotator.maybeRotate(currentTimeMillis);
200 }
201 }
202
203 /**
204 * Force persisting any pending deltas.
205 */
206 public void forcePersistLocked(long currentTimeMillis) {
207 if (mPending.isDirty()) {
208 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
209 try {
210 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
211 mRotator.maybeRotate(currentTimeMillis);
212 mPending.reset();
213 } catch (IOException e) {
214 Log.wtf(TAG, "problem persisting pending stats", e);
215 }
216 }
217 }
218
219 /**
220 * Remove the given UID from all {@link FileRotator} history, migrating it
221 * to {@link TrafficStats#UID_REMOVED}.
222 */
223 public void removeUidLocked(int uid) {
224 try {
225 // process all existing data to migrate uid
226 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
227 } catch (IOException e) {
228 Log.wtf(TAG, "problem removing UID " + uid, e);
229 }
230
231 // clear UID from current stats snapshot
232 if (mLastSnapshot != null) {
233 mLastSnapshot = mLastSnapshot.withoutUid(uid);
234 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700235
236 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
237 if (complete != null) {
238 complete.removeUid(uid);
239 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800240 }
241
242 /**
243 * Rewriter that will combine current {@link NetworkStatsCollection} values
244 * with anything read from disk, and write combined set to disk. Clears the
245 * original {@link NetworkStatsCollection} when finished writing.
246 */
247 private static class CombiningRewriter implements FileRotator.Rewriter {
248 private final NetworkStatsCollection mCollection;
249
250 public CombiningRewriter(NetworkStatsCollection collection) {
251 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
252 }
253
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700254 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800255 public void reset() {
256 // ignored
257 }
258
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700259 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800260 public void read(InputStream in) throws IOException {
261 mCollection.read(in);
262 }
263
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700264 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800265 public boolean shouldWrite() {
266 return true;
267 }
268
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700269 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800270 public void write(OutputStream out) throws IOException {
271 mCollection.write(new DataOutputStream(out));
272 mCollection.reset();
273 }
274 }
275
276 /**
277 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
278 * the requested UID, only writing data back when modified.
279 */
280 public static class RemoveUidRewriter implements FileRotator.Rewriter {
281 private final NetworkStatsCollection mTemp;
282 private final int mUid;
283
284 public RemoveUidRewriter(long bucketDuration, int uid) {
285 mTemp = new NetworkStatsCollection(bucketDuration);
286 mUid = uid;
287 }
288
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700289 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800290 public void reset() {
291 mTemp.reset();
292 }
293
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700294 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800295 public void read(InputStream in) throws IOException {
296 mTemp.read(in);
297 mTemp.clearDirty();
298 mTemp.removeUid(mUid);
299 }
300
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700301 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800302 public boolean shouldWrite() {
303 return mTemp.isDirty();
304 }
305
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700306 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800307 public void write(OutputStream out) throws IOException {
308 mTemp.write(new DataOutputStream(out));
309 }
310 }
311
312 public void importLegacyNetworkLocked(File file) throws IOException {
313 // legacy file still exists; start empty to avoid double importing
314 mRotator.deleteAll();
315
316 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
317 collection.readLegacyNetwork(file);
318
319 final long startMillis = collection.getStartMillis();
320 final long endMillis = collection.getEndMillis();
321
322 if (!collection.isEmpty()) {
323 // process legacy data, creating active file at starting time, then
324 // using end time to possibly trigger rotation.
325 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
326 mRotator.maybeRotate(endMillis);
327 }
328 }
329
330 public void importLegacyUidLocked(File file) throws IOException {
331 // legacy file still exists; start empty to avoid double importing
332 mRotator.deleteAll();
333
334 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
335 collection.readLegacyUid(file, mOnlyTags);
336
337 final long startMillis = collection.getStartMillis();
338 final long endMillis = collection.getEndMillis();
339
340 if (!collection.isEmpty()) {
341 // process legacy data, creating active file at starting time, then
342 // using end time to possibly trigger rotation.
343 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
344 mRotator.maybeRotate(endMillis);
345 }
346 }
347
348 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
349 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
350 if (fullHistory) {
351 pw.println("Complete history:");
352 getOrLoadCompleteLocked().dump(pw);
353 } else {
354 pw.println("History since boot:");
355 mSinceBoot.dump(pw);
356 }
357 }
358}