From 86f0b4bd20cdec86dfb5bdb7978d1f6ff4af111a Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 14 Aug 2026 22:41:54 +0800
Subject: [PATCH] [verified] feat: export performance diagnostics
---
C1.source/Sources/Aligner/PerformanceDiagnosticsExporter.swift | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 157 insertions(+), 0 deletions(-)
diff --git a/C1.source/Sources/Aligner/PerformanceDiagnosticsExporter.swift b/C1.source/Sources/Aligner/PerformanceDiagnosticsExporter.swift
new file mode 100644
index 0000000..1aea448
--- /dev/null
+++ b/C1.source/Sources/Aligner/PerformanceDiagnosticsExporter.swift
@@ -0,0 +1,157 @@
+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)
+ }
+}
--
Gitblit v1.9.3