Skip to content

Commit 4af03a8

Browse files
committed
[swift-inspect] Inspect all processes and add summary option
With this change swift-inspect can inspect all processes to see if metadata allocation iteration is enabled. We also added summary option that sorts metadata to popularity.
1 parent f0a6b02 commit 4af03a8

File tree

8 files changed

+242
-54
lines changed

8 files changed

+242
-54
lines changed

tools/swift-inspect/Sources/swift-inspect/DarwinRemoteProcess.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ internal final class DarwinRemoteProcess: RemoteProcess {
2020
public typealias ProcessHandle = task_t
2121

2222
private var task: task_t
23+
internal var processIdentifier: ProcessIdentifier
24+
internal lazy var processName = getProcessName(processId: processIdentifier) ?? "<unknown process>"
2325

2426
public var process: ProcessHandle { task }
2527
public private(set) var context: SwiftReflectionContextRef!
@@ -114,20 +116,32 @@ internal final class DarwinRemoteProcess: RemoteProcess {
114116
}
115117

116118
init?(processId: ProcessIdentifier, forkCorpse: Bool) {
119+
processIdentifier = processId
117120
var task: task_t = task_t()
118121
let taskResult = task_for_pid(mach_task_self_, processId, &task)
119122
guard taskResult == KERN_SUCCESS else {
120-
print("unable to get task for pid \(processId): \(String(cString: mach_error_string(taskResult))) \(hex: taskResult)")
123+
print("unable to get task for pid \(processId): \(String(cString: mach_error_string(taskResult))) \(hex: taskResult)",
124+
to: &Std.err)
121125
return nil
122126
}
123127

124128
if forkCorpse {
125129
var corpse = task_t()
126-
let corpseResult = task_generate_corpse(task, &corpse)
127-
if corpseResult == KERN_SUCCESS {
128-
task = corpse
129-
} else {
130-
print("unable to fork corpse for pid \(processId): \(String(cString: mach_error_string(corpseResult))) \(hex: corpseResult)")
130+
let maxRetry = 6
131+
for retry in 0..<maxRetry {
132+
let corpseResult = task_generate_corpse(task, &corpse)
133+
if corpseResult == KERN_SUCCESS {
134+
task_stop_peeking(task)
135+
mach_port_deallocate(mach_task_self_, task)
136+
task = corpse
137+
break
138+
}
139+
if corpseResult != KERN_RESOURCE_SHORTAGE || retry == maxRetry {
140+
print("unable to fork corpse for pid \(processId): \(String(cString: mach_error_string(corpseResult))) \(hex: corpseResult)",
141+
to: &Std.err)
142+
return nil
143+
}
144+
sleep(UInt32(1 << retry))
131145
}
132146
}
133147

@@ -161,6 +175,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
161175
deinit {
162176
task_stop_peeking(self.task)
163177
mach_port_deallocate(mach_task_self_, self.task)
178+
mach_port_mod_refs(mach_task_self_, self.task, MACH_PORT_RIGHT_SEND, -1);
164179
}
165180

166181
func symbolicate(_ address: swift_addr_t) -> (module: String?, symbol: String?) {

tools/swift-inspect/Sources/swift-inspect/Operations/DumpGenericMetadata.swift

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,25 @@ private struct Metadata: Encodable {
5555
}
5656
}
5757

58+
private struct ProcessMetadata: Encodable {
59+
var name: String
60+
var pid: ProcessIdentifier
61+
var metadata: [Metadata]
62+
}
63+
64+
private struct MetadataSummary: Encodable {
65+
var totalSize: Int
66+
var processes: Set<String>
67+
}
68+
5869
internal struct Output: TextOutputStream {
5970
let fileHandle: FileHandle
6071
init(_ outputFile: String? = nil) throws {
6172
if let outputFile {
6273
if FileManager().createFile(atPath: outputFile, contents: nil) {
63-
self.fileHandle = try FileHandle(forWritingAtPath: outputFile)!
74+
self.fileHandle = FileHandle(forWritingAtPath: outputFile)!
6475
} else {
65-
print("Unable to create file \(outputFile)")
76+
print("Unable to create file \(outputFile)", to: &Std.err)
6677
exit(1)
6778
}
6879
} else {
@@ -75,7 +86,6 @@ internal struct Output: TextOutputStream {
7586
fileHandle.write(encodedString)
7687
}
7788
}
78-
7989
}
8090

8191
internal struct DumpGenericMetadata: ParsableCommand {
@@ -92,6 +102,9 @@ internal struct DumpGenericMetadata: ParsableCommand {
92102
var genericMetadataOptions: GenericMetadataOptions
93103

94104
func run() throws {
105+
disableStdErrBuffer()
106+
var metadataSummary = [String: MetadataSummary]()
107+
var allProcesses = [ProcessMetadata]()
95108
try inspect(options: options) { process in
96109
let allocations: [swift_metadata_allocation_t] =
97110
try process.context.allocations.sorted()
@@ -117,19 +130,47 @@ internal struct DumpGenericMetadata: ParsableCommand {
117130
isArrayOfClass: process.context.isArrayOfClass(pointer),
118131
garbage: garbage,
119132
backtrace: currentBacktrace)
133+
} // generics
134+
135+
// Update summary
136+
generics.forEach { metadata in
137+
if let allocation = metadata.allocation {
138+
let name = metadata.name
139+
if metadataSummary.keys.contains(name) {
140+
metadataSummary[name]!.totalSize += allocation.size
141+
metadataSummary[name]!.processes.insert(process.processName)
142+
} else {
143+
metadataSummary[name] = MetadataSummary(totalSize: allocation.size,
144+
processes: Set([process.processName]))
145+
}
146+
}
120147
}
121148

122149
if genericMetadataOptions.json {
123-
try dumpJson(process: process, generics: generics)
124-
} else {
150+
let processMetadata = ProcessMetadata(name: process.processName,
151+
pid: process.processIdentifier as! ProcessIdentifier,
152+
metadata: generics)
153+
allProcesses.append(processMetadata)
154+
} else if !genericMetadataOptions.summary {
125155
try dumpText(process: process, generics: generics)
126-
}
156+
}
157+
} // inspect
158+
159+
if genericMetadataOptions.json {
160+
if genericMetadataOptions.summary {
161+
try dumpJson(of: metadataSummary)
162+
} else {
163+
try dumpJson(of: allProcesses)
164+
}
165+
} else if genericMetadataOptions.summary {
166+
try dumpTextSummary(of: metadataSummary)
127167
}
128168
}
129169

130170
private func dumpText(process: any RemoteProcess, generics: [Metadata]) throws {
131171
var errorneousMetadata: [(ptr: swift_reflection_ptr_t, name: String)] = []
132172
var output = try Output(genericMetadataOptions.outputFile)
173+
print("\(process.processName)(\(process.processIdentifier)):\n", to: &output)
133174
print("Address", "Allocation", "Size", "Offset", "isArrayOfClass", "Name", separator: "\t", to: &output)
134175
generics.forEach {
135176
print("\(hex: $0.ptr)", terminator: "\t", to: &output)
@@ -149,22 +190,18 @@ internal struct DumpGenericMetadata: ParsableCommand {
149190
}
150191

151192
if errorneousMetadata.count > 0 {
152-
print("Error: The following metadata was not found in any DATA or AUTH segments, may be garbage.", to: &output)
193+
print("Warning: The following metadata was not found in any DATA or AUTH segments, may be garbage.", to: &output)
153194
errorneousMetadata.forEach {
154195
print("\(hex: $0.ptr)\t\($0.name)", to: &output)
155196
}
156197
}
198+
print("", to: &output)
157199
}
158200

159-
private func dumpJson(process: any RemoteProcess,
160-
generics: [Metadata]) throws {
161-
struct AllMetadataEntries: Encodable {
162-
var metadata: [Metadata]
163-
}
164-
let allMetadataEntries = AllMetadataEntries(metadata: generics)
201+
private func dumpJson(of: (any Encodable)) throws {
165202
let encoder = JSONEncoder()
166-
encoder.outputFormatting = .prettyPrinted
167-
let data = try encoder.encode(allMetadataEntries)
203+
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
204+
let data = try encoder.encode(of)
168205
let jsonOutput = String(data: data, encoding: .utf8)!
169206
if let outputFile = genericMetadataOptions.outputFile {
170207
try jsonOutput.write(toFile: outputFile, atomically: true, encoding: .utf8)
@@ -173,4 +210,23 @@ internal struct DumpGenericMetadata: ParsableCommand {
173210
}
174211
}
175212

213+
private func dumpTextSummary(of: [String: MetadataSummary]) throws {
214+
var output = try Output(genericMetadataOptions.outputFile)
215+
print("Size", "Owners", "Name", separator: "\t", to: &output)
216+
var totalSize = 0
217+
var unknownSize = 0
218+
of.sorted { first, second in
219+
first.value.processes.count > second.value.processes.count
220+
}
221+
.forEach { summary in
222+
totalSize += summary.value.totalSize
223+
if summary.key == "<unknown>" {
224+
unknownSize += summary.value.totalSize
225+
}
226+
print(summary.value.totalSize, summary.value.processes.count, summary.key,
227+
separator: "\t", to: &output)
228+
}
229+
print("\nTotal size:\t\(totalSize / 1024) KiB", to: &output)
230+
print("Unknown size:\t\(unknownSize / 1024) KiB", to: &output)
231+
}
176232
}

tools/swift-inspect/Sources/swift-inspect/Process.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,62 @@ private func refersToSelf(_ str: String) -> Bool {
4343
// No match.
4444
return false
4545
}
46+
47+
internal func getRemoteProcess(processId: ProcessIdentifier,
48+
options: UniversalOptions) -> (any RemoteProcess)? {
49+
return DarwinRemoteProcess(processId: processId,
50+
forkCorpse: options.forkCorpse)
51+
}
52+
53+
internal func getProcessName(processId: ProcessIdentifier) -> String? {
54+
var info = proc_bsdinfo()
55+
let bsdinfoSize = Int32(MemoryLayout<proc_bsdinfo>.stride)
56+
let size = proc_pidinfo(processId, PROC_PIDTBSDINFO, 0, &info, bsdinfoSize)
57+
if (size != bsdinfoSize) {
58+
return nil
59+
}
60+
let processName = withUnsafeBytes(of: info.pbi_name) { buffer in
61+
let nonnullBuffer = buffer.prefix { $0 != 0 }
62+
return String(decoding: nonnullBuffer, as: UTF8.self)
63+
}
64+
return processName
65+
}
66+
67+
internal func getAllProcesses(options: UniversalOptions) -> [ProcessIdentifier]? {
68+
var ProcessIdentifiers = [ProcessIdentifier]()
69+
let kinfo_stride = MemoryLayout<kinfo_proc>.stride
70+
var bufferSize: Int = 0
71+
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_ALL]
72+
73+
guard sysctl(&name, u_int(name.count), nil, &bufferSize, nil, 0) == 0 else {
74+
return nil
75+
}
76+
let count = bufferSize / kinfo_stride
77+
var buffer = Array(repeating: kinfo_proc(), count: count)
78+
guard sysctl(&name, u_int(name.count), &buffer, &bufferSize, nil, 0) == 0 else {
79+
return nil
80+
}
81+
let newCount = bufferSize / kinfo_stride
82+
if count > newCount {
83+
buffer.dropLast(count - newCount)
84+
}
85+
let sorted = buffer.sorted { first, second in
86+
first.kp_proc.p_pid > second.kp_proc.p_pid
87+
}
88+
let myPid = getpid()
89+
for kinfo in sorted {
90+
let pid = kinfo.kp_proc.p_pid
91+
if pid <= 1 {
92+
break
93+
}
94+
if pid == myPid { // skip self
95+
continue
96+
}
97+
ProcessIdentifiers.append(pid)
98+
}
99+
return ProcessIdentifiers
100+
}
101+
46102
#elseif os(Windows)
47103
import WinSDK
48104

@@ -81,6 +137,12 @@ internal func process(matching: String) -> ProcessIdentifier? {
81137

82138
return matches.first?.0
83139
}
140+
141+
internal func getRemoteProcess(processId: ProcessIdentifier,
142+
options: UniversalOptions) -> (any RemoteProcess)? {
143+
return WindowsRemoteProcess(processId: processId)
144+
}
145+
84146
#else
85147
#error("Unsupported platform")
86148
#endif

tools/swift-inspect/Sources/swift-inspect/RemoteProcess.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ internal protocol RemoteProcess: AnyObject {
1818

1919
var process: ProcessHandle { get }
2020
var context: SwiftReflectionContextRef! { get }
21+
var processIdentifier: ProcessIdentifier { get }
22+
var processName: String { get }
2123

2224
typealias QueryDataLayoutFunction =
2325
@convention(c) (UnsafeMutableRawPointer?, DataLayoutQueryType,

tools/swift-inspect/Sources/swift-inspect/String+Extensions.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,27 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import SwiftRemoteMirror
14+
import Foundation
1415

1516
extension DefaultStringInterpolation {
1617
mutating func appendInterpolation<T>(hex: T) where T: BinaryInteger {
1718
appendInterpolation("0x")
1819
appendInterpolation(String(hex, radix: 16))
1920
}
2021
}
22+
23+
enum Std {
24+
struct File: TextOutputStream {
25+
var underlying: UnsafeMutablePointer<FILE>
26+
27+
mutating func write(_ string: String) {
28+
fputs(string, underlying)
29+
}
30+
}
31+
32+
static var err = File(underlying: stderr)
33+
}
34+
35+
internal func disableStdErrBuffer() {
36+
setvbuf(stderr, nil, Int32(_IONBF), 0)
37+
}

tools/swift-inspect/Sources/swift-inspect/Symbolication+Extensions.swift

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,6 @@
1515
import Foundation
1616
import SymbolicationShims
1717

18-
enum Std {
19-
struct File: TextOutputStream {
20-
var underlying: UnsafeMutablePointer<FILE>
21-
22-
mutating func write(_ string: String) {
23-
fputs(string, underlying)
24-
}
25-
}
26-
27-
static var err = File(underlying: stderr)
28-
}
29-
3018
private let symbolicationPath =
3119
"/System/Library/PrivateFrameworks/Symbolication.framework/Symbolication"
3220
private let symbolicationHandle = dlopen(symbolicationPath, RTLD_LAZY)!
@@ -133,7 +121,7 @@ func CSSymbolOwnerForeachSymbol(
133121
}
134122

135123
func CSSymbolOwnerGetSymbolWithMangledName(
136-
_ owner: CSTypeRef,
124+
_ owner: CSTypeRef,
137125
_ name: String
138126
) -> CSTypeRef {
139127
Sym.CSSymbolOwnerGetSymbolWithMangledName(owner, name)
@@ -196,7 +184,7 @@ func task_start_peeking(_ task: task_t) -> Bool {
196184
if result == KERN_SUCCESS {
197185
return true
198186
}
199-
187+
200188
print("task_start_peeking failed: \(machErrStr(result))", to: &Std.err)
201189
return false
202190
}

tools/swift-inspect/Sources/swift-inspect/WindowsRemoteProcess.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ internal final class WindowsRemoteProcess: RemoteProcess {
2323

2424
public private(set) var process: ProcessHandle
2525
public private(set) var context: SwiftReflectionContextRef!
26+
public private(set) var processIdentifier: ProcessIdentifier
27+
public private(set) var processName: String = "<unknown process>"
2628

2729
private var hSwiftCore: HMODULE = HMODULE(bitPattern: -1)!
2830

@@ -136,6 +138,7 @@ internal final class WindowsRemoteProcess: RemoteProcess {
136138
}
137139

138140
init?(processId: ProcessIdentifier) {
141+
self.processIdentifier = processId
139142
// Get process handle.
140143
self.process =
141144
OpenProcess(

0 commit comments

Comments
 (0)