diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift index 791e98dc..84dd70ab 100644 --- a/Stats/Modules/Battery/BatteryReader.swift +++ b/Stats/Modules/Battery/BatteryReader.swift @@ -33,6 +33,7 @@ class BatteryReader: Reader { public var updateInterval: Observable = Observable(0) public var usage: Observable = Observable(BatteryUsage()) public var updateTimer: Timer! + public var availableAdditional: Bool = false private var service: io_connect_t = 0 private var internalChecked: Bool = false diff --git a/Stats/Modules/CPU/CPUReader.swift b/Stats/Modules/CPU/CPUReader.swift index 0e425e99..2df108e9 100644 --- a/Stats/Modules/CPU/CPUReader.swift +++ b/Stats/Modules/CPU/CPUReader.swift @@ -7,6 +7,7 @@ // import Foundation +import Cocoa struct CPUUsage { var value: Double = 0 @@ -27,7 +28,9 @@ class CPUReader: Reader { public var usage: Observable = Observable(CPUUsage()) public var processes: Observable<[TopProcess]> = Observable([TopProcess]()) public var available: Bool = true + public var availableAdditional: Bool = true public var updateTimer: Timer! + public var updateAdditionalTimer: Timer! public var perCoreMode: Bool = false public var hyperthreading: Bool = false @@ -39,17 +42,10 @@ class CPUReader: Reader { private let CPUUsageLock: NSLock = NSLock() private var loadPrevious = host_cpu_load_info() - private var topProcess: Process = Process() - private var pipe: Pipe = Pipe() - init() { let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ] self.value = Observable([]) - self.topProcess.launchPath = "/usr/bin/top" - self.topProcess.arguments = ["-s", "3", "-o", "cpu", "-n", "5", "-stats", "pid,command,cpu"] - self.topProcess.standardOutput = pipe - mibKeys.withUnsafeBufferPointer() { mib in var sizeOfNumCPUs: size_t = MemoryLayout.size let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0) @@ -71,45 +67,6 @@ class CPUReader: Reader { return } updateTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.updateInterval.value), target: self, selector: #selector(read), userInfo: nil, repeats: true) - - if topProcess.isRunning { - return - } - self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() - - NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: self.pipe.fileHandleForReading , queue: nil) { _ -> Void in - defer { - self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() - } - - let output = self.pipe.fileHandleForReading.availableData - if output.isEmpty { - return - } - - let outputString = String(data: output, encoding: String.Encoding.utf8) ?? "" - var processes: [TopProcess] = [] - outputString.enumerateLines { (line, stop) -> () in - if line.matches("^\\d+ + .+ +\\d+.\\d *$") { - var str = line.trimmingCharacters(in: .whitespaces) - let pidString = str.findAndCrop(pattern: "^\\d+") - let usageString = str.findAndCrop(pattern: " [0-9]+\\.[0-9]*$") - let command = str.trimmingCharacters(in: .whitespaces) - - let pid = Int(pidString) ?? 0 - let usage = Double(usageString) ?? 0 - let process = TopProcess(pid: pid, command: command, usage: usage) - processes.append(process) - } - } - self.processes << processes - } - - do { - try topProcess.run() - } catch let error { - print(error) - } } func stop() { @@ -118,7 +75,70 @@ class CPUReader: Reader { } updateTimer.invalidate() updateTimer = nil - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil) + } + + func startAdditional() { + readAdditional() + + if updateAdditionalTimer != nil { + return + } + updateAdditionalTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.updateInterval.value), target: self, selector: #selector(readAdditional), userInfo: nil, repeats: true) + } + + func stopAdditional() { + if updateAdditionalTimer == nil { + return + } + updateAdditionalTimer.invalidate() + updateAdditionalTimer = nil + } + + @objc func readAdditional() { + let task = Process() + task.launchPath = "/bin/ps" + task.arguments = ["-Aceo pid,pcpu,comm", "-r"] + + let outputPipe = Pipe() + let errorPipe = Pipe() + + task.standardOutput = outputPipe + task.standardError = errorPipe + + do { + try task.run() + } catch let error { + print(error) + } + + 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 + } + + var index = 0 + var processes: [TopProcess] = [] + output.enumerateLines { (line, stop) -> () in + if index != 0 { + var str = line.trimmingCharacters(in: .whitespaces) + let pidString = str.findAndCrop(pattern: "^\\d+") + let usageString = str.findAndCrop(pattern: "^[0-9]+\\.[0-9]* ") + let command = str.trimmingCharacters(in: .whitespaces) + + let pid = Int(pidString) ?? 0 + let usage = Double(usageString) ?? 0 + + processes.append(TopProcess(pid: pid, command: command, usage: usage)) + } + + if index == 5 { stop = true } + index += 1 + } + self.processes << processes } @objc func read() { diff --git a/Stats/Modules/Disk/DiskReader.swift b/Stats/Modules/Disk/DiskReader.swift index aad302a7..048eabb6 100644 --- a/Stats/Modules/Disk/DiskReader.swift +++ b/Stats/Modules/Disk/DiskReader.swift @@ -12,6 +12,7 @@ class DiskReader: Reader { public var value: Observable<[Double]>! public var updateInterval: Observable = Observable(0) public var available: Bool = true + public var availableAdditional: Bool = false public var updateTimer: Timer! init() { diff --git a/Stats/Modules/Memory/MemoryReader.swift b/Stats/Modules/Memory/MemoryReader.swift index f979bc13..1b239547 100644 --- a/Stats/Modules/Memory/MemoryReader.swift +++ b/Stats/Modules/Memory/MemoryReader.swift @@ -20,21 +20,16 @@ class MemoryReader: Reader { public var usage: Observable = Observable(MemoryUsage()) public var processes: Observable<[TopProcess]> = Observable([TopProcess]()) public var available: Bool = true + public var availableAdditional: Bool = true public var updateTimer: Timer! + public var updateAdditionalTimer: Timer! public var totalSize: Float - private var topProcess: Process = Process() - private var pipe: Pipe = Pipe() - init() { self.value = Observable([]) var stats = host_basic_info() var count = UInt32(MemoryLayout.size / MemoryLayout.size) - self.topProcess.launchPath = "/usr/bin/top" - self.topProcess.arguments = ["-s", "3", "-o", "mem", "-n", "5", "-stats", "pid,command,mem"] - self.topProcess.standardOutput = pipe - let kerr: kern_return_t = withUnsafeMutablePointer(to: &stats) { $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { host_info(mach_host_self(), HOST_BASIC_INFO, $0, &count) @@ -62,52 +57,6 @@ class MemoryReader: Reader { return } updateTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.updateInterval.value), target: self, selector: #selector(read), userInfo: nil, repeats: true) - - if topProcess.isRunning { - return - } - self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() - - NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: self.pipe.fileHandleForReading , queue: nil) { _ -> Void in - defer { - self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() - } - - let output = self.pipe.fileHandleForReading.availableData - if output.isEmpty { - return - } - - let outputString = String(data: output, encoding: String.Encoding.utf8) ?? "" - var processes: [TopProcess] = [] - outputString.enumerateLines { (line, stop) -> () in - if line.matches("^\\d+ + .+ +\\d+.\\d[M\\+\\-]+ *$") { - var str = line.trimmingCharacters(in: .whitespaces) - let pidString = str.findAndCrop(pattern: "^\\d+") - let usageString = str.findAndCrop(pattern: " [0-9]+M(\\+|\\-)*$") - var command = str.trimmingCharacters(in: .whitespaces) - - if let regex = try? NSRegularExpression(pattern: " (\\+|\\-)*$", options: .caseInsensitive) { - command = regex.stringByReplacingMatches(in: command, options: [], range: NSRange(location: 0, length: command.count), withTemplate: "") - } - - let pid = Int(pidString) ?? 0 - guard let usage = Double(usageString.filter("01234567890.".contains)) else { - return - } - let process = TopProcess(pid: pid, command: command, usage: usage * Double(1024 * 1024)) - processes.append(process) - } - } - - self.processes << processes - } - - do { - try topProcess.run() - } catch let error { - print(error) - } } func stop() { @@ -116,7 +65,76 @@ class MemoryReader: Reader { } updateTimer.invalidate() updateTimer = nil - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil) + } + + func startAdditional() { + if self.processes.value.isEmpty { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.readAdditional() + } + } + + if updateAdditionalTimer != nil { + return + } + updateAdditionalTimer = Timer.scheduledTimer(timeInterval: TimeInterval(self.updateInterval.value), target: self, selector: #selector(readAdditional), userInfo: nil, repeats: true) + } + + func stopAdditional() { + if updateAdditionalTimer == nil { + return + } + updateAdditionalTimer.invalidate() + updateAdditionalTimer = nil + } + + @objc func readAdditional() { + let task = Process() + task.launchPath = "/usr/bin/top" + task.arguments = ["-l", "1", "-o", "mem", "-n", "5", "-stats", "pid,command,mem"] + + let outputPipe = Pipe() + let errorPipe = Pipe() + + task.standardOutput = outputPipe + task.standardError = errorPipe + + do { + try task.run() + } catch let error { + print(error) + } + + 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 + } + + var processes: [TopProcess] = [] + output.enumerateLines { (line, stop) -> () in + if line.matches("^\\d+ + .+ +\\d+.\\d[M\\+\\-]+ *$") { + var str = line.trimmingCharacters(in: .whitespaces) + let pidString = str.findAndCrop(pattern: "^\\d+") + let usageString = str.findAndCrop(pattern: " [0-9]+M(\\+|\\-)*$") + var command = str.trimmingCharacters(in: .whitespaces) + + if let regex = try? NSRegularExpression(pattern: " (\\+|\\-)*$", options: .caseInsensitive) { + command = regex.stringByReplacingMatches(in: command, options: [], range: NSRange(location: 0, length: command.count), withTemplate: "") + } + + let pid = Int(pidString) ?? 0 + guard let usage = Double(usageString.filter("01234567890.".contains)) else { + return + } + let process = TopProcess(pid: pid, command: command, usage: usage * Double(1024 * 1024)) + processes.append(process) + } + } + self.processes << processes } @objc func read() { diff --git a/Stats/Modules/Network/NetworkReader.swift b/Stats/Modules/Network/NetworkReader.swift index 4e205a6e..6e442fb0 100644 --- a/Stats/Modules/Network/NetworkReader.swift +++ b/Stats/Modules/Network/NetworkReader.swift @@ -12,6 +12,7 @@ class NetworkReader: Reader { public var value: Observable<[Double]>! public var updateInterval: Observable = Observable(0) public var available: Bool = true + public var availableAdditional: Bool = false public var updateTimer: Timer! private var netProcess: Process = Process() diff --git a/Stats/Modules/Reader.swift b/Stats/Modules/Reader.swift index fe26bbf9..4d42f8fb 100644 --- a/Stats/Modules/Reader.swift +++ b/Stats/Modules/Reader.swift @@ -13,9 +13,17 @@ protocol Reader { var updateInterval: Observable { get } var available: Bool { get } + var availableAdditional: Bool { get } var updateTimer: Timer! { get set } func start() func stop() - func read() + + func startAdditional() + func stopAdditional() +} + +extension Reader { + func startAdditional() {} + func stopAdditional() {} } diff --git a/Stats/Views/PopupViewController.swift b/Stats/Views/PopupViewController.swift index 94535fe8..ba128f6e 100644 --- a/Stats/Views/PopupViewController.swift +++ b/Stats/Views/PopupViewController.swift @@ -52,6 +52,22 @@ class MainViewController: NSViewController { } } + override func viewWillAppear() { + for module in modules.value { + if module.tabAvailable && module.available.value && module.active.value && module.reader.availableAdditional { + module.reader.startAdditional() + } + } + } + + override func viewWillDisappear() { + for module in modules.value { + if module.tabAvailable && module.available.value && module.active.value && module.reader.availableAdditional { + module.reader.stopAdditional() + } + } + } + func makeHeader() { var list: [String] = [] for module in modules.value {