blob: 743a746683fe56a1d7d0b39a41ff31e043c3e1c5 [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;
20import static com.android.internal.util.Preconditions.checkNotNull;
21
22import android.net.NetworkStats;
23import android.net.NetworkStats.NonMonotonicObserver;
24import android.net.NetworkStatsHistory;
25import android.net.NetworkTemplate;
26import android.net.TrafficStats;
27import android.util.Log;
28import android.util.Slog;
29
30import com.android.internal.util.FileRotator;
31import com.android.internal.util.IndentingPrintWriter;
32import com.google.android.collect.Sets;
33
34import java.io.DataOutputStream;
35import java.io.File;
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.lang.ref.WeakReference;
40import java.util.HashSet;
41import java.util.Map;
42
43/**
44 * Logic to record deltas between periodic {@link NetworkStats} snapshots into
45 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
46 * Keeps pending changes in memory until they pass a specific threshold, in
47 * bytes. Uses {@link FileRotator} for persistence logic.
48 * <p>
49 * Not inherently thread safe.
50 */
51public class NetworkStatsRecorder {
52 private static final String TAG = "NetworkStatsRecorder";
Jeff Sharkeye7bb71d2012-02-28 15:13:08 -080053 private static final boolean LOGD = false;
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -080054 private static final boolean LOGV = false;
Jeff Sharkey63abc372012-01-11 18:38:16 -080055
56 private final FileRotator mRotator;
57 private final NonMonotonicObserver<String> mObserver;
58 private final String mCookie;
59
60 private final long mBucketDuration;
61 private final long mPersistThresholdBytes;
62 private final boolean mOnlyTags;
63
64 private NetworkStats mLastSnapshot;
65
66 private final NetworkStatsCollection mPending;
67 private final NetworkStatsCollection mSinceBoot;
68
69 private final CombiningRewriter mPendingRewriter;
70
71 private WeakReference<NetworkStatsCollection> mComplete;
72
73 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
74 String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) {
75 mRotator = checkNotNull(rotator, "missing FileRotator");
76 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
77 mCookie = cookie;
78
79 mBucketDuration = bucketDuration;
80 mPersistThresholdBytes = persistThresholdBytes;
81 mOnlyTags = onlyTags;
82
83 mPending = new NetworkStatsCollection(bucketDuration);
84 mSinceBoot = new NetworkStatsCollection(bucketDuration);
85
86 mPendingRewriter = new CombiningRewriter(mPending);
87 }
88
89 public void resetLocked() {
90 mLastSnapshot = null;
91 mPending.reset();
92 mSinceBoot.reset();
93 mComplete.clear();
94 }
95
96 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
97 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
98 }
99
100 /**
101 * Load complete history represented by {@link FileRotator}. Caches
102 * internally as a {@link WeakReference}, and updated with future
103 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
104 * as reference is valid.
105 */
106 public NetworkStatsCollection getOrLoadCompleteLocked() {
107 NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
108 if (complete == null) {
109 if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
110 try {
111 complete = new NetworkStatsCollection(mBucketDuration);
112 mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
113 complete.recordCollection(mPending);
114 mComplete = new WeakReference<NetworkStatsCollection>(complete);
115 } catch (IOException e) {
116 Log.wtf(TAG, "problem completely reading network stats", e);
117 }
118 }
119 return complete;
120 }
121
122 /**
123 * Record any delta that occurred since last {@link NetworkStats} snapshot,
124 * using the given {@link Map} to identify network interfaces. First
125 * snapshot is considered bootstrap, and is not counted as delta.
126 */
127 public void recordSnapshotLocked(NetworkStats snapshot,
128 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
129 final HashSet<String> unknownIfaces = Sets.newHashSet();
130
Jeff Sharkeye8914c32012-05-01 16:26:09 -0700131 // skip recording when snapshot missing
132 if (snapshot == null) return;
133
Jeff Sharkey63abc372012-01-11 18:38:16 -0800134 // assume first snapshot is bootstrap and don't record
135 if (mLastSnapshot == null) {
136 mLastSnapshot = snapshot;
137 return;
138 }
139
140 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
141
142 final NetworkStats delta = NetworkStats.subtract(
143 snapshot, mLastSnapshot, mObserver, mCookie);
144 final long end = currentTimeMillis;
145 final long start = end - delta.getElapsedRealtime();
146
147 NetworkStats.Entry entry = null;
148 for (int i = 0; i < delta.size(); i++) {
149 entry = delta.getValues(i, entry);
150 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
151 if (ident == null) {
152 unknownIfaces.add(entry.iface);
153 continue;
154 }
155
156 // skip when no delta occured
157 if (entry.isEmpty()) continue;
158
159 // only record tag data when requested
160 if ((entry.tag == TAG_NONE) != mOnlyTags) {
161 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
162
163 // also record against boot stats when present
164 if (mSinceBoot != null) {
165 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
166 }
167
168 // also record against complete dataset when present
169 if (complete != null) {
170 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
171 }
172 }
173 }
174
175 mLastSnapshot = snapshot;
176
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800177 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800178 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
179 }
180 }
181
182 /**
183 * Consider persisting any pending deltas, if they are beyond
184 * {@link #mPersistThresholdBytes}.
185 */
186 public void maybePersistLocked(long currentTimeMillis) {
187 final long pendingBytes = mPending.getTotalBytes();
188 if (pendingBytes >= mPersistThresholdBytes) {
189 forcePersistLocked(currentTimeMillis);
190 } else {
191 mRotator.maybeRotate(currentTimeMillis);
192 }
193 }
194
195 /**
196 * Force persisting any pending deltas.
197 */
198 public void forcePersistLocked(long currentTimeMillis) {
199 if (mPending.isDirty()) {
200 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
201 try {
202 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
203 mRotator.maybeRotate(currentTimeMillis);
204 mPending.reset();
205 } catch (IOException e) {
206 Log.wtf(TAG, "problem persisting pending stats", e);
207 }
208 }
209 }
210
211 /**
212 * Remove the given UID from all {@link FileRotator} history, migrating it
213 * to {@link TrafficStats#UID_REMOVED}.
214 */
215 public void removeUidLocked(int uid) {
216 try {
217 // process all existing data to migrate uid
218 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
219 } catch (IOException e) {
220 Log.wtf(TAG, "problem removing UID " + uid, e);
221 }
222
223 // clear UID from current stats snapshot
224 if (mLastSnapshot != null) {
225 mLastSnapshot = mLastSnapshot.withoutUid(uid);
226 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700227
228 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
229 if (complete != null) {
230 complete.removeUid(uid);
231 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800232 }
233
234 /**
235 * Rewriter that will combine current {@link NetworkStatsCollection} values
236 * with anything read from disk, and write combined set to disk. Clears the
237 * original {@link NetworkStatsCollection} when finished writing.
238 */
239 private static class CombiningRewriter implements FileRotator.Rewriter {
240 private final NetworkStatsCollection mCollection;
241
242 public CombiningRewriter(NetworkStatsCollection collection) {
243 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
244 }
245
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700246 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800247 public void reset() {
248 // ignored
249 }
250
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700251 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800252 public void read(InputStream in) throws IOException {
253 mCollection.read(in);
254 }
255
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700256 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800257 public boolean shouldWrite() {
258 return true;
259 }
260
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700261 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800262 public void write(OutputStream out) throws IOException {
263 mCollection.write(new DataOutputStream(out));
264 mCollection.reset();
265 }
266 }
267
268 /**
269 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
270 * the requested UID, only writing data back when modified.
271 */
272 public static class RemoveUidRewriter implements FileRotator.Rewriter {
273 private final NetworkStatsCollection mTemp;
274 private final int mUid;
275
276 public RemoveUidRewriter(long bucketDuration, int uid) {
277 mTemp = new NetworkStatsCollection(bucketDuration);
278 mUid = uid;
279 }
280
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700281 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800282 public void reset() {
283 mTemp.reset();
284 }
285
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700286 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800287 public void read(InputStream in) throws IOException {
288 mTemp.read(in);
289 mTemp.clearDirty();
290 mTemp.removeUid(mUid);
291 }
292
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700293 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800294 public boolean shouldWrite() {
295 return mTemp.isDirty();
296 }
297
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700298 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800299 public void write(OutputStream out) throws IOException {
300 mTemp.write(new DataOutputStream(out));
301 }
302 }
303
304 public void importLegacyNetworkLocked(File file) throws IOException {
305 // legacy file still exists; start empty to avoid double importing
306 mRotator.deleteAll();
307
308 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
309 collection.readLegacyNetwork(file);
310
311 final long startMillis = collection.getStartMillis();
312 final long endMillis = collection.getEndMillis();
313
314 if (!collection.isEmpty()) {
315 // process legacy data, creating active file at starting time, then
316 // using end time to possibly trigger rotation.
317 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
318 mRotator.maybeRotate(endMillis);
319 }
320 }
321
322 public void importLegacyUidLocked(File file) throws IOException {
323 // legacy file still exists; start empty to avoid double importing
324 mRotator.deleteAll();
325
326 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
327 collection.readLegacyUid(file, mOnlyTags);
328
329 final long startMillis = collection.getStartMillis();
330 final long endMillis = collection.getEndMillis();
331
332 if (!collection.isEmpty()) {
333 // process legacy data, creating active file at starting time, then
334 // using end time to possibly trigger rotation.
335 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
336 mRotator.maybeRotate(endMillis);
337 }
338 }
339
340 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
341 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
342 if (fullHistory) {
343 pw.println("Complete history:");
344 getOrLoadCompleteLocked().dump(pw);
345 } else {
346 pw.println("History since boot:");
347 mSinceBoot.dump(pw);
348 }
349 }
350}