diff --git a/README.md b/README.md index 4649b47..4f7876e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,114 @@ This package builds with Swift Package Manager and is part of the [Perfect](http Ensure you have installed and activated the latest Swift 3.1 tool chain. +## Quick Start + +Add Perfect SysInfo library to your Package.swift: + +``` swift + +.Package(url: "https://github.com/PerfectlySoft/Perfect-SysInfo.git", majorVersion: 1) + +``` + +Add library header to your source code: + +``` swift + +import PerfectSysInfo + +``` + +Now SysInfo class is available to call. + +### CPU Usage + +Call static variable `SysInfo.CPU` will return a dictionary `[String: [String: Int]]` of all CPU usage, for example: + +``` swift + +print(SysInfo.CPU) + +/* +here is a typical return of single CPU: + +[ + "cpu0": + ["nice": 1201, "system": 3598, "user": 8432, "idle": 8657606], + "cpu": + ["nice": 1201, "system": 3598, "user": 8432, "idle": 8657606] +] + +and the following is another example with 8 cores: +[ + "cpu3": + ["user": 18095, "idle": 9708265, "nice": 0, "system": 16177], + "cpu5": + ["user": 18032, "idle": 9708329, "nice": 0, "system": 16079], + "cpu7": + ["user": 18186, "idle": 9707892, "nice": 0, "system": 16285], + "cpu": + ["user": 344301, "idle": 9201762, "nice": 0, "system": 196763], + "cpu0": + ["user": 730263, "idle": 8387000, "nice": 0, "system": 626684], + "cpu2": + ["user": 648287, "idle": 8799969, "nice": 0, "system": 294749], + "cpu1": + ["user": 17708, "idle": 9708996, "nice": 0, "system": 15950], + "cpu4": + ["user": 647701, "idle": 8800643, "nice": 0, "system": 294544], + "cpu6": ["user": 656136, "idle": 8793002, "nice": 0, "system": 293640]] +*/ + +``` + +The record is a structure of `N+1` entries, where `N` is the number of CPU and `1` is the summary, so the each record will be tagged with "cpu0" ... "cpuN-1" and the tag "cpu" represents the average of overall. Each entries will contain `idle`, `user`, `system` and `nice` to represent the cpu usage time. In a common sense, `idle` shall be as large as possible to indicate the CPU is not busy. + +### Memory Usage + +Call static property `SysInfo.Memory` will return a dictionary `[String: Int]` with memory metrics in ** MB **: + +``` swift + +print(SysInfo.Memory) + +``` + +** Note ** Since system information is subject to operating system type, so please use directive `#if os(Linux) #else #endif` determine OS type before reading system metrics; The definition of each counter is out of the scope of this document, please see OS manual for detail. + +Typical Linux memory looks like this ( 1G total memory with about 599MB available): + +``` +[ + "Inactive": 283, "MemTotal": 992, "CmaFree": 0, + "VmallocTotal": 33554431, "CmaTotal": 0, "Mapped": 74, + "SUnreclaim": 14, "Writeback": 0, "Active(anon)": 98, + "Shmem": 26, "PageTables": 7, "VmallocUsed": 0, + "MemFree": 98, "Inactive(file)": 179, "SwapCached": 0, + "HugePages_Total": 0, "Inactive(anon)": 104, "HugePages_Rsvd": 0, + "Buffers": 21, "SReclaimable": 39, "Cached": 613, + "Mlocked": 3, "SwapTotal": 1021, "NFS_Unstable": 0, + "CommitLimit": 1518, "Hugepagesize": 2, "SwapFree": 1016, + "WritebackTmp": 0, "Committed_AS": 1410, "AnonHugePages": 130, + "DirectMap2M": 966, "Unevictable": 3, "HugePages_Surp": 0, + "Dirty": 3, "HugePages_Free": 0, "MemAvailable": 599, + "Active(file)": 426, "Slab": 54, "Active": 525, + "KernelStack": 2, "VmallocChunk": 0, "AnonPages": 177, + "Bounce": 0, "HardwareCorrupted": 0, "DirectMap4k": 57 +] +``` + +And here is a typical mac OS X memory summary, which indicates that there is about 4.5GB free memory: + +``` +[ + "hits": 0, "faults": 3154324, "cow": 31476, + "wired": 3576, "reactivations": 366, "zero_filled": 2296248, + "pageins": 13983, "lookups": 1021, "pageouts": 0, + "active": 6967, "free": 4455, "inactive": 1008 +] + +``` ## Issues diff --git a/README.zh_CN.md b/README.zh_CN.md index 1455788..97c6c9f 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -45,6 +45,118 @@ 请确保您的系统已经安装了Swift 3.1工具链。 + +## 快速上手 + +首先请在您的 Package.swift 文件中增加依存关系: + +``` swift + +.Package(url: "https://github.com/PerfectlySoft/Perfect-SysInfo.git", majorVersion: 1) + +``` + +在源程序中导入函数库: + +``` swift + +import PerfectSysInfo + +``` + +之后您就可以使用SysInfo 类函数了。 + +### CPU 用量 + +调用系统静态变量 `SysInfo.CPU` 可以获得所有处理器内核用量,返回结果为字典形式`[String: [String: Int]]`,举例如下: + +``` swift + +print(SysInfo.CPU) + +/* +典型的单核处理器统计结果: + +[ + "cpu0": + ["nice": 1201, "system": 3598, "user": 8432, "idle": 8657606], + "cpu": + ["nice": 1201, "system": 3598, "user": 8432, "idle": 8657606] +] + +下面是另外一个例子,8核处理器统计结果: +[ + "cpu3": + ["user": 18095, "idle": 9708265, "nice": 0, "system": 16177], + "cpu5": + ["user": 18032, "idle": 9708329, "nice": 0, "system": 16079], + "cpu7": + ["user": 18186, "idle": 9707892, "nice": 0, "system": 16285], + "cpu": + ["user": 344301, "idle": 9201762, "nice": 0, "system": 196763], + "cpu0": + ["user": 730263, "idle": 8387000, "nice": 0, "system": 626684], + "cpu2": + ["user": 648287, "idle": 8799969, "nice": 0, "system": 294749], + "cpu1": + ["user": 17708, "idle": 9708996, "nice": 0, "system": 15950], + "cpu4": + ["user": 647701, "idle": 8800643, "nice": 0, "system": 294544], + "cpu6": ["user": 656136, "idle": 8793002, "nice": 0, "system": 293640]] +*/ + +``` + +返回的字典为 `N+1` 组数据,其中 N是内核数,命名分别为"cpu0"、"cpu1" ... "cpu N-1",单独的命名"cpu"为所有内核平均综合结果。 +每组数据都会包括 `idle`(空闲)、`user`(用户)、`system`(系统)和`nice`(调度)四个统计值。通常`idle`应该尽量大一些,表示系统不是那么繁忙。 + +### 内存用量 + +调用静态属性 `SysInfo.Memory` 会返回另外一个字典`[String: Int]` 用于表示内存用量,单位是 ** MB (兆字节)**: + +``` swift + +print(SysInfo.Memory) + +``` + +** 注意 ** 由于该信息会因操作系统而表述方法不一样,因此请在调用前用条件编译选项`#if os(Linux) #else #endif`来判定操作系统类型。每个指标定义已经超出本文的范围,详细内容请参考具体的操作系统使用手册。 + +比如调用上述命令后,一个典型的Linux系统将输出如下报告(1G内存,大约599兆可用): + +``` +[ + "Inactive": 283, "MemTotal": 992, "CmaFree": 0, + "VmallocTotal": 33554431, "CmaTotal": 0, "Mapped": 74, + "SUnreclaim": 14, "Writeback": 0, "Active(anon)": 98, + "Shmem": 26, "PageTables": 7, "VmallocUsed": 0, + "MemFree": 98, "Inactive(file)": 179, "SwapCached": 0, + "HugePages_Total": 0, "Inactive(anon)": 104, "HugePages_Rsvd": 0, + "Buffers": 21, "SReclaimable": 39, "Cached": 613, + "Mlocked": 3, "SwapTotal": 1021, "NFS_Unstable": 0, + "CommitLimit": 1518, "Hugepagesize": 2, "SwapFree": 1016, + "WritebackTmp": 0, "Committed_AS": 1410, "AnonHugePages": 130, + "DirectMap2M": 966, "Unevictable": 3, "HugePages_Surp": 0, + "Dirty": 3, "HugePages_Free": 0, "MemAvailable": 599, + "Active(file)": 426, "Slab": 54, "Active": 525, + "KernelStack": 2, "VmallocChunk": 0, "AnonPages": 177, + "Bounce": 0, "HardwareCorrupted": 0, "DirectMap4k": 57 +] +``` + +而下面则是一个典型的mac OS X内存摘要,看起来还有 4.5GB 空闲内存: + +``` +[ + "hits": 0, "faults": 3154324, "cow": 31476, + "wired": 3576, "reactivations": 366, "zero_filled": 2296248, + "pageins": 13983, "lookups": 1021, "pageouts": 0, + "active": 6967, "free": 4455, "inactive": 1008 +] + +``` + + ### 问题报告、内容贡献和客户支持 我们目前正在过渡到使用JIRA来处理所有源代码资源合并申请、修复漏洞以及其它有关问题。因此,GitHub 的“issues”问题报告功能已经被禁用了。 diff --git a/Sources/PerfectSysInfo.swift b/Sources/PerfectSysInfo.swift index 01104dd..e6e1e0b 100644 --- a/Sources/PerfectSysInfo.swift +++ b/Sources/PerfectSysInfo.swift @@ -22,9 +22,9 @@ import SwiftGlibc import Darwin #endif -public extension String { +extension String { - public var trimmed: String { + internal var trimmed: String { var buf = [UInt8]() var trimming = true for c in self.utf8 { @@ -40,7 +40,7 @@ public extension String { }//end trim /// split a string into an array of lines - public var asLines: [String] { + internal var asLines: [String] { get { return self.utf8 .split(separator: 10) @@ -50,11 +50,11 @@ public extension String { } /// a quick buffer size definition - public static let szSTR = 4096 + internal static let szSTR = 4096 /// treat the string as a file name and get the content by this name, /// will return nil if failed - public var asFile: String? { + internal var asFile: String? { get { guard let f = fopen(self, "r") else { return nil } var content = [Int8]() @@ -79,7 +79,7 @@ public extension String { /// - prefix: the prefix string to looking for /// - returns: /// true if the string has such a prefix - public func match(prefix: String) -> Bool { + internal func match(prefix: String) -> Bool { if prefix == self { return true } guard prefix.utf8.count > 0, self.utf8.count > prefix.utf8.count, @@ -96,14 +96,14 @@ public extension String { /// - parameters: /// - definition: an array for the expected string definition, each element is a name/type pair, which type only means string or non-string simply because only string needs quote in output /// - returns: dictionary - public func parse(definition: [(keyName: String, isString: Bool )]) -> [String: String] { + internal func parse(definition: [(keyName: String, isString: Bool )]) -> [String: String] { let values = self.utf8.split(separator: 32).map { String($0) ?? "" }.filter { !$0.isEmpty } let size = min(values.count, definition.count) guard size > 0 else { return [:] } var content: [String: String] = [:] for i in 0 ... size - 1 { let key = definition[i].keyName - let value = definition[i].isString ? "\"\(values[i])\"" : values[i] + let value = values[i] content[key] = value }//next i return content @@ -112,22 +112,37 @@ public extension String { public class SysInfo { - public static var CPU: String? { + /// return physical CPU information + public static var CPU: [String: [String: Int]] { get { #if os(Linux) - guard let content = "/proc/stat".asFile else { return nil } - let lines = content.asLines.filter { $0.match(prefix: "cpu") } + let definition: [(keyName: String, isString: Bool)] + = [("name", true), + ("user", false), ("nice", false), + ("system", false), ("idle", false)] + guard let content = "/proc/stat".asFile else { return [:] } + let array = content.asLines.filter { $0.match(prefix: "cpu") } + .map { $0.parse(definition: definition) } + var lines: [String: [String: Int]] = [:] + for item in array { + guard let title = item["name"] else { continue } + var stat: [String: Int] = [:] + for (k,v) in (item.filter { $0.key != "name" }) { + stat[k] = Int(v) ?? 0 + }//next + lines[title] = stat + }//next #else var pCPULoadArray = processor_info_array_t(bitPattern: 0) var processorMsgCount = mach_msg_type_name_t() var processorCount = natural_t() - var totalUser = UInt32(0) - var totalIdle = UInt32(0) - var totalSystem = UInt32(0) - var totalNice = UInt32(0) + var totalUser = 0 + var totalIdle = 0 + var totalSystem = 0 + var totalNice = 0 guard 0 == host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &processorCount, &pCPULoadArray, &processorMsgCount), let cpuLoadArray = pCPULoadArray - else { return nil } + else { return [:] } //print(CPU_STATE_MAX, CPU_STATE_IDLE, CPU_STATE_NICE, CPU_STATE_USER, CPU_STATE_SYSTEM) //4 2 3 0 1 let cpuLoad = cpuLoadArray.withMemoryRebound( @@ -137,60 +152,64 @@ public class SysInfo { ptr -> processor_cpu_load_info_t in return ptr } - var lines = [String]() + var lines: [String:[String:Int]] = [:] let count = Int(processorCount) for i in 0 ... count - 1 { - let user = cpuLoad[i].cpu_ticks.0 - let system = cpuLoad[i].cpu_ticks.1 - let idle = cpuLoad[i].cpu_ticks.2 - let nice = cpuLoad[i].cpu_ticks.3 - lines.append("cpu\(i) \(user) \(nice) \(system) \(idle)") + let user = Int(cpuLoad[i].cpu_ticks.0) + let system = Int(cpuLoad[i].cpu_ticks.1) + let idle = Int(cpuLoad[i].cpu_ticks.2) + let nice = Int(cpuLoad[i].cpu_ticks.3) + lines["cpu\(i)"] = ["user": user, "system": system, "idle": idle, "nice": nice] totalUser += user totalSystem += system totalIdle += idle totalNice += nice }//next - let cnt = UInt32(count) - totalUser /= cnt - totalSystem /= cnt - totalIdle /= cnt - totalNice /= cnt - lines.append("cpu \(totalUser) \(totalNice) \(totalSystem) \(totalIdle)") + munmap(cpuLoadArray, Int(vm_page_size)) + totalUser /= count + totalSystem /= count + totalIdle /= count + totalNice /= count + lines["cpu"] = ["user": totalUser, "system": totalSystem, "idle": totalIdle, "nice": totalNice] #endif - let definition: [(keyName: String, isString: Bool)] - = [("name", true), - ("user", false), ("nice", false), - ("system", false), ("idle", false)] - let json = lines.map { $0.parse(definition: definition) } - .map { stat -> String in - guard let title = stat["name"] else { return "" } - let res = stat.filter { $0.key != "name" }.map { k, v in - return "\"\(k)\":\(v)" - }.joined(separator: ",") - return "\(title):{\(res)}" - }.joined(separator: ",") - return "{\(json)}" + return lines } } - public static var Memory: String? { + /// return Metrics of Physical Memory, each counter in Megabytes + public static var Memory: [String: Int] { get { #if os(Linux) - guard let content = "/proc/meminfo".asFile else { return nil } - let json = content.utf8.split(separator: 10).map { line -> String in - let lines = line.split(separator: 58).map { String(describing: $0) } - let key = lines[0] - guard lines.count > 1, let str = strdup(lines[1]) else { return "" } - if let kb = strstr(str, "kB") { - kb.pointee = 0 - }//end if - let value = String(cString: str).trimmed - free(str) - return "\"\(key)\":\(value)" - }.joined(separator: ",") - return "{\(json)}" + guard let content = "/proc/meminfo".asFile else { return [:] } + var stat:[String: Int] = [:] + content.utf8.split(separator: 10).forEach { line in + let lines = line.split(separator: 58).map { String(describing: $0) } + let key = lines[0] + guard lines.count > 1, let str = strdup(lines[1]) else { return } + if let kb = strstr(str, "kB") { + kb.pointee = 0 + }//end if + let value = String(cString: str).trimmed + stat[key] = (Int(value) ?? 0) / 1024 + } + return stat #else - return nil + let size = MemoryLayout.size / MemoryLayout.size + let pStat = UnsafeMutablePointer.allocate(capacity: size) + var stat: [String: Int] = [:] + var count = mach_msg_type_number_t(size) + if 0 == host_statistics(mach_host_self(), HOST_VM_INFO, pStat, &count){ + let array = Array(UnsafeBufferPointer(start: pStat, count: size)) + let tags = ["free", "active", "inactive", "wired", "zero_filled", "reactivations", "pageins", "pageouts", "faults", "cow", "lookups", "hits"] + let cnt = min(tags.count, array.count) + for i in 0 ... cnt - 1 { + let key = tags[i] + let value = array[i] + stat[key] = Int(value) / 256 + }//next i + }//end if + pStat.deallocate(capacity: size) + return stat #endif } } diff --git a/Tests/PerfectSysInfoTests/PerfectSysInfoTests.swift b/Tests/PerfectSysInfoTests/PerfectSysInfoTests.swift index 3e9a89a..bc7f511 100644 --- a/Tests/PerfectSysInfoTests/PerfectSysInfoTests.swift +++ b/Tests/PerfectSysInfoTests/PerfectSysInfoTests.swift @@ -20,21 +20,21 @@ import XCTest @testable import PerfectSysInfo class PerfectSysInfoTests: XCTestCase { - func testExample() { - guard let cpu = SysInfo.CPU else { - XCTFail("CPU FAULT") - return - } - print(cpu) - guard let mem = SysInfo.Memory else { - XCTFail("MEM FAULT") - return - } - print(mem) + func testExample() { + let cpu = SysInfo.CPU + guard cpu.count > 0 else { + XCTFail("CPU FAULT") + return } - - - static var allTests = [ - ("testExample", testExample), + print(cpu) + let mem = SysInfo.Memory + guard mem.count > 0 else { + XCTFail("MEM FAULT") + return + } + print(mem) + } + static var allTests = [ + ("testExample", testExample), ] }