mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
- add top processes to Network module
- add application names to CPU and RAM top processes view
This commit is contained in:
@@ -154,7 +154,7 @@ internal class Popup: NSView {
|
||||
let process = list[i]
|
||||
let index = list.count-i-1
|
||||
if self.processes.indices.contains(index) {
|
||||
self.processes[index].label = process.command
|
||||
self.processes[index].label = process.name != nil ? process.name! : process.command
|
||||
self.processes[index].value = "\(process.usage)%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,12 @@ public class ProcessReader: Reader<[TopProcess]> {
|
||||
let pid = Int(pidString) ?? 0
|
||||
let usage = Double(usageString.replacingOccurrences(of: ",", with: ".")) ?? 0
|
||||
|
||||
processes.append(TopProcess(pid: pid, command: command, usage: usage))
|
||||
var name: String? = nil
|
||||
if let app = NSRunningApplication(processIdentifier: pid_t(pid) ) {
|
||||
name = app.localizedName ?? nil
|
||||
}
|
||||
|
||||
processes.append(TopProcess(pid: pid, command: command, name: name, usage: usage))
|
||||
}
|
||||
|
||||
if index == 5 { stop = true }
|
||||
|
||||
@@ -144,7 +144,7 @@ internal class Popup: NSView {
|
||||
let process = list[i]
|
||||
let index = list.count-i-1
|
||||
if self.processes.indices.contains(index) {
|
||||
self.processes[index].label = process.command
|
||||
self.processes[index].label = process.name != nil ? process.name! : process.command
|
||||
self.processes[index].value = Units(bytes: Int64(process.usage)).getReadableMemory()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,22 +111,28 @@ public class ProcessReader: Reader<[TopProcess]> {
|
||||
}
|
||||
|
||||
var processes: [TopProcess] = []
|
||||
output.enumerateLines { (line, stop) -> () in
|
||||
output.enumerateLines { (line, _) -> () 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))
|
||||
|
||||
var name: String? = nil
|
||||
if let app = NSRunningApplication(processIdentifier: pid_t(pid) ) {
|
||||
name = app.localizedName ?? nil
|
||||
}
|
||||
|
||||
let process = TopProcess(pid: pid, command: command, name: name, usage: usage * Double(1024 * 1024))
|
||||
processes.append(process)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,17 @@ public struct Network_Usage: value_t {
|
||||
public var widget_value: Double = 0
|
||||
}
|
||||
|
||||
public struct Network_Process {
|
||||
var time: Date = Date()
|
||||
var name: String = ""
|
||||
var pid: String = ""
|
||||
var download: Int = 0
|
||||
var upload: Int = 0
|
||||
}
|
||||
|
||||
public class Network: Module {
|
||||
private var usageReader: UsageReader?
|
||||
private var usageReader: UsageReader? = nil
|
||||
private var processReader: ProcessReader? = nil
|
||||
private let popupView: Popup = Popup()
|
||||
private var settingsView: Settings
|
||||
|
||||
@@ -75,6 +84,8 @@ public class Network: Module {
|
||||
self.usageReader = UsageReader()
|
||||
self.usageReader?.store = store
|
||||
|
||||
self.processReader = ProcessReader()
|
||||
|
||||
self.usageReader?.readyCallback = { [unowned self] in
|
||||
self.readyHandler()
|
||||
}
|
||||
@@ -82,6 +93,12 @@ public class Network: Module {
|
||||
self.usageCallback(value)
|
||||
}
|
||||
|
||||
self.processReader?.callbackHandler = { [unowned self] value in
|
||||
if let list = value {
|
||||
self.popupView.processCallback(list)
|
||||
}
|
||||
}
|
||||
|
||||
self.settingsView.callback = { [unowned self] in
|
||||
self.usageReader?.getDetails()
|
||||
self.usageReader?.read()
|
||||
@@ -90,6 +107,9 @@ public class Network: Module {
|
||||
if let reader = self.usageReader {
|
||||
self.addReader(reader)
|
||||
}
|
||||
if let reader = self.processReader {
|
||||
self.addReader(reader)
|
||||
}
|
||||
}
|
||||
|
||||
public override func isAvailable() -> Bool {
|
||||
|
||||
@@ -14,8 +14,9 @@ import ModuleKit
|
||||
import StatsKit
|
||||
|
||||
internal class Popup: NSView {
|
||||
let dashboardHeight: CGFloat = 90
|
||||
let detailsHeight: CGFloat = 110
|
||||
private let dashboardHeight: CGFloat = 90
|
||||
private let detailsHeight: CGFloat = 110
|
||||
private let processesHeight: CGFloat = 22*5
|
||||
|
||||
private var dashboardView: NSView? = nil
|
||||
|
||||
@@ -37,11 +38,14 @@ internal class Popup: NSView {
|
||||
|
||||
private var initialized: Bool = false
|
||||
|
||||
private var processes: [NetworkProcessView] = []
|
||||
|
||||
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))
|
||||
|
||||
initDashboard()
|
||||
initDetails()
|
||||
initProcesses()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -157,6 +161,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(NetworkProcessView(0))
|
||||
self.processes.append(NetworkProcessView(1))
|
||||
self.processes.append(NetworkProcessView(2))
|
||||
self.processes.append(NetworkProcessView(3))
|
||||
self.processes.append(NetworkProcessView(4))
|
||||
|
||||
self.processes.forEach{ view.addSubview($0) }
|
||||
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
public func usageCallback(_ value: Network_Usage) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if !(self.window?.isVisible ?? false) && self.initialized {
|
||||
@@ -199,6 +220,20 @@ internal class Popup: NSView {
|
||||
self.initialized = true
|
||||
})
|
||||
}
|
||||
|
||||
public func processCallback(_ list: [Network_Process]) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
for i in 0..<list.count {
|
||||
let process = list[i]
|
||||
let index = list.count-i-1
|
||||
if self.processes.indices.contains(index) {
|
||||
self.processes[index].label = process.name
|
||||
self.processes[index].upload = Units(bytes: Int64(process.upload)).getReadableSpeed()
|
||||
self.processes[index].download = Units(bytes: Int64(process.download)).getReadableSpeed()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension ValueField {
|
||||
@@ -233,3 +268,61 @@ extension ValueField {
|
||||
pasteboard.setString(value, forType: .string)
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkProcessView: 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 upload: String {
|
||||
get { return "" }
|
||||
set {
|
||||
self.uploadView?.stringValue = newValue
|
||||
}
|
||||
}
|
||||
public var download: String {
|
||||
get { return "" }
|
||||
set {
|
||||
self.downloadView?.stringValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var labelView: LabelField? = nil
|
||||
private var uploadView: ValueField? = nil
|
||||
private var downloadView: 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 - 120, height: 15), "")
|
||||
let uploadView: ValueField = ValueField(frame: NSRect(x: rowView.frame.width - 120, y: 1.75, width: 60, height: 12), "")
|
||||
let downloadView: ValueField = ValueField(frame: NSRect(x: rowView.frame.width - 60, y: 1.75, width: 60, height: 12), "")
|
||||
uploadView.font = NSFont.systemFont(ofSize: 10, weight: .regular)
|
||||
downloadView.font = NSFont.systemFont(ofSize: 10, weight: .regular)
|
||||
|
||||
rowView.addSubview(labelView)
|
||||
rowView.addSubview(uploadView)
|
||||
rowView.addSubview(downloadView)
|
||||
|
||||
self.labelView = labelView
|
||||
self.uploadView = uploadView
|
||||
self.downloadView = downloadView
|
||||
|
||||
self.addSubview(rowView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,3 +188,107 @@ internal class UsageReader: Reader<Network_Usage> {
|
||||
return (upload: Int64(data?.pointee.ifi_obytes ?? 0), download: Int64(data?.pointee.ifi_ibytes ?? 0))
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcessReader: Reader<[Network_Process]> {
|
||||
private var previous: [Network_Process] = []
|
||||
|
||||
public override func setup() {
|
||||
self.popup = true
|
||||
}
|
||||
|
||||
public override func read() {
|
||||
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
|
||||
}
|
||||
|
||||
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 list: [Network_Process] = []
|
||||
var firstLine = false
|
||||
output.enumerateLines { (line, _) -> () in
|
||||
if !firstLine {
|
||||
firstLine = true
|
||||
return
|
||||
}
|
||||
|
||||
let parsedLine = line.split(separator: ",")
|
||||
guard parsedLine.count >= 3 else {
|
||||
return
|
||||
}
|
||||
|
||||
var process = Network_Process()
|
||||
process.time = Date()
|
||||
|
||||
let nameArray = parsedLine[0].split(separator: ".")
|
||||
if let pid = nameArray.last {
|
||||
process.pid = String(pid)
|
||||
}
|
||||
if let app = NSRunningApplication(processIdentifier: pid_t(process.pid) ?? 0) {
|
||||
process.name = app.localizedName ?? nameArray.dropLast().joined(separator: ".")
|
||||
} else {
|
||||
process.name = nameArray.dropLast().joined(separator: ".")
|
||||
}
|
||||
|
||||
if let download = Int(parsedLine[1]) {
|
||||
process.download = download
|
||||
}
|
||||
if let upload = Int(parsedLine[2]) {
|
||||
process.upload = upload
|
||||
}
|
||||
|
||||
list.append(process)
|
||||
}
|
||||
|
||||
var processes: [Network_Process] = []
|
||||
if self.previous.count == 0 {
|
||||
self.previous = list
|
||||
processes = list
|
||||
} else {
|
||||
self.previous.forEach { (pp: Network_Process) in
|
||||
if let i = list.firstIndex(where: { $0.pid == pp.pid }) {
|
||||
let p = list[i]
|
||||
|
||||
let download = p.download - pp.download
|
||||
let upload = p.upload - pp.upload
|
||||
let time = download == 0 && upload == 0 ? pp.time : Date()
|
||||
list[i].time = time
|
||||
|
||||
processes.append(Network_Process(time: time, name: p.name, pid: p.pid, download: download, upload: upload))
|
||||
}
|
||||
}
|
||||
self.previous = list
|
||||
}
|
||||
|
||||
processes.sort {
|
||||
if $0.download != $1.download {
|
||||
return $0.download < $1.download
|
||||
} else if $0.upload < $1.upload {
|
||||
return $0.upload < $1.upload
|
||||
} else {
|
||||
return $0.time < $1.time
|
||||
}
|
||||
}
|
||||
|
||||
self.callback(processes.suffix(5).reversed())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user