- disk IO stats (mvp)

This commit is contained in:
Serhiy Mytrovtsiy
2020-08-08 20:42:53 +02:00
parent 8e003cf762
commit 2904b090a4
12 changed files with 173 additions and 33 deletions

View File

@@ -26,6 +26,8 @@ public class SpeedWidget: Widget {
private var state: Bool = false
private var valueState: Bool = true
private var symbols: [String] = ["U", "D"]
private var uploadField: NSTextField? = nil
private var downloadField: NSTextField? = nil
@@ -38,6 +40,14 @@ public class SpeedWidget: Widget {
public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer<Store>?) {
let widgetTitle: String = title
self.store = store
if config != nil {
if let symbols = config!["Symbols"] as? [String] {
self.symbols = symbols
}
if let iconName = config!["Icon"] as? String, let icon = speed_icon_t(rawValue: iconName) {
self.icon = icon
}
}
super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: width, height: Constants.Widget.height - (2*Constants.Widget.margin)))
self.title = widgetTitle
self.type = .speed
@@ -172,23 +182,27 @@ public class SpeedWidget: Widget {
private func drawChars(_ dirtyRect: NSRect) {
let rowHeight: CGFloat = self.frame.height / 2
let downloadAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: downloadValue >= 1_024 ? NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8) : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
var rect = CGRect(x: Constants.Widget.margin, y: 1, width: 8, height: rowHeight)
var str = NSAttributedString.init(string: "D", attributes: downloadAttributes)
str.draw(with: rect)
if self.symbols.count > 1 {
let downloadAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: downloadValue >= 1_024 ? NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8) : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let rect = CGRect(x: Constants.Widget.margin, y: 1, width: 8, height: rowHeight)
let str = NSAttributedString.init(string: self.symbols[1], attributes: downloadAttributes)
str.draw(with: rect)
}
let uploadAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: uploadValue >= 1_024 ? NSColor.red : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
rect = CGRect(x: Constants.Widget.margin, y: rect.height+1, width: 8, height: rowHeight)
str = NSAttributedString.init(string: "U", attributes: uploadAttributes)
str.draw(with: rect)
if self.symbols.count > 0 {
let uploadAttributes = [
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
NSAttributedString.Key.foregroundColor: uploadValue >= 1_024 ? NSColor.red : NSColor.textColor,
NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
]
let rect = CGRect(x: Constants.Widget.margin, y: rowHeight+1, width: 8, height: rowHeight)
let str = NSAttributedString.init(string: self.symbols[0], attributes: uploadAttributes)
str.draw(with: rect)
}
}
public override func settings(superview: NSView) {
@@ -255,11 +269,11 @@ public class SpeedWidget: Widget {
var updated: Bool = false
if self.downloadValue != download {
self.downloadValue = download
self.downloadValue = abs(download)
updated = true
}
if self.uploadValue != upload {
self.uploadValue = upload
self.uploadValue = abs(upload)
updated = true
}

View File

@@ -234,11 +234,11 @@ public class ProcessReader: Reader<[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

17
Modules/Disk/Bridge.h Normal file
View File

@@ -0,0 +1,17 @@
//
// Bridge.h
// Disk
//
// Created by Serhiy Mytrovtsiy on 07/08/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
#ifndef Bridge_h
#define Bridge_h
#include <libproc.h>
#endif /* Bridge_h */

View File

@@ -68,6 +68,20 @@
<key>Order</key>
<integer>2</integer>
</dict>
<key>speed</key>
<dict>
<key>Default</key>
<false/>
<key>Order</key>
<integer>3</integer>
<key>Icon</key>
<string>Character</string>
<key>Symbols</key>
<array>
<string>W</string>
<string>R</string>
</array>
</dict>
</dict>
</dict>
</plist>

View File

@@ -63,9 +63,15 @@ struct DiskList: value_t {
}
}
public struct IO {
var read: Int = 0
var write: Int = 0
}
public class Disk: Module {
private let popupView: Popup = Popup()
private var capacityReader: CapacityReader? = nil
private var ioReader: IOReader? = nil
private var settingsView: Settings
private var selectedDisk: String = ""
@@ -83,12 +89,17 @@ public class Disk: Module {
self.capacityReader?.store = store
self.selectedDisk = store.pointee.string(key: "\(self.config.name)_disk", defaultValue: self.selectedDisk)
self.ioReader = IOReader()
self.capacityReader?.readyCallback = { [unowned self] in
self.readyHandler()
}
self.capacityReader?.callbackHandler = { [unowned self] value in
self.capacityCallback(value: value)
}
self.ioReader?.callbackHandler = { [unowned self] value in
self.ioCallback(value: value)
}
self.settingsView.selectedDiskHandler = { [unowned self] value in
self.selectedDisk = value
@@ -104,6 +115,9 @@ public class Disk: Module {
if let reader = self.capacityReader {
self.addReader(reader)
}
if let reader = self.ioReader {
self.addReader(reader)
}
}
private func capacityCallback(value: DiskList?) {
@@ -137,4 +151,14 @@ public class Disk: Module {
widget.setValue((free, usedSpace))
}
}
private func ioCallback(value: IO?) {
if value == nil {
return
}
if let widget = self.widget as? SpeedWidget {
widget.setValue(upload: Int64(value!.write), download: Int64(value!.read))
}
}
}

View File

@@ -12,6 +12,8 @@
import Cocoa
import ModuleKit
import StatsKit
import IOKit
import Darwin
internal class CapacityReader: Reader<DiskList> {
private var disks: DiskList = DiskList()
@@ -124,3 +126,46 @@ internal class CapacityReader: Reader<DiskList> {
}
}
}
// https://gist.github.com/kainjow/0e7650cc797a52261e0f4ba851477c2f
internal class IOReader: Reader<IO> {
public var stats: IO = IO()
public override func read() {
let initialNumPids = proc_listallpids(nil, 0)
let buffer = UnsafeMutablePointer<pid_t>.allocate(capacity: Int(initialNumPids))
defer {
buffer.deallocate()
}
let bufferLength = initialNumPids * Int32(MemoryLayout<pid_t>.size)
let numPids = proc_listallpids(buffer, bufferLength)
var read: Int = 0
var write: Int = 0
for i in 0..<numPids {
let pid = buffer[Int(i)]
var usage = rusage_info_current()
let result = withUnsafeMutablePointer(to: &usage) {
$0.withMemoryRebound(to: rusage_info_t?.self, capacity: 1) {
proc_pid_rusage(pid, RUSAGE_INFO_CURRENT, $0)
}
}
if result == kIOReturnSuccess {
read += Int(usage.ri_diskio_bytesread)
write += Int(usage.ri_diskio_byteswritten)
}
}
if self.stats.read != 0 && self.stats.write != 0 {
self.stats.read = read - self.stats.read
self.stats.write = write - self.stats.write
}
self.callback(self.stats)
self.stats.read = read
self.stats.write = write
}
}

View File

@@ -53,16 +53,19 @@ internal class Settings: NSView, Settings_v {
self.subviews.forEach{ $0.removeFromSuperview() }
let rowHeight: CGFloat = 30
let num: CGFloat = widget != .speed ? 3 : 1
self.addSubview(SelectTitleRow(
frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * 2, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight),
title: "Update interval",
action: #selector(changeUpdateInterval),
items: self.listOfUpdateIntervals.map{ "\($0) sec" },
selected: "\(self.updateIntervalValue) sec"
))
self.addDiskSelector()
if widget != .speed {
self.addSubview(SelectTitleRow(
frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * 2, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight),
title: "Update interval",
action: #selector(changeUpdateInterval),
items: self.listOfUpdateIntervals.map{ "\($0) sec" },
selected: "\(self.updateIntervalValue) sec"
))
self.addDiskSelector()
}
self.addSubview(ToggleTitleRow(
frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin + (rowHeight + Constants.Settings.margin) * 0, width: self.frame.width - (Constants.Settings.margin*2), height: rowHeight),
@@ -71,7 +74,7 @@ internal class Settings: NSView, Settings_v {
state: self.removableState
))
self.setFrameSize(NSSize(width: self.frame.width, height: rowHeight*3 + (Constants.Settings.margin*4)))
self.setFrameSize(NSSize(width: self.frame.width, height: rowHeight*num + (Constants.Settings.margin*(num+1))))
}
private func addDiskSelector() {

View File

@@ -14,6 +14,11 @@
<true/>
<key>Order</key>
<integer>0</integer>
<key>Symbols</key>
<array>
<string>U</string>
<string>D</string>
</array>
</dict>
</dict>
</dict>

View File

@@ -70,6 +70,7 @@
9AA64260244B274200416A33 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6425F244B274200416A33 /* popup.swift */; };
9AA64262244B57C800416A33 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA64261244B57C800416A33 /* settings.swift */; };
9AA64264244B94F300416A33 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA64263244B94F300416A33 /* LineChart.swift */; };
9AA95A6424DD7C6300801B69 /* Bridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AA95A6324DD7C6300801B69 /* Bridge.h */; };
9AABEAE4243FB13500668CB0 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
9AABEAE5243FB13500668CB0 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9AABEAEA243FB15E00668CB0 /* module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEAE9243FB15E00668CB0 /* module.swift */; };
@@ -451,6 +452,7 @@
9AA6425F244B274200416A33 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = "<group>"; };
9AA64261244B57C800416A33 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = "<group>"; };
9AA64263244B94F300416A33 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = "<group>"; };
9AA95A6324DD7C6300801B69 /* Bridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Bridge.h; sourceTree = "<group>"; };
9AABEADD243FB13500668CB0 /* ModuleKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ModuleKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9AABEAE0243FB13500668CB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9AABEAE9243FB15E00668CB0 /* module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = module.swift; sourceTree = "<group>"; };
@@ -808,6 +810,7 @@
9AB7FD7B246B48DB00387FDA /* settings.swift */,
9AF9EE0524648751005D2270 /* Info.plist */,
9AF9EE12246492E8005D2270 /* config.plist */,
9AA95A6324DD7C6300801B69 /* Bridge.h */,
);
path = Disk;
sourceTree = "<group>";
@@ -869,6 +872,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
9AA95A6424DD7C6300801B69 /* Bridge.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2214,6 +2218,7 @@
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = Modules/Disk/Bridge.h;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -2246,6 +2251,7 @@
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = Modules/Disk/Bridge.h;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";

View File

@@ -103,21 +103,21 @@ public class SMCService {
var result: kern_return_t
var iterator: io_iterator_t = 0
let device: io_object_t
let matchingDictionary: CFMutableDictionary = IOServiceMatching("AppleSMC")
result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator)
if (result != kIOReturnSuccess) {
print("Error IOServiceGetMatchingServices(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
return
}
device = IOIteratorNext(iterator)
IOObjectRelease(iterator)
if (device == 0) {
print("Error IOIteratorNext(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
return
}
result = IOServiceOpen(device, mach_task_self_, 0, &conn)
IOObjectRelease(device)
if (result != kIOReturnSuccess) {

View File

@@ -180,6 +180,8 @@ public class SystemKit {
}
}
}
IOObjectRelease(device)
}
}

View File

@@ -156,6 +156,16 @@ extension String: LocalizedError {
public func matches(_ regex: String) -> Bool {
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
public func removedRegexMatches(pattern: String, replaceWith: String = "") -> String {
do {
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
let range = NSMakeRange(0, self.count)
return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith)
} catch {
return self
}
}
}
public extension Int {