• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 
17 package com.android.internal.net;
18 
19 import static android.net.NetworkStats.SET_DEFAULT;
20 import static android.net.NetworkStats.TAG_NONE;
21 import static android.net.NetworkStats.UID_ALL;
22 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
23 
24 import android.net.NetworkStats;
25 import android.os.SystemClock;
26 import android.util.Slog;
27 
28 import com.google.android.collect.Lists;
29 import com.google.android.collect.Maps;
30 import com.google.android.collect.Sets;
31 
32 import java.io.BufferedReader;
33 import java.io.File;
34 import java.io.FileReader;
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.StringTokenizer;
40 
41 import libcore.io.IoUtils;
42 
43 /**
44  * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
45  * files as needed.
46  */
47 public class NetworkStatsFactory {
48     private static final String TAG = "NetworkStatsFactory";
49 
50     // TODO: consider moving parsing to native code
51 
52     /** Path to {@code /proc/net/dev}. */
53     @Deprecated
54     private final File mStatsIface;
55     /** Path to {@code /proc/net/xt_qtaguid/iface_stat}. */
56     @Deprecated
57     private final File mStatsXtIface;
58     /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
59     private final File mStatsXtIfaceAll;
60     /** Path to {@code /proc/net/xt_qtaguid/stats}. */
61     private final File mStatsXtUid;
62 
63     /** {@link #mStatsXtUid} and {@link #mStatsXtIfaceAll} headers. */
64     private static final String KEY_IDX = "idx";
65     private static final String KEY_IFACE = "iface";
66     private static final String KEY_ACTIVE = "active";
67     private static final String KEY_UID = "uid_tag_int";
68     private static final String KEY_COUNTER_SET = "cnt_set";
69     private static final String KEY_TAG_HEX = "acct_tag_hex";
70     private static final String KEY_SNAP_RX_BYTES = "snap_rx_bytes";
71     private static final String KEY_SNAP_RX_PACKETS = "snap_rx_packets";
72     private static final String KEY_SNAP_TX_BYTES = "snap_tx_bytes";
73     private static final String KEY_SNAP_TX_PACKETS = "snap_tx_packets";
74     private static final String KEY_RX_BYTES = "rx_bytes";
75     private static final String KEY_RX_PACKETS = "rx_packets";
76     private static final String KEY_TX_BYTES = "tx_bytes";
77     private static final String KEY_TX_PACKETS = "tx_packets";
78 
NetworkStatsFactory()79     public NetworkStatsFactory() {
80         this(new File("/proc/"));
81     }
82 
83     // @VisibleForTesting
NetworkStatsFactory(File procRoot)84     public NetworkStatsFactory(File procRoot) {
85         mStatsIface = new File(procRoot, "net/dev");
86         mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
87         mStatsXtIface = new File(procRoot, "net/xt_qtaguid/iface_stat");
88         mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
89     }
90 
91     /**
92      * Parse and return interface-level summary {@link NetworkStats}. Values
93      * monotonically increase since device boot, and may include details about
94      * inactive interfaces.
95      *
96      * @throws IllegalStateException when problem parsing stats.
97      */
readNetworkStatsSummary()98     public NetworkStats readNetworkStatsSummary() throws IllegalStateException {
99         if (mStatsXtIfaceAll.exists()) {
100             return readNetworkStatsSummarySingleFile();
101         } else {
102             return readNetworkStatsSummaryMultipleFiles();
103         }
104     }
105 
readNetworkStatsSummarySingleFile()106     private NetworkStats readNetworkStatsSummarySingleFile() {
107         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
108         final NetworkStats.Entry entry = new NetworkStats.Entry();
109 
110         // TODO: read directly from proc once headers are added
111         final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
112                 KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
113                 KEY_RX_PACKETS, KEY_TX_BYTES, KEY_TX_PACKETS);
114         final ArrayList<String> values = Lists.newArrayList();
115         final HashMap<String, String> parsed = Maps.newHashMap();
116 
117         BufferedReader reader = null;
118         try {
119             reader = new BufferedReader(new FileReader(mStatsXtIfaceAll));
120 
121             String line;
122             while ((line = reader.readLine()) != null) {
123                 splitLine(line, values);
124                 parseLine(keys, values, parsed);
125 
126                 entry.iface = parsed.get(KEY_IFACE);
127                 entry.uid = UID_ALL;
128                 entry.set = SET_DEFAULT;
129                 entry.tag = TAG_NONE;
130 
131                 // always include snapshot values
132                 entry.rxBytes = getParsedLong(parsed, KEY_SNAP_RX_BYTES);
133                 entry.rxPackets = getParsedLong(parsed, KEY_SNAP_RX_PACKETS);
134                 entry.txBytes = getParsedLong(parsed, KEY_SNAP_TX_BYTES);
135                 entry.txPackets = getParsedLong(parsed, KEY_SNAP_TX_PACKETS);
136 
137                 // fold in active numbers, but only when active
138                 final boolean active = getParsedInt(parsed, KEY_ACTIVE) != 0;
139                 if (active) {
140                     entry.rxBytes += getParsedLong(parsed, KEY_RX_BYTES);
141                     entry.rxPackets += getParsedLong(parsed, KEY_RX_PACKETS);
142                     entry.txBytes += getParsedLong(parsed, KEY_TX_BYTES);
143                     entry.txPackets += getParsedLong(parsed, KEY_TX_PACKETS);
144                 }
145 
146                 stats.addValues(entry);
147             }
148         } catch (NullPointerException e) {
149             throw new IllegalStateException("problem parsing stats: " + e);
150         } catch (NumberFormatException e) {
151             throw new IllegalStateException("problem parsing stats: " + e);
152         } catch (IOException e) {
153             throw new IllegalStateException("problem parsing stats: " + e);
154         } finally {
155             IoUtils.closeQuietly(reader);
156         }
157         return stats;
158     }
159 
160     /**
161      * @deprecated remove once {@code iface_stat_all} is merged to all kernels.
162      */
163     @Deprecated
readNetworkStatsSummaryMultipleFiles()164     private NetworkStats readNetworkStatsSummaryMultipleFiles() {
165         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
166         final NetworkStats.Entry entry = new NetworkStats.Entry();
167 
168         final HashSet<String> knownIfaces = Sets.newHashSet();
169         final HashSet<String> activeIfaces = Sets.newHashSet();
170 
171         // collect any historical stats and active state
172         for (String iface : fileListWithoutNull(mStatsXtIface)) {
173             final File ifacePath = new File(mStatsXtIface, iface);
174 
175             final long active = readSingleLongFromFile(new File(ifacePath, "active"));
176             if (active == 1) {
177                 knownIfaces.add(iface);
178                 activeIfaces.add(iface);
179             } else if (active == 0) {
180                 knownIfaces.add(iface);
181             } else {
182                 continue;
183             }
184 
185             entry.iface = iface;
186             entry.uid = UID_ALL;
187             entry.set = SET_DEFAULT;
188             entry.tag = TAG_NONE;
189             entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
190             entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
191             entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes"));
192             entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets"));
193 
194             stats.addValues(entry);
195         }
196 
197         final ArrayList<String> values = Lists.newArrayList();
198 
199         BufferedReader reader = null;
200         try {
201             reader = new BufferedReader(new FileReader(mStatsIface));
202 
203             // skip first two header lines
204             reader.readLine();
205             reader.readLine();
206 
207             // parse remaining lines
208             String line;
209             while ((line = reader.readLine()) != null) {
210                 splitLine(line, values);
211 
212                 try {
213                     entry.iface = values.get(0);
214                     entry.uid = UID_ALL;
215                     entry.set = SET_DEFAULT;
216                     entry.tag = TAG_NONE;
217                     entry.rxBytes = Long.parseLong(values.get(1));
218                     entry.rxPackets = Long.parseLong(values.get(2));
219                     entry.txBytes = Long.parseLong(values.get(9));
220                     entry.txPackets = Long.parseLong(values.get(10));
221 
222                     if (activeIfaces.contains(entry.iface)) {
223                         // combine stats when iface is active
224                         stats.combineValues(entry);
225                     } else if (!knownIfaces.contains(entry.iface)) {
226                         // add stats when iface is unknown
227                         stats.addValues(entry);
228                     }
229                 } catch (NumberFormatException e) {
230                     Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
231                 }
232             }
233         } catch (NullPointerException e) {
234             throw new IllegalStateException("problem parsing stats: " + e);
235         } catch (NumberFormatException e) {
236             throw new IllegalStateException("problem parsing stats: " + e);
237         } catch (IOException e) {
238             throw new IllegalStateException("problem parsing stats: " + e);
239         } finally {
240             IoUtils.closeQuietly(reader);
241         }
242 
243         return stats;
244     }
245 
readNetworkStatsDetail()246     public NetworkStats readNetworkStatsDetail() {
247         return readNetworkStatsDetail(UID_ALL);
248     }
249 
250     /**
251      * Parse and return {@link NetworkStats} with UID-level details. Values
252      * monotonically increase since device boot.
253      *
254      * @throws IllegalStateException when problem parsing stats.
255      */
readNetworkStatsDetail(int limitUid)256     public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException {
257         final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
258         final NetworkStats.Entry entry = new NetworkStats.Entry();
259 
260         // TODO: remove knownLines check once 5087722 verified
261         final HashSet<String> knownLines = Sets.newHashSet();
262         // TODO: remove lastIdx check once 5270106 verified
263         int lastIdx;
264 
265         final ArrayList<String> keys = Lists.newArrayList();
266         final ArrayList<String> values = Lists.newArrayList();
267         final HashMap<String, String> parsed = Maps.newHashMap();
268 
269         BufferedReader reader = null;
270         String line = null;
271         try {
272             reader = new BufferedReader(new FileReader(mStatsXtUid));
273 
274             // parse first line as header
275             line = reader.readLine();
276             splitLine(line, keys);
277             lastIdx = 1;
278 
279             // parse remaining lines
280             while ((line = reader.readLine()) != null) {
281                 splitLine(line, values);
282                 parseLine(keys, values, parsed);
283 
284                 if (!knownLines.add(line)) {
285                     throw new IllegalStateException("duplicate proc entry: " + line);
286                 }
287 
288                 final int idx = getParsedInt(parsed, KEY_IDX);
289                 if (idx != lastIdx + 1) {
290                     throw new IllegalStateException(
291                             "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
292                 }
293                 lastIdx = idx;
294 
295                 entry.iface = parsed.get(KEY_IFACE);
296                 entry.uid = getParsedInt(parsed, KEY_UID);
297                 entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
298                 entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
299                 entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
300                 entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
301                 entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
302                 entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
303 
304                 if (limitUid == UID_ALL || limitUid == entry.uid) {
305                     stats.addValues(entry);
306                 }
307             }
308         } catch (NullPointerException e) {
309             throw new IllegalStateException("problem parsing line: " + line, e);
310         } catch (NumberFormatException e) {
311             throw new IllegalStateException("problem parsing line: " + line, e);
312         } catch (IOException e) {
313             throw new IllegalStateException("problem parsing line: " + line, e);
314         } finally {
315             IoUtils.closeQuietly(reader);
316         }
317         return stats;
318     }
319 
getParsedInt(HashMap<String, String> parsed, String key)320     private static int getParsedInt(HashMap<String, String> parsed, String key) {
321         final String value = parsed.get(key);
322         return value != null ? Integer.parseInt(value) : 0;
323     }
324 
getParsedLong(HashMap<String, String> parsed, String key)325     private static long getParsedLong(HashMap<String, String> parsed, String key) {
326         final String value = parsed.get(key);
327         return value != null ? Long.parseLong(value) : 0;
328     }
329 
330     /**
331      * Split given line into {@link ArrayList}.
332      */
splitLine(String line, ArrayList<String> outSplit)333     private static void splitLine(String line, ArrayList<String> outSplit) {
334         outSplit.clear();
335 
336         final StringTokenizer t = new StringTokenizer(line, " \t\n\r\f:");
337         while (t.hasMoreTokens()) {
338             outSplit.add(t.nextToken());
339         }
340     }
341 
342     /**
343      * Zip the two given {@link ArrayList} as key and value pairs into
344      * {@link HashMap}.
345      */
parseLine( ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed)346     private static void parseLine(
347             ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
348         outParsed.clear();
349 
350         final int size = Math.min(keys.size(), values.size());
351         for (int i = 0; i < size; i++) {
352             outParsed.put(keys.get(i), values.get(i));
353         }
354     }
355 
356     /**
357      * Utility method to read a single plain-text {@link Long} from the given
358      * {@link File}, usually from a {@code /proc/} filesystem.
359      */
readSingleLongFromFile(File file)360     private static long readSingleLongFromFile(File file) {
361         try {
362             final byte[] buffer = IoUtils.readFileAsByteArray(file.toString());
363             return Long.parseLong(new String(buffer).trim());
364         } catch (NumberFormatException e) {
365             return -1;
366         } catch (IOException e) {
367             return -1;
368         }
369     }
370 
371     /**
372      * Wrapper for {@link File#list()} that returns empty array instead of
373      * {@code null}.
374      */
fileListWithoutNull(File file)375     private static String[] fileListWithoutNull(File file) {
376         final String[] list = file.list();
377         return list != null ? list : new String[0];
378     }
379 }
380