mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
- fix unnecessary popup interface update (when popup closed)
GPU module MVP
This commit is contained in:
@@ -189,13 +189,15 @@ internal class Popup: NSView {
|
||||
|
||||
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)%"
|
||||
self.processes[index].icon = process.icon
|
||||
if (self.window?.isVisible ?? false) {
|
||||
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)%"
|
||||
self.processes[index].icon = process.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -159,20 +159,20 @@ internal class Popup: NSView {
|
||||
let v = Int(value.totalUsage.rounded(toPlaces: 2) * 100)
|
||||
self.loadField?.stringValue = "\(v) %"
|
||||
self.ready = true
|
||||
}
|
||||
|
||||
self.circle?.setValue(value.totalUsage)
|
||||
self.circle?.setSegments([
|
||||
circle_segment(value: value.systemLoad, color: NSColor.systemRed),
|
||||
circle_segment(value: value.userLoad, color: NSColor.systemBlue),
|
||||
])
|
||||
if tempValue != nil {
|
||||
self.temperatureCircle?.setValue(tempValue!)
|
||||
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.numberFormatter.maximumFractionDigits = 0
|
||||
let measurement = Measurement(value: tempValue!, unit: UnitTemperature.celsius)
|
||||
self.temperatureCircle?.setText(formatter.string(from: measurement))
|
||||
self.circle?.setValue(value.totalUsage)
|
||||
self.circle?.setSegments([
|
||||
circle_segment(value: value.systemLoad, color: NSColor.systemRed),
|
||||
circle_segment(value: value.userLoad, color: NSColor.systemBlue),
|
||||
])
|
||||
if tempValue != nil {
|
||||
self.temperatureCircle?.setValue(tempValue!)
|
||||
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.numberFormatter.maximumFractionDigits = 0
|
||||
let measurement = Measurement(value: tempValue!, unit: UnitTemperature.celsius)
|
||||
self.temperatureCircle?.setText(formatter.string(from: measurement))
|
||||
}
|
||||
}
|
||||
self.chart?.addValue(value.totalUsage)
|
||||
})
|
||||
@@ -180,13 +180,15 @@ internal class Popup: NSView {
|
||||
|
||||
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)%"
|
||||
self.processes[index].icon = process.icon
|
||||
if (self.window?.isVisible ?? false) {
|
||||
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)%"
|
||||
self.processes[index].icon = process.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -64,6 +64,7 @@ internal class DiskView: NSView {
|
||||
public let name: String
|
||||
public let size: Int64
|
||||
private let uri: URL?
|
||||
private var ready: Bool = false
|
||||
|
||||
private let nameHeight: CGFloat = 20
|
||||
private let legendHeight: CGFloat = 12
|
||||
@@ -196,22 +197,26 @@ internal class DiskView: NSView {
|
||||
|
||||
public func update(free: Int64, read: Int64?, write: Int64?) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if self.legendField != nil {
|
||||
self.legendField?.stringValue = "Used \(Units(bytes: (self.size - free)).getReadableMemory()) from \(Units(bytes: self.size).getReadableMemory())"
|
||||
self.percentageField?.stringValue = "\(Int8((Double(self.size - free) / Double(self.size)) * 100))%"
|
||||
}
|
||||
|
||||
if self.usedBarSpace != nil {
|
||||
let percentage = CGFloat(self.size - free) / CGFloat(self.size)
|
||||
let width: CGFloat = ((self.mainView.frame.width - 2) * percentage) / 1
|
||||
self.usedBarSpace?.setFrameSize(NSSize(width: width, height: self.usedBarSpace!.frame.height))
|
||||
}
|
||||
|
||||
if read != nil {
|
||||
self.setReadState(read != 0)
|
||||
}
|
||||
if write != nil {
|
||||
self.setWriteState(write != 0)
|
||||
if (self.window?.isVisible ?? false) || !self.ready {
|
||||
if self.legendField != nil {
|
||||
self.legendField?.stringValue = "Used \(Units(bytes: (self.size - free)).getReadableMemory()) from \(Units(bytes: self.size).getReadableMemory())"
|
||||
self.percentageField?.stringValue = "\(Int8((Double(self.size - free) / Double(self.size)) * 100))%"
|
||||
}
|
||||
|
||||
if self.usedBarSpace != nil {
|
||||
let percentage = CGFloat(self.size - free) / CGFloat(self.size)
|
||||
let width: CGFloat = ((self.mainView.frame.width - 2) * percentage) / 1
|
||||
self.usedBarSpace?.setFrameSize(NSSize(width: width, height: self.usedBarSpace!.frame.height))
|
||||
}
|
||||
|
||||
if read != nil {
|
||||
self.setReadState(read != 0)
|
||||
}
|
||||
if write != nil {
|
||||
self.setWriteState(write != 0)
|
||||
}
|
||||
|
||||
self.ready = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@ public struct GPU_Info {
|
||||
public var state: Bool = false
|
||||
|
||||
public var utilization: Double = 0
|
||||
|
||||
public var totalVram: Int = 0
|
||||
public var freeVram: Int = 0
|
||||
public var coreClock: Int = 0
|
||||
public var power: Int = 0
|
||||
public var temperature: Int = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import StatsKit
|
||||
import ModuleKit
|
||||
|
||||
internal class Popup: NSView {
|
||||
private var list: [String: GPUView] = [:]
|
||||
private let gpuViewHeight: CGFloat = 162
|
||||
|
||||
public init() {
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0))
|
||||
}
|
||||
@@ -23,10 +26,152 @@ internal class Popup: NSView {
|
||||
}
|
||||
|
||||
internal func infoCallback(_ value: GPUs) {
|
||||
print(value)
|
||||
if self.list.count != value.list.count {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.subviews.forEach{ $0.removeFromSuperview() }
|
||||
})
|
||||
self.list = [:]
|
||||
}
|
||||
|
||||
value.list.forEach { (gpu: GPU_Info) in
|
||||
if self.list[gpu.name] == nil {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.list[gpu.name] = GPUView(
|
||||
NSRect(x: 0, y: (self.gpuViewHeight + Constants.Popup.margins) * CGFloat(self.list.count), width: self.frame.width, height: self.gpuViewHeight),
|
||||
gpu: gpu
|
||||
)
|
||||
self.addSubview(self.list[gpu.name]!)
|
||||
})
|
||||
} else {
|
||||
self.list[gpu.name]?.update(gpu)
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
let h: CGFloat = ((self.gpuViewHeight + Constants.Popup.margins) * CGFloat(self.list.count)) - Constants.Popup.margins
|
||||
if self.frame.size.height != h {
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
NotificationCenter.default.post(name: .updatePopupSize, object: nil, userInfo: ["module": "GPU"])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class GPUView: NSView {
|
||||
private let height: CGFloat = 60
|
||||
private let margin: CGFloat = 4
|
||||
|
||||
private var name: String
|
||||
private var state: Bool
|
||||
|
||||
private var chart: LineChartView? = nil
|
||||
private var utilization: HalfCircleGraphView? = nil
|
||||
private var temperature: HalfCircleGraphView? = nil
|
||||
|
||||
private var stateView: NSView? = nil
|
||||
|
||||
public init(_ frame: NSRect, gpu: GPU_Info) {
|
||||
self.name = gpu.name
|
||||
self.state = gpu.state
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.wantsLayer = true
|
||||
self.layer?.cornerRadius = 2
|
||||
|
||||
self.initName()
|
||||
self.initCircles()
|
||||
self.initChart()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func initName() {
|
||||
let y: CGFloat = self.frame.height - Constants.Popup.separatorHeight
|
||||
let width: CGFloat = self.name.widthOfString(usingFont: NSFont.systemFont(ofSize: 12, weight: .medium)) + 16
|
||||
|
||||
let view: NSView = NSView(frame: NSRect(x: (self.frame.width - width)/2, y: y, width: width, height: 30))
|
||||
|
||||
let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: (view.frame.height-15)/2, width: width - 8, height: 15))
|
||||
labelView.alignment = .center
|
||||
labelView.textColor = .secondaryLabelColor
|
||||
labelView.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
labelView.stringValue = self.name
|
||||
|
||||
let stateView: NSView = NSView(frame: NSRect(x: width - 8, y: (view.frame.height-7)/2, width: 6, height: 6))
|
||||
stateView.wantsLayer = true
|
||||
stateView.layer?.backgroundColor = (self.state ? NSColor.systemGreen : NSColor.systemRed).cgColor
|
||||
stateView.toolTip = "GPU \(self.state ? "enabled" : "disabled")"
|
||||
stateView.layer?.cornerRadius = 4
|
||||
|
||||
view.addSubview(labelView)
|
||||
view.addSubview(stateView)
|
||||
|
||||
self.addSubview(view)
|
||||
self.stateView = stateView
|
||||
}
|
||||
|
||||
private func initCircles() {
|
||||
let view: NSView = NSView(frame: NSRect(
|
||||
x: self.margin,
|
||||
y: self.height + (self.margin*2),
|
||||
width: self.frame.width - (self.margin*2),
|
||||
height: self.height
|
||||
))
|
||||
|
||||
let circleSize: CGFloat = 50
|
||||
self.temperature = HalfCircleGraphView(frame: NSRect(
|
||||
x: ((view.frame.width/2) - circleSize)/2 + 10,
|
||||
y: 5,
|
||||
width: circleSize,
|
||||
height: circleSize
|
||||
))
|
||||
self.temperature!.toolTip = "GPU temperature"
|
||||
self.utilization = HalfCircleGraphView(frame: NSRect(
|
||||
x: (view.frame.width/2) + (((view.frame.width/2) - circleSize)/2) - 10,
|
||||
y: 5,
|
||||
width: circleSize,
|
||||
height: circleSize
|
||||
))
|
||||
self.utilization!.toolTip = "GPU utilization"
|
||||
|
||||
view.addSubview(self.temperature!)
|
||||
view.addSubview(self.utilization!)
|
||||
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
private func initChart() {
|
||||
let view: NSView = NSView(frame: NSRect(x: self.margin, y: self.margin, width: self.frame.width - (self.margin*2), height: self.height))
|
||||
view.wantsLayer = true
|
||||
view.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor
|
||||
view.layer?.cornerRadius = 3
|
||||
|
||||
self.chart = LineChartView(frame: NSRect(x: 1, y: 0, width: view.frame.width, height: view.frame.height), num: 120)
|
||||
|
||||
view.addSubview(self.chart!)
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
public func update(_ gpu: GPU_Info) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if (self.window?.isVisible ?? false) {
|
||||
self.stateView?.layer?.backgroundColor = (gpu.state ? NSColor.systemGreen : NSColor.systemRed).cgColor
|
||||
self.stateView?.toolTip = "GPU \(gpu.state ? "enabled" : "disabled")"
|
||||
|
||||
self.utilization?.setValue(gpu.utilization)
|
||||
self.utilization?.setText("\(Int(gpu.utilization*100))%")
|
||||
self.temperature?.setValue(Double(gpu.temperature))
|
||||
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.numberFormatter.maximumFractionDigits = 0
|
||||
let measurement = Measurement(value: Double(gpu.temperature), unit: UnitTemperature.celsius)
|
||||
self.temperature?.setText(formatter.string(from: measurement))
|
||||
|
||||
self.chart?.addValue(gpu.utilization)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +66,9 @@ internal class InfoReader: Reader<GPUs> {
|
||||
}
|
||||
|
||||
let utilization = stats["Device Utilization %"] as? Int ?? 0
|
||||
let totalVram = accelerator["VRAM,totalMB"] as? Int ?? matchedGPU["VRAM,totalMB"] as? Int ?? 0
|
||||
let freeVram = stats["vramFreeBytes"] as? Int ?? 0
|
||||
let coreClock = stats["Core Clock(MHz)"] as? Int ?? 0
|
||||
var power = stats["Total Power(W)"] as? Int ?? 0
|
||||
// let totalVram = (accelerator["VRAM,totalMB"] as? Int ?? matchedGPU["VRAM,totalMB"] as? Int ?? 0) * 1000000
|
||||
// let freeVram = stats["vramFreeBytes"] as? Int ?? 0
|
||||
// let coreClock = stats["Core Clock(MHz)"] as? Int ?? 0
|
||||
var temperature = stats["Temperature(C)"] as? Int ?? 0
|
||||
|
||||
if IOClass == "IntelAccelerator" {
|
||||
@@ -80,25 +79,11 @@ internal class InfoReader: Reader<GPUs> {
|
||||
temperature = Int(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
if power == 0 {
|
||||
if let pwr = self.smc?.pointee.getValue("PCPG") {
|
||||
power = Int(pwr)
|
||||
} else if let pwr = self.smc?.pointee.getValue("PCGC") {
|
||||
power = Int(pwr)
|
||||
} else if let pwr = self.smc?.pointee.getValue("PCGM") {
|
||||
power = Int(pwr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.gpus.list[idx].state = agcInfo["poweredOffByAGC"] == 0
|
||||
|
||||
self.gpus.list[idx].utilization = utilization == 0 ? 0 : Double(utilization)/100
|
||||
self.gpus.list[idx].totalVram = totalVram
|
||||
self.gpus.list[idx].freeVram = freeVram
|
||||
self.gpus.list[idx].coreClock = coreClock
|
||||
self.gpus.list[idx].power = power
|
||||
self.gpus.list[idx].temperature = temperature
|
||||
}
|
||||
|
||||
|
||||
@@ -161,29 +161,32 @@ internal class Popup: NSView {
|
||||
self.totalField?.stringValue = Units(bytes: Int64(value.total)).getReadableMemory()
|
||||
self.usedField?.stringValue = Units(bytes: Int64(value.used)).getReadableMemory()
|
||||
self.freeField?.stringValue = Units(bytes: Int64(value.free)).getReadableMemory()
|
||||
|
||||
self.circle?.setValue(value.usage)
|
||||
self.circle?.setSegments([
|
||||
circle_segment(value: value.active/value.total, color: NSColor.systemBlue),
|
||||
circle_segment(value: value.wired/value.total, color: NSColor.systemOrange),
|
||||
circle_segment(value: value.compressed/value.total, color: NSColor.systemPink)
|
||||
])
|
||||
self.level?.setLevel(value.pressureLevel)
|
||||
|
||||
self.initialized = true
|
||||
}
|
||||
|
||||
self.circle?.setValue(value.usage)
|
||||
self.circle?.setSegments([
|
||||
circle_segment(value: value.active/value.total, color: NSColor.systemBlue),
|
||||
circle_segment(value: value.wired/value.total, color: NSColor.systemOrange),
|
||||
circle_segment(value: value.compressed/value.total, color: NSColor.systemPink)
|
||||
])
|
||||
self.chart?.addValue(value.usage)
|
||||
self.level?.setLevel(value.pressureLevel)
|
||||
})
|
||||
}
|
||||
|
||||
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 = Units(bytes: Int64(process.usage)).getReadableMemory()
|
||||
self.processes[index].icon = process.icon
|
||||
if (self.window?.isVisible ?? false) {
|
||||
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 = Units(bytes: Int64(process.usage)).getReadableMemory()
|
||||
self.processes[index].icon = process.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -222,44 +222,42 @@ internal class Popup: NSView {
|
||||
|
||||
public func usageCallback(_ value: Network_Usage) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if !(self.window?.isVisible ?? false) && self.initialized {
|
||||
return
|
||||
}
|
||||
|
||||
self.uploadValue = value.upload
|
||||
self.downloadValue = value.download
|
||||
self.setUploadDownloadFields()
|
||||
|
||||
if let interface = value.interface {
|
||||
self.interfaceField?.stringValue = "\(interface.displayName) (\(interface.BSDName))"
|
||||
self.macAdressField?.stringValue = interface.address
|
||||
} else {
|
||||
self.interfaceField?.stringValue = "Unknown"
|
||||
self.macAdressField?.stringValue = "Unknown"
|
||||
}
|
||||
|
||||
if value.connectionType == .wifi {
|
||||
self.ssidField?.stringValue = value.ssid ?? "Unknown"
|
||||
} else {
|
||||
self.ssidField?.stringValue = "Unavailable"
|
||||
}
|
||||
|
||||
if self.publicIPField?.stringValue != value.raddr {
|
||||
if value.raddr == nil {
|
||||
self.publicIPField?.stringValue = "Unknown"
|
||||
if (self.window?.isVisible ?? false) || !self.initialized {
|
||||
self.uploadValue = value.upload
|
||||
self.downloadValue = value.download
|
||||
self.setUploadDownloadFields()
|
||||
|
||||
if let interface = value.interface {
|
||||
self.interfaceField?.stringValue = "\(interface.displayName) (\(interface.BSDName))"
|
||||
self.macAdressField?.stringValue = interface.address
|
||||
} else {
|
||||
if value.countryCode == nil {
|
||||
self.publicIPField?.stringValue = value.raddr!
|
||||
self.interfaceField?.stringValue = "Unknown"
|
||||
self.macAdressField?.stringValue = "Unknown"
|
||||
}
|
||||
|
||||
if value.connectionType == .wifi {
|
||||
self.ssidField?.stringValue = value.ssid ?? "Unknown"
|
||||
} else {
|
||||
self.ssidField?.stringValue = "Unavailable"
|
||||
}
|
||||
|
||||
if self.publicIPField?.stringValue != value.raddr {
|
||||
if value.raddr == nil {
|
||||
self.publicIPField?.stringValue = "Unknown"
|
||||
} else {
|
||||
self.publicIPField?.stringValue = "\(value.raddr!) (\(value.countryCode!))"
|
||||
if value.countryCode == nil {
|
||||
self.publicIPField?.stringValue = value.raddr!
|
||||
} else {
|
||||
self.publicIPField?.stringValue = "\(value.raddr!) (\(value.countryCode!))"
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.localIPField?.stringValue != value.laddr {
|
||||
self.localIPField?.stringValue = value.laddr ?? "Unknown"
|
||||
}
|
||||
|
||||
self.initialized = true
|
||||
}
|
||||
if self.localIPField?.stringValue != value.laddr {
|
||||
self.localIPField?.stringValue = value.laddr ?? "Unknown"
|
||||
}
|
||||
|
||||
self.initialized = true
|
||||
|
||||
self.chart?.addValues([Double(value.upload), Double(value.download)])
|
||||
})
|
||||
@@ -267,14 +265,16 @@ internal class Popup: NSView {
|
||||
|
||||
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()
|
||||
self.processes[index].icon = process.icon
|
||||
if (self.window?.isVisible ?? false) {
|
||||
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()
|
||||
self.processes[index].icon = process.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -781,8 +781,8 @@
|
||||
children = (
|
||||
9A90E19524EAD35F00471E9A /* main.swift */,
|
||||
9A90E1A224EAD66600471E9A /* reader.swift */,
|
||||
9A53EBF824EAFA5200648841 /* settings.swift */,
|
||||
9A53EBFA24EB041E00648841 /* popup.swift */,
|
||||
9A53EBF824EAFA5200648841 /* settings.swift */,
|
||||
9A90E18C24EAD2BB00471E9A /* Info.plist */,
|
||||
9A90E19724EAD3B000471E9A /* config.plist */,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user