215 lines
9.7 KiB
Swift
215 lines
9.7 KiB
Swift
import AppKit
|
|
import Foundation
|
|
import SwiftUI
|
|
import Testing
|
|
|
|
@testable import Termind
|
|
|
|
@Suite
|
|
struct MetricsProcessTests {
|
|
@Test
|
|
func procStatHandlesParenthesesAndUsesRemotePageSize() throws {
|
|
let source = """
|
|
cpu 100 0 0 900 0 0 0 0 0 0
|
|
cpu0 50 0 0 450 0 0 0 0 0 0
|
|
cpu1 50 0 0 450 0 0 0 0 0 0
|
|
pagesize 4096
|
|
__TERMIND_PROCESSES__
|
|
\(stat(pid: 42, name: "worker (a)", user: 10, system: 5, started: 100, pages: 25))
|
|
"""
|
|
let result = try LinuxProcessParser.parse(source)
|
|
#expect(result.coreCount == 2)
|
|
let process = try #require(result.processes.first)
|
|
#expect(process.pid == 42 && process.name == "worker (a)")
|
|
#expect(process.memory == 102_400.0)
|
|
#expect(process.ticks == 15 && process.started == 100)
|
|
}
|
|
|
|
@Test
|
|
func processCPUIsPerCoreAndPIDReuseDoesNotInheritCounters() {
|
|
let previous = MetricsProcessSample(
|
|
cpu: [100, 0, 0, 900, 0, 0, 0, 0], coreCount: 2,
|
|
processes: [
|
|
MetricsProcessCounter(pid: 1, name: "first", ticks: 10, started: 1),
|
|
MetricsProcessCounter(pid: 2, name: "reused", ticks: 10, started: 1),
|
|
])
|
|
let current = MetricsProcessSample(
|
|
cpu: [200, 0, 0, 900, 0, 0, 0, 0], coreCount: 2,
|
|
processes: [
|
|
MetricsProcessCounter(pid: 2, name: "new", ticks: 200, started: 2),
|
|
MetricsProcessCounter(pid: 1, name: "first", ticks: 85, started: 1),
|
|
])
|
|
let rows = MetricsProcess.rows(previous: previous, current: current)
|
|
#expect(rows.first?.pid == 1)
|
|
#expect(rows.first?.cpu == 150)
|
|
#expect(rows.last?.cpu == nil)
|
|
#expect(MetricsProcess.rows(previous: nil, current: current).allSatisfy { $0.cpu == nil })
|
|
}
|
|
|
|
@Test
|
|
func exitedMalformedAndUnreadableProcessesDoNotFailTheSnapshot() throws {
|
|
let source = """
|
|
cpu 1 0 0 10 0 0 0 0
|
|
cpu0 1 0 0 10 0 0 0 0
|
|
pagesize unknown
|
|
__TERMIND_PROCESSES__
|
|
invalid
|
|
2 (gone) R
|
|
\(stat(pid: 42, name: "valid", user: 1, system: 0, started: 1, pages: 3))
|
|
"""
|
|
let sample = try LinuxProcessParser.parse(source)
|
|
#expect(sample.processes.count == 1)
|
|
#expect(sample.processes.first?.memory == nil)
|
|
#expect(throws: MetricsReadError.self) { try LinuxProcessParser.parse("missing procfs") }
|
|
}
|
|
|
|
private func stat(pid: Int, name: String, user: Int, system: Int, started: Int, pages: Int) -> String {
|
|
var fields = Array(repeating: "0", count: 22)
|
|
fields[0] = "R"
|
|
fields[11] = String(user)
|
|
fields[12] = String(system)
|
|
fields[19] = String(started)
|
|
fields[21] = String(pages)
|
|
return "\(pid) (\(name)) " + fields.joined(separator: " ")
|
|
}
|
|
}
|
|
|
|
@Suite
|
|
@MainActor
|
|
struct MetricsPresentationTests {
|
|
@Test
|
|
func connectionAndMetricsBodiesKeepTheSameCardHeight() throws {
|
|
let model = ServerMetricsModel(
|
|
hosts: HostLibraryModel(), preferences: PreviewMetricsPreferences(),
|
|
makeSampler: { SSHMetricsSampler(session: RemoteConnectionCoordinator().makeExecSession()) },
|
|
discover: { _, _, _ in throw MetricsReadError.output }
|
|
)
|
|
let server = ServerMetric(selection: MetricsSelection(id: UUID()), name: "Fixture")
|
|
func height() throws -> CGFloat {
|
|
let renderer = ImageRenderer(
|
|
content: ServerMetricsCard(server: server, model: model, compact: true).frame(width: 350))
|
|
return try #require(renderer.nsImage).size.height
|
|
}
|
|
let connecting = try height()
|
|
server.isConnecting = false
|
|
server.status = String(localized: "Connected — collecting metrics…")
|
|
#expect(try height() == connecting)
|
|
server.point = MetricsPoint(
|
|
date: Date(), cpu: 20, memory: 50, diskRead: 0, diskWrite: 12, networkReceive: 10, networkSend: 5)
|
|
server.status = String(localized: "Live")
|
|
#expect(try height() == connecting)
|
|
server.selection.enabled = false
|
|
server.status = String(localized: "Paused")
|
|
#expect(try height() == connecting)
|
|
}
|
|
|
|
@Test
|
|
func diskColumnsRenderAcrossTheFullAvailableWidth() throws {
|
|
let io = MetricsDevicePoint(
|
|
id: "8:1", read: 0, write: 0, readTotal: 438.4 * 1_073_741_824,
|
|
writeTotal: 625.2 * 1_073_741_824, readIOPS: 0, writeIOPS: 0
|
|
)
|
|
for width in [246.0, 300.0] {
|
|
let renderer = ImageRenderer(
|
|
content: MetricsDiskTable(io: io).frame(width: width)
|
|
.environment(\.locale, Locale(identifier: "zh-Hans")))
|
|
renderer.scale = 2
|
|
let image = try #require(renderer.nsImage)
|
|
#expect(image.size.width == CGFloat(width))
|
|
#expect(image.size.height == 52)
|
|
let bitmap = try #require(image.tiffRepresentation.flatMap { NSBitmapImageRep(data: $0) })
|
|
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
|
try png.write(
|
|
to: FileManager.default.temporaryDirectory.appending(path: "termind-disk-table-\(Int(width)).png"))
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func nvidiaDetailsRenderWithTwoSimulatedDevicesWithoutConnecting() throws {
|
|
let gib = 1_073_741_824.0
|
|
let server = ServerMetric(selection: MetricsSelection(id: UUID()), name: "GPU Preview")
|
|
server.isConnecting = false
|
|
server.gpuState = .available
|
|
server.gpuSnapshot = NvidiaSnapshot(
|
|
cudaVersion: "12.4",
|
|
devices: [
|
|
NvidiaGPU(
|
|
id: "GPU-00000000-0000-0000-0000-000000000001", name: "NVIDIA GPU 0",
|
|
utilization: 74, memoryUsed: 17.3 * gib, memoryTotal: 24 * gib, memoryFree: 6.7 * gib,
|
|
memoryUtilization: 51, encoder: 12, decoder: 0, fan: 45, vbios: "95.02.3C.00.01"
|
|
),
|
|
NvidiaGPU(
|
|
id: "GPU-00000000-0000-0000-0000-000000000002", name: "NVIDIA GPU 1",
|
|
utilization: 18, memoryUsed: 4.1 * gib, memoryTotal: 24 * gib, memoryFree: 19.9 * gib,
|
|
memoryUtilization: 9, encoder: 0, decoder: 0, fan: nil, vbios: "95.02.3C.00.01"
|
|
),
|
|
])
|
|
for language in ["en", "zh-Hans"] {
|
|
for expanded in [false, true] {
|
|
let content = MetricsGPUSection(server: server, initiallyExpanded: expanded)
|
|
.frame(width: 324, alignment: .leading)
|
|
.padding(12).background(TermindColor.windowSurface)
|
|
.environment(\.locale, Locale(identifier: language))
|
|
.environment(\.colorScheme, .light)
|
|
let renderer = ImageRenderer(content: content)
|
|
renderer.scale = 2
|
|
var image: NSImage?
|
|
NSAppearance(named: .aqua)?.performAsCurrentDrawingAppearance { image = renderer.nsImage }
|
|
let bitmap = try #require(image?.tiffRepresentation.flatMap { NSBitmapImageRep(data: $0) })
|
|
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
|
#expect(bitmap.pixelsWide == 696)
|
|
#expect(bitmap.pixelsHigh > 600)
|
|
try png.write(
|
|
to: FileManager.default.temporaryDirectory.appending(
|
|
path: "termind-gpu-preview-\(language)\(expanded ? "-expanded" : "").png"))
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func compactCardsRenderInBothLanguagesAndAppearances() throws {
|
|
let hosts = HostLibraryModel()
|
|
let model = ServerMetricsModel(
|
|
hosts: hosts, preferences: PreviewMetricsPreferences(),
|
|
makeSampler: { SSHMetricsSampler(session: RemoteConnectionCoordinator().makeExecSession()) },
|
|
discover: { _, _, _ in throw MetricsReadError.output }
|
|
)
|
|
let server = ServerMetric(selection: MetricsSelection(id: UUID()), name: "ElasticSearch")
|
|
server.isConnecting = false
|
|
server.status = ""
|
|
server.point = MetricsPoint(
|
|
date: Date(), cpu: 94, memory: 65, memoryUsed: 2_400_000_000, memoryTotal: 4_000_000_000,
|
|
diskRead: 89_000_000, diskWrite: 161_000, networkReceive: 32_000, networkSend: 18_000
|
|
)
|
|
server.gpuState = .unavailable
|
|
for language in ["en", "zh-Hans"] {
|
|
for dark in [false, true] {
|
|
let content = ServerMetricsCard(
|
|
server: server, model: model, compact: true, inspect: {}, openTerminal: {}
|
|
)
|
|
.frame(width: 350).padding(14)
|
|
.background(TermindColor.windowSurface)
|
|
.environment(\.locale, Locale(identifier: language))
|
|
.environment(\.colorScheme, dark ? .dark : .light)
|
|
let renderer = ImageRenderer(content: content)
|
|
renderer.scale = 2
|
|
var image: NSImage?
|
|
NSAppearance(named: dark ? .darkAqua : .aqua)?.performAsCurrentDrawingAppearance {
|
|
image = renderer.nsImage
|
|
}
|
|
let bitmap = try #require(image?.tiffRepresentation.flatMap { NSBitmapImageRep(data: $0) })
|
|
let png = try #require(bitmap.representation(using: .png, properties: [:]))
|
|
#expect(bitmap.pixelsWide >= 700)
|
|
try png.write(
|
|
to: FileManager.default.temporaryDirectory.appending(
|
|
path: "termind-metrics-\(language)-\(dark ? "dark" : "light").png"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private final class PreviewMetricsPreferences: MetricsConfigurationStoring {
|
|
func load() -> [MetricsSelection] { [] }
|
|
func save(_ selections: [MetricsSelection]) {}
|
|
}
|