blob: 540f6066f429b199e178a5b0b2ab68bb84ea7118 [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
131 // assume first snapshot is bootstrap and don't record
132 if (mLastSnapshot == null) {
133 mLastSnapshot = snapshot;
134 return;
135 }
136
137 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
138
139 final NetworkStats delta = NetworkStats.subtract(
140 snapshot, mLastSnapshot, mObserver, mCookie);
141 final long end = currentTimeMillis;
142 final long start = end - delta.getElapsedRealtime();
143
144 NetworkStats.Entry entry = null;
145 for (int i = 0; i < delta.size(); i++) {
146 entry = delta.getValues(i, entry);
147 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
148 if (ident == null) {
149 unknownIfaces.add(entry.iface);
150 continue;
151 }
152
153 // skip when no delta occured
154 if (entry.isEmpty()) continue;
155
156 // only record tag data when requested
157 if ((entry.tag == TAG_NONE) != mOnlyTags) {
158 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
159
160 // also record against boot stats when present
161 if (mSinceBoot != null) {
162 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
163 }
164
165 // also record against complete dataset when present
166 if (complete != null) {
167 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
168 }
169 }
170 }
171
172 mLastSnapshot = snapshot;
173
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800174 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800175 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
176 }
177 }
178
179 /**
180 * Consider persisting any pending deltas, if they are beyond
181 * {@link #mPersistThresholdBytes}.
182 */
183 public void maybePersistLocked(long currentTimeMillis) {
184 final long pendingBytes = mPending.getTotalBytes();
185 if (pendingBytes >= mPersistThresholdBytes) {
186 forcePersistLocked(currentTimeMillis);
187 } else {
188 mRotator.maybeRotate(currentTimeMillis);
189 }
190 }
191
192 /**
193 * Force persisting any pending deltas.
194 */
195 public void forcePersistLocked(long currentTimeMillis) {
196 if (mPending.isDirty()) {
197 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
198 try {
199 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
200 mRotator.maybeRotate(currentTimeMillis);
201 mPending.reset();
202 } catch (IOException e) {
203 Log.wtf(TAG, "problem persisting pending stats", e);
204 }
205 }
206 }
207
208 /**
209 * Remove the given UID from all {@link FileRotator} history, migrating it
210 * to {@link TrafficStats#UID_REMOVED}.
211 */
212 public void removeUidLocked(int uid) {
213 try {
214 // process all existing data to migrate uid
215 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
216 } catch (IOException e) {
217 Log.wtf(TAG, "problem removing UID " + uid, e);
218 }
219
220 // clear UID from current stats snapshot
221 if (mLastSnapshot != null) {
222 mLastSnapshot = mLastSnapshot.withoutUid(uid);
223 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700224
225 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
226 if (complete != null) {
227 complete.removeUid(uid);
228 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800229 }
230
231 /**
232 * Rewriter that will combine current {@link NetworkStatsCollection} values
233 * with anything read from disk, and write combined set to disk. Clears the
234 * original {@link NetworkStatsCollection} when finished writing.
235 */
236 private static class CombiningRewriter implements FileRotator.Rewriter {
237 private final NetworkStatsCollection mCollection;
238
239 public CombiningRewriter(NetworkStatsCollection collection) {
240 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
241 }
242
243 /** {@inheritDoc} */
244 public void reset() {
245 // ignored
246 }
247
248 /** {@inheritDoc} */
249 public void read(InputStream in) throws IOException {
250 mCollection.read(in);
251 }
252
253 /** {@inheritDoc} */
254 public boolean shouldWrite() {
255 return true;
256 }
257
258 /** {@inheritDoc} */
259 public void write(OutputStream out) throws IOException {
260 mCollection.write(new DataOutputStream(out));
261 mCollection.reset();
262 }
263 }
264
265 /**
266 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
267 * the requested UID, only writing data back when modified.
268 */
269 public static class RemoveUidRewriter implements FileRotator.Rewriter {
270 private final NetworkStatsCollection mTemp;
271 private final int mUid;
272
273 public RemoveUidRewriter(long bucketDuration, int uid) {
274 mTemp = new NetworkStatsCollection(bucketDuration);
275 mUid = uid;
276 }
277
278 /** {@inheritDoc} */
279 public void reset() {
280 mTemp.reset();
281 }
282
283 /** {@inheritDoc} */
284 public void read(InputStream in) throws IOException {
285 mTemp.read(in);
286 mTemp.clearDirty();
287 mTemp.removeUid(mUid);
288 }
289
290 /** {@inheritDoc} */
291 public boolean shouldWrite() {
292 return mTemp.isDirty();
293 }
294
295 /** {@inheritDoc} */
296 public void write(OutputStream out) throws IOException {
297 mTemp.write(new DataOutputStream(out));
298 }
299 }
300
301 public void importLegacyNetworkLocked(File file) throws IOException {
302 // legacy file still exists; start empty to avoid double importing
303 mRotator.deleteAll();
304
305 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
306 collection.readLegacyNetwork(file);
307
308 final long startMillis = collection.getStartMillis();
309 final long endMillis = collection.getEndMillis();
310
311 if (!collection.isEmpty()) {
312 // process legacy data, creating active file at starting time, then
313 // using end time to possibly trigger rotation.
314 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
315 mRotator.maybeRotate(endMillis);
316 }
317 }
318
319 public void importLegacyUidLocked(File file) throws IOException {
320 // legacy file still exists; start empty to avoid double importing
321 mRotator.deleteAll();
322
323 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
324 collection.readLegacyUid(file, mOnlyTags);
325
326 final long startMillis = collection.getStartMillis();
327 final long endMillis = collection.getEndMillis();
328
329 if (!collection.isEmpty()) {
330 // process legacy data, creating active file at starting time, then
331 // using end time to possibly trigger rotation.
332 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
333 mRotator.maybeRotate(endMillis);
334 }
335 }
336
337 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
338 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
339 if (fullHistory) {
340 pw.println("Complete history:");
341 getOrLoadCompleteLocked().dump(pw);
342 } else {
343 pw.println("History since boot:");
344 mSinceBoot.dump(pw);
345 }
346 }
347}