本文即简单介绍了如何在Swift中开发命令行工具,以及与Shell命令的交互。水文一篇,不喜勿喷。shell
主要是使用该工具来解析微信的性能监控组件Matrix的OOM Log。swift
这里,仅简单介绍了常见的基本模块。数组
Process类能够用来打开另一个子进程,并监控其运行状况。xcode
Pipe这个类就是操做系统的管道,在这里用来接受子进程的输出。这里,能够用于将process的输出传递至管道指定的地方,如一个output变量,或者文件也能够。bash
用于获取脚本参数而已。微信
print(CommandLine.argc) // 2
print(CommandLine.arguments) // ["./test.swift", "hello"]
复制代码
这里提供了两种调用Shell命令的封装函数,我的更倾向于第二种,直接将Shell命令及参数封装成一个字符串传入便可。app
@discardableResult
func runShell(_ command: String) -> Int32 {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
@discardableResult
func runShellWithArgs(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}
复制代码
使用以下:async
runShell("pwd")
runShell("ls -l")
runShellWithArgs("pwd")
runShellWithArgs("ls", "-l")
复制代码
这里就须要使用到Pipe了。函数
@discardableResult
func runShellAndOutput(_ command: String) -> (Int32, String?) {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (task.terminationStatus, output)
}
@discardableResult
func runShellWithArgsAndOutput(_ args: String...) -> (Int32, String?) {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
task.waitUntilExit()
return (task.terminationStatus, output)
}
复制代码
使用以下:工具
let (ret1, output1) = runShellAndOutput("ls -l")
if let output11 = output1 {
print(output11)
}
let (ret2, output2) = runShellWithArgsAndOutput("ls", "-l")
if let output22 = output2 {
print(output2)
}
复制代码
Matrix的OOM Log格式以下,其实就是一个大JSON:
{
"head": {
"protocol_ver": 1,
"phone": "iPhone10,1",
"os_ver": "13.4",
"launch_time": 1589361495000,
"report_time": 1589362109100,
"app_uuid": ""
},
"items": [
{
"tag": "iOS_MemStat",
"info": "",
"scene": "",
"name": "Malloc 12.54 MiB",
"size": 146313216,
"count": 1,
"stacks": [
{
"caller": "f07199ac8a903127b17f0a906ffb0237@84128",
"size": 146313216,
"count": 1,
"frames": [
{
"uuid": "a0a7d67af0f3399a8f006f92716d8e6f",
"offset": 67308
},
{
"uuid": "a0a7d67af0f3399a8f006f92716d8e6f",
"offset": 69836
},
{
"uuid": "f07199ac8a903127b17f0a906ffb0237",
"offset": 84128
},
{
"uuid": "b80198f7beb93e79b25c7a27d68bb489",
"offset": 14934312
},
{
"uuid": "1a46239df2fc34b695bc9f38869f0c85",
"offset": 1126304
},
{
"uuid": "1a46239df2fc34b695bc9f38869f0c85",
"offset": 123584
},
{
"uuid": "1a46239df2fc34b695bc9f38869f0c85",
"offset": 1135100
}]
}
]
}
]
}
复制代码
解析的思路其实很是简单,将JSON转为Model,而后根据所需,提取对应的信息便可。
uuid是mach-o的惟一标识,offset则是符号相对于mach-o基地址的偏移量。拿到dSYM文件,使用 atos 命令便可进行符号化。
guard let rawLogModel = MatrixOOMLogParser.parse() else { exit(-1) }
print("______ Start to process Matrix OOM Log ...")
let group = DispatchGroup()
var metaLog = ""
for item in bodyInfo.items {
guard let stacks = item.stacks else { continue }
group.enter()
DispatchQueue.global().async {
var log = "______ item ______ name: \(item.name), size: \(item.size), count: \(item.count) \n"
metaLog += log
for stack in stacks {
let outputs = stack.frames.map({ (frame: MatrixOOMLogModelFrame) -> String in
// let uuid = frame.uuid
let offset = frame.offset
let instructionAddress = loadAddress + offset
let (_, output) = runShellAndOutput("xcrun atos -o \(dwarf) -arch arm64 -l 0x1 \(instructionAddress.hexValue)")
return output ?? ""
})
log += outputs.joined()
print(log)
}
group.leave()
}
}
group.wait()
print("\n\(metaLog)\n")
print("______ Finished processing Matrix OOM Log ...")
复制代码
MatrixOOMLogParser.parse() 就是将JSON转为Model,这里用的就是Swift里边的Codable。
这里有一个须要注意的点,Mac CLI没有Bundle的概念,只有一个bin文件。因此对于原始的JSON文件,只能经过外部bundle的方式来添加。经过 New->Target 单独创建一个bundle。须要在 Xcode -> Build Phases -> Copy Files 中添加该bundle名,而后便可经过 Bundle(url: mockDataBundleURL) 来加载该bundle并获取其中的log文件了。
由于atos的执行时间较长,因此大量的符号化操做会很是耗时。通常来讲,这段代码执行六七分钟左右,能够将一个Matrix的OOM Log彻底符号化。而符号化以后的记录如何分析,就是另一个话题了。