diff --git a/Podfile b/Podfile new file mode 100644 index 00000000..b60c7612 --- /dev/null +++ b/Podfile @@ -0,0 +1,13 @@ +target 'Stats' do + use_frameworks! + + pod 'Charts' + +end + +target 'StatsLauncher' do + use_frameworks! + + # Pods for StatsLauncher + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 00000000..dc163430 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,18 @@ +PODS: + - Charts (3.3.0): + - Charts/Core (= 3.3.0) + - Charts/Core (3.3.0) + +DEPENDENCIES: + - Charts + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - Charts + +SPEC CHECKSUMS: + Charts: ec1f57f9340054155691e84d4544a1d239d382c5 + +PODFILE CHECKSUM: 1935eab6769b7093597e74f1286aedc1ea7c755a + +COCOAPODS: 1.7.1 diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj old mode 100755 new mode 100644 index 411d1bbe..5d8721e3 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -3,10 +3,12 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ + 556DD3DCB38374D039BECE89 /* Pods_StatsLauncher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B76EECB31DFC37E1EA558662 /* Pods_StatsLauncher.framework */; }; + 628D2DE0AAA753E9F47625B0 /* Pods_Stats.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */; }; 9A09C89E22B3A7C90018426F /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89D22B3A7C90018426F /* Battery.swift */; }; 9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89F22B3A7E20018426F /* BatteryReader.swift */; }; 9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C8A122B3D94D0018426F /* BatteryView.swift */; }; @@ -18,6 +20,8 @@ 9A57A19D22A1E3270033E318 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A19C22A1E3270033E318 /* CPU.swift */; }; 9A58D1B022C150C800405315 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58D1AF22C150C800405315 /* Network.swift */; }; 9A58D1B222C150D700405315 /* NetworkReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58D1B122C150D700405315 /* NetworkReader.swift */; }; + 9A59AE54231ED1AC007989D6 /* CPUView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE53231ED1AC007989D6 /* CPUView.swift */; }; + 9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE55231EE02F007989D6 /* ChartMarker.swift */; }; 9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CBE229E78F0008B9D3C /* Observable.swift */; }; 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */; }; 9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */; }; @@ -61,6 +65,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0BAA6D5F418C0F6999BD18BD /* Pods-StatsLauncher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatsLauncher.release.xcconfig"; path = "Target Support Files/Pods-StatsLauncher/Pods-StatsLauncher.release.xcconfig"; sourceTree = ""; }; + 1A1A00B8A6C7702FA7C3FD9A /* Pods-Stats.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stats.release.xcconfig"; path = "Target Support Files/Pods-Stats/Pods-Stats.release.xcconfig"; sourceTree = ""; }; + 2650ED499D83382C9C938E76 /* Pods-StatsLauncher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatsLauncher.debug.xcconfig"; path = "Target Support Files/Pods-StatsLauncher/Pods-StatsLauncher.debug.xcconfig"; sourceTree = ""; }; + 38B7BF640C895BAFBF1A44BE /* Pods-Stats.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stats.debug.xcconfig"; path = "Target Support Files/Pods-Stats/Pods-Stats.debug.xcconfig"; sourceTree = ""; }; + 56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stats.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9A09C89D22B3A7C90018426F /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; 9A09C89F22B3A7E20018426F /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = ""; }; 9A09C8A122B3D94D0018426F /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.swift; sourceTree = ""; }; @@ -75,6 +84,8 @@ 9A57A19C22A1E3270033E318 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = ""; }; 9A58D1AF22C150C800405315 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; 9A58D1B122C150D700405315 /* NetworkReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkReader.swift; sourceTree = ""; }; + 9A59AE53231ED1AC007989D6 /* CPUView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUView.swift; sourceTree = ""; }; + 9A59AE55231EE02F007989D6 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = ""; }; 9A5B1CBE229E78F0008B9D3C /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -105,6 +116,7 @@ 9AFA402A22AE49A200FE90BC /* StatsLauncher.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StatsLauncher.entitlements; sourceTree = ""; }; 9AFA402E22AE49AE00FE90BC /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = About.storyboard; sourceTree = ""; }; + B76EECB31DFC37E1EA558662 /* Pods_StatsLauncher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StatsLauncher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -112,6 +124,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 628D2DE0AAA753E9F47625B0 /* Pods_Stats.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -119,6 +132,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 556DD3DCB38374D039BECE89 /* Pods_StatsLauncher.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -141,6 +155,7 @@ 9AFA401F22AE49A100FE90BC /* StatsLauncher */, 9A1410F6229E721100D29793 /* Products */, 9A998CD622A199920087ADE7 /* Frameworks */, + A159F8578E30ECA1F2F6028E /* Pods */, ); sourceTree = ""; }; @@ -209,6 +224,7 @@ 9A5B1CBE229E78F0008B9D3C /* Observable.swift */, 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */, 9A426DB722C2B5EE00C064C4 /* macAppUpdater.swift */, + 9A59AE55231EE02F007989D6 /* ChartMarker.swift */, ); path = libs; sourceTree = ""; @@ -230,6 +246,7 @@ children = ( 9A57A19C22A1E3270033E318 /* CPU.swift */, 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */, + 9A59AE53231ED1AC007989D6 /* CPUView.swift */, ); path = CPU; sourceTree = ""; @@ -257,6 +274,8 @@ children = ( 9A998CD922A199970087ADE7 /* ServiceManagement.framework */, 9A998CD722A199920087ADE7 /* Cocoa.framework */, + 56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */, + B76EECB31DFC37E1EA558662 /* Pods_StatsLauncher.framework */, ); name = Frameworks; sourceTree = ""; @@ -303,6 +322,17 @@ path = StatsLauncher; sourceTree = ""; }; + A159F8578E30ECA1F2F6028E /* Pods */ = { + isa = PBXGroup; + children = ( + 38B7BF640C895BAFBF1A44BE /* Pods-Stats.debug.xcconfig */, + 1A1A00B8A6C7702FA7C3FD9A /* Pods-Stats.release.xcconfig */, + 2650ED499D83382C9C938E76 /* Pods-StatsLauncher.debug.xcconfig */, + 0BAA6D5F418C0F6999BD18BD /* Pods-StatsLauncher.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -310,10 +340,12 @@ isa = PBXNativeTarget; buildConfigurationList = 9A141105229E721200D29793 /* Build configuration list for PBXNativeTarget "Stats" */; buildPhases = ( + 813D45DAD934E69BA5C6A78F /* [CP] Check Pods Manifest.lock */, 9A1410F1229E721100D29793 /* Sources */, 9A1410F2229E721100D29793 /* Frameworks */, 9A1410F3229E721100D29793 /* Resources */, 9AB54DAE22A19F96006192E0 /* Copy Files */, + EDB5CB3173CB4B8BADA1278D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -328,6 +360,7 @@ isa = PBXNativeTarget; buildConfigurationList = 9AFA402B22AE49A200FE90BC /* Build configuration list for PBXNativeTarget "StatsLauncher" */; buildPhases = ( + 3AB22E5D98D11E0384E0FF21 /* [CP] Check Pods Manifest.lock */, 9AFA401A22AE49A100FE90BC /* Sources */, 9AFA401B22AE49A100FE90BC /* Frameworks */, 9AFA401C22AE49A100FE90BC /* Resources */, @@ -359,7 +392,7 @@ enabled = 0; }; com.apple.Sandbox = { - enabled = 1; + enabled = 0; }; }; }; @@ -416,6 +449,70 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 3AB22E5D98D11E0384E0FF21 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-StatsLauncher-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 813D45DAD934E69BA5C6A78F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Stats-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EDB5CB3173CB4B8BADA1278D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Stats/Pods-Stats-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Stats/Pods-Stats-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Stats/Pods-Stats-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 9A1410F1229E721100D29793 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -424,6 +521,7 @@ 9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */, 9A426DB822C2B5EE00C064C4 /* macAppUpdater.swift in Sources */, 9A79B36E22D3BEF900BF1C3A /* Reader.swift in Sources */, + 9A59AE54231ED1AC007989D6 /* CPUView.swift in Sources */, 9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */, 9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */, 9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */, @@ -446,6 +544,7 @@ 9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */, 9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */, 9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */, + 9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */, 9A74D59722B44498004FE1FA /* Mini.swift in Sources */, 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */, 9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */, @@ -600,6 +699,7 @@ }; 9A141106229E721200D29793 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 38B7BF640C895BAFBF1A44BE /* Pods-Stats.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Stats/Supporting Files/Stats.entitlements"; @@ -627,6 +727,7 @@ }; 9A141107229E721200D29793 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 1A1A00B8A6C7702FA7C3FD9A /* Pods-Stats.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Stats/Supporting Files/Stats.entitlements"; @@ -654,6 +755,7 @@ }; 9AFA402C22AE49A200FE90BC /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 2650ED499D83382C9C938E76 /* Pods-StatsLauncher.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = StatsLauncher/StatsLauncher.entitlements; @@ -676,6 +778,7 @@ }; 9AFA402D22AE49A200FE90BC /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0BAA6D5F418C0F6999BD18BD /* Pods-StatsLauncher.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = StatsLauncher/StatsLauncher.entitlements; diff --git a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme index b0eb1edf..144292f3 100644 --- a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme +++ b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme @@ -1,6 +1,6 @@ + + - - + + + + + + + + + + diff --git a/Stats.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Stats.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Stats.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift index 69046759..fbe3f198 100644 --- a/Stats/Modules/CPU/CPU.swift +++ b/Stats/Modules/CPU/CPU.swift @@ -7,23 +7,25 @@ // import Cocoa +import Charts class CPU: Module { - let name: String = "CPU" - let shortName: String = "CPU" - var view: NSView = NSView() - var menu: NSMenuItem = NSMenuItem() - var submenu: NSMenu = NSMenu() - var active: Observable - var available: Observable - var hyperthreading: Observable - var reader: Reader = CPUReader() - var tabView: NSTabViewItem = NSTabViewItem() + public let name: String = "CPU" + public let shortName: String = "CPU" + public var view: NSView = NSView() + public var menu: NSMenuItem = NSMenuItem() + public var active: Observable + public var available: Observable + public var hyperthreading: Observable + public var reader: Reader = CPUReader() + public var tabView: NSTabViewItem = NSTabViewItem() + public var viewAvailable: Bool = true + public var widgetType: WidgetType - var viewAvailable: Bool = true + public var chart: LineChartView = LineChartView() - let defaults = UserDefaults.standard - var widgetType: WidgetType + private let defaults = UserDefaults.standard + private var submenu: NSMenu = NSMenu() init() { self.available = Observable(true) @@ -42,24 +44,6 @@ class CPU: Module { initTab() } - func initTab() { - self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) - - let text: NSTextField = NSTextField(string: self.name) - text.isEditable = false - text.isSelectable = false - text.isBezeled = false - text.wantsLayer = true - text.textColor = .labelColor - text.canDrawSubviewsIntoLayer = true - text.alignment = .natural - text.font = NSFont.systemFont(ofSize: 13, weight: .regular) - text.frame.origin.x = ((self.tabView.view?.frame.size.width)! - 30) / 2 - text.frame.origin.y = ((self.tabView.view?.frame.size.height)! - 22) / 2 - - self.tabView.view?.addSubview(text) - } - func initMenu() { menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") submenu = NSMenu() diff --git a/Stats/Modules/CPU/CPUReader.swift b/Stats/Modules/CPU/CPUReader.swift index ad4e578c..1bbd3a23 100644 --- a/Stats/Modules/CPU/CPUReader.swift +++ b/Stats/Modules/CPU/CPUReader.swift @@ -8,23 +8,47 @@ import Foundation +struct CPUUsage { + var value: Double = 0 + var system: Double = 0 + var user: Double = 0 + var idle: Double = 0 +} + +struct CPUProcess { + var pid: Int = 0 + var command: String = "" + var usage: Double = 0 +} + class CPUReader: Reader { - var value: Observable<[Double]>! - var available: Bool = true - var cpuInfo: processor_info_array_t! - var prevCpuInfo: processor_info_array_t? - var numCpuInfo: mach_msg_type_number_t = 0 - var numPrevCpuInfo: mach_msg_type_number_t = 0 - var numCPUs: uint = 0 - var updateTimer: Timer! - let CPUUsageLock: NSLock = NSLock() + public var value: Observable<[Double]>! + public var usage: Observable = Observable(CPUUsage()) + public var processes: Observable<[CPUProcess]> = Observable([CPUProcess]()) + public var available: Bool = true + public var updateTimer: Timer! + public var perCoreMode: Bool = false + public var hyperthreading: Bool = true - var perCoreMode: Bool = false - var hyperthreading: Bool = true + private var cpuInfo: processor_info_array_t! + private var prevCpuInfo: processor_info_array_t? + private var numCpuInfo: mach_msg_type_number_t = 0 + private var numPrevCpuInfo: mach_msg_type_number_t = 0 + private var numCPUs: uint = 0 + private let CPUUsageLock: NSLock = NSLock() + private var loadPrevious = host_cpu_load_info() + + private var topProcess: Process = Process() + private var pipe: Pipe = Pipe() init() { let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ] self.value = Observable([]) + + self.topProcess.launchPath = "/usr/bin/top" + self.topProcess.arguments = ["-s", "1", "-o", "cpu", "-n", "5", "-stats", "pid,command,cpu"] + self.topProcess.standardOutput = pipe + mibKeys.withUnsafeBufferPointer() { mib in var sizeOfNumCPUs: size_t = MemoryLayout.size let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0) @@ -40,6 +64,43 @@ class CPUReader: Reader { return } updateTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(read), userInfo: nil, repeats: true) + + if topProcess.isRunning { + return + } + self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() + + NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: self.pipe.fileHandleForReading , queue: nil) { _ -> Void in + defer { + self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() + } + + let output = self.pipe.fileHandleForReading.availableData + if output.isEmpty { + return + } + + let outputString = String(data: output, encoding: String.Encoding.utf8) ?? "" + var processes: [CPUProcess] = [] + outputString.enumerateLines { (line, stop) -> () in + if line.matches("^\\d+ + .+ +\\d+.\\d *$") { + let arr = line.condenseWhitespace().split(separator: " ") + let pid = Int(arr[0]) ?? 0 + let command = String(arr[1]) + let usage = Double(arr[2]) ?? 0 + let process = CPUProcess(pid: pid, command: command, usage: usage) + processes.append(process) + } + } + + self.processes << processes + } + + do { + try topProcess.run() + } catch let error { + print(error) + } } func stop() { @@ -48,11 +109,14 @@ class CPUReader: Reader { } updateTimer.invalidate() updateTimer = nil + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil) } @objc func read() { var numCPUsU: natural_t = 0 let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo); + let usage = getUsage() + if err == KERN_SUCCESS { CPUUsageLock.lock() @@ -65,7 +129,7 @@ class CPUReader: Reader { incrementNumber = 2 } - for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber){ + for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber) { var inUse: Int32 var total: Int32 if let prevCpuInfo = prevCpuInfo { @@ -96,6 +160,9 @@ class CPUReader: Reader { } else { self.value << [(Double(inUseOnAllCores) / Double(totalOnAllCores))] } + if !usage.system.isNaN && !usage.user.isNaN && !usage.idle.isNaN { + self.usage << CPUUsage(value: Double(inUseOnAllCores) / Double(totalOnAllCores), system: usage.system, user: usage.user, idle: usage.idle) + } CPUUsageLock.unlock() @@ -113,4 +180,46 @@ class CPUReader: Reader { print("Error KERN_SUCCESS!") } } + + func hostCPULoadInfo() -> host_cpu_load_info? { + let HOST_CPU_LOAD_INFO_COUNT = MemoryLayout.stride/MemoryLayout.stride + var size = mach_msg_type_number_t(HOST_CPU_LOAD_INFO_COUNT) + var cpuLoadInfo = host_cpu_load_info() + + let result = withUnsafeMutablePointer(to: &cpuLoadInfo) { + $0.withMemoryRebound(to: integer_t.self, capacity: HOST_CPU_LOAD_INFO_COUNT) { + host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size) + } + } + if result != KERN_SUCCESS { + print("Error - \(#file): \(#function) - kern_result_t = \(result)") + return nil + } + return cpuLoadInfo + } + + public func getUsage() -> (system: Double, user: Double, idle : Double) { + let load = hostCPULoadInfo() + + let userDiff = Double(load!.cpu_ticks.0 - loadPrevious.cpu_ticks.0) + let sysDiff = Double(load!.cpu_ticks.1 - loadPrevious.cpu_ticks.1) + let idleDiff = Double(load!.cpu_ticks.2 - loadPrevious.cpu_ticks.2) + let niceDiff = Double(load!.cpu_ticks.3 - loadPrevious.cpu_ticks.3) + + let totalTicks = sysDiff + userDiff + niceDiff + idleDiff + + let sys = sysDiff / totalTicks * 100.0 + let user = userDiff / totalTicks * 100.0 + let idle = idleDiff / totalTicks * 100.0 + + self.loadPrevious = load! + + return (sys, user, idle) + } +} + +extension String { + func matches(_ regex: String) -> Bool { + return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil + } } diff --git a/Stats/Modules/CPU/CPUView.swift b/Stats/Modules/CPU/CPUView.swift new file mode 100644 index 00000000..1de0cb7a --- /dev/null +++ b/Stats/Modules/CPU/CPUView.swift @@ -0,0 +1,258 @@ +// +// CPUView.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 03/09/2019. +// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Foundation +import Charts + +extension CPU { + + func initTab() { + self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) + + makeChart() + makeOverview() + makeProcesses() + + (self.reader as! CPUReader).usage.subscribe(observer: self) { (value, _) in + let v: Double = Double((value.value * 100).roundTo(decimalPlaces: 2))! + self.updateChart(value: v) + } + } + + func makeChart() { + let lineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0) + let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5) + + self.chart = LineChartView(frame: CGRect(x: 0, y: TabHeight - 108, width: TabWidth, height: 100)) + self.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic) + self.chart.backgroundColor = .white + self.chart.noDataText = "No data about CPU usage" + self.chart.legend.enabled = false + self.chart.scaleXEnabled = false + self.chart.scaleYEnabled = false + self.chart.pinchZoomEnabled = false + self.chart.doubleTapToZoomEnabled = false + self.chart.drawBordersEnabled = false + + self.chart.rightAxis.enabled = false + + self.chart.leftAxis.axisMinimum = 0 + self.chart.leftAxis.axisMaximum = 100 + self.chart.leftAxis.labelCount = 6 + self.chart.leftAxis.drawGridLinesEnabled = false + self.chart.leftAxis.drawAxisLineEnabled = false + + self.chart.leftAxis.gridColor = NSColor(red:220/255, green:220/255, blue:220/255, alpha:1) + self.chart.leftAxis.gridLineWidth = 0.5 + self.chart.leftAxis.drawGridLinesEnabled = true + self.chart.leftAxis.labelTextColor = NSColor(red:150/255, green:150/255, blue:150/255, alpha:1) + + self.chart.xAxis.drawAxisLineEnabled = false + self.chart.xAxis.drawLimitLinesBehindDataEnabled = false + self.chart.xAxis.gridLineWidth = 0.5 + self.chart.xAxis.drawGridLinesEnabled = false + self.chart.xAxis.drawLabelsEnabled = false + + let marker = ChartMarker() + marker.chartView = self.chart + self.chart.marker = marker + + var lineChartEntry = [ChartDataEntry]() + + lineChartEntry.append(ChartDataEntry(x: 0, y: 50)) + lineChartEntry.append(ChartDataEntry(x: 1, y: 25)) + + let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "CPU Usage") + chartDataSet.drawCirclesEnabled = false + chartDataSet.mode = .cubicBezier + chartDataSet.cubicIntensity = 0.1 + chartDataSet.colors = [lineColor] + chartDataSet.fillColor = gradientColor + chartDataSet.drawFilledEnabled = true + + let data = LineChartData() + data.addDataSet(chartDataSet) + data.setDrawValues(false) + + self.chart.data = LineChartData(dataSet: chartDataSet) + + self.tabView.view?.addSubview(self.chart) + } + + func updateChart(value: Double) { + let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!) + self.chart.data?.addEntry(ChartDataEntry(x: index, y: value), dataSetIndex: 0) + + if index > 120 { + self.chart.xAxis.axisMinimum = index - 120 + } + self.chart.xAxis.axisMaximum = index + self.chart.notifyDataSetChanged() + self.chart.moveViewToX(index) + } + + func makeOverview() { + let overviewLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 140, width: TabWidth, height: 25)) + + overviewLabel.wantsLayer = true + overviewLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor + + let overviewText: NSTextField = NSTextField(string: "Overview") + overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: overviewLabel.frame.size.height - 5) + overviewText.isEditable = false + overviewText.isSelectable = false + overviewText.isBezeled = false + overviewText.wantsLayer = true + overviewText.textColor = .labelColor + overviewText.canDrawSubviewsIntoLayer = true + overviewText.alignment = .center + overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0) + overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium) + + overviewLabel.addSubview(overviewText) + self.tabView.view?.addSubview(overviewLabel) + + let stackHeight: CGFloat = 22 + let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 147, width: TabWidth, height: stackHeight*3)) + vertical.orientation = .vertical + + let system: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight)) + system.orientation = .horizontal + system.distribution = .equalCentering + let systemLabel = labelField(string: "System") + let systemValue = valueField(string: "0 %") + system.addView(systemLabel, in: .center) + system.addView(systemValue, in: .center) + + let user: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight)) + user.orientation = .horizontal + user.distribution = .equalCentering + let userLabel = labelField(string: "User") + let userValue = valueField(string: "0 %") + user.addView(userLabel, in: .center) + user.addView(userValue, in: .center) + + let idle: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight)) + idle.orientation = .horizontal + idle.distribution = .equalCentering + let idleLabel = labelField(string: "Idle") + let idleValue = valueField(string: "0 %") + idle.addView(idleLabel, in: .center) + idle.addView(idleValue, in: .center) + + vertical.addSubview(system) + vertical.addSubview(user) + vertical.addSubview(idle) + + self.tabView.view?.addSubview(vertical) + + (self.reader as! CPUReader).usage.subscribe(observer: self) { (value, _) in + systemValue.stringValue = "\(value.system.roundTo(decimalPlaces: 2)) %" + userValue.stringValue = "\(value.user.roundTo(decimalPlaces: 2)) %" + idleValue.stringValue = "\(value.idle.roundTo(decimalPlaces: 2)) %" + } + } + + func makeProcesses() { + let label: NSView = NSView(frame: NSRect(x: 0, y: 0, width: TabWidth, height: 25)) + + label.wantsLayer = true + label.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor + + let text: NSTextField = NSTextField(string: "Top Processes") + text.frame = NSRect(x: 0, y: 0, width: TabWidth, height: label.frame.size.height - 5) + text.isEditable = false + text.isSelectable = false + text.isBezeled = false + text.wantsLayer = true + text.textColor = .labelColor + text.canDrawSubviewsIntoLayer = true + text.alignment = .center + text.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0) + text.font = NSFont.systemFont(ofSize: 12, weight: .medium) + + label.addSubview(text) + self.tabView.view?.addSubview(label) + + let stackHeight: CGFloat = 22 + let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*5)) + vertical.orientation = .vertical + vertical.distribution = .fill + + var processViewList: [NSStackView] = [] + let process_1 = makeProcessView(num: 4, height: stackHeight, label: "", value: "") + let process_2 = makeProcessView(num: 3, height: stackHeight, label: "", value: "") + let process_3 = makeProcessView(num: 2, height: stackHeight, label: "", value: "") + let process_4 = makeProcessView(num: 1, height: stackHeight, label: "", value: "") + let process_5 = makeProcessView(num: 0, height: stackHeight, label: "", value: "") + + processViewList.append(process_1) + processViewList.append(process_2) + processViewList.append(process_3) + processViewList.append(process_4) + processViewList.append(process_5) + + vertical.addSubview(process_1) + vertical.addSubview(process_2) + vertical.addSubview(process_3) + vertical.addSubview(process_4) + vertical.addSubview(process_5) + self.tabView.view?.addSubview(vertical) + + label.frame = NSRect(x: 0, y: vertical.frame.origin.y + vertical.frame.size.height + 2, width: TabWidth, height: 25) + self.tabView.view?.addSubview(label) + + (self.reader as! CPUReader).processes.subscribe(observer: self) { (processes, _) in + for (i, process) in processes.enumerated() { + let processView = processViewList[i] + + (processView.subviews[0] as! NSTextField).stringValue = process.command + (processView.subviews[1] as! NSTextField).stringValue = "\(process.usage.roundTo(decimalPlaces: 2)) %" + } + } + } + + func makeProcessView(num: Int, height: CGFloat, label: String, value: String) -> NSStackView { + let view: NSStackView = NSStackView(frame: NSRect(x: 10, y: CGFloat(num)*height, width: TabWidth - 20, height: height)) + view.orientation = .horizontal + view.distribution = .equalCentering + let viewLabel = labelField(string: label) + let viewValue = valueField(string: value) + view.addView(viewLabel, in: .center) + view.addView(viewValue, in: .center) + + return view + } + + func labelField(string: String) -> NSTextField { + let label: NSTextField = NSTextField(string: string) + + label.isEditable = false + label.isSelectable = false + label.isBezeled = false + label.textColor = .labelColor + label.alignment = .center + label.font = NSFont.systemFont(ofSize: 12, weight: .regular) + + return label + } + + func valueField(string: String) -> NSTextField { + let label: NSTextField = NSTextField(string: string) + + label.isEditable = false + label.isSelectable = false + label.isBezeled = false + label.textColor = .black + label.alignment = .center + label.font = NSFont.systemFont(ofSize: 13, weight: .regular) + + return label + } +} diff --git a/Stats/Modules/Network/Network.swift b/Stats/Modules/Network/Network.swift index d5f9c186..f42b04a1 100644 --- a/Stats/Modules/Network/Network.swift +++ b/Stats/Modules/Network/Network.swift @@ -18,7 +18,7 @@ class Network: Module { var available: Observable var reader: Reader = NetworkReader() var widgetType: WidgetType = 2.0 - var viewAvailable: Bool = true + var viewAvailable: Bool = false var tabView: NSTabViewItem = NSTabViewItem() let defaults = UserDefaults.standard diff --git a/Stats/Supporting Files/Stats.entitlements b/Stats/Supporting Files/Stats.entitlements index 92624a8a..2eb7e333 100755 --- a/Stats/Supporting Files/Stats.entitlements +++ b/Stats/Supporting Files/Stats.entitlements @@ -2,13 +2,7 @@ - com.apple.security.app-sandbox - com.apple.security.application-groups - com.apple.security.files.user-selected.read-only - - com.apple.security.network.client - diff --git a/Stats/Views/MainViewController.swift b/Stats/Views/MainViewController.swift index aa5fcf9a..4232c072 100644 --- a/Stats/Views/MainViewController.swift +++ b/Stats/Views/MainViewController.swift @@ -9,8 +9,8 @@ import Cocoa import ServiceManagement -let TabWidth: CGFloat = 300 -let TabHeight: CGFloat = 356 +public let TabWidth: CGFloat = 300 +public let TabHeight: CGFloat = 356 class MainViewController: NSViewController { let defaults = UserDefaults.standard diff --git a/Stats/libs/ChartMarker.swift b/Stats/libs/ChartMarker.swift new file mode 100644 index 00000000..3070293f --- /dev/null +++ b/Stats/libs/ChartMarker.swift @@ -0,0 +1,41 @@ +// +// ChartMarker.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 03/09/2019. +// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Foundation +import Charts + +class ChartMarker: MarkerView { + var text = "" + + override func refreshContent(entry: ChartDataEntry, highlight: Highlight) { + super.refreshContent(entry: entry, highlight: highlight) + text = String(entry.y) + } + + override func draw(context: CGContext, point: CGPoint) { + super.draw(context: context, point: point) + + var drawAttributes = [NSAttributedString.Key : Any]() + drawAttributes[.font] = NSFont.systemFont(ofSize: 13) + drawAttributes[.foregroundColor] = NSColor.white + drawAttributes[.backgroundColor] = NSColor.darkGray + + self.bounds.size = (" \(text) " as NSString).size(withAttributes: drawAttributes) + self.offset = CGPoint(x: 0, y: self.bounds.size.height) + + let offset = self.offsetForDrawing(atPoint: point) + drawText(text: " \(text) " as NSString, rect: CGRect(origin: CGPoint(x: point.x + offset.x, y: point.y + offset.y), size: self.bounds.size), withAttributes: drawAttributes) + } + + func drawText(text: NSString, rect: CGRect, withAttributes attributes: [NSAttributedString.Key : Any]? = nil) { + let size = text.size(withAttributes: attributes) + let centeredRect = CGRect(x: rect.origin.x + (rect.size.width - size.width) / 2.0, y: rect.origin.y + (rect.size.height - size.height) / 2.0, width: size.width, height: size.height) + text.draw(in: centeredRect, withAttributes: attributes) + } +} diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift index 3b047c10..7eda9133 100755 --- a/Stats/libs/Extensions.swift +++ b/Stats/libs/Extensions.swift @@ -141,3 +141,34 @@ extension NSBezierPath { self.line(to: arrowLine2) } } + +extension NSColor { + + convenience init(hexString: String, alpha: CGFloat = 1.0) { + let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let scanner = Scanner(string: hexString) + if (hexString.hasPrefix("#")) { + scanner.scanLocation = 1 + } + var color: UInt32 = 0 + scanner.scanHexInt32(&color) + let mask = 0x000000FF + let r = Int(color >> 16) & mask + let g = Int(color >> 8) & mask + let b = Int(color) & mask + let red = CGFloat(r) / 255.0 + let green = CGFloat(g) / 255.0 + let blue = CGFloat(b) / 255.0 + self.init(red:red, green:green, blue:blue, alpha:alpha) + } + + func toHexString() -> String { + var r:CGFloat = 0 + var g:CGFloat = 0 + var b:CGFloat = 0 + var a:CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 + return String(format:"#%06x", rgb) + } +} diff --git a/Stats/libs/Observable.swift b/Stats/libs/Observable.swift index 98ccd942..aa7f02d2 100755 --- a/Stats/libs/Observable.swift +++ b/Stats/libs/Observable.swift @@ -13,7 +13,6 @@ protocol ObservableProtocol { var value: T { get set } func subscribe(observer: AnyObject, block: @escaping (_ newValue: T, _ oldValue: T) -> ()) func unsubscribe(observer: AnyObject) - func userDefaults(key: String) } public final class Observable: ObservableProtocol { @@ -34,7 +33,6 @@ public final class Observable: ObservableProtocol { let (_, block) = entry block(value, oldValue) } - updateUserDefaults() } } @@ -51,14 +49,6 @@ public final class Observable: ObservableProtocol { observers = filtered } - - func userDefaults(key: String) { - self.userDefaultsKey = key - } - - func updateUserDefaults() { - self.defaults.set(self.value, forKey: self.userDefaultsKey) - } } func <<(observable: Observable, value: T) {