NetworkStatsFactory.java revision 1059c3c30ad96a15695c1a92ae8896e078a6309f
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
17package com.android.internal.net;
18
19import static android.net.NetworkStats.SET_DEFAULT;
20import static android.net.NetworkStats.TAG_NONE;
21import static android.net.NetworkStats.UID_ALL;
22import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
23
24import android.net.NetworkStats;
25import android.os.SystemClock;
26import android.util.Slog;
27
28import com.google.android.collect.Lists;
29import com.google.android.collect.Maps;
30import com.google.android.collect.Sets;
31
32import java.io.BufferedReader;
33import java.io.File;
34import java.io.FileReader;
35import java.io.IOException;
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.StringTokenizer;
40
41import libcore.io.IoUtils;
42
43/**
44 * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
45 * files as needed.
46 */
47public 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
79    public NetworkStatsFactory() {
80        this(new File("/proc/"));
81    }
82
83    // @VisibleForTesting
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     */
98    public NetworkStats readNetworkStatsSummary() throws IllegalStateException {
99        if (mStatsXtIfaceAll.exists()) {
100            return readNetworkStatsSummarySingleFile();
101        } else {
102            return readNetworkStatsSummaryMultipleFiles();
103        }
104    }
105
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
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
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     */
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
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
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     */
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     */
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     */
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     */
375    private static String[] fileListWithoutNull(File file) {
376        final String[] list = file.list();
377        return list != null ? list : new String[0];
378    }
379}
380