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