12
12
13
13
import ArgumentParser
14
14
import SwiftRemoteMirror
15
+ import Foundation
15
16
16
- private struct Metadata {
17
+ private struct Metadata : Encodable {
17
18
let ptr : swift_reflection_ptr_t
18
19
var allocation : swift_metadata_allocation_t ?
19
-
20
20
let name : String
21
21
let isArrayOfClass : Bool
22
-
22
+ var garbage : Bool = false
23
23
var offset : Int ? { allocation. map { Int ( self . ptr - $0. ptr) } }
24
+ var backtrace : String ?
25
+
26
+ enum CodingKeys : String , CodingKey {
27
+ case ptr = " address "
28
+ case allocation
29
+ case name
30
+ case isArrayOfClass
31
+ case garbage
32
+ case offset
33
+ case backtrace
34
+ }
35
+
36
+ func encode( to encoder: Encoder ) throws {
37
+ var container = encoder. container ( keyedBy: CodingKeys . self)
38
+ try container. encode ( ptr, forKey: . ptr)
39
+ try container. encode ( name, forKey: . name)
40
+ if isArrayOfClass {
41
+ try container. encode ( isArrayOfClass, forKey: . isArrayOfClass)
42
+ }
43
+ if garbage {
44
+ try container. encode ( garbage, forKey: . garbage)
45
+ }
46
+ if let offset {
47
+ try container. encode ( offset, forKey: . offset)
48
+ }
49
+ if let backtrace {
50
+ try container. encode ( backtrace, forKey: . backtrace)
51
+ }
52
+ if let allocation {
53
+ try container. encode ( allocation, forKey: . allocation)
54
+ }
55
+ }
56
+ }
57
+
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
+
69
+ internal struct Output : TextOutputStream {
70
+ let fileHandle : FileHandle
71
+ init ( _ outputFile: String ? = nil ) throws {
72
+ if let outputFile {
73
+ if FileManager ( ) . createFile ( atPath: outputFile, contents: nil ) {
74
+ self . fileHandle = FileHandle ( forWritingAtPath: outputFile) !
75
+ } else {
76
+ print ( " Unable to create file \( outputFile) " , to: & Std. err)
77
+ exit ( 1 )
78
+ }
79
+ } else {
80
+ self . fileHandle = FileHandle . standardOutput
81
+ }
82
+ }
83
+
84
+ mutating func write( _ string: String ) {
85
+ if let encodedString = string. data ( using: . utf8) {
86
+ fileHandle. write ( encodedString)
87
+ }
88
+ }
24
89
}
25
90
26
91
internal struct DumpGenericMetadata : ParsableCommand {
@@ -37,56 +102,131 @@ internal struct DumpGenericMetadata: ParsableCommand {
37
102
var genericMetadataOptions : GenericMetadataOptions
38
103
39
104
func run( ) throws {
105
+ disableStdErrBuffer ( )
106
+ var metadataSummary = [ String: MetadataSummary] ( )
107
+ var allProcesses = [ ProcessMetadata] ( )
40
108
try inspect ( options: options) { process in
41
109
let allocations : [ swift_metadata_allocation_t ] =
42
110
try process. context. allocations. sorted ( )
43
111
44
- let generics : [ Metadata ] = allocations. compactMap { allocation -> Metadata ? in
112
+ let stacks : [ swift_reflection_ptr_t : [ swift_reflection_ptr_t ] ] =
113
+ backtraceOptions. style == nil
114
+ ? [ swift_reflection_ptr_t: [ swift_reflection_ptr_t] ] ( )
115
+ : try process. context. allocationStacks
116
+
117
+ let generics : [ Metadata ] = allocations. compactMap { allocation in
45
118
let pointer = swift_reflection_allocationMetadataPointer ( process. context, allocation)
46
119
if pointer == 0 { return nil }
120
+ let allocation = allocations. last ( where: { pointer >= $0. ptr && pointer < $0. ptr + UInt64( $0. size) } )
121
+ let garbage = ( allocation == nil && swift_reflection_ownsAddressStrict ( process. context, UInt ( pointer) ) == 0 )
122
+ var currentBacktrace : String ?
123
+ if let style = backtraceOptions. style, let allocation, let stack = stacks [ allocation. ptr] {
124
+ currentBacktrace = backtrace ( stack, style: style, process. symbolicate)
125
+ }
47
126
48
127
return Metadata ( ptr: pointer,
49
- allocation: allocations. last ( where: { pointer >= $0. ptr && pointer < $0. ptr + swift_reflection_ptr_t( $0. size) } ) ,
50
- name: ( process. context. name ( type: pointer, mangled: genericMetadataOptions. mangled) ?? " <unknown> " ) ,
51
- isArrayOfClass: process. context. isArrayOfClass ( pointer) )
128
+ allocation: allocation,
129
+ name: process. context. name ( type: pointer, mangled: genericMetadataOptions. mangled) ?? " <unknown> " ,
130
+ isArrayOfClass: process. context. isArrayOfClass ( pointer) ,
131
+ garbage: garbage,
132
+ 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
+ }
52
147
}
53
148
54
- let stacks : [ swift_reflection_ptr_t : [ swift_reflection_ptr_t ] ] ? =
55
- backtraceOptions. style == nil
56
- ? nil
57
- : try process. context. allocationStacks
58
-
59
- var errorneousMetadata : [ ( ptr: swift_reflection_ptr_t , name: String ) ] = [ ]
60
-
61
- print ( " Address " , " Allocation " , " Size " , " Offset " , " isArrayOfClass " , " Name " , separator: " \t " )
62
- generics. forEach {
63
- print ( " \( hex: $0. ptr) " , terminator: " \t " )
64
- if let allocation = $0. allocation, let offset = $0. offset {
65
- print ( " \( hex: allocation. ptr) \t \( allocation. size) \t \( offset) " , terminator: " \t " )
66
- } else {
67
- if ( swift_reflection_ownsAddressStrict ( process. context, UInt ( $0. ptr) ) ) == 0 {
68
- errorneousMetadata. append ( ( ptr: $0. ptr, name: $0. name) )
69
- }
70
- print ( " ??? \t ?? \t ??? " , terminator: " \t " )
149
+ if genericMetadataOptions. json {
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 {
155
+ try dumpText ( process: process, generics: generics)
71
156
}
72
- print ( $0. isArrayOfClass, terminator: " \t " )
73
- print ( $0. name)
74
- if let style = backtraceOptions. style, let allocation = $0. allocation {
75
- if let stack = stacks ? [ allocation. ptr] {
76
- print ( backtrace ( stack, style: style, process. symbolicate) )
77
- } else {
78
- print ( " No stacktrace available " )
79
- }
157
+ } // inspect
158
+
159
+ if genericMetadataOptions. json {
160
+ if genericMetadataOptions. summary {
161
+ try dumpJson ( of: metadataSummary)
162
+ } else {
163
+ try dumpJson ( of: allProcesses)
80
164
}
81
- }
165
+ } else if genericMetadataOptions. summary {
166
+ try dumpTextSummary ( of: metadataSummary)
167
+ }
168
+ }
82
169
83
- if errorneousMetadata. count > 0 {
84
- print ( " Error: The following metadata was not found in any DATA or AUTH segments, may be garbage. " )
85
- errorneousMetadata. forEach {
86
- print ( " \( hex: $0. ptr) \t \( $0. name) " )
170
+ private func dumpText( process: any RemoteProcess , generics: [ Metadata ] ) throws {
171
+ var errorneousMetadata : [ ( ptr: swift_reflection_ptr_t , name: String ) ] = [ ]
172
+ var output = try Output ( genericMetadataOptions. outputFile)
173
+ print ( " \( process. processName) ( \( process. processIdentifier) ): \n " , to: & output)
174
+ print ( " Address " , " Allocation " , " Size " , " Offset " , " isArrayOfClass " , " Name " , separator: " \t " , to: & output)
175
+ generics. forEach {
176
+ print ( " \( hex: $0. ptr) " , terminator: " \t " , to: & output)
177
+ if let allocation = $0. allocation, let offset = $0. offset {
178
+ print ( " \( hex: allocation. ptr) \t \( allocation. size) \t \( offset) " , terminator: " \t " , to: & output)
179
+ } else {
180
+ if $0. garbage {
181
+ errorneousMetadata. append ( ( ptr: $0. ptr, name: $0. name) )
87
182
}
183
+ print ( " ??? \t ?? \t ??? " , terminator: " \t " , to: & output)
88
184
}
185
+ print ( $0. isArrayOfClass, terminator: " \t " , to: & output)
186
+ print ( $0. name, to: & output)
187
+ if let _ = backtraceOptions. style, let _ = $0. allocation {
188
+ print ( $0. backtrace ?? " No stacktrace available " , to: & output)
189
+ }
190
+ }
191
+
192
+ if errorneousMetadata. count > 0 {
193
+ print ( " Warning: The following metadata was not found in any DATA or AUTH segments, may be garbage. " , to: & output)
194
+ errorneousMetadata. forEach {
195
+ print ( " \( hex: $0. ptr) \t \( $0. name) " , to: & output)
196
+ }
197
+ }
198
+ print ( " " , to: & output)
199
+ }
89
200
201
+ private func dumpJson( of: ( any Encodable ) ) throws {
202
+ let encoder = JSONEncoder ( )
203
+ encoder. outputFormatting = [ . prettyPrinted, . sortedKeys]
204
+ let data = try encoder. encode ( of)
205
+ let jsonOutput = String ( data: data, encoding: . utf8) !
206
+ if let outputFile = genericMetadataOptions. outputFile {
207
+ try jsonOutput. write ( toFile: outputFile, atomically: true , encoding: . utf8)
208
+ } else {
209
+ print ( jsonOutput)
210
+ }
211
+ }
212
+
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)
90
228
}
229
+ print ( " \n Total size: \t \( totalSize / 1024 ) KiB " , to: & output)
230
+ print ( " Unknown size: \t \( unknownSize / 1024 ) KiB " , to: & output)
91
231
}
92
232
}
0 commit comments