1212
1313import ArgumentParser
1414import SwiftRemoteMirror
15+ import Foundation
1516
16- private struct Metadata {
17+ private struct Metadata : Encodable {
1718 let ptr : swift_reflection_ptr_t
1819 var allocation : swift_metadata_allocation_t ?
19-
2020 let name : String
2121 let isArrayOfClass : Bool
22-
22+ var garbage : Bool = false
2323 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+ }
2489}
2590
2691internal struct DumpGenericMetadata : ParsableCommand {
@@ -37,56 +102,131 @@ internal struct DumpGenericMetadata: ParsableCommand {
37102 var genericMetadataOptions : GenericMetadataOptions
38103
39104 func run( ) throws {
105+ disableStdErrBuffer ( )
106+ var metadataSummary = [ String: MetadataSummary] ( )
107+ var allProcesses = [ ProcessMetadata] ( )
40108 try inspect ( options: options) { process in
41109 let allocations : [ swift_metadata_allocation_t ] =
42110 try process. context. allocations. sorted ( )
43111
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
45118 let pointer = swift_reflection_allocationMetadataPointer ( process. context, allocation)
46119 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+ }
47126
48127 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+ }
52147 }
53148
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)
71156 }
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)
80164 }
81- }
165+ } else if genericMetadataOptions. summary {
166+ try dumpTextSummary ( of: metadataSummary)
167+ }
168+ }
82169
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) )
87182 }
183+ print ( " ??? \t ?? \t ??? " , terminator: " \t " , to: & output)
88184 }
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+ }
89200
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)
90228 }
229+ print ( " \n Total size: \t \( totalSize / 1024 ) KiB " , to: & output)
230+ print ( " Unknown size: \t \( unknownSize / 1024 ) KiB " , to: & output)
91231 }
92232}
0 commit comments