For important background information, read Extra-ordinary Networking before reading this.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Network Interface Statistics
One FAQ when it comes to network interfaces is “How do I get network interface statistics?” There are numerous variants of this:
-
Some folks ask about specific network interfaces: “How do I get cellular data usage?”
-
Some folks are interested in per-app statistics: “How do I get cellular data usage statistics for each app?” or “How do I get cellular data usage statistics for my app?”
-
Some folks only care about recent statistics: “How can I tell how much network data this operation generated?”
-
Some folks care about usage across restarts: “How do I get the cellular data usage shown in the Settings app on iOS?”
Most of these questions have no supported answers. However, there are a some supported techniques available. This post explains those techniques, and their limitations.
MetricKit
To get network usage for your app, use MetricKit. Specifically, look at the MXNetworkTransferMetric
payload.
MetricKit has a number of design points:
-
You only get metrics for your app.
-
You get metrics periodically; you can’t monitor these statistics in real time.
Legacy Techniques
The getifaddrs
routine returns rudimentary network interface statistics. See the getifaddrs
man page and the struct if_data
definition in <net/if_var.h>
. Here’s an example of how you might use this:
func legacyNetworkInterfaceStatisticsForInterfaceNamed(_ name: String) -> LegacyNetworkInterfaceStatistics? {
var addrList: UnsafeMutablePointer<ifaddrs>? = nil
let err = getifaddrs(&addrList)
// In theory we could check `errno` here but, honestly, what are gonna
// do with that info?
guard
err >= 0,
let first = addrList
else { return nil }
defer { freeifaddrs(addrList) }
return sequence(first: first, next: { $0.pointee.ifa_next })
.compactMap { addr in
guard
let nameC = addr.pointee.ifa_name,
name == String(cString: nameC),
let sa = addr.pointee.ifa_addr,
sa.pointee.sa_family == AF_LINK,
let data = addr.pointee.ifa_data
else { return nil }
return LegacyNetworkInterfaceStatistics(if_data: data.assumingMemoryBound(to: if_data.self).pointee)
}
.first
}
struct LegacyNetworkInterfaceStatistics {
var packetsIn: UInt32 // ifi_ipackets
var packetsOut: UInt32 // ifi_opackets
var bytesIn: UInt32 // ifi_ibytes
var bytesOut: UInt32 // ifi_obytes
}
extension LegacyNetworkInterfaceStatistics {
init(if_data ifData: if_data) {
self.packetsIn = ifData.ifi_ipackets
self.packetsOut = ifData.ifi_opackets
self.bytesIn = ifData.ifi_ibytes
self.bytesOut = ifData.ifi_obytes
}
}
This is a legacy interface. macOS inherited this API from its ancestor platforms, and iOS inherited it from macOS. That history means that the API has significant limitations:
-
The counters reset each time the device restarts.
-
The counters are represented as a
UInt32
, and so wrap at 4 GiB [1].
Due to its legacy nature, there’s little point filing an enhancement request against this API.
[1] The <net/if_var.h>
header defines an if_data64
structure, but there’s no supported way to get that value on Apple platforms.
Limitations
When it comes to network interface statistics, certain tasks have no supported solutions:
-
Getting per-app statistics
-
Getting whole device statistics that persist across a restart
-
Getting real-time statistics for your app that persist across a restart
If you need one of these features, feel free to file an enhancement request for it. In your ER:
-
Be specific about the platforms you need this on [1].
-
Make sure that your request is aligned with that platforms privacy constraints. For example, iOS isolates your app from other apps, so you’re unlikely to get an API that returns per-app statistics for all apps on the system.
-
Supply a clear justification for why this is important to your product.
[1] If it’s macOS, be clear about:
-
Whether your app is sandboxed or not.
-
Whether it’s a Mac Catalyst.
-
Or running via iOS Apps on Mac.