Last active
September 4, 2017 21:21
-
-
Save wakim/3f07fb3c2956345e725d9d11c53060eb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package br.com.wozapp.networkstatistics | |
import android.os.StrictMode | |
import br.com.wozapp.data.model.AppStatistics | |
import br.com.wozapp.data.model.TotalStatistics | |
class NetworkStatistics private constructor() { | |
companion object { | |
@JvmStatic | |
val INSTANCE = NetworkStatistics() | |
const val UID_ALL = -2 | |
} | |
val lock = Any() | |
fun getTotalInBytes(): TotalStatistics { | |
return synchronized(lock) { | |
// reference: https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/java/com/android/internal/net/NetworkStatsFactory.java | |
val savedPolicy = StrictMode.allowThreadDiskReads() | |
try { | |
totalFromDetailedFile()?.also { return@synchronized it } | |
totalFromSummaryFile()?.also { return@synchronized it } | |
totalFromFiles()?.also { return@synchronized it } | |
return totalFromApi() | |
} finally { | |
StrictMode.setThreadPolicy(savedPolicy) | |
} | |
} | |
} | |
fun getMobileTrafficByUid(): Map<Int, Long> { | |
return synchronized(lock) { | |
// reference: https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/java/com/android/internal/net/NetworkStatsFactory.java | |
val savedPolicy = StrictMode.allowThreadDiskReads() | |
try { | |
fromDetailSummaryFile(AppStatistics::mobileAccumulated)?.also { return@synchronized it } | |
fromTrafficFiles()?.also { return@synchronized it } | |
return fromTrafficApi() | |
} finally { | |
StrictMode.setThreadPolicy(savedPolicy) | |
} | |
} | |
} | |
fun getWifiTrafficByUid(): Map<Int, Long> { | |
return synchronized(lock) { | |
// reference: https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/java/com/android/internal/net/NetworkStatsFactory.java | |
val savedPolicy = StrictMode.allowThreadDiskReads() | |
try { | |
fromDetailSummaryFile(AppStatistics::wifiAccumulated)?.also { return@synchronized it } | |
fromTrafficFiles()?.also { return@synchronized it } | |
return fromTrafficApi() | |
} finally { | |
StrictMode.setThreadPolicy(savedPolicy) | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package br.com.wozapp.networkstatistics | |
import android.annotation.SuppressLint | |
import android.net.TrafficStats | |
import br.com.wozapp.AppLog | |
import br.com.wozapp.data.model.AppStatistics | |
import br.com.wozapp.data.model.TotalStatistics | |
import br.com.wozapp.extensions.asMapInverse | |
import java.io.File | |
import java.util.* | |
fun File.readRxBytes(): Long = File(this, "rx_bytes").readLong() | |
fun File.readTxBytes(): Long = File(this, "tx_bytes").readLong() | |
fun File.readRxBytesFromUid(): Long = File(this, "tcp_rcv").readLong() | |
fun File.readTxBytesFromUid(): Long = File(this, "tcp_snd").readLong() | |
fun File.readLong(): Long = readLines().firstOrNull()?.toLong() ?: -1L | |
fun AppStatistics.incrementFromDetailedLine(split: List<String>): AppStatistics { | |
val iface = split[1] | |
val rxTcp = split[9].toLong() | |
val rxUdp = split[11].toLong() | |
val rxOthers = split[13].toLong() | |
val txTcp = split[15].toLong() | |
val txUdp = split[17].toLong() | |
val txOthers = split[20].toLong() | |
val sum = rxTcp + txTcp + rxUdp + txUdp + rxOthers + txOthers | |
// background data (cnt_set is 0) | |
// foreground data (cnt_set is 1) | |
// foreground data is correct, but background data is slightly greater than data usage reports... | |
if (iface.contains("rmnet")) { | |
mobileAccumulated += sum | |
} else if (iface.contains("wlan")) { | |
wifiAccumulated += sum | |
} | |
return this | |
} | |
fun AppStatistics.incrementFromSummaryLine(split: List<String>): AppStatistics { | |
val iface = split[0] | |
val rx = split[1].toLong() | |
val tx = split[3].toLong() | |
val sum = rx + tx | |
// background data (cnt_set is 0) | |
// foreground data (cnt_set is 1) | |
// foreground data is correct, but background data is slightly greater than data usage reports... | |
if (iface.contains("rmnet")) { | |
mobileAccumulated += sum | |
} else if (iface.contains("wlan")) { | |
wifiAccumulated += sum | |
} | |
return this | |
} | |
fun totalFromDetailedFile(): TotalStatistics? { | |
if (!hasReadableNetworkDetailedFile()) return null | |
var wifi = 0L | |
var mobile = 0L | |
val time = System.currentTimeMillis() | |
val parsed = parseNetworkDetailedFile() | |
if (parsed.isEmpty()) return null | |
parsed.forEach { | |
wifi += it.wifiAccumulated | |
mobile += it.mobileAccumulated | |
} | |
AppLog.d("Using total network detailed file") | |
return TotalStatistics(wifi, mobile, time, time) | |
} | |
fun totalFromSummaryFile(): TotalStatistics? { | |
val time = System.currentTimeMillis() | |
if (!hasReadableNetworkSummaryFile()) return null | |
var wifi = 0L | |
var mobile = 0L | |
val parsed = parseNetworkSummaryFile() | |
if (parsed.isEmpty()) return null | |
parsed.forEach { | |
wifi += it.wifiAccumulated | |
mobile += it.mobileAccumulated | |
} | |
AppLog.d("Using total network summary file") | |
return TotalStatistics(wifi, mobile, time, time) | |
} | |
fun totalFromFiles(): TotalStatistics? { | |
val mobile = getAllMobileTrafficFromFiles() | |
val wifi = getAllWifiTrafficFromFiles() | |
val time = System.currentTimeMillis() | |
if (mobile < 0L && wifi < 0L) return null | |
AppLog.d("Using total network files") | |
return TotalStatistics(wifi, mobile, time, time) | |
} | |
fun totalFromApi(): TotalStatistics { | |
val mobile = getAllMobileTrafficFromApi() | |
val wifi = getAllWifiTrafficFromApi() | |
val time = System.currentTimeMillis() | |
AppLog.d("Using total traffic stats api") | |
return TotalStatistics(wifi, mobile, time, time) | |
} | |
fun hasReadableNetworkDetailedFile() = | |
File("/proc/net/xt_qtaguid/stats").run { exists() && canRead() } | |
fun hasReadableNetworkSummaryFile() = | |
File("/proc/net/xt_qtaguid/iface_stat_fmt").run { exists() && canRead() } | |
@SuppressLint("UseSparseArrays") | |
fun parseNetworkDetailedFile(): List<AppStatistics> { | |
val file = File("/proc/net/xt_qtaguid/stats") | |
val map = HashMap<Int, AppStatistics>() | |
val time = Date().time | |
file.readLines() | |
.forEachIndexed { i, line -> | |
if (i > 0) { | |
val split = line.split(" ") | |
val uid = split[3].toInt() | |
val stats = map.getOrPut(uid) { AppStatistics(uid, timestamp = time) } | |
stats.incrementFromDetailedLine(split) | |
} | |
} | |
return map.map { it.value } | |
} | |
@SuppressLint("UseSparseArrays") | |
fun parseNetworkSummaryFile(): List<AppStatistics> { | |
val file = File("/proc/net/xt_qtaguid/iface_stat_fmt") | |
val time = Date().time | |
val allStats = AppStatistics(NetworkStatistics.UID_ALL, timestamp = time) | |
file.readLines() | |
.forEachIndexed { i, line -> | |
if (i == 0) return@forEachIndexed | |
val split = line.split(" ") | |
allStats.incrementFromSummaryLine(split) | |
} | |
return arrayListOf(allStats) | |
} | |
fun getAllMobileTrafficFromApi() = TrafficStats.getMobileRxBytes() + TrafficStats.getMobileTxBytes() | |
fun getAllWifiTrafficFromApi() = TrafficStats.getTotalRxBytes() + TrafficStats.getMobileTxBytes() | |
fun getAllMobileTrafficFromFiles() = sumTrafficFromFiles(getMobileInterfaces()) | |
fun getAllWifiTrafficFromFiles(): Long = sumTrafficFromFiles(getWifiInterfaces()) | |
fun sumTrafficFromFiles(list: List<File>): Long = | |
list.map { | |
val file = File(it, "statistics") | |
file.readRxBytes() + file.readTxBytes() | |
} | |
.sum() | |
fun getMobileInterfaces(): List<File> = | |
File("/sys/class/net/").listFiles().filter { it.name.contains("rmnet") } | |
fun getWifiInterfaces(): List<File> = | |
File("/sys/class/net/").listFiles().filter { it.name.contains("wlan") } | |
fun fromDetailSummaryFile(accessor: (AppStatistics) -> Long): Map<Int, Long>? { | |
if (!hasReadableNetworkDetailedFile()) return null | |
AppLog.d("Using per app network summary file") | |
val parsed = parseNetworkDetailedFile() | |
if (parsed.isEmpty()) return null | |
return parsed | |
.map { it.uid to accessor.invoke(it) } | |
.toMap() | |
} | |
fun fromTrafficFiles(): Map<Int, Long>? { | |
val mobile = getAllUidTrafficFromFiles() | |
val sum = mobile.values.firstOrNull { it > 0 } ?: -1L | |
if (sum < 0L) return null | |
AppLog.d("Using mobile network files") | |
return mobile | |
} | |
fun getAllUidTrafficFromFiles(): Map<Int, Long> = | |
getAllAvailableUidsFromFiles() | |
.asMapInverse { | |
val file = File("/proc/uid_stat/$this") | |
file.readRxBytesFromUid() + file.readTxBytesFromUid() | |
} | |
@SuppressLint("UseSparseArrays") | |
fun fromTrafficApi(): Map<Int, Long> { | |
val map = HashMap<Int, Long>() | |
getAllAvailableUidsFromFiles() | |
.forEach { | |
val consume = TrafficStats.getUidRxBytes(it) + TrafficStats.getUidTxBytes(it) | |
if (consume >= 0) { | |
map.put(it, consume) | |
} | |
} | |
return map | |
} | |
fun getAllAvailableUidsFromFiles(): List<Int> { | |
val dir = File("/proc/uid_stat/") | |
val children = dir.list() ?: emptyArray() | |
return children | |
.map { Integer.parseInt(it) } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment