import AppKit
|
import Darwin
|
import Foundation
|
import AlignerCore
|
|
@MainActor
|
final class PerformanceDiagnosticsExporter {
|
func export(
|
version: String,
|
build: String,
|
preferences: AlignerPreferences,
|
accessibility: String,
|
screenRecording: String,
|
quickSwitch: [String: Any]
|
) {
|
let savePanel = NSSavePanel()
|
savePanel.title = "导出性能诊断包"
|
savePanel.nameFieldStringValue = "Aligner-Diagnostics-\(fileTimestamp()).zip"
|
savePanel.allowedContentTypes = [.zip]
|
savePanel.canCreateDirectories = true
|
|
NSApp.activate(ignoringOtherApps: true)
|
guard savePanel.runModal() == .OK, let destinationURL = savePanel.url else {
|
DevelopmentDiagnostics.log("diagnostics.export.cancelled")
|
return
|
}
|
|
let summaryData: Data
|
do {
|
summaryData = try makeSummaryData(
|
version: version,
|
build: build,
|
preferences: preferences,
|
accessibility: accessibility,
|
screenRecording: screenRecording,
|
quickSwitch: quickSwitch
|
)
|
} catch {
|
showFailure(error)
|
return
|
}
|
|
let logURLs = DevelopmentDiagnostics.logURLs
|
DevelopmentDiagnostics.logSync("diagnostics.export.start", ["logFileCount": logURLs.count])
|
DispatchQueue.global(qos: .utility).async {
|
do {
|
_ = try PerformanceDiagnosticArchiveWriter.write(
|
to: destinationURL,
|
summaryJSON: summaryData,
|
eventLogURLs: logURLs
|
)
|
DispatchQueue.main.async {
|
DevelopmentDiagnostics.log("diagnostics.export.succeeded", [
|
"archiveCreated": true
|
])
|
self.showSuccess(destinationURL)
|
}
|
} catch {
|
DispatchQueue.main.async {
|
DevelopmentDiagnostics.log("diagnostics.export.failed", DevelopmentDiagnostics.errorSummaryFields(error))
|
self.showFailure(error)
|
}
|
}
|
}
|
}
|
|
private func makeSummaryData(
|
version: String,
|
build: String,
|
preferences: AlignerPreferences,
|
accessibility: String,
|
screenRecording: String,
|
quickSwitch: [String: Any]
|
) throws -> Data {
|
let processInfo = ProcessInfo.processInfo
|
let systemVersion = processInfo.operatingSystemVersion
|
let displaySummaries = NSScreen.screens.map { screen in
|
[
|
"frame": NSStringFromRect(screen.frame),
|
"visibleFrame": NSStringFromRect(screen.visibleFrame),
|
"scale": screen.backingScaleFactor,
|
"isMain": screen == NSScreen.main
|
] as [String: Any]
|
}
|
let summary: [String: Any] = [
|
"schemaVersion": 1,
|
"collectedAt": ISO8601DateFormatter().string(from: Date()),
|
"app": [
|
"name": "Aligner",
|
"version": version,
|
"build": build,
|
"bundleIdentifier": Bundle.main.bundleIdentifier ?? ""
|
],
|
"system": [
|
"model": hardwareModel(),
|
"osVersion": processInfo.operatingSystemVersionString,
|
"osVersionComponents": "\(systemVersion.majorVersion).\(systemVersion.minorVersion).\(systemVersion.patchVersion)",
|
"processorCount": processInfo.processorCount,
|
"activeProcessorCount": processInfo.activeProcessorCount,
|
"physicalMemoryBytes": processInfo.physicalMemory,
|
"displays": displaySummaries
|
],
|
"permissions": [
|
"accessibility": accessibility,
|
"screenRecording": screenRecording
|
],
|
"preferences": [
|
"showMinimizedWindows": preferences.showMinimizedWindows,
|
"showFullscreenWindows": preferences.showFullscreenWindows,
|
"waterfallViewMode": preferences.quickSwitchWaterfallViewMode.rawValue,
|
"theme": preferences.theme.rawValue,
|
"language": preferences.language.rawValue,
|
"spaceDisplayStrategy": preferences.spaceDisplayStrategy.rawValue
|
],
|
"quickSwitch": quickSwitch,
|
"collection": [
|
"includesApplicationNames": true,
|
"includesWindowCounts": true,
|
"includesWindowTitles": false,
|
"includesScreenshots": false,
|
"includesTypedText": false
|
]
|
]
|
return try JSONSerialization.data(withJSONObject: summary, options: [.prettyPrinted, .sortedKeys])
|
}
|
|
private func showSuccess(_ destinationURL: URL) {
|
let alert = NSAlert()
|
alert.messageText = "性能诊断包已导出"
|
alert.informativeText = "请把 \(destinationURL.lastPathComponent) 发给 Aligner 支持人员。"
|
alert.alertStyle = .informational
|
alert.addButton(withTitle: "好")
|
alert.runModal()
|
}
|
|
private func showFailure(_ error: Error) {
|
let alert = NSAlert(error: error)
|
alert.messageText = "无法导出性能诊断包"
|
alert.runModal()
|
}
|
|
private func fileTimestamp() -> String {
|
let formatter = DateFormatter()
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
formatter.dateFormat = "yyyyMMdd-HHmmss"
|
return formatter.string(from: Date())
|
}
|
|
private func hardwareModel() -> String {
|
var size = 0
|
guard sysctlbyname("hw.model", nil, &size, nil, 0) == 0, size > 0 else { return "unknown" }
|
var buffer = [CChar](repeating: 0, count: size)
|
guard sysctlbyname("hw.model", &buffer, &size, nil, 0) == 0 else { return "unknown" }
|
let bytes = buffer.prefix { $0 != 0 }.map { UInt8(bitPattern: $0) }
|
return String(decoding: bytes, as: UTF8.self)
|
}
|
}
|