Skip to content

Instantly share code, notes, and snippets.

@wakim
Last active September 4, 2017 21:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wakim/3f07fb3c2956345e725d9d11c53060eb to your computer and use it in GitHub Desktop.
Save wakim/3f07fb3c2956345e725d9d11c53060eb to your computer and use it in GitHub Desktop.
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)
}
}
}
}
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