mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
- add top processes to Battery module
This commit is contained in:
@@ -41,6 +41,7 @@ struct Battery_Usage: value_t {
|
||||
|
||||
public class Battery: Module {
|
||||
private var usageReader: UsageReader? = nil
|
||||
private var processReader: ProcessReader? = nil
|
||||
private let popupView: Popup = Popup()
|
||||
private var settingsView: Settings
|
||||
|
||||
@@ -60,6 +61,7 @@ public class Battery: Module {
|
||||
guard self.available else { return }
|
||||
|
||||
self.usageReader = UsageReader()
|
||||
self.processReader = ProcessReader()
|
||||
|
||||
self.usageReader?.readyCallback = { [unowned self] in
|
||||
self.readyHandler()
|
||||
@@ -68,9 +70,18 @@ public class Battery: Module {
|
||||
self.usageCallback(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)
|
||||
}
|
||||
}
|
||||
|
||||
public override func isAvailable() -> Bool {
|
||||
|
||||
@@ -14,10 +14,11 @@ import ModuleKit
|
||||
import StatsKit
|
||||
|
||||
internal class Popup: NSView {
|
||||
let dashboardHeight: CGFloat = 90
|
||||
let detailsHeight: CGFloat = 88
|
||||
let batteryHeight: CGFloat = 66
|
||||
let adapterHeight: CGFloat = 44
|
||||
private let dashboardHeight: CGFloat = 90
|
||||
private let detailsHeight: CGFloat = 88
|
||||
private let batteryHeight: CGFloat = 66
|
||||
private let adapterHeight: CGFloat = 44
|
||||
private let processesHeight: CGFloat = 22*5
|
||||
|
||||
private var dashboardView: NSView? = nil
|
||||
private var dashboardBatteryView: BatteryView? = nil
|
||||
@@ -38,18 +39,21 @@ internal class Popup: NSView {
|
||||
private var powerField: NSTextField? = nil
|
||||
private var chargingStateField: NSTextField? = nil
|
||||
|
||||
private var processes: [ProcessView] = []
|
||||
|
||||
public init() {
|
||||
super.init(frame: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: Constants.Popup.width,
|
||||
height: dashboardHeight + detailsHeight + batteryHeight + adapterHeight + (Constants.Popup.separatorHeight * 3)
|
||||
height: dashboardHeight + detailsHeight + batteryHeight + adapterHeight + (Constants.Popup.separatorHeight * 4) + processesHeight
|
||||
))
|
||||
|
||||
self.initDashboard()
|
||||
self.initDetails()
|
||||
self.initBattery()
|
||||
self.initAdapter()
|
||||
self.initProcesses()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -127,6 +131,23 @@ internal class Popup: NSView {
|
||||
self.adapterView = 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)
|
||||
}
|
||||
|
||||
public func usageCallback(_ value: Battery_Usage) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.dashboardBatteryView?.setValue(abs(value.level))
|
||||
@@ -165,6 +186,19 @@ internal class Popup: NSView {
|
||||
self.chargingStateField?.stringValue = value.isCharging ? "Yes" : "No"
|
||||
})
|
||||
}
|
||||
|
||||
public func processCallback(_ list: [TopProcess]) {
|
||||
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 != nil ? process.name! : process.command
|
||||
self.processes[index].value = "\(process.usage)%"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class BatteryView: NSView {
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import StatsKit
|
||||
import ModuleKit
|
||||
import os.log
|
||||
|
||||
internal class UsageReader: Reader<Battery_Usage> {
|
||||
private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
|
||||
@@ -128,3 +130,127 @@ internal class UsageReader: Reader<Battery_Usage> {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public class ProcessReader: Reader<[TopProcess]> {
|
||||
private var task: Process? = nil
|
||||
private var initialized: Bool = false
|
||||
|
||||
public override func setup() {
|
||||
self.popup = true
|
||||
}
|
||||
|
||||
public override func start() {
|
||||
if !self.initialized {
|
||||
DispatchQueue.global().async {
|
||||
self.read()
|
||||
}
|
||||
self.initialized = true
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
self.task = Process()
|
||||
let pipe = Pipe()
|
||||
|
||||
self.task?.standardOutput = pipe
|
||||
self.task?.launchPath = "/usr/bin/top"
|
||||
self.task?.arguments = ["-o", "power", "-n", "5", "-stats", "pid,command,power"]
|
||||
|
||||
pipe.fileHandleForReading.readabilityHandler = { (fileHandle) -> Void in
|
||||
let output = String(decoding: fileHandle.availableData, as: UTF8.self)
|
||||
var processes: [TopProcess] = []
|
||||
|
||||
output.enumerateLines { (line, _) -> () 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
|
||||
guard let usage = Double(usageString.filter("01234567890.".contains)) else {
|
||||
return
|
||||
}
|
||||
|
||||
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 processes.count != 0 {
|
||||
self.callback(processes)
|
||||
}
|
||||
}
|
||||
|
||||
self.task?.launch()
|
||||
self.task?.waitUntilExit()
|
||||
}
|
||||
}
|
||||
|
||||
public override func stop() {
|
||||
if self.task == nil || !self.task!.isRunning {
|
||||
return
|
||||
}
|
||||
|
||||
self.task?.interrupt()
|
||||
self.task = nil
|
||||
}
|
||||
|
||||
public override func read() {
|
||||
let task = Process()
|
||||
task.launchPath = "/usr/bin/top"
|
||||
task.arguments = ["-l", "1", "-o", "power", "-n", "5", "-stats", "pid,command,power"]
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
|
||||
do {
|
||||
try task.run()
|
||||
} catch let error {
|
||||
os_log(.error, log: log, "top(): %s", "\(error.localizedDescription)")
|
||||
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, _) -> () 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
|
||||
guard let usage = Double(usageString.filter("01234567890.".contains)) else {
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
self.callback(processes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Cocoa
|
||||
import StatsKit
|
||||
import ModuleKit
|
||||
import os.log
|
||||
|
||||
internal class LoadReader: Reader<CPU_Load> {
|
||||
public var store: UnsafePointer<Store>? = nil
|
||||
@@ -96,7 +97,7 @@ internal class LoadReader: Reader<CPU_Load> {
|
||||
self.cpuInfo = nil
|
||||
self.numCpuInfo = 0
|
||||
} else {
|
||||
print("ERROR host_processor_info(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
os_log(.error, log: log, "host_processor_info(): %s", "\((String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))")
|
||||
}
|
||||
|
||||
let cpuInfo = hostCPULoadInfo()
|
||||
@@ -141,7 +142,7 @@ internal class LoadReader: Reader<CPU_Load> {
|
||||
}
|
||||
}
|
||||
if result != KERN_SUCCESS {
|
||||
print("Error - \(#file): \(#function) - kern_result_t = \(result)")
|
||||
os_log(.error, log: log, "kern_result_t: %s", "\(result)")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,7 +169,7 @@ public class ProcessReader: Reader<[TopProcess]> {
|
||||
do {
|
||||
try task.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
os_log(.error, log: log, "error read ps: %s", "\(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Cocoa
|
||||
import StatsKit
|
||||
import ModuleKit
|
||||
import os.log
|
||||
|
||||
internal class UsageReader: Reader<RAM_Usage> {
|
||||
public var totalSize: Double = 0
|
||||
@@ -32,7 +33,7 @@ internal class UsageReader: Reader<RAM_Usage> {
|
||||
}
|
||||
|
||||
self.totalSize = 0
|
||||
print("Error with host_info(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
os_log(.error, log: log, "host_info(): %s", "\((String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))")
|
||||
}
|
||||
|
||||
public override func read() {
|
||||
@@ -74,7 +75,7 @@ internal class UsageReader: Reader<RAM_Usage> {
|
||||
return
|
||||
}
|
||||
|
||||
print("Error with host_statistics64(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
os_log(.error, log: log, "host_statistics64(): %s", "\((String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ public class ProcessReader: Reader<[TopProcess]> {
|
||||
do {
|
||||
try task.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
os_log(.error, log: log, "top(): %s", "\(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user