diff --git a/ModuleKit/module.swift b/ModuleKit/module.swift index f61798c5..2032f8bc 100644 --- a/ModuleKit/module.swift +++ b/ModuleKit/module.swift @@ -277,13 +277,15 @@ open class Module: Module_p { // call when popup appear/disappear private func visibilityCallback(_ state: Bool) { - self.readers.filter{ $0.popup }.forEach { (reader: Reader_p) in - if state { - reader.unlock() - reader.start() - } else { - reader.stop() - reader.lock() + DispatchQueue.global(qos: .background).async { + self.readers.filter{ $0.popup }.forEach { (reader: Reader_p) in + if state { + reader.unlock() + reader.start() + } else { + reader.stop() + reader.lock() + } } } } diff --git a/ModuleKit/popup.swift b/ModuleKit/popup.swift index bedfff32..b86d0dd0 100644 --- a/ModuleKit/popup.swift +++ b/ModuleKit/popup.swift @@ -233,3 +233,49 @@ internal class HeaderView: NSView { NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": self.titleView?.stringValue ?? ""]) } } + +public class ProcessView: NSView { + public var width: CGFloat { + get { return 0 } + set { + self.setFrameSize(NSSize(width: newValue, height: self.frame.height)) + } + } + + public var label: String { + get { return "" } + set { + self.labelView?.stringValue = newValue + } + } + public var value: String { + get { return "" } + set { + self.valueView?.stringValue = newValue + } + } + + private var labelView: LabelField? = nil + private var valueView: ValueField? = nil + + public init(_ n: CGFloat) { + super.init(frame: NSRect(x: 0, y: n*22, width: Constants.Popup.width, height: 16)) + + let rowView: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 16)) + + let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: 0.5, width: rowView.frame.width - 70, height: 15), "") + let valueView: ValueField = ValueField(frame: NSRect(x: rowView.frame.width - 70, y: 0, width: 70, height: 16), "") + + rowView.addSubview(labelView) + rowView.addSubview(valueView) + + self.labelView = labelView + self.valueView = valueView + + self.addSubview(rowView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/ModuleKit/reader.swift b/ModuleKit/reader.swift index 75d01154..b141cb36 100644 --- a/ModuleKit/reader.swift +++ b/ModuleKit/reader.swift @@ -122,7 +122,12 @@ open class Reader: ReaderInternal_p { open func terminate() {} open func start() { - if !self.enabled || (self.popup && self.locked) { + if !self.enabled { + return + } else if self.popup && self.locked { + if !self.ready { + self.read() + } return } diff --git a/Modules/CPU/main.swift b/Modules/CPU/main.swift index ad96d99d..87c5a41b 100644 --- a/Modules/CPU/main.swift +++ b/Modules/CPU/main.swift @@ -57,7 +57,6 @@ public class CPU: Module { self.loadReader?.store = store self.processReader = ProcessReader() - self.processReader?.store = store self.settingsView.callback = { [unowned self] in self.loadReader?.read() diff --git a/Modules/CPU/popup.swift b/Modules/CPU/popup.swift index c79a8272..f37898eb 100644 --- a/Modules/CPU/popup.swift +++ b/Modules/CPU/popup.swift @@ -32,28 +32,16 @@ internal class Popup: NSView { private var ready: Bool = false private var processes: [ProcessView] = [] - private var processesView: NSView? = nil - - private var topProcessState: Bool { - get { - return self.store.pointee.bool(key: "\(self.title)_topProcesses", defaultValue: false) - } - } public init(_ title: String, store: UnsafePointer) { self.store = store self.title = title - let topProcessState = store.pointee.bool(key: "\(title)_topProcesses", defaultValue: false) - let height = topProcessState ? dashboardHeight + (Constants.Popup.separatorHeight*2) + detailsHeight + processesHeight : dashboardHeight + Constants.Popup.separatorHeight + detailsHeight - - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: height)) + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + (Constants.Popup.separatorHeight*2) + detailsHeight + processesHeight)) initDashboard() initDetails() - if topProcessState { - self.initProcesses() - } + initProcesses() } required init?(coder: NSCoder) { @@ -102,16 +90,15 @@ internal class Popup: NSView { let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.processesHeight)) - processes.append(ProcessView(0)) - processes.append(ProcessView(1)) - processes.append(ProcessView(2)) - processes.append(ProcessView(3)) - processes.append(ProcessView(4)) + self.processes.append(ProcessView(0)) + self.processes.append(ProcessView(1)) + self.processes.append(ProcessView(2)) + self.processes.append(ProcessView(3)) + self.processes.append(ProcessView(4)) - processes.forEach{ view.addSubview($0) } + self.processes.forEach{ view.addSubview($0) } self.addSubview(view) - self.processesView = view } private func addFirstRow(mView: NSView, y: CGFloat, title: String, value: String) -> NSTextField { @@ -174,49 +161,3 @@ internal class Popup: NSView { }) } } - -private class ProcessView: NSView { - public var width: CGFloat { - get { return 0 } - set { - self.setFrameSize(NSSize(width: newValue, height: self.frame.height)) - } - } - - public var label: String { - get { return "" } - set { - self.labelView?.stringValue = newValue - } - } - public var value: String { - get { return "" } - set { - self.valueView?.stringValue = newValue - } - } - - private var labelView: LabelField? = nil - private var valueView: ValueField? = nil - - init(_ n: CGFloat) { - super.init(frame: NSRect(x: 0, y: n*22, width: Constants.Popup.width, height: 16)) - - let rowView: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 16)) - - let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: 0.5, width: rowView.frame.width - 50, height: 15), "") - let valueView: ValueField = ValueField(frame: NSRect(x: rowView.frame.width - 50, y: 0, width: 50, height: 16), "") - - rowView.addSubview(labelView) - rowView.addSubview(valueView) - - self.labelView = labelView - self.valueView = valueView - - self.addSubview(rowView) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Modules/CPU/readers.swift b/Modules/CPU/readers.swift index 15d367e0..08046a1c 100644 --- a/Modules/CPU/readers.swift +++ b/Modules/CPU/readers.swift @@ -150,10 +150,6 @@ internal class LoadReader: Reader { } public class ProcessReader: Reader<[TopProcess]> { - public var store: UnsafePointer? = nil - - private var loadPrevious = host_cpu_load_info() - public override func setup() { self.popup = true } diff --git a/Modules/Memory/main.swift b/Modules/Memory/main.swift index 662380d9..4de54c5c 100644 --- a/Modules/Memory/main.swift +++ b/Modules/Memory/main.swift @@ -36,6 +36,7 @@ public struct RAM_Usage: value_t { public class Memory: Module { private let popupView: Popup = Popup() private var usageReader: UsageReader? = nil + private var processReader: ProcessReader? = nil private var settingsView: Settings public init(_ store: UnsafePointer) { @@ -53,6 +54,7 @@ public class Memory: Module { } self.usageReader = UsageReader() + self.processReader = ProcessReader() self.usageReader?.readyCallback = { [unowned self] in self.readyHandler() @@ -61,9 +63,18 @@ public class Memory: Module { self.loadCallback(value: value) } + self.processReader?.callbackHandler = { [unowned self] value in + if let list = value { + self.popupView.processCallback(list) + } + } + if let reader = self.usageReader { self.addReader(reader) } + if let reader = self.processReader { + self.addReader(reader) + } } private func loadCallback(value: RAM_Usage?) { diff --git a/Modules/Memory/popup.swift b/Modules/Memory/popup.swift index c9b383a9..d7082d64 100644 --- a/Modules/Memory/popup.swift +++ b/Modules/Memory/popup.swift @@ -16,6 +16,7 @@ import StatsKit internal class Popup: NSView { private let dashboardHeight: CGFloat = 90 private let detailsHeight: CGFloat = 66 + private let processesHeight: CGFloat = 22*5 private var totalField: NSTextField? = nil private var usedField: NSTextField? = nil @@ -29,11 +30,14 @@ internal class Popup: NSView { private var chart: LineChartView? = nil private var initialized: Bool = false + private var processes: [ProcessView] = [] + public init() { - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight)) + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + (Constants.Popup.separatorHeight*2) + detailsHeight + processesHeight)) initFirstView() initDetails() + initProcesses() } required init?(coder: NSCoder) { @@ -78,6 +82,23 @@ internal class Popup: NSView { self.addSubview(view) } + private func initProcesses() { + let separator = SeparatorView("Top processes", origin: NSPoint(x: 0, y: self.processesHeight), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.processesHeight)) + + self.processes.append(ProcessView(0)) + self.processes.append(ProcessView(1)) + self.processes.append(ProcessView(2)) + self.processes.append(ProcessView(3)) + self.processes.append(ProcessView(4)) + + self.processes.forEach{ view.addSubview($0) } + + self.addSubview(view) + } + private func addFirstRow(mView: NSView, y: CGFloat, title: String, value: String) -> NSTextField { let rowView: NSView = NSView(frame: NSRect(x: 0, y: y, width: mView.frame.width, height: 16)) @@ -116,4 +137,17 @@ internal class Popup: NSView { self.chart?.addValue(value.usage!) }) } + + public func processCallback(_ list: [TopProcess]) { + DispatchQueue.main.async(execute: { + for i in 0.. { @@ -76,3 +77,60 @@ internal class UsageReader: Reader { print("Error with host_statistics64(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) } } + +public class ProcessReader: Reader<[TopProcess]> { + public override func setup() { + self.popup = true + } + + public override func read() { + 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) + return + } + + 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.callback(processes) + } +} diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift index 956a5951..497eaa38 100644 --- a/StatsKit/extensions.swift +++ b/StatsKit/extensions.swift @@ -152,6 +152,10 @@ extension String: LocalizedError { buf.append(0) return String(cString: buf) } + + public func matches(_ regex: String) -> Bool { + return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil + } } public extension Int {