diag(client/macos): 把 /group/delay 原始响应塞进 diag,定位 urltest 为空
v47 诊断:clash 注入生效(组名 auto 已识别),但 /group/auto/delay 返回无数字延迟。
本次把该接口原始响应体(含 HTTP 状态码)透出到 diag,并兼容嵌套 {tag:{delay:ms}} 形态,
一眼区分:内核探测失败(raw={} / 错误消息)还是返回结构不符(解析问题)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user