feat: added a combined processes option to the RAM module

This commit is contained in:
Serhiy Mytrovtsiy
2025-06-26 19:50:10 +02:00
parent 89e7ad1c63
commit 85ccc7bcdf
2 changed files with 81 additions and 5 deletions

View File

@@ -107,11 +107,15 @@ public class ProcessReader: Reader<[TopProcess]> {
private let title: String = "RAM"
private var numberOfProcesses: Int {
get {
return Store.shared.int(key: "\(self.title)_processes", defaultValue: 8)
}
get { Store.shared.int(key: "\(self.title)_processes", defaultValue: 8) }
}
private var combinedProcesses: Bool{
get { Store.shared.bool(key: "\(self.title)_combinedProcesses", defaultValue: false) }
}
private typealias dynGetResponsiblePidFuncType = @convention(c) (CInt) -> CInt
public override func setup() {
self.popup = true
self.setInterval(Store.shared.int(key: "\(self.title)_updateTopInterval", defaultValue: 1))
@@ -124,7 +128,11 @@ public class ProcessReader: Reader<[TopProcess]> {
let task = Process()
task.launchPath = "/usr/bin/top"
task.arguments = ["-l", "1", "-o", "mem", "-n", "\(self.numberOfProcesses)", "-stats", "pid,command,mem"]
if self.combinedProcesses {
task.arguments = ["-l", "1", "-o", "mem", "-stats", "pid,command,mem"]
} else {
task.arguments = ["-l", "1", "-o", "mem", "-n", "\(self.numberOfProcesses)", "-stats", "pid,command,mem"]
}
let outputPipe = Pipe()
let errorPipe = Pipe()
@@ -157,7 +165,64 @@ public class ProcessReader: Reader<[TopProcess]> {
}
}
self.callback(processes)
if !self.combinedProcesses {
self.callback(processes)
return
}
var processGroups: [String: [TopProcess]] = [:]
for process in processes {
let responsiblePid = ProcessReader.getResponsiblePid(process.pid)
let groupKey = "\(responsiblePid)"
if processGroups[groupKey] != nil {
processGroups[groupKey]!.append(process)
} else {
processGroups[groupKey] = [process]
}
}
var result: [TopProcess] = []
for (_, processes) in processGroups {
let totalUsage = processes.reduce(0) { $0 + $1.usage }
let firstProcess = processes.first!
let name: String
if let app = NSRunningApplication(processIdentifier: pid_t(ProcessReader.getResponsiblePid(firstProcess.pid))),
let appName = app.localizedName {
name = appName
} else {
name = firstProcess.name
}
result.append(TopProcess(
pid: ProcessReader.getResponsiblePid(firstProcess.pid),
name: name,
usage: totalUsage
))
}
result.sort { $0.usage > $1.usage }
self.callback(Array(result.prefix(self.numberOfProcesses)))
}
private static let dynGetResponsiblePidFunc: UnsafeMutableRawPointer? = {
let result = dlsym(UnsafeMutableRawPointer(bitPattern: -1), "responsibility_get_pid_responsible_for_pid")
if result == nil {
error("Error loading responsibility_get_pid_responsible_for_pid")
}
return result
}()
static func getResponsiblePid(_ childPid: Int) -> Int {
guard ProcessReader.dynGetResponsiblePidFunc != nil else {
return childPid
}
let responsiblePid = unsafeBitCast(ProcessReader.dynGetResponsiblePidFunc, to: dynGetResponsiblePidFuncType.self)(CInt(childPid))
guard responsiblePid != -1 else {
return childPid
}
return Int(responsiblePid)
}
static public func parseProcess(_ raw: String) -> TopProcess {

View File

@@ -49,6 +49,7 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
private var splitValueState: Bool = false
private var notificationLevel: String = "Disabled"
private var textValue: String = "$mem.used/$mem.total ($pressure.value)"
private var combinedProcessesState: Bool = false
private let title: String
@@ -67,6 +68,7 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
self.splitValueState = Store.shared.bool(key: "\(self.title)_splitValue", defaultValue: self.splitValueState)
self.notificationLevel = Store.shared.string(key: "\(self.title)_notificationLevel", defaultValue: self.notificationLevel)
self.textValue = Store.shared.string(key: "\(self.title)_textWidgetValue", defaultValue: self.textValue)
self.combinedProcessesState = Store.shared.bool(key: "\(self.title)_combinedProcesses", defaultValue: self.combinedProcessesState)
super.init(frame: NSRect.zero)
@@ -96,6 +98,10 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
]))
self.addArrangedSubview(PreferencesSection([
PreferencesRow(localizedString("Combined processes"), component: switchView(
action: #selector(toggleCombinedProcesses),
state: self.combinedProcessesState
)),
PreferencesRow(localizedString("Number of top processes"), component: selectView(
action: #selector(changeNumberOfProcesses),
items: NumbersOfProcesses.map{ KeyValue_t(key: "\($0)", value: "\($0)") },
@@ -163,6 +169,11 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
Store.shared.set(key: "\(self.title)_splitValue", value: self.splitValueState)
self.callback()
}
@objc private func toggleCombinedProcesses(_ sender: NSControl) {
self.combinedProcessesState = controlState(sender)
Store.shared.set(key: "\(self.title)_combinedProcesses", value: self.combinedProcessesState)
self.callback()
}
func controlTextDidChange(_ notification: Notification) {
if let field = notification.object as? NSTextField {