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/AlignerCore/Diagnostics/PerformanceDiagnosticArchiveWriter.swift | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 154 insertions(+), 0 deletions(-)
diff --git a/C1.source/Sources/AlignerCore/Diagnostics/PerformanceDiagnosticArchiveWriter.swift b/C1.source/Sources/AlignerCore/Diagnostics/PerformanceDiagnosticArchiveWriter.swift
new file mode 100644
index 0000000..31200ef
--- /dev/null
+++ b/C1.source/Sources/AlignerCore/Diagnostics/PerformanceDiagnosticArchiveWriter.swift
@@ -0,0 +1,154 @@
+import Foundation
+
+public enum PerformanceDiagnosticArchiveWriter {
+ public static func write(
+ to destinationURL: URL,
+ summaryJSON: Data,
+ eventLogURLs: [URL],
+ fileManager: FileManager = .default
+ ) throws -> URL {
+ guard !fileManager.fileExists(atPath: destinationURL.path) else {
+ throw ArchiveError.destinationAlreadyExists
+ }
+ let workingDirectory = fileManager.temporaryDirectory
+ .appendingPathComponent("Aligner-Diagnostics-\(UUID().uuidString)", isDirectory: true)
+ let contentsDirectory = workingDirectory
+ .appendingPathComponent("Aligner-Diagnostics", isDirectory: true)
+
+ try fileManager.createDirectory(at: contentsDirectory, withIntermediateDirectories: true)
+ defer { try? fileManager.removeItem(at: workingDirectory) }
+
+ try summaryJSON.write(
+ to: contentsDirectory.appendingPathComponent("summary.json"),
+ options: [.atomic]
+ )
+ try readmeText.write(
+ to: contentsDirectory.appendingPathComponent("README.txt"),
+ atomically: true,
+ encoding: .utf8
+ )
+
+ var copiedLogCount = 0
+ for (index, sourceURL) in eventLogURLs.enumerated() where fileManager.fileExists(atPath: sourceURL.path) {
+ let name = index == 0 ? "performance-events.log" : "performance-events-previous.log"
+ try sanitizedEventLogData(from: sourceURL).write(
+ to: contentsDirectory.appendingPathComponent(name),
+ options: [.atomic]
+ )
+ copiedLogCount += 1
+ }
+
+ if copiedLogCount == 0 {
+ try "No performance events have been recorded yet. Reproduce the issue, then export again.\n".write(
+ to: contentsDirectory.appendingPathComponent("performance-events.log"),
+ atomically: true,
+ encoding: .utf8
+ )
+ }
+
+ let temporaryArchiveURL = destinationURL
+ .deletingLastPathComponent()
+ .appendingPathComponent(".Aligner-Diagnostics-\(UUID().uuidString).zip")
+ defer { try? fileManager.removeItem(at: temporaryArchiveURL) }
+ try createArchive(
+ sourceDirectory: contentsDirectory,
+ destinationURL: temporaryArchiveURL
+ )
+ try fileManager.moveItem(at: temporaryArchiveURL, to: destinationURL)
+ return destinationURL
+ }
+
+ private static func createArchive(sourceDirectory: URL, destinationURL: URL) throws {
+ let process = Process()
+ process.executableURL = URL(fileURLWithPath: "/usr/bin/ditto")
+ process.arguments = [
+ "-c",
+ "-k",
+ "--sequesterRsrc",
+ "--keepParent",
+ sourceDirectory.path,
+ destinationURL.path
+ ]
+ try process.run()
+ process.waitUntilExit()
+ guard process.terminationStatus == 0 else {
+ throw ArchiveError.dittoFailed(status: process.terminationStatus)
+ }
+ }
+
+ private static func sanitizedEventLogData(from sourceURL: URL) throws -> Data {
+ let source = try String(contentsOf: sourceURL, encoding: .utf8)
+ let sanitized = source
+ .split(whereSeparator: \.isNewline)
+ .compactMap(sanitizedEventLine)
+ .joined(separator: "\n")
+ return Data((sanitized.isEmpty ? "No exportable performance events were recorded.\n" : sanitized + "\n").utf8)
+ }
+
+ private static func sanitizedEventLine(_ line: Substring) -> String? {
+ let source = String(line)
+ guard let event = tokenValue(named: "event", in: source) else { return nil }
+
+ let allowedKeys = [
+ "ts",
+ "event",
+ "pid",
+ "runID",
+ "generation",
+ "durationMilliseconds",
+ "overlayOpenElapsedMilliseconds",
+ "startElapsedMilliseconds",
+ "snapshotStartElapsedMilliseconds",
+ "lastActivationDurationMilliseconds",
+ "appCount",
+ "windowCount",
+ "displayCount",
+ "spaceCount",
+ "screenshotEligibleCount",
+ "screenshotResolvedCount",
+ "screenshotPendingCount",
+ "screenshotNotRequestedCount",
+ "ranOnMainThread",
+ "result",
+ "reason"
+ ]
+ let values = allowedKeys.compactMap { key -> String? in
+ let value = key == "event" ? event : tokenValue(named: key, in: source)
+ return value.map { "\(key)=\($0)" }
+ }
+ return values.isEmpty ? nil : values.joined(separator: " ")
+ }
+
+ private static func tokenValue(named key: String, in line: String) -> String? {
+ let prefix = "\(key)="
+ guard let range = line.range(of: prefix) else { return nil }
+ let suffix = line[range.upperBound...]
+ guard let first = suffix.first, first != "\"" else { return nil }
+ let value = suffix.prefix { !$0.isWhitespace }
+ return value.isEmpty ? nil : String(value)
+ }
+
+ private static let readmeText = """
+ Aligner performance diagnostics
+
+ This archive contains local performance information for troubleshooting:
+ - summary.json: app version, system, display, permissions, preferences, recent Quick Switch timings, application names, and window counts.
+ - performance-events.log: recent performance events with a fixed allowlist of timing and count fields.
+
+ It does not include window titles, screenshots, or typed text.
+ """
+
+ private enum ArchiveError: LocalizedError {
+ case destinationAlreadyExists
+ case dittoFailed(status: Int32)
+
+ var errorDescription: String? {
+ switch self {
+ case .destinationAlreadyExists:
+ return "A diagnostic archive already exists at the selected location."
+ case .dittoFailed(let status):
+ return "Failed to create the diagnostic archive (ditto exit status \(status))."
+ }
+ }
+ }
+}
--
Gitblit v1.9.3