Skip to content

Commit 900db4c

Browse files
authored
Merge pull request #1628 from ahoppen/active-requests-debug-command
Add a debug subcommand that shows the requests that are currently being handled by SourceKit-LSP
2 parents dd9c0af + d3cfe3e commit 900db4c

13 files changed

+125
-25
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import Foundation
15+
import RegexBuilder
16+
import class TSCBasic.Process
17+
18+
package struct ActiveRequestsCommand: AsyncParsableCommand {
19+
package static let configuration: CommandConfiguration = CommandConfiguration(
20+
commandName: "active-requests",
21+
abstract: "Shows the requests that are currently being handled by sourcekit-lsp.",
22+
discussion: "This command only works on macOS."
23+
)
24+
25+
@Option(
26+
name: .customLong("log-file"),
27+
help: """
28+
Instead of reading the currently executing requests from recent system messages, read them from a log file, \
29+
generated by `log show`.
30+
"""
31+
)
32+
var logFile: String?
33+
34+
package init() {}
35+
36+
/// Read the last 3 minutes of OSLog output, including signpost messages.
37+
package func readOSLog() async throws -> String {
38+
var data = Data()
39+
let process = Process(
40+
arguments: [
41+
"/usr/bin/log",
42+
"show",
43+
"--last", "3m",
44+
"--predicate", #"subsystem = "org.swift.sourcekit-lsp.message-handling" AND process = "sourcekit-lsp""#,
45+
"--signpost",
46+
],
47+
outputRedirection: .stream(
48+
stdout: { data += $0 },
49+
stderr: { _ in }
50+
)
51+
)
52+
try process.launch()
53+
try await process.waitUntilExit()
54+
guard let result = String(data: data, encoding: .utf8) else {
55+
throw GenericError("Failed to decode string from OS Log")
56+
}
57+
return result
58+
}
59+
60+
package func run() async throws {
61+
let log: String
62+
if let logFile {
63+
log = try String(contentsOf: URL(fileURLWithPath: logFile), encoding: .utf8)
64+
} else {
65+
log = try await readOSLog()
66+
}
67+
let logParseRegex = Regex {
68+
/.*/
69+
"[spid 0x"
70+
Capture { // Signpost ID
71+
OneOrMore(.hexDigit)
72+
}
73+
", process, "
74+
ZeroOrMore(.whitespace)
75+
Capture { // Event ("begin", "event", "end")
76+
/[a-z]+/
77+
}
78+
"]"
79+
ZeroOrMore(.any)
80+
}
81+
var messagesBySignpostID: [Substring: [Substring]] = [:]
82+
var endedSignposts: Set<Substring> = []
83+
for line in log.split(separator: "\n") {
84+
guard let match = try logParseRegex.wholeMatch(in: line) else {
85+
continue
86+
}
87+
let (signpostID, event) = (match.1, match.2)
88+
messagesBySignpostID[signpostID, default: []].append(line)
89+
if event == "end" {
90+
endedSignposts.insert(signpostID)
91+
}
92+
}
93+
let activeSignpostMessages =
94+
messagesBySignpostID
95+
.filter({ !endedSignposts.contains($0.key) })
96+
.sorted(by: { $0.key < $1.key })
97+
.flatMap(\.value)
98+
print(activeSignpostMessages.joined(separator: "\n"))
99+
}
100+
}

Sources/Diagnose/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
add_library(Diagnose STATIC
2+
ActiveRequestsCommand.swift
23
CommandLineArgumentsReducer.swift
34
DebugCommand.swift
45
DiagnoseCommand.swift
6+
GenericError.swift
57
IndexCommand.swift
68
MergeSwiftFiles.swift
79
OSLogScraper.swift
810
ReduceCommand.swift
911
ReduceFrontendCommand.swift
1012
ReduceSourceKitDRequest.swift
1113
ReduceSwiftFrontend.swift
12-
ReductionError.swift
1314
ReproducerBundle.swift
1415
RequestInfo.swift
1516
RunSourcekitdRequestCommand.swift

Sources/Diagnose/DebugCommand.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package struct DebugCommand: ParsableCommand {
1717
commandName: "debug",
1818
abstract: "Commands to debug sourcekit-lsp. Intended for developers of sourcekit-lsp",
1919
subcommands: [
20+
ActiveRequestsCommand.self,
2021
IndexCommand.self,
2122
ReduceCommand.self,
2223
ReduceFrontendCommand.self,

Sources/Diagnose/DiagnoseCommand.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ package struct DiagnoseCommand: AsyncParsableCommand {
101101
#if canImport(OSLog)
102102
return try OSLogScraper(searchDuration: TimeInterval(osLogScrapeDuration * 60)).getCrashedRequests()
103103
#else
104-
throw ReductionError("Reduction of sourcekitd crashes is not supported on platforms other than macOS")
104+
throw GenericError("Reduction of sourcekitd crashes is not supported on platforms other than macOS")
105105
#endif
106106
}
107107

@@ -214,7 +214,7 @@ package struct DiagnoseCommand: AsyncParsableCommand {
214214
reportProgress(.collectingLogMessages(progress: 0), message: "Collecting log messages")
215215
let outputFileUrl = bundlePath.appendingPathComponent("log.txt")
216216
guard FileManager.default.createFile(atPath: outputFileUrl.path, contents: nil) else {
217-
throw ReductionError("Failed to create log.txt")
217+
throw GenericError("Failed to create log.txt")
218218
}
219219
let fileHandle = try FileHandle(forWritingTo: outputFileUrl)
220220
var bytesCollected = 0
@@ -307,7 +307,7 @@ package struct DiagnoseCommand: AsyncParsableCommand {
307307
private func addSwiftVersion(toBundle bundlePath: URL) async throws {
308308
let outputFileUrl = bundlePath.appendingPathComponent("swift-versions.txt")
309309
guard FileManager.default.createFile(atPath: outputFileUrl.path, contents: nil) else {
310-
throw ReductionError("Failed to create file at \(outputFileUrl)")
310+
throw GenericError("Failed to create file at \(outputFileUrl)")
311311
}
312312
let fileHandle = try FileHandle(forWritingTo: outputFileUrl)
313313

@@ -434,13 +434,13 @@ package struct DiagnoseCommand: AsyncParsableCommand {
434434
progressUpdate: (_ progress: Double, _ message: String) -> Void
435435
) async throws {
436436
guard let toolchain else {
437-
throw ReductionError("Unable to find a toolchain")
437+
throw GenericError("Unable to find a toolchain")
438438
}
439439
guard let sourcekitd = toolchain.sourcekitd else {
440-
throw ReductionError("Unable to find sourcekitd.framework")
440+
throw GenericError("Unable to find sourcekitd.framework")
441441
}
442442
guard let swiftFrontend = toolchain.swiftFrontend else {
443-
throw ReductionError("Unable to find swift-frontend")
443+
throw GenericError("Unable to find swift-frontend")
444444
}
445445

446446
let requestInfo = requestInfo

Sources/Diagnose/ReductionError.swift renamed to Sources/Diagnose/GenericError.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
/// Generic error that can be thrown if reducing the crash failed in a non-recoverable way.
14-
struct ReductionError: Error, CustomStringConvertible {
13+
struct GenericError: Error, CustomStringConvertible {
1514
let description: String
1615

1716
init(_ description: String) {

Sources/Diagnose/ReduceCommand.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ package struct ReduceCommand: AsyncParsableCommand {
7070
@MainActor
7171
package func run() async throws {
7272
guard let sourcekitd = try await toolchain?.sourcekitd else {
73-
throw ReductionError("Unable to find sourcekitd.framework")
73+
throw GenericError("Unable to find sourcekitd.framework")
7474
}
7575
guard let swiftFrontend = try await toolchain?.swiftFrontend else {
76-
throw ReductionError("Unable to find sourcekitd.framework")
76+
throw GenericError("Unable to find sourcekitd.framework")
7777
}
7878

7979
let progressBar = PercentProgressAnimation(stream: stderrStreamConcurrencySafe, header: "Reducing sourcekitd issue")

Sources/Diagnose/ReduceFrontendCommand.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ package struct ReduceFrontendCommand: AsyncParsableCommand {
7878
@MainActor
7979
package func run() async throws {
8080
guard let sourcekitd = try await toolchain?.sourcekitd else {
81-
throw ReductionError("Unable to find sourcekitd.framework")
81+
throw GenericError("Unable to find sourcekitd.framework")
8282
}
8383
guard let swiftFrontend = try await toolchain?.swiftFrontend else {
84-
throw ReductionError("Unable to find swift-frontend")
84+
throw GenericError("Unable to find swift-frontend")
8585
}
8686

8787
let progressBar = PercentProgressAnimation(

Sources/Diagnose/ReduceSwiftFrontend.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ package func reduceFrontendIssue(
1919
let requestInfo = try RequestInfo(frontendArgs: frontendArgs)
2020
let initialResult = try await executor.run(request: requestInfo)
2121
guard case .reproducesIssue = initialResult else {
22-
throw ReductionError("Unable to reproduce the swift-frontend issue")
22+
throw GenericError("Unable to reproduce the swift-frontend issue")
2323
}
2424
let mergedSwiftFilesRequestInfo = try await requestInfo.mergeSwiftFiles(using: executor) { progress, message in
2525
progressUpdate(0, message)
2626
}
2727
guard let mergedSwiftFilesRequestInfo else {
28-
throw ReductionError("Merging all .swift files did not reproduce the issue. Unable to reduce it.")
28+
throw GenericError("Merging all .swift files did not reproduce the issue. Unable to reduce it.")
2929
}
3030
return try await mergedSwiftFilesRequestInfo.reduce(using: executor, progressUpdate: progressUpdate)
3131
}

Sources/Diagnose/RequestInfo.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ package struct RequestInfo: Sendable {
3535
let encoder = JSONEncoder()
3636
encoder.outputFormatting = .prettyPrinted
3737
guard var compilerArgs = String(data: try encoder.encode(compilerArgs), encoding: .utf8) else {
38-
throw ReductionError("Failed to encode compiler arguments")
38+
throw GenericError("Failed to encode compiler arguments")
3939
}
4040
// Drop the opening `[` and `]`. The request template already contains them
4141
compilerArgs = String(compilerArgs.dropFirst().dropLast())
@@ -92,7 +92,7 @@ package struct RequestInfo: Sendable {
9292
"\""
9393
}
9494
guard let sourceFileMatch = requestTemplate.matches(of: sourceFileRegex).only else {
95-
throw ReductionError("Failed to find key.sourcefile in the request")
95+
throw GenericError("Failed to find key.sourcefile in the request")
9696
}
9797
let sourceFilePath = String(sourceFileMatch.1)
9898
requestTemplate.replace(sourceFileMatch.1, with: "$FILE")
@@ -124,7 +124,7 @@ package struct RequestInfo: Sendable {
124124
_ = iterator.next()
125125
case "-filelist":
126126
guard let fileList = iterator.next() else {
127-
throw ReductionError("Expected file path after -filelist command line argument")
127+
throw GenericError("Expected file path after -filelist command line argument")
128128
}
129129
frontendArgsWithFilelistInlined += try String(contentsOfFile: fileList, encoding: .utf8)
130130
.split(separator: "\n")

Sources/Diagnose/RunSourcekitdRequestCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ package struct RunSourceKitdRequestCommand: AsyncParsableCommand {
7878
var error: UnsafeMutablePointer<CChar>?
7979
let req = sourcekitd.api.request_create_from_yaml(buffer.baseAddress!, &error)!
8080
if let error {
81-
throw ReductionError("Failed to parse sourcekitd request from JSON: \(String(cString: error))")
81+
throw GenericError("Failed to parse sourcekitd request from JSON: \(String(cString: error))")
8282
}
8383
return req
8484
}

Sources/Diagnose/SourceKitD+RunWithYaml.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ extension SourceKitD {
1919
var error: UnsafeMutablePointer<CChar>?
2020
let req = api.request_create_from_yaml(buffer.baseAddress!, &error)
2121
if let error {
22-
throw ReductionError("Failed to parse sourcekitd request from YAML: \(String(cString: error))")
22+
throw GenericError("Failed to parse sourcekitd request from YAML: \(String(cString: error))")
2323
}
2424
return req
2525
}

Sources/Diagnose/SourceReducer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ fileprivate class SourceReducer {
117117
case .reduced:
118118
break
119119
case .didNotReproduce:
120-
throw ReductionError("Initial request info did not reproduce the issue")
120+
throw GenericError("Initial request info did not reproduce the issue")
121121
case .noChange:
122122
preconditionFailure("The reduction step always returns empty edits and not `done` so we shouldn't hit this")
123123
}
@@ -658,7 +658,7 @@ fileprivate func getSwiftInterface(
658658
areFallbackArgs: true
659659
)
660660
default:
661-
throw ReductionError("Failed to get Swift Interface for \(moduleName)")
661+
throw GenericError("Failed to get Swift Interface for \(moduleName)")
662662
}
663663

664664
// Extract the line containing the source text and parse that using JSON decoder.
@@ -677,7 +677,7 @@ fileprivate func getSwiftInterface(
677677
return line
678678
}.only
679679
guard let quotedSourceText else {
680-
throw ReductionError("Failed to decode Swift interface response for \(moduleName)")
680+
throw GenericError("Failed to decode Swift interface response for \(moduleName)")
681681
}
682682
// Filter control characters. JSONDecoder really doensn't like them and they are likely not important if they occur eg. in a comment.
683683
let sanitizedData = Data(quotedSourceText.utf8.filter { $0 >= 32 })

Tests/SourceKitLSPTests/PullDiagnosticsTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ final class PullDiagnosticsTests: XCTestCase {
6262
let report = try await testClient.send(DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri)))
6363
let diagnostics = try XCTUnwrap(report.fullReport?.items)
6464

65-
XCTAssertEqual(diagnostics.count, 1)
66-
let diagnostic = try XCTUnwrap(diagnostics.first)
65+
let diagnostic = try XCTUnwrap(diagnostics.only)
6766
XCTAssert(
6867
diagnostic.range == Range(positions["1️⃣"]) || diagnostic.range == Range(positions["2️⃣"]),
6968
"Unexpected range: \(diagnostic.range)"

0 commit comments

Comments
 (0)