blob: 240cc1c87501b6810d945721789e913bb8ab2ae0 [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";
53 private static final boolean LOGD = true;
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 }
224 }
225
226 /**
227 * Rewriter that will combine current {@link NetworkStatsCollection} values
228 * with anything read from disk, and write combined set to disk. Clears the
229 * original {@link NetworkStatsCollection} when finished writing.
230 */
231 private static class CombiningRewriter implements FileRotator.Rewriter {
232 private final NetworkStatsCollection mCollection;
233
234 public CombiningRewriter(NetworkStatsCollection collection) {
235 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
236 }
237
238 /** {@inheritDoc} */
239 public void reset() {
240 // ignored
241 }
242
243 /** {@inheritDoc} */
244 public void read(InputStream in) throws IOException {
245 mCollection.read(in);
246 }
247
248 /** {@inheritDoc} */
249 public boolean shouldWrite() {
250 return true;
251 }
252
253 /** {@inheritDoc} */
254 public void write(OutputStream out) throws IOException {
255 mCollection.write(new DataOutputStream(out));
256 mCollection.reset();
257 }
258 }
259
260 /**
261 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
262 * the requested UID, only writing data back when modified.
263 */
264 public static class RemoveUidRewriter implements FileRotator.Rewriter {
265 private final NetworkStatsCollection mTemp;
266 private final int mUid;
267
268 public RemoveUidRewriter(long bucketDuration, int uid) {
269 mTemp = new NetworkStatsCollection(bucketDuration);
270 mUid = uid;
271 }
272
273 /** {@inheritDoc} */
274 public void reset() {
275 mTemp.reset();
276 }
277
278 /** {@inheritDoc} */
279 public void read(InputStream in) throws IOException {
280 mTemp.read(in);
281 mTemp.clearDirty();
282 mTemp.removeUid(mUid);
283 }
284
285 /** {@inheritDoc} */
286 public boolean shouldWrite() {
287 return mTemp.isDirty();
288 }
289
290 /** {@inheritDoc} */
291 public void write(OutputStream out) throws IOException {
292 mTemp.write(new DataOutputStream(out));
293 }
294 }
295
296 public void importLegacyNetworkLocked(File file) throws IOException {
297 // legacy file still exists; start empty to avoid double importing
298 mRotator.deleteAll();
299
300 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
301 collection.readLegacyNetwork(file);
302
303 final long startMillis = collection.getStartMillis();
304 final long endMillis = collection.getEndMillis();
305
306 if (!collection.isEmpty()) {
307 // process legacy data, creating active file at starting time, then
308 // using end time to possibly trigger rotation.
309 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
310 mRotator.maybeRotate(endMillis);
311 }
312 }
313
314 public void importLegacyUidLocked(File file) throws IOException {
315 // legacy file still exists; start empty to avoid double importing
316 mRotator.deleteAll();
317
318 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
319 collection.readLegacyUid(file, mOnlyTags);
320
321 final long startMillis = collection.getStartMillis();
322 final long endMillis = collection.getEndMillis();
323
324 if (!collection.isEmpty()) {
325 // process legacy data, creating active file at starting time, then
326 // using end time to possibly trigger rotation.
327 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
328 mRotator.maybeRotate(endMillis);
329 }
330 }
331
332 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
333 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
334 if (fullHistory) {
335 pw.println("Complete history:");
336 getOrLoadCompleteLocked().dump(pw);
337 } else {
338 pw.println("History since boot:");
339 mSinceBoot.dump(pw);
340 }
341 }
342}