diff --git a/client/macos/PacketTunnel/PacketTunnelProvider.swift b/client/macos/PacketTunnel/PacketTunnelProvider.swift index 682cf73..f32fe49 100644 --- a/client/macos/PacketTunnel/PacketTunnelProvider.swift +++ b/client/macos/PacketTunnel/PacketTunnelProvider.swift @@ -448,12 +448,13 @@ final class StatsCollector: NSObject { private func pollClash() { if groupNames.isEmpty { - clashGet("/proxies", query: nil) { [weak self] obj in + clashGet("/proxies", query: nil) { [weak self] obj, raw in guard let self else { return } let names = StatsCollector.extractUrltestGroups(obj) self.queue.async { self.groupNames = names - self.setDiag("groups=\(names)") + if names.isEmpty { self.setDiag("groups=[] raw=\(raw.prefix(120))") } + else { self.setDiag("groups=\(names)") } self.triggerGroupDelays() } } @@ -465,19 +466,27 @@ final class StatsCollector: NSObject { private func triggerGroupDelays() { for name in groupNames { clashGet("/group/\(name)/delay", - query: ["url": "http://www.gstatic.com/generate_204", "timeout": "3000"]) { [weak self] obj in - guard let self, let dict = obj as? [String: Any] else { return } + query: ["url": "http://www.gstatic.com/generate_204", "timeout": "3000"]) { [weak self] obj, raw in + guard let self else { return } var urltest: [(tag: String, delayMs: Int)] = [] - for (tag, v) in dict { - if let d = (v as? NSNumber)?.intValue { urltest.append((tag: tag, delayMs: d)) } + if let dict = obj as? [String: Any] { + for (tag, v) in dict { + if let d = (v as? NSNumber)?.intValue { + urltest.append((tag: tag, delayMs: d)) // 扁平 {tag:ms} + } else if let inner = v as? [String: Any], + let d = (inner["delay"] as? NSNumber)?.intValue { + urltest.append((tag: tag, delayMs: d)) // 嵌套 {tag:{delay:ms}} + } + } } - let summary = urltest.map { "\($0.tag):\($0.delayMs)" }.joined(separator: ",") - NSLog("[pangolin/collector] kernel delay(%@)=%@", name, summary) // 内核侧实测打点 + NSLog("[pangolin/collector] kernel delay(%@) n=%d raw=%@", name, urltest.count, raw) self.queue.async { self.lock.lock() - self.latestUrltest = urltest - self.latest["urltestResults"] = urltest.map { ["tag": $0.tag, "delayMs": $0.delayMs] } - self.latest["diag"] = "delay(\(name))=\(summary)" + if !urltest.isEmpty { + self.latestUrltest = urltest + self.latest["urltestResults"] = urltest.map { ["tag": $0.tag, "delayMs": $0.delayMs] } + } + self.latest["diag"] = "d(\(name)) n=\(urltest.count) raw=\(raw.prefix(120))" self.lock.unlock() } } @@ -489,19 +498,21 @@ final class StatsCollector: NSObject { } private func clashGet(_ path: String, query: [String: String]?, - completion: @escaping (Any?) -> Void) { - guard var comps = URLComponents(string: clashBase + path) else { completion(nil); return } + completion: @escaping (Any?, String) -> Void) { + guard var comps = URLComponents(string: clashBase + path) else { completion(nil, "badurl"); return } if let query { comps.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) } } - guard let url = comps.url else { completion(nil); return } + guard let url = comps.url else { completion(nil, "badurl"); return } var req = URLRequest(url: url) req.timeoutInterval = 8 if !clashSecret.isEmpty { req.setValue("Bearer \(clashSecret)", forHTTPHeaderField: "Authorization") } - URLSession.shared.dataTask(with: req) { data, _, err in - if let err { NSLog("[pangolin/collector] clash %@ err=%@", path, err.localizedDescription) } - guard let data else { completion(nil); return } - completion(try? JSONSerialization.jsonObject(with: data)) + URLSession.shared.dataTask(with: req) { data, resp, err in + if let err { completion(nil, "err:\(err.localizedDescription)"); return } + let code = (resp as? HTTPURLResponse)?.statusCode ?? -1 + guard let data else { completion(nil, "http\(code):nodata"); return } + let raw = String(data: data, encoding: .utf8) ?? "<\(data.count)B>" + completion(try? JSONSerialization.jsonObject(with: data), "http\(code):\(raw)") }.resume() } diff --git a/client/macos/Runner.xcodeproj/project.pbxproj b/client/macos/Runner.xcodeproj/project.pbxproj index a3e2a2e..1315f69 100644 --- a/client/macos/Runner.xcodeproj/project.pbxproj +++ b/client/macos/Runner.xcodeproj/project.pbxproj @@ -659,7 +659,7 @@ baseConfigurationReference = C57BBFD43F9175D1E23685EF /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests; @@ -674,7 +674,7 @@ baseConfigurationReference = F8904897A48DC81799B8752E /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests; @@ -689,7 +689,7 @@ baseConfigurationReference = B21E68FC1F5D33DD67A0DF5E /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests; @@ -960,7 +960,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = BYL4KQHMTN; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -1010,7 +1010,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = BYL4KQHMTN; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -1059,7 +1059,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 47; + CURRENT_PROJECT_VERSION = 48; DEVELOPMENT_TEAM = BYL4KQHMTN; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES;