142 lines
5.9 KiB
Swift
142 lines
5.9 KiB
Swift
import Foundation
|
|
import Localization
|
|
|
|
public struct History<Element: Sendable>: Sendable {
|
|
public let capacity: Int
|
|
public private(set) var elements: [Element] = []
|
|
|
|
public init(capacity: Int) {
|
|
self.capacity = max(1, capacity)
|
|
elements.reserveCapacity(self.capacity)
|
|
}
|
|
|
|
public mutating func append(_ element: Element) {
|
|
if elements.count == capacity { elements.removeFirst() }
|
|
elements.append(element)
|
|
}
|
|
}
|
|
|
|
public enum Format {
|
|
public enum ByteBase: Sendable {
|
|
case binary // 内存:1 GB = 1024³,与活动监视器一致
|
|
case decimal // 磁盘:1 GB = 1000³,与访达一致
|
|
}
|
|
|
|
private static let units = ["B", "KB", "MB", "GB", "TB", "PB"]
|
|
|
|
/// 逻辑核心数(性能核与能效核合计),进程 CPU 换算成整机占比时用
|
|
public static let logicalCores = max(1, ProcessInfo.processInfo.activeProcessorCount)
|
|
|
|
/// 进程 CPU 按单核满载为 100% 的写法(活动监视器、top 的算法),多线程进程可以超过 100%。例:99.5%
|
|
public static func coreShare(_ value: Double) -> String {
|
|
"\((max(0, value) * 100).formatted(.number.precision(.fractionLength(1))))%"
|
|
}
|
|
|
|
/// 进程 CPU 占整机的比例:全部核心跑满为 100%,和系统总占用是同一把尺子。`value` 是单核口径。
|
|
/// 小于 0.1% 但不为 0 时写 “<0.1%”,不把正在跑的进程显示成 0
|
|
public static func machineShare(_ value: Double) -> String {
|
|
let share = max(0, value) / Double(logicalCores) * 100
|
|
if share > 0, share < 0.05 { return "<0.1%" }
|
|
return "\(share.formatted(.number.precision(.fractionLength(1))))%"
|
|
}
|
|
|
|
public static func bytes(_ value: UInt64, base: ByteBase = .binary) -> String {
|
|
bytes(Double(value), base: base)
|
|
}
|
|
|
|
public static func bytes(_ value: Double, base: ByteBase = .binary) -> String {
|
|
let step: Double = base == .binary ? 1024 : 1000
|
|
var amount = max(0, value)
|
|
var index = 0
|
|
while amount >= step, index < units.count - 1 {
|
|
amount /= step
|
|
index += 1
|
|
}
|
|
if index == 0 { return "\(Int(amount)) B" }
|
|
let digits = amount >= 100 ? 0 : 1
|
|
return "\(amount.formatted(.number.precision(.fractionLength(digits)))) \(units[index])"
|
|
}
|
|
|
|
/// 菜单栏网速:保留完整单位,数字最多 3 位,宽度稳定
|
|
/// 例:512 B/s、12 KB/s、154 KB/s、1.2 MB/s、12 MB/s、1.1 GB/s
|
|
public static func menuBarRate(_ bytesPerSecond: Double) -> String {
|
|
let units = ["B/s", "KB/s", "MB/s", "GB/s"]
|
|
var value = max(0, bytesPerSecond)
|
|
var index = 0
|
|
// 超过 999 就进位,避免出现 1023 KB/s 这种 4 位数
|
|
while value >= 999.5, index < units.count - 1 {
|
|
value /= 1024
|
|
index += 1
|
|
}
|
|
let text = index >= 2 && value < 9.95
|
|
? value.formatted(.number.precision(.fractionLength(1)))
|
|
: String(Int(value.rounded()))
|
|
return "\(text) \(units[index])"
|
|
}
|
|
|
|
/// 测速用的带宽:按 1000 进位,单位与运营商、测速站一致
|
|
/// 例:820 kbps、95.3 Mbps、1.21 Gbps
|
|
public static func bandwidth(_ bitsPerSecond: Double) -> String {
|
|
let units = ["bps", "kbps", "Mbps", "Gbps"]
|
|
var value = max(0, bitsPerSecond)
|
|
guard value >= 1 else { return "0 bps" }
|
|
var index = 0
|
|
while value >= 1000, index < units.count - 1 {
|
|
value /= 1000
|
|
index += 1
|
|
}
|
|
let digits = value >= 100 ? 0 : (value >= 10 ? 1 : 2)
|
|
return "\(value.formatted(.number.precision(.fractionLength(digits)))) \(units[index])"
|
|
}
|
|
|
|
public static func percent(_ fraction: Double) -> String {
|
|
"\(Int((min(1, max(0, fraction)) * 100).rounded()))%"
|
|
}
|
|
|
|
public static func temperature(_ celsius: Double, fahrenheit: Bool = false) -> String {
|
|
fahrenheit ? "\(Int((celsius * 9 / 5 + 32).rounded()))°F" : "\(Int(celsius.rounded()))°C"
|
|
}
|
|
|
|
public static func watts(_ value: Double) -> String {
|
|
value < 10 ? "\(value.formatted(.number.precision(.fractionLength(1)))) W" : "\(Int(value.rounded())) W"
|
|
}
|
|
|
|
/// 频率:MHz 输入,1000 以上显示为 GHz
|
|
public static func frequency(megahertz value: Double) -> String {
|
|
value >= 1000 ? "\((value / 1000).formatted(.number.precision(.fractionLength(2)))) GHz" : "\(Int(value.rounded())) MHz"
|
|
}
|
|
|
|
public static func rpm(_ value: Double) -> String {
|
|
"\(Int(value.rounded())) RPM"
|
|
}
|
|
|
|
public static func duration(minutes: Int) -> String {
|
|
let hours = minutes / 60
|
|
let rest = minutes % 60
|
|
switch (hours, rest) {
|
|
case (0, _): return tr("\(rest) 分钟")
|
|
case (_, 0): return tr("\(hours) 小时")
|
|
default: return tr("\(hours) 小时 \(rest) 分钟")
|
|
}
|
|
}
|
|
|
|
/// CPU 时间:不到 1 分钟显示“12.34 秒”,不到 1 小时“12:34”,更长“1:02:03”
|
|
public static func cpuTime(_ seconds: Double) -> String {
|
|
let total = max(0, seconds)
|
|
if total < 60 { return tr("\(total.formatted(.number.precision(.fractionLength(2)))) 秒") }
|
|
let whole = Int(total)
|
|
let hours = whole / 3600, minutes = whole % 3600 / 60, secs = whole % 60
|
|
return hours > 0 ? String(format: "%d:%02d:%02d", hours, minutes, secs) : String(format: "%d:%02d", minutes, secs)
|
|
}
|
|
|
|
/// `compact` 只保留最大的单位:“1 天 14 小时” → “1 天”
|
|
public static func uptime(since date: Date, now: Date = Date(), compact: Bool = false) -> String {
|
|
let minutes = max(0, Int(now.timeIntervalSince(date) / 60))
|
|
let days = minutes / (60 * 24)
|
|
let hours = (minutes / 60) % 24
|
|
if days > 0 { return hours > 0 && !compact ? tr("\(days) 天 \(hours) 小时") : tr("\(days) 天") }
|
|
if hours > 0 { return tr("\(hours) 小时") }
|
|
return tr("\(minutes) 分钟")
|
|
}
|
|
}
|