From 6b5830208960d413c03fbe0c61d2457b18eb2b98 Mon Sep 17 00:00:00 2001 From: Ahmed Osama Date: Thu, 26 Nov 2020 17:02:04 +0200 Subject: [PATCH] Fix: No download speed (#157) * Implemented alternative approach to get download speed * Early check * Reset data on interface change * Trigger alternative approach manually * Fix --- Modules/Net/readers.swift | 133 +++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 37 deletions(-) diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift index f1586398..9eb0de19 100644 --- a/Modules/Net/readers.swift +++ b/Modules/Net/readers.swift @@ -24,11 +24,15 @@ struct ipResponse: Decodable { } internal class UsageReader: Reader { + typealias BandwidthUsage = (upload: Int64, download: Int64) + public var store: UnsafePointer? = nil private var reachability: Reachability? = nil private var usage: Network_Usage = Network_Usage() + private var shouldReportSelectedInterfaceBandwidthOnly = true + private var primaryInterface: String { get { if let global = SCDynamicStoreCopyValue(nil, "State:/Network/Global/IPv4" as CFString), let name = global["PrimaryInterface"] as? String { @@ -69,51 +73,23 @@ internal class UsageReader: Reader { } public override func read() { - var interfaceAddresses: UnsafeMutablePointer? = nil - var upload: Int64 = 0 - var download: Int64 = 0 - guard getifaddrs(&interfaceAddresses) == 0 else { return } - - var pointer = interfaceAddresses - while pointer != nil { - defer { pointer = pointer?.pointee.ifa_next } - - if String(cString: pointer!.pointee.ifa_name) != self.interfaceID { - continue - } - - if let ip = getLocalIP(pointer!), self.usage.laddr != ip { - self.usage.laddr = ip - } - - if let info = getBytesInfo(pointer!) { - upload += info.upload - download += info.download - } - } - freeifaddrs(interfaceAddresses) - - if self.usage.upload != 0 { - self.usage.upload = upload - self.usage.upload - } - if self.usage.download != 0 { - self.usage.download = download - self.usage.download + let currentUsage: BandwidthUsage + if shouldReportSelectedInterfaceBandwidthOnly { + currentUsage = interfaceBandwidthUsage() + } else { + currentUsage = allProcessesBandwidthUsage() } - if self.usage.upload < 0 { - self.usage.upload = 0 - } - if self.usage.download < 0 { - self.usage.download = 0 - } + self.usage.upload = max(currentUsage.upload - self.usage.upload, 0) + self.usage.download = max(currentUsage.download - self.usage.download, 0) self.usage.totalUpload += self.usage.upload self.usage.totalDownload += self.usage.download self.callback(self.usage) - self.usage.upload = upload - self.usage.download = download + self.usage.upload = currentUsage.upload + self.usage.download = currentUsage.download } public func getDetails() { @@ -196,6 +172,89 @@ internal class UsageReader: Reader { let data: UnsafeMutablePointer? = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self) return (upload: Int64(data?.pointee.ifi_obytes ?? 0), download: Int64(data?.pointee.ifi_ibytes ?? 0)) } + + private func interfaceBandwidthUsage() -> BandwidthUsage { + var interfaceAddresses: UnsafeMutablePointer? = nil + var totalUpload: Int64 = 0 + var totalDownload: Int64 = 0 + guard getifaddrs(&interfaceAddresses) == 0 else { + return (0, 0) + } + + var pointer = interfaceAddresses + while pointer != nil { + defer { pointer = pointer?.pointee.ifa_next } + + if String(cString: pointer!.pointee.ifa_name) != self.interfaceID { + continue + } + + if let ip = getLocalIP(pointer!), self.usage.laddr != ip { + self.usage.laddr = ip + } + + if let info = getBytesInfo(pointer!) { + totalUpload += info.upload + totalDownload += info.download + } + } + freeifaddrs(interfaceAddresses) + + return (totalUpload, totalDownload) + } + + private func allProcessesBandwidthUsage() -> BandwidthUsage { + let task = Process() + task.launchPath = "/usr/bin/nettop" + task.arguments = ["-P", "-L", "1", "-k", "time,interface,state,rx_dupe,rx_ooo,re-tx,rtt_avg,rcvsize,tx_win,tc_class,tc_mgt,cc_algo,P,C,R,W,arch"] + + let outputPipe = Pipe() + let errorPipe = Pipe() + + task.standardOutput = outputPipe + task.standardError = errorPipe + + do { + try task.run() + } catch let error { + print(error) + return (0, 0) + } + + let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() + let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile() + let output = String(decoding: outputData, as: UTF8.self) + _ = String(decoding: errorData, as: UTF8.self) + + if output.isEmpty { + return (0, 0) + } + + var totalUpload: Int64 = 0 + var totalDownload: Int64 = 0 + var firstLine = false + output.enumerateLines { (line, _) -> () in + if !firstLine { + firstLine = true + return + } + + let parsedLine = line.split(separator: ",") + guard parsedLine.count >= 3 else { + return + } + + if let download = Int(parsedLine[1]) { + totalDownload += Int64(download) + } + if let upload = Int(parsedLine[2]) { + totalUpload += Int64(upload) + } + } + + return (totalUpload, totalDownload) + } + } public class ProcessReader: Reader<[Network_Process]> {